fix: resolve upload delay and refine trash functionality
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m22s
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m22s
This commit is contained in:
parent
45989ef97d
commit
db946461d7
3 changed files with 49 additions and 21 deletions
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
Reference in a new issue