Update.
This commit is contained in:
parent
81f1cfd200
commit
c6fb77c89d
@ -1,5 +1,6 @@
|
|||||||
from .views.auth import LoginView, RegistrationView, LogoutView, ForgotPasswordView, ResetPasswordView
|
from .views.auth import LoginView, RegistrationView, LogoutView, ForgotPasswordView, ResetPasswordView
|
||||||
from .views.site import SiteView, OrderView, FileBrowserView, UserManagementView
|
from .views.site import SiteView, OrderView, FileBrowserView, UserManagementView
|
||||||
|
from .views.upload import UploadView
|
||||||
from .views.admin import get_users, add_user, update_user_quota, delete_user, get_user_details, delete_team
|
from .views.admin import get_users, add_user, update_user_quota, delete_user, get_user_details, delete_team
|
||||||
|
|
||||||
|
|
||||||
@ -30,7 +31,7 @@ def setup_routes(app):
|
|||||||
app.router.add_post("/users/{email}/delete", UserManagementView, name="delete_user_page")
|
app.router.add_post("/users/{email}/delete", UserManagementView, name="delete_user_page")
|
||||||
app.router.add_view("/files", FileBrowserView, name="file_browser")
|
app.router.add_view("/files", FileBrowserView, name="file_browser")
|
||||||
app.router.add_post("/files/new_folder", FileBrowserView, name="new_folder")
|
app.router.add_post("/files/new_folder", FileBrowserView, name="new_folder")
|
||||||
app.router.add_post("/files/upload", FileBrowserView, name="upload_file")
|
app.router.add_post("/files/upload", UploadView, name="upload_file")
|
||||||
app.router.add_get("/files/download/{file_path:.*}", FileBrowserView, name="download_file")
|
app.router.add_get("/files/download/{file_path:.*}", FileBrowserView, name="download_file")
|
||||||
app.router.add_post("/files/share/{file_path:.*}", FileBrowserView, name="share_file")
|
app.router.add_post("/files/share/{file_path:.*}", FileBrowserView, name="share_file")
|
||||||
app.router.add_post("/files/delete/{file_path:.*}", FileBrowserView, name="delete_item")
|
app.router.add_post("/files/delete/{file_path:.*}", FileBrowserView, name="delete_item")
|
||||||
|
|||||||
@ -236,3 +236,107 @@
|
|||||||
min-width: 120px;
|
min-width: 120px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Styles for the new upload functionality */
|
||||||
|
.upload-area {
|
||||||
|
border: 2px dashed var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-button {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 10px 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-files-preview {
|
||||||
|
margin-top: 20px;
|
||||||
|
text-align: left;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-entry {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 0;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-entry:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-entry .file-name {
|
||||||
|
flex-grow: 1;
|
||||||
|
margin-right: 10px;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-entry .file-size {
|
||||||
|
color: var(--light-text-color);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-entry .thumbnail-preview {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
margin-left: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-entry .thumbnail-preview img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
display: block;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar-container {
|
||||||
|
margin-top: 15px;
|
||||||
|
text-align: left;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar-container .file-name {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 10px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
width: 0%;
|
||||||
|
background-color: var(--accent-color);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: width 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-text {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--light-text-color);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import './components/slider.js';
|
import './components/slider.js';
|
||||||
import './components/navigation.js'; // Assuming navigation.js might be needed globally
|
import './components/navigation.js'; // Assuming navigation.js might be needed globally
|
||||||
|
import { showUploadModal } from './components/upload.js'; // Import showUploadModal
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// Logic for custom-slider on order page
|
// Logic for custom-slider on order page
|
||||||
@ -82,4 +83,199 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// Initial display based on active button (default to monthly)
|
// Initial display based on active button (default to monthly)
|
||||||
updatePricingDisplay('monthly');
|
updatePricingDisplay('monthly');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- File Browser Specific Logic ---
|
||||||
|
|
||||||
|
// Helper functions for modals
|
||||||
|
function showNewFolderModal() {
|
||||||
|
document.getElementById('new-folder-modal').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal(modalId) {
|
||||||
|
document.getElementById(modalId).style.display = 'none';
|
||||||
|
}
|
||||||
|
window.closeModal = closeModal; // Make it globally accessible
|
||||||
|
|
||||||
|
window.onclick = function(event) {
|
||||||
|
if (event.target.classList.contains('modal')) {
|
||||||
|
event.target.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// File actions
|
||||||
|
function downloadFile(path) {
|
||||||
|
window.location.href = `/files/download/${path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function shareFile(path, name) {
|
||||||
|
const modal = document.getElementById('share-modal');
|
||||||
|
const linkContainer = document.getElementById('share-link-container');
|
||||||
|
const loading = document.getElementById('share-loading');
|
||||||
|
|
||||||
|
document.getElementById('share-file-name').textContent = `Sharing: ${name}`;
|
||||||
|
linkContainer.style.display = 'none';
|
||||||
|
loading.style.display = 'block';
|
||||||
|
modal.style.display = 'block';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/files/share/${path}`, {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.share_link) {
|
||||||
|
document.getElementById('share-link-input').value = data.share_link;
|
||||||
|
linkContainer.style.display = 'block';
|
||||||
|
loading.style.display = 'none';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
loading.textContent = 'Error generating share link';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyShareLink() {
|
||||||
|
const input = document.getElementById('share-link-input');
|
||||||
|
input.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
alert('Share link copied to clipboard');
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteFile(path, name) {
|
||||||
|
document.getElementById('delete-message').textContent = `Are you sure you want to delete "${name}"? This action cannot be undone.`;
|
||||||
|
document.getElementById('delete-form').action = `/files/delete/${path}`;
|
||||||
|
document.getElementById('delete-modal').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selection and action buttons
|
||||||
|
function updateActionButtons() {
|
||||||
|
const checked = document.querySelectorAll('.file-checkbox:checked');
|
||||||
|
const downloadBtn = document.getElementById('download-selected-btn');
|
||||||
|
const shareBtn = document.getElementById('share-selected-btn');
|
||||||
|
const deleteBtn = document.getElementById('delete-selected-btn');
|
||||||
|
|
||||||
|
const hasSelection = checked.length > 0;
|
||||||
|
const hasFiles = Array.from(checked).some(cb => cb.dataset.isDir === 'False');
|
||||||
|
|
||||||
|
if (downloadBtn) downloadBtn.disabled = !hasFiles;
|
||||||
|
if (shareBtn) shareBtn.disabled = !hasSelection;
|
||||||
|
if (deleteBtn) deleteBtn.disabled = !hasSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadSelected() {
|
||||||
|
const checked = document.querySelectorAll('.file-checkbox:checked');
|
||||||
|
if (checked.length === 1 && checked[0].dataset.isDir === 'False') {
|
||||||
|
downloadFile(checked[0].dataset.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function shareSelected() {
|
||||||
|
const checked = document.querySelectorAll('.file-checkbox:checked');
|
||||||
|
if (checked.length === 1) {
|
||||||
|
const path = checked[0].dataset.path;
|
||||||
|
const name = checked[0].closest('tr').querySelector('td:nth-child(2)').textContent.trim();
|
||||||
|
shareFile(path, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteSelected() {
|
||||||
|
const checked = document.querySelectorAll('.file-checkbox:checked');
|
||||||
|
if (checked.length > 0) {
|
||||||
|
const paths = Array.from(checked).map(cb => cb.dataset.path);
|
||||||
|
const names = Array.from(checked).map(cb =>
|
||||||
|
cb.closest('tr').querySelector('td:nth-child(2)').textContent.trim()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (checked.length === 1) {
|
||||||
|
deleteFile(paths[0], names[0]);
|
||||||
|
} else {
|
||||||
|
document.getElementById('delete-message').textContent =
|
||||||
|
`Are you sure you want to delete ${checked.length} items? This action cannot be undone.`;
|
||||||
|
document.getElementById('delete-modal').style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event Listeners for File Browser
|
||||||
|
const newFolderBtn = document.getElementById('new-folder-btn');
|
||||||
|
if (newFolderBtn) {
|
||||||
|
console.log('Attaching event listener to new-folder-btn');
|
||||||
|
newFolderBtn.addEventListener('click', showNewFolderModal);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadBtn = document.getElementById('upload-btn');
|
||||||
|
if (uploadBtn) {
|
||||||
|
console.log('Attaching event listener to upload-btn');
|
||||||
|
uploadBtn.addEventListener('click', showUploadModal);
|
||||||
|
}
|
||||||
|
|
||||||
|
const createFirstFolderBtn = document.getElementById('create-first-folder-btn');
|
||||||
|
if (createFirstFolderBtn) {
|
||||||
|
console.log('Attaching event listener to create-first-folder-btn');
|
||||||
|
createFirstFolderBtn.addEventListener('click', showNewFolderModal);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadFirstFileBtn = document.getElementById('upload-first-file-btn');
|
||||||
|
if (uploadFirstFileBtn) {
|
||||||
|
console.log('Attaching event listener to upload-first-file-btn');
|
||||||
|
uploadFirstFileBtn.addEventListener('click', showUploadModal);
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadSelectedBtn = document.getElementById('download-selected-btn');
|
||||||
|
if (downloadSelectedBtn) {
|
||||||
|
downloadSelectedBtn.addEventListener('click', downloadSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
const shareSelectedBtn = document.getElementById('share-selected-btn');
|
||||||
|
if (shareSelectedBtn) {
|
||||||
|
shareSelectedBtn.addEventListener('click', shareSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteSelectedBtn = document.getElementById('delete-selected-btn');
|
||||||
|
if (deleteSelectedBtn) {
|
||||||
|
deleteSelectedBtn.addEventListener('click', deleteSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyShareLinkBtn = document.getElementById('copy-share-link-btn');
|
||||||
|
if (copyShareLinkBtn) {
|
||||||
|
copyShareLinkBtn.addEventListener('click', copyShareLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('select-all')?.addEventListener('change', function(e) {
|
||||||
|
const checkboxes = document.querySelectorAll('.file-checkbox');
|
||||||
|
checkboxes.forEach(cb => cb.checked = e.target.checked);
|
||||||
|
updateActionButtons();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.file-checkbox').forEach(cb => {
|
||||||
|
cb.addEventListener('change', updateActionButtons);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Event listeners for dynamically created download/share/delete buttons
|
||||||
|
document.addEventListener('click', (event) => {
|
||||||
|
if (event.target.classList.contains('download-file-btn')) {
|
||||||
|
downloadFile(event.target.dataset.path);
|
||||||
|
} else if (event.target.classList.contains('share-file-btn')) {
|
||||||
|
shareFile(event.target.dataset.path, event.target.dataset.name);
|
||||||
|
} else if (event.target.classList.contains('delete-file-btn')) {
|
||||||
|
deleteFile(event.target.dataset.path, event.target.dataset.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
document.getElementById('search-bar')?.addEventListener('input', function(e) {
|
||||||
|
const searchTerm = e.target.value.toLowerCase();
|
||||||
|
const rows = document.querySelectorAll('#file-list-body tr');
|
||||||
|
|
||||||
|
rows.forEach(row => {
|
||||||
|
const name = row.querySelector('td:nth-child(2)')?.textContent.toLowerCase();
|
||||||
|
if (name && name.includes(searchTerm)) {
|
||||||
|
row.style.display = '';
|
||||||
|
} else {
|
||||||
|
row.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initial update of action buttons
|
||||||
|
updateActionButtons();
|
||||||
});
|
});
|
||||||
@ -9,11 +9,11 @@
|
|||||||
{% block page_title %}My Files{% endblock %}
|
{% block page_title %}My Files{% endblock %}
|
||||||
|
|
||||||
{% block dashboard_actions %}
|
{% block dashboard_actions %}
|
||||||
<button class="btn-primary" onclick="showNewFolderModal()">+ New</button>
|
<button class="btn-primary" id="new-folder-btn">+ New</button>
|
||||||
<button class="btn-outline" onclick="showUploadModal()">Upload</button>
|
<button class="btn-outline" id="upload-btn">Upload</button>
|
||||||
<button class="btn-outline" onclick="downloadSelected()" id="download-btn" disabled>Download</button>
|
<button class="btn-outline" id="download-selected-btn" disabled>Download</button>
|
||||||
<button class="btn-outline" onclick="shareSelected()" id="share-btn" disabled>Share</button>
|
<button class="btn-outline" id="share-selected-btn" disabled>Share</button>
|
||||||
<button class="btn-outline" onclick="deleteSelected()" id="delete-btn" disabled>Delete</button>
|
<button class="btn-outline" id="delete-selected-btn" disabled>Delete</button>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block dashboard_content %}
|
{% block dashboard_content %}
|
||||||
@ -70,10 +70,10 @@
|
|||||||
<td>
|
<td>
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
{% if not item.is_dir %}
|
{% if not item.is_dir %}
|
||||||
<button class="btn-small" onclick="downloadFile('{{ item.path }}')">Download</button>
|
<button class="btn-small download-file-btn" data-path="{{ item.path }}">Download</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn-small" onclick="shareFile('{{ item.path }}', '{{ item.name }}')">Share</button>
|
<button class="btn-small share-file-btn" data-path="{{ item.path }}" data-name="{{ item.name }}">Share</button>
|
||||||
<button class="btn-small btn-danger" onclick="deleteFile('{{ item.path }}', '{{ item.name }}')">Delete</button>
|
<button class="btn-small btn-danger delete-file-btn" data-path="{{ item.path }}" data-name="{{ item.name }}">Delete</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -82,8 +82,8 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td colspan="6" style="text-align: center; padding: 40px;">
|
<td colspan="6" style="text-align: center; padding: 40px;">
|
||||||
<p>No files found in this directory.</p>
|
<p>No files found in this directory.</p>
|
||||||
<button class="btn-primary" onclick="showNewFolderModal()" style="margin-top: 10px;">Create your first folder</button>
|
<button class="btn-primary" id="create-first-folder-btn" style="margin-top: 10px;">Create your first folder</button>
|
||||||
<button class="btn-outline" onclick="showUploadModal()" style="margin-top: 10px;">Upload a file</button>
|
<button class="btn-outline" id="upload-first-file-btn" style="margin-top: 10px;">Upload a file</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -108,15 +108,17 @@
|
|||||||
<div id="upload-modal" class="modal">
|
<div id="upload-modal" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<span class="close" onclick="closeModal('upload-modal')">×</span>
|
<span class="close" onclick="closeModal('upload-modal')">×</span>
|
||||||
<h3>Upload File</h3>
|
<h3>Upload Files</h3>
|
||||||
<form action="/files/upload" method="post" enctype="multipart/form-data">
|
<div class="upload-area">
|
||||||
<input type="file" name="file" required class="form-input" id="file-input">
|
<input type="file" name="file" multiple class="form-input" id="file-input-multiple" style="display: none;">
|
||||||
<div class="file-info" id="file-info"></div>
|
<label for="file-input-multiple" class="btn-outline upload-button">Select Files</label>
|
||||||
<div class="modal-actions">
|
<div id="selected-files-preview" class="selected-files-preview"></div>
|
||||||
<button type="submit" class="btn-primary">Upload</button>
|
<div id="upload-progress-container" class="upload-progress-container"></div>
|
||||||
<button type="button" class="btn-outline" onclick="closeModal('upload-modal')">Cancel</button>
|
</div>
|
||||||
</div>
|
<div class="modal-actions">
|
||||||
</form>
|
<button type="button" class="btn-primary" id="start-upload-btn" disabled>Upload</button>
|
||||||
|
<button type="button" class="btn-outline" onclick="closeModal('upload-modal')">Cancel</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -127,7 +129,7 @@
|
|||||||
<p id="share-file-name"></p>
|
<p id="share-file-name"></p>
|
||||||
<div id="share-link-container" style="display: none;">
|
<div id="share-link-container" style="display: none;">
|
||||||
<input type="text" id="share-link-input" readonly class="form-input">
|
<input type="text" id="share-link-input" readonly class="form-input">
|
||||||
<button class="btn-primary" onclick="copyShareLink()">Copy Link</button>
|
<button class="btn-primary" id="copy-share-link-btn">Copy Link</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="share-loading">Generating share link...</div>
|
<div id="share-loading">Generating share link...</div>
|
||||||
<div class="modal-actions">
|
<div class="modal-actions">
|
||||||
@ -150,146 +152,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script type="module" src="/static/js/components/upload.js"></script>
|
||||||
function showNewFolderModal() {
|
<script type="module" src="/static/js/main.js"></script>
|
||||||
document.getElementById('new-folder-modal').style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
function showUploadModal() {
|
|
||||||
document.getElementById('upload-modal').style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeModal(modalId) {
|
|
||||||
document.getElementById(modalId).style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onclick = function(event) {
|
|
||||||
if (event.target.classList.contains('modal')) {
|
|
||||||
event.target.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('file-input').addEventListener('change', function(e) {
|
|
||||||
const file = e.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
const size = (file.size / 1024 / 1024).toFixed(2);
|
|
||||||
document.getElementById('file-info').innerHTML = `Selected: ${file.name} (${size} MB)`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function downloadFile(path) {
|
|
||||||
window.location.href = `/files/download/${path}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function shareFile(path, name) {
|
|
||||||
const modal = document.getElementById('share-modal');
|
|
||||||
const linkContainer = document.getElementById('share-link-container');
|
|
||||||
const loading = document.getElementById('share-loading');
|
|
||||||
|
|
||||||
document.getElementById('share-file-name').textContent = `Sharing: ${name}`;
|
|
||||||
linkContainer.style.display = 'none';
|
|
||||||
loading.style.display = 'block';
|
|
||||||
modal.style.display = 'block';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/files/share/${path}`, {
|
|
||||||
method: 'POST'
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.share_link) {
|
|
||||||
document.getElementById('share-link-input').value = data.share_link;
|
|
||||||
linkContainer.style.display = 'block';
|
|
||||||
loading.style.display = 'none';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
loading.textContent = 'Error generating share link';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyShareLink() {
|
|
||||||
const input = document.getElementById('share-link-input');
|
|
||||||
input.select();
|
|
||||||
document.execCommand('copy');
|
|
||||||
alert('Share link copied to clipboard');
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteFile(path, name) {
|
|
||||||
document.getElementById('delete-message').textContent = `Are you sure you want to delete "${name}"? This action cannot be undone.`;
|
|
||||||
document.getElementById('delete-form').action = `/files/delete/${path}`;
|
|
||||||
document.getElementById('delete-modal').style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('select-all').addEventListener('change', function(e) {
|
|
||||||
const checkboxes = document.querySelectorAll('.file-checkbox');
|
|
||||||
checkboxes.forEach(cb => cb.checked = e.target.checked);
|
|
||||||
updateActionButtons();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelectorAll('.file-checkbox').forEach(cb => {
|
|
||||||
cb.addEventListener('change', updateActionButtons);
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateActionButtons() {
|
|
||||||
const checked = document.querySelectorAll('.file-checkbox:checked');
|
|
||||||
const downloadBtn = document.getElementById('download-btn');
|
|
||||||
const shareBtn = document.getElementById('share-btn');
|
|
||||||
const deleteBtn = document.getElementById('delete-btn');
|
|
||||||
|
|
||||||
const hasSelection = checked.length > 0;
|
|
||||||
const hasFiles = Array.from(checked).some(cb => cb.dataset.isDir === 'False');
|
|
||||||
|
|
||||||
downloadBtn.disabled = !hasFiles;
|
|
||||||
shareBtn.disabled = !hasSelection;
|
|
||||||
deleteBtn.disabled = !hasSelection;
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadSelected() {
|
|
||||||
const checked = document.querySelectorAll('.file-checkbox:checked');
|
|
||||||
if (checked.length === 1 && checked[0].dataset.isDir === 'False') {
|
|
||||||
downloadFile(checked[0].dataset.path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function shareSelected() {
|
|
||||||
const checked = document.querySelectorAll('.file-checkbox:checked');
|
|
||||||
if (checked.length === 1) {
|
|
||||||
const path = checked[0].dataset.path;
|
|
||||||
const name = checked[0].closest('tr').querySelector('td:nth-child(2)').textContent.trim();
|
|
||||||
shareFile(path, name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteSelected() {
|
|
||||||
const checked = document.querySelectorAll('.file-checkbox:checked');
|
|
||||||
if (checked.length > 0) {
|
|
||||||
const paths = Array.from(checked).map(cb => cb.dataset.path);
|
|
||||||
const names = Array.from(checked).map(cb =>
|
|
||||||
cb.closest('tr').querySelector('td:nth-child(2)').textContent.trim()
|
|
||||||
);
|
|
||||||
|
|
||||||
if (checked.length === 1) {
|
|
||||||
deleteFile(paths[0], names[0]);
|
|
||||||
} else {
|
|
||||||
document.getElementById('delete-message').textContent =
|
|
||||||
`Are you sure you want to delete ${checked.length} items? This action cannot be undone.`;
|
|
||||||
document.getElementById('delete-modal').style.display = 'block';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('search-bar').addEventListener('input', function(e) {
|
|
||||||
const searchTerm = e.target.value.toLowerCase();
|
|
||||||
const rows = document.querySelectorAll('#file-list-body tr');
|
|
||||||
|
|
||||||
rows.forEach(row => {
|
|
||||||
const name = row.querySelector('td:nth-child(2)')?.textContent.toLowerCase();
|
|
||||||
if (name && name.includes(searchTerm)) {
|
|
||||||
row.style.display = '';
|
|
||||||
} else {
|
|
||||||
row.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -182,46 +182,6 @@ class FileBrowserView(web.View):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
elif route_name == "upload_file":
|
|
||||||
try:
|
|
||||||
reader = await self.request.multipart()
|
|
||||||
field = await reader.next()
|
|
||||||
if not field or field.name != "file":
|
|
||||||
return web.HTTPFound(
|
|
||||||
self.request.app.router["file_browser"].url_for().with_query(
|
|
||||||
error="No file selected for upload"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
filename = field.filename
|
|
||||||
if not filename:
|
|
||||||
return web.HTTPFound(
|
|
||||||
self.request.app.router["file_browser"].url_for().with_query(
|
|
||||||
error="Filename is required"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
content = await field.read()
|
|
||||||
success = await file_service.upload_file(user_email, filename, content)
|
|
||||||
if success:
|
|
||||||
return web.HTTPFound(
|
|
||||||
self.request.app.router["file_browser"].url_for().with_query(
|
|
||||||
success=f"File '{filename}' uploaded successfully"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return web.HTTPFound(
|
|
||||||
self.request.app.router["file_browser"].url_for().with_query(
|
|
||||||
error=f"Failed to upload file '{filename}'"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
return web.HTTPFound(
|
|
||||||
self.request.app.router["file_browser"].url_for().with_query(
|
|
||||||
error=f"Upload error: {str(e)}"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif route_name == "share_file":
|
elif route_name == "share_file":
|
||||||
file_path = self.request.match_info.get("file_path")
|
file_path = self.request.match_info.get("file_path")
|
||||||
if not file_path:
|
if not file_path:
|
||||||
|
|||||||
@ -369,8 +369,7 @@ async def test_file_browser_upload_file(logged_in_client: TestClient, file_servi
|
|||||||
content_type='text/plain')
|
content_type='text/plain')
|
||||||
|
|
||||||
resp = await logged_in_client.post("/files/upload", data=data, allow_redirects=False)
|
resp = await logged_in_client.post("/files/upload", data=data, allow_redirects=False)
|
||||||
assert resp.status == 302 # Redirect
|
assert resp.status == 200
|
||||||
assert resp.headers["Location"].startswith("/files")
|
|
||||||
|
|
||||||
expected_path = temp_user_files_dir / user_email / "uploaded.txt"
|
expected_path = temp_user_files_dir / user_email / "uploaded.txt"
|
||||||
assert expected_path.is_file()
|
assert expected_path.is_file()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user