diff --git a/apps/admin-ui/src/app/dashboard/page.tsx b/apps/admin-ui/src/app/dashboard/page.tsx
index 6256b2d..d15e547 100644
--- a/apps/admin-ui/src/app/dashboard/page.tsx
+++ b/apps/admin-ui/src/app/dashboard/page.tsx
@@ -50,6 +50,9 @@ export default function DashboardPage() {
const [dedupeMsg, setDedupeMsg] = useState("");
const [imdbMsg, setImdbMsg] = useState("");
const [repairMsg, setRepairMsg] = useState("");
+ const [repairBusy, setRepairBusy] = useState(false);
+ const [dedupeBusy, setDedupeBusy] = useState(false);
+ const [imdbBusy, setImdbBusy] = useState(false);
const load = useCallback(() => {
fetch("/api/v1/admin/dashboard", { credentials: "include" })
@@ -73,26 +76,39 @@ export default function DashboardPage() {
async function repairLibrary() {
if (
!confirm(
- "Bibliotheek herstellen na pad-migratie?\n\n• Duplicaten samenvoegen naar IMDb/TMDB-records\n• Dode (unavailable) bestanden verwijderen\n• Ontbrekende IMDb-IDs backfillen\n\nDit kan enkele minuten duren."
+ "Bibliotheek herstellen na pad-migratie?\n\n• Duplicaten samenvoegen naar IMDb/TMDB-records\n• Dode (unavailable) bestanden verwijderen\n• Dubbele file-rijen opruimen\n• Ontbrekende IMDb-IDs backfillen (max ~400 per run)\n\nDuurt meestal 1–3 minuten. Herhaal indien nodig."
)
) {
return;
}
- setRepairMsg("Bezig…");
- const res = await fetch("/api/v1/admin/library/repair", {
- method: "POST",
- credentials: "include",
- });
- const json = await res.json();
- if (!res.ok) {
- setRepairMsg(json.error?.message ?? "Mislukt");
- return;
+ setRepairBusy(true);
+ setRepairMsg("Bezig — duplicaten en dode files opruimen…");
+ try {
+ const res = await fetch("/api/v1/admin/library/repair", {
+ method: "POST",
+ credentials: "include",
+ });
+ const json = await res.json().catch(() => ({}));
+ if (!res.ok) {
+ setRepairMsg(json.error?.message ?? `Mislukt (HTTP ${res.status})`);
+ return;
+ }
+ const imdb = json.imdb ?? {};
+ const imdbRemaining =
+ (imdb.movies?.remaining ?? 0) + (imdb.series?.remaining ?? 0);
+ setRepairMsg(
+ `Klaar: ${json.moviesMerged ?? 0} film-merges, ${json.seriesMerged ?? 0} serie-merges, ${json.staleFilesRemoved ?? 0} dode files, ${json.duplicateFilesRemoved ?? 0} dubbele files · IMDb +${imdb.movies?.updated ?? 0} films, +${imdb.series?.updated ?? 0} series${imdbRemaining > 0 ? ` · nog ${imdbRemaining} IMDb open (herhaal of gebruik backfill-knop)` : ""}. Herlaad daarna de Stremio-addon.`
+ );
+ load();
+ } catch (err) {
+ setRepairMsg(
+ err instanceof Error
+ ? `Verbinding mislukt: ${err.message}`
+ : "Verbinding mislukt — timeout of server offline?"
+ );
+ } finally {
+ setRepairBusy(false);
}
- const imdb = json.imdb ?? {};
- setRepairMsg(
- `Klaar: ${json.moviesMerged ?? 0} film-merges, ${json.seriesMerged ?? 0} serie-merges, ${json.staleFilesRemoved ?? 0} dode files weg · IMDb films +${imdb.movies?.updated ?? 0}, series +${imdb.series?.updated ?? 0}. Herlaad daarna de Stremio-addon.`
- );
- load();
}
async function backfillImdb() {
@@ -103,22 +119,29 @@ export default function DashboardPage() {
) {
return;
}
+ setImdbBusy(true);
setImdbMsg("Bezig…");
- const res = await fetch("/api/v1/admin/library/backfill-imdb", {
- method: "POST",
- credentials: "include",
- });
- const json = await res.json();
- if (!res.ok) {
- setImdbMsg(json.error?.message ?? "Mislukt");
- return;
+ try {
+ const res = await fetch("/api/v1/admin/library/backfill-imdb", {
+ method: "POST",
+ credentials: "include",
+ });
+ const json = await res.json().catch(() => ({}));
+ if (!res.ok) {
+ setImdbMsg(json.error?.message ?? "Mislukt");
+ return;
+ }
+ const movies = json.movies ?? json;
+ const series = json.series ?? {};
+ setImdbMsg(
+ `Films: gecontroleerd ${movies.checked ?? 0}, bijgewerkt ${movies.updated ?? 0}, open ${movies.remaining ?? 0} · Series: gecontroleerd ${series.checked ?? 0}, bijgewerkt ${series.updated ?? 0}, open ${series.remaining ?? 0}`
+ );
+ load();
+ } catch (err) {
+ setImdbMsg(err instanceof Error ? `Mislukt: ${err.message}` : "Mislukt");
+ } finally {
+ setImdbBusy(false);
}
- const movies = json.movies ?? json;
- const series = json.series ?? {};
- setImdbMsg(
- `Films: gecontroleerd ${movies.checked ?? 0}, bijgewerkt ${movies.updated ?? 0}, open ${movies.remaining ?? 0} · Series: gecontroleerd ${series.checked ?? 0}, bijgewerkt ${series.updated ?? 0}, open ${series.remaining ?? 0}`
- );
- load();
}
async function dedupe() {
@@ -129,20 +152,27 @@ export default function DashboardPage() {
) {
return;
}
+ setDedupeBusy(true);
setDedupeMsg("Bezig…");
- const res = await fetch("/api/v1/admin/library/dedupe", {
- method: "POST",
- credentials: "include",
- });
- const json = await res.json();
- if (!res.ok) {
- setDedupeMsg(json.error?.message ?? "Mislukt");
- return;
+ try {
+ const res = await fetch("/api/v1/admin/library/dedupe", {
+ method: "POST",
+ credentials: "include",
+ });
+ const json = await res.json().catch(() => ({}));
+ if (!res.ok) {
+ setDedupeMsg(json.error?.message ?? "Mislukt");
+ return;
+ }
+ setDedupeMsg(
+ `Klaar: ${json.moviesMerged} film-duplicaten, ${json.seriesMerged} serie-duplicaten, ${json.emptied} lege records opgeruimd.`
+ );
+ load();
+ } catch (err) {
+ setDedupeMsg(err instanceof Error ? `Mislukt: ${err.message}` : "Mislukt");
+ } finally {
+ setDedupeBusy(false);
}
- setDedupeMsg(
- `Klaar: ${json.moviesMerged} film-duplicaten, ${json.seriesMerged} serie-duplicaten, ${json.emptied} lege records opgeruimd.`
- );
- load();
}
return (
@@ -234,16 +264,16 @@ export default function DashboardPage() {
Duplicaten + dode files + IMDb-backfill na Unraid pad-migratie.
-