Fix Downloads search UI and clarify DS destination errors.

Clear results after starting a job, show one clean list layout, and map Synology 403 to a readable destination hint.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jos Vooges | STH 2026-08-30 18:19:24 +02:00
parent 12a99aec0f
commit 9843d20614
3 changed files with 91 additions and 57 deletions

View file

@ -273,6 +273,9 @@ export default function DownloadsPage() {
return;
}
setPick(null);
setHits([]);
setQuery("");
setSearchErr(null);
loadJobs();
} finally {
setJobBusy(false);
@ -346,7 +349,7 @@ export default function DownloadsPage() {
/>
</label>
<label>
DS destination (share-relatief)
DS destination (share-naam, géén /volume1)
<input
value={cfg.downloadDestination}
onChange={(e) => setCfg({ ...cfg, downloadDestination: e.target.value })}
@ -354,7 +357,7 @@ export default function DownloadsPage() {
/>
</label>
<label>
Download host-pad (node)
Download host-pad (node, wél /volume1/…)
<input
value={cfg.downloadHostPath}
onChange={(e) => setCfg({ ...cfg, downloadHostPath: e.target.value })}
@ -401,27 +404,29 @@ export default function DownloadsPage() {
{searchErr && <p className="danger-text">{searchErr}</p>}
{!hasConfig && <p className="muted">Sla eerst de DS-configuratie op.</p>}
<div className="mobile-cards" style={{ marginTop: "1rem" }}>
{hits.length > 0 && (
<>
<div className="list-cards" style={{ marginTop: "1rem" }}>
{hits.map((h) => (
<article key={`${h.title}-${h.size}-${h.seeds}`} className="mobile-card">
<div className="mobile-card-title">{h.title}</div>
<div className="muted">
{formatBytes(h.size)} · {h.seeds} seeds · {h.module || "module"}
</div>
<button
type="button"
disabled={!h.uri}
onClick={() => void openMatch(h)}
style={{ marginTop: "0.5rem" }}
>
<article key={`${h.title}-${h.size}-${h.seeds}`} className="list-card">
<div className="list-card-top">
<div className="list-card-title">{h.title}</div>
<button type="button" disabled={!h.uri} onClick={() => void openMatch(h)}>
Kiezen
</button>
</div>
<div className="list-card-meta muted">
<span>{formatBytes(h.size)}</span>
<span>
{h.seeds}/{h.peers} seeds
</span>
<span>{h.module || "—"}</span>
</div>
</article>
))}
</div>
<div className="desktop-table" style={{ marginTop: "1rem" }}>
{hits.length > 0 && (
<table>
<thead>
<tr>
@ -451,8 +456,9 @@ export default function DownloadsPage() {
))}
</tbody>
</table>
)}
</div>
</>
)}
</div>
</section>
@ -461,14 +467,14 @@ export default function DownloadsPage() {
<h2 className="section-title">Jobs</h2>
</div>
<div className="card">
<div className="mobile-cards">
<div className="list-cards">
{jobs.map((j) => (
<article key={j.id} className="mobile-card">
<div className="mobile-card-head">
<article key={j.id} className="list-card">
<div className="list-card-top">
<span className={statusClass(j.status)}>{j.status}</span>
<span className="muted">{j.nodeName}</span>
</div>
<div className="mobile-card-title">
<div className="list-card-title">
{j.movieTitle}
{j.movieYear ? ` (${j.movieYear})` : ""}
</div>

View file

@ -779,6 +779,16 @@ tr.row-selected {
gap: 0.75rem;
}
.list-card-title {
font-weight: 600;
color: var(--text-bright);
font-size: 0.92rem;
line-height: 1.35;
flex: 1;
min-width: 0;
word-break: break-word;
}
.list-card-meta {
display: flex;
justify-content: space-between;

View file

@ -246,7 +246,7 @@ export class SynologyDownloadStation {
const url = `${this.root()}/webapi/DownloadStation/task.cgi`;
const res = (await requestJson(url, { method: "POST", body })) as DsApiResponse;
if (!res.success) {
throw new Error(`Download-taak aanmaken mislukt (code ${res.error?.code ?? "?"})`);
throw new Error(dsTaskError(res.error?.code, "Download-taak aanmaken mislukt"));
}
});
}
@ -294,6 +294,24 @@ function sleep(ms: number): Promise<void> {
return new Promise((r) => setTimeout(r, ms));
}
/** Human-readable Synology Download Station Task API errors. */
function dsTaskError(code: number | undefined, prefix: string): string {
const hints: Record<number, string> = {
400: "bestand-upload mislukt",
401: "maximaal aantal taken bereikt",
402: "geen schrijfrecht op destination",
403: "destination bestaat niet — gebruik share-relatief pad, bv. Downloads of Downloads/complete (niet /volume1/...)",
404: "ongeldige task-id",
405: "ongeldige actie",
406: "geen default destination in Download Station",
407: "destination instellen mislukt",
408: "bestand bestaat niet",
};
const hint = code != null ? hints[code] : undefined;
if (hint) return `${prefix}: ${hint} (code ${code})`;
return `${prefix} (code ${code ?? "?"})`;
}
/** Strip common release tags so TMDB search works better. */
export function cleanReleaseQuery(raw: string): string {
let s = raw.trim();