diff --git a/frontend/src/components/config/MetadataConfigSection.tsx b/frontend/src/components/config/MetadataConfigSection.tsx index 1b638bbd7..996a324e5 100644 --- a/frontend/src/components/config/MetadataConfigSection.tsx +++ b/frontend/src/components/config/MetadataConfigSection.tsx @@ -315,30 +315,6 @@ export function MetadataConfigSection({ - - diff --git a/frontend/src/components/config/WorkersConfigSection.tsx b/frontend/src/components/config/WorkersConfigSection.tsx index 981639ad1..14c82b7ba 100644 --- a/frontend/src/components/config/WorkersConfigSection.tsx +++ b/frontend/src/components/config/WorkersConfigSection.tsx @@ -282,6 +282,33 @@ export function ImportConfigSection({ + +
+ +
diff --git a/frontend/src/pages/QueuePage.tsx b/frontend/src/pages/QueuePage.tsx index 989b11467..ce23cf45e 100644 --- a/frontend/src/pages/QueuePage.tsx +++ b/frontend/src/pages/QueuePage.tsx @@ -34,6 +34,7 @@ import { Pagination } from "../components/ui/Pagination"; import { PathDisplay } from "../components/ui/PathDisplay"; import { StatusBadge } from "../components/ui/StatusBadge"; import { useConfirm } from "../contexts/ModalContext"; +import { useToast } from "../contexts/ToastContext"; import { useAddTestQueueItem, useBulkCancelQueueItems, @@ -130,6 +131,7 @@ export function QueuePage() { const addTestQueueItem = useAddTestQueueItem(); const regenerateSymlinks = useRegenerateSymlinks(); const { confirmDelete, confirmAction } = useConfirm(); + const { showToast } = useToast(); const handleDelete = useCallback( async (id: number) => { @@ -166,10 +168,36 @@ export function QueuePage() { [confirmAction, cancelItem], ); - const handleDownload = async (id: number) => { + const handleDownload = async (id: number, status?: string) => { try { const response = await fetch(`/api/queue/${id}/download`); - if (!response.ok) throw new Error("Failed to download NZB file"); + if (!response.ok) { + let title = "Download Failed"; + let message = `Server returned ${response.status} ${response.statusText}`; + try { + const body = (await response.json()) as { + error?: { message?: string; details?: string }; + }; + if (body?.error?.message) { + title = body.error.message; + message = body.error.details || ""; + } + } catch { + // Non-JSON error body — fall back to status text. + } + // For completed items, a missing file almost always means the server + // cleaned it up post-import (delete_completed_nzb). Soften the toast. + if (response.status === 404 && status === "completed") { + showToast({ + type: "info", + title: "NZB file already removed", + message: "This NZB was cleaned up after successful import.", + }); + return; + } + showToast({ type: "error", title, message }); + return; + } const contentDisposition = response.headers.get("Content-Disposition"); const filenameMatch = contentDisposition?.match(/filename[^;=\n]*=["']?([^"'\n]*)["']?/); const filename = filenameMatch?.[1] || `queue-${id}.nzb`; @@ -184,6 +212,11 @@ export function QueuePage() { document.body.removeChild(a); } catch (error) { console.error("Failed to download NZB:", error); + showToast({ + type: "error", + title: "Download Failed", + message: error instanceof Error ? error.message : "Network error", + }); } }; @@ -944,7 +977,7 @@ export function QueuePage() {