Update.
This commit is contained in:
parent
ea8af383cc
commit
5d3d0b162d
@ -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")
|
||||
|
||||
|
||||
@ -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;
|
||||
linkContainer.style.display = 'block';
|
||||
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');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user