fix: resolve upload delay and refine trash functionality
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m22s

This commit is contained in:
Elijah 2026-05-23 14:11:23 -07:00
parent 45989ef97d
commit db946461d7
3 changed files with 49 additions and 21 deletions

View file

@ -534,6 +534,19 @@ func (h *FSHandler) Delete(c *fiber.Ctx) error {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "file not found"}) return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "file not found"})
} }
// Check if permanent
isPermanent := c.Query("permanent") == "true"
if isPermanent {
if err := os.RemoveAll(resolvedPath); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to permanently delete"})
}
cleanPath := filepath.ToSlash(relativePath)
h.DB.Exec(`DELETE FROM files WHERE path = ? OR path LIKE ?`, cleanPath, cleanPath+"/%")
h.DB.AddAuditLog("permanently_deleted", cleanPath, c.IP())
return c.JSON(fiber.Map{"message": "permanently deleted"})
}
// Soft-delete: move to .trash directory // Soft-delete: move to .trash directory
trashPath := filepath.Join(h.Config.TrashDir, relativePath) trashPath := filepath.Join(h.Config.TrashDir, relativePath)
if err := os.MkdirAll(filepath.Dir(trashPath), 0755); err != nil { if err := os.MkdirAll(filepath.Dir(trashPath), 0755); err != nil {

View file

@ -631,20 +631,22 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
async function executeDeleteSelected() { async function executeDeleteSelected() {
const count = selectedFiles.size; const count = selectedFiles.size;
const isPermanent = activeSection === 'trash';
for (const path of selectedFiles) { for (const path of selectedFiles) {
await api.deleteFile(path); await api.deleteFile(path, isPermanent);
} }
setSelectedFiles(new Set()); setSelectedFiles(new Set());
loadFiles(undefined, true); loadFiles(undefined, true);
setDeleteConfirm(null); setDeleteConfirm(null);
showToast(`Deleted ${count} item${count > 1 ? 's' : ''}`); showToast(isPermanent ? `Permanently deleted ${count} item${count > 1 ? 's' : ''}` : `Deleted ${count} item${count > 1 ? 's' : ''}`);
} }
async function executeDeleteSingle(file: FileItem) { async function executeDeleteSingle(file: FileItem) {
await api.deleteFile(file.path); const isPermanent = activeSection === 'trash';
await api.deleteFile(file.path, isPermanent);
loadFiles(undefined, true); loadFiles(undefined, true);
setDeleteConfirm(null); setDeleteConfirm(null);
showToast(`Deleted ${file.name}`); showToast(isPermanent ? `Permanently deleted ${file.name}` : `Deleted ${file.name}`);
} }
async function handlePin(file: FileItem) { async function handlePin(file: FileItem) {
@ -880,16 +882,16 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
} }
await Promise.all(workers); await Promise.all(workers);
loadFiles(undefined, true);
setTimeout(() => { setTimeout(() => {
setUploads(prev => prev.filter(u => u.id !== batchId)); setUploads(prev => prev.filter(u => u.id !== batchId));
loadFiles(undefined, true);
}, 3000); }, 3000);
} }
async function handleDrop(e: React.DragEvent) { async function handleDrop(e: React.DragEvent) {
e.preventDefault(); e.preventDefault();
setDragOver(false); setDragOver(false);
if (draggedFile) return; if (draggedFile || activeSection === 'trash') return;
if (e.dataTransfer.items) { if (e.dataTransfer.items) {
const filesToUpload: { file: File; path: string }[] = []; const filesToUpload: { file: File; path: string }[] = [];
@ -968,7 +970,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
async function handleFileUpload(e: React.ChangeEvent<HTMLInputElement>) { async function handleFileUpload(e: React.ChangeEvent<HTMLInputElement>) {
const uploadFiles = e.target.files; const uploadFiles = e.target.files;
if (!uploadFiles) return; if (!uploadFiles || activeSection === 'trash') return;
const filesArray = Array.from(uploadFiles).map(f => ({ file: f, path: '' })); const filesArray = Array.from(uploadFiles).map(f => ({ file: f, path: '' }));
const fileNames = filesArray.map(f => f.file.name); const fileNames = filesArray.map(f => f.file.name);
@ -1777,6 +1779,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
> >
<Download className="w-4 h-4" /> <Download className="w-4 h-4" />
</button> </button>
{activeSection !== 'trash' && (
<button <button
className="p-2 hover:bg-white/5 rounded-lg transition-colors flex items-center gap-2 text-sm" className="p-2 hover:bg-white/5 rounded-lg transition-colors flex items-center gap-2 text-sm"
style={{ color: 'var(--color-text-primary)' }} style={{ color: 'var(--color-text-primary)' }}
@ -1789,6 +1792,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
> >
<Move className="w-4 h-4" /> <Move className="w-4 h-4" />
</button> </button>
)}
<button <button
className="p-2 hover:bg-red-500/10 rounded-lg transition-colors flex items-center gap-2 text-sm text-red-500" className="p-2 hover:bg-red-500/10 rounded-lg transition-colors flex items-center gap-2 text-sm text-red-500"
title="Delete Selected" title="Delete Selected"
@ -1839,6 +1843,16 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
label="Restore" label="Restore"
onClick={() => { handleRestore(contextMenu.file); setContextMenu(null); }} onClick={() => { handleRestore(contextMenu.file); setContextMenu(null); }}
/> />
<div className="h-px mx-2 my-1" style={{ backgroundColor: 'var(--color-border-subtle)' }} />
<ContextMenuItem
icon={<Trash2 className="w-4 h-4" />}
label="Delete Permanently"
danger
onClick={() => {
setDeleteConfirm({ type: 'single', file: contextMenu.file });
setContextMenu(null);
}}
/>
</> </>
) : ( ) : (
<> <>

View file

@ -201,8 +201,9 @@ class ApiClient {
return res.json(); return res.json();
} }
async deleteFile(path: string) { async deleteFile(path: string, permanent: boolean = false) {
const res = await this.request(`/api/files/?path=${encodeURIComponent(path)}`, { const query = permanent ? '&permanent=true' : '';
const res = await this.request(`/api/files/?path=${encodeURIComponent(path)}${query}`, {
method: 'DELETE', method: 'DELETE',
}); });
return res.json(); return res.json();