This commit is contained in:
retoor 2025-11-09 01:54:31 +01:00
parent ea8af383cc
commit 5d3d0b162d
4 changed files with 158 additions and 29 deletions

View File

@ -35,6 +35,8 @@ def setup_routes(app):
app.router.add_get("/files/download/{file_path:.*}", FileBrowserView.get_download_file, name="download_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_multiple", FileBrowserView, name="delete_multiple_items")
app.router.add_post("/files/share_multiple", FileBrowserView, name="share_multiple_items")
app.router.add_get("/shared_file/{share_id}", FileBrowserView.shared_file_handler, name="shared_file")
app.router.add_get("/shared_file/{share_id}/download", FileBrowserView.download_shared_file_handler, name="download_shared_file")

View File

@ -107,29 +107,64 @@ document.addEventListener('DOMContentLoaded', () => {
window.location.href = `/files/download/${path}`;
}
async function shareFile(path, name) {
async function shareFile(paths, names) {
const modal = document.getElementById('share-modal');
const linkContainer = document.getElementById('share-link-container');
const loading = document.getElementById('share-loading');
const shareLinkInput = document.getElementById('share-link-input');
const shareFileName = document.getElementById('share-file-name');
const shareLinksList = document.getElementById('share-links-list'); // New element for multiple links
document.getElementById('share-file-name').textContent = `Sharing: ${name}`;
// Clear previous content
shareLinkInput.value = '';
if (shareLinksList) shareLinksList.innerHTML = '';
linkContainer.style.display = 'none';
loading.style.display = 'block';
modal.style.display = 'block';
if (paths.length === 1) {
shareFileName.textContent = `Sharing: ${names[0]}`;
} else {
shareFileName.textContent = `Sharing ${paths.length} items`;
}
try {
const response = await fetch(`/files/share/${path}`, {
method: 'POST'
const response = await fetch(`/files/share_multiple`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ paths: paths })
});
const data = await response.json();
if (data.share_link) {
document.getElementById('share-link-input').value = data.share_link;
if (data.share_links && data.share_links.length > 0) {
if (data.share_links.length === 1) {
shareLinkInput.value = data.share_links[0];
linkContainer.style.display = 'block';
} else {
// Display multiple links
if (!shareLinksList) {
// Create the list if it doesn't exist
const ul = document.createElement('ul');
ul.id = 'share-links-list';
linkContainer.appendChild(ul);
shareLinksList = ul;
}
data.share_links.forEach(item => {
const li = document.createElement('li');
li.innerHTML = `<strong>${item.name}:</strong> <input type="text" value="${item.link}" readonly class="form-input share-link-item-input"> <button class="btn-primary copy-share-link-item-btn" data-link="${item.link}">Copy</button>`;
shareLinksList.appendChild(li);
});
linkContainer.style.display = 'block';
}
loading.style.display = 'none';
} else {
loading.textContent = 'Error generating share link(s)';
}
} catch (error) {
loading.textContent = 'Error generating share link';
console.error('Error sharing files:', error);
loading.textContent = 'Error generating share link(s)';
}
}
@ -140,10 +175,31 @@ document.addEventListener('DOMContentLoaded', () => {
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';
function deleteFile(paths, names) {
const deleteForm = document.getElementById('delete-form');
const deleteMessage = document.getElementById('delete-message');
const deleteModal = document.getElementById('delete-modal');
// Clear previous hidden inputs
deleteForm.querySelectorAll('input[name="paths[]"]').forEach(input => input.remove());
if (Array.isArray(paths) && paths.length > 1) {
deleteMessage.textContent = `Are you sure you want to delete ${paths.length} items? This action cannot be undone.`;
deleteForm.action = `/files/delete_multiple`;
paths.forEach(path => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'paths[]';
input.value = path;
deleteForm.appendChild(input);
});
} else {
const path = Array.isArray(paths) ? paths[0] : paths;
const name = Array.isArray(names) ? names[0] : names;
deleteMessage.textContent = `Are you sure you want to delete "${name}"? This action cannot be undone.`;
deleteForm.action = `/files/delete/${path}`;
}
deleteModal.style.display = 'block';
}
// Selection and action buttons
@ -170,10 +226,12 @@ document.addEventListener('DOMContentLoaded', () => {
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);
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()
);
shareFile(paths, names);
}
}
@ -184,14 +242,7 @@ document.addEventListener('DOMContentLoaded', () => {
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';
}
deleteFile(paths, names);
}
}
@ -255,9 +306,21 @@ document.addEventListener('DOMContentLoaded', () => {
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);
const path = event.target.dataset.path;
const name = event.target.dataset.name;
shareFile([path], [name]);
} else if (event.target.classList.contains('delete-file-btn')) {
deleteFile(event.target.dataset.path, event.target.dataset.name);
const path = event.target.dataset.path;
const name = event.target.dataset.name;
deleteFile([path], [name]);
} else if (event.target.classList.contains('copy-share-link-item-btn')) {
const linkToCopy = event.target.dataset.link;
navigator.clipboard.writeText(linkToCopy).then(() => {
alert('Share link copied to clipboard');
}).catch(err => {
console.error('Failed to copy link: ', err);
alert('Failed to copy link');
});
}
});

View File

@ -130,6 +130,7 @@
<div id="share-link-container" style="display: none;">
<input type="text" id="share-link-input" readonly class="form-input">
<button class="btn-primary" id="copy-share-link-btn">Copy Link</button>
<div id="share-links-list" class="share-links-list"></div>
</div>
<div id="share-loading">Generating share link...</div>
<div class="modal-actions">

View File

@ -235,14 +235,77 @@ class FileBrowserView(web.View):
)
)
elif route_name == "delete_multiple_items":
data = await self.request.post()
paths = data.getall("paths[]")
logger.debug(f"FileBrowserView: Delete multiple items request for paths: {paths} by user {user_email}")
if not paths:
logger.warning("FileBrowserView: Delete multiple items request missing paths")
raise web.HTTPFound(
self.request.app.router["file_browser"].url_for().with_query(
error="No items selected for deletion"
)
)
all_successful = True
for path in paths:
success = await file_service.delete_item(user_email, path)
if not success:
all_successful = False
logger.error(f"FileBrowserView: Failed to delete item '{path}' for user {user_email} during bulk delete")
if all_successful:
logger.info(f"FileBrowserView: All {len(paths)} items deleted successfully for user {user_email}")
raise web.HTTPFound(
self.request.app.router["file_browser"].url_for().with_query(
success=f"Successfully deleted {len(paths)} items"
)
)
else:
logger.error(f"FileBrowserView: Some items failed to delete for user {user_email} during bulk delete")
raise web.HTTPFound(
self.request.app.router["file_browser"].url_for().with_query(
error="Some items failed to delete"
)
)
elif route_name == "share_multiple_items":
data = await self.request.json()
paths = data.get("paths", [])
logger.debug(f"FileBrowserView: Share multiple items request for paths: {paths} by user {user_email}")
if not paths:
logger.warning("FileBrowserView: Share multiple items request missing paths")
return json_response({"error": "No items selected for sharing"}, status=400)
share_links = []
for path in paths:
share_id = await file_service.generate_share_link(user_email, path)
if share_id:
share_link = f"{self.request.scheme}://{self.request.host}/shared_file/{share_id}"
# Extract file name from path
file_name = os.path.basename(path)
share_links.append({"name": file_name, "link": share_link})
else:
logger.error(f"FileBrowserView: Failed to generate share link for file: {path} by user {user_email}")
if share_links:
logger.info(f"FileBrowserView: Generated {len(share_links)} share links for user {user_email}")
return json_response({"share_links": share_links})
else:
logger.error(f"FileBrowserView: Failed to generate any share links for user {user_email}")
return json_response({"error": "Failed to generate share links for any selected items"}, status=500)
logger.warning(f"FileBrowserView: Unknown file action for POST request: {route_name}")
raise web.HTTPBadRequest(text="Unknown file action")
@login_required
async def get_download_file(self):
user_email = self.request["user"]["email"]
file_service = self.request.app["file_service"]
file_path = self.request.match_info.get("file_path")
request = self # self is the request object here
user_email = request["user"]["email"]
file_service = request.app["file_service"]
file_path = request.match_info.get("file_path")
logger.debug(f"FileBrowserView: Download file request for path: {file_path} by user {user_email}")
if not file_path:
logger.warning("FileBrowserView: Download file request missing file_path")