feat(web/m7-381): admin overview Library Scan section + manual trigger

Adds getScanStatus/triggerScan helpers, createScanStatusQuery factory
(3s poll), qk.scanStatus(), and a Library Scan section on the admin
overview page with per-stage tallies and inline Run scan button.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-04 20:16:29 -04:00
parent 3d0e213081
commit 05bc2628a3
4 changed files with 201 additions and 1 deletions
+54
View File
@@ -181,3 +181,57 @@ export type RefetchMissingResponse = {
export async function refetchMissingCovers(): Promise<RefetchMissingResponse> {
return api.post<RefetchMissingResponse>('/api/admin/covers/refetch-missing', {});
}
// Library scan -------------------------------------------------------------
export type ScanStageLibrary = {
scanned: number;
added: number;
updated: number;
skipped: number;
errored: number;
};
export type ScanStageMbidBackfill = {
processed: number;
healed: number;
skipped: number;
};
export type ScanStageCoverEnrich = {
processed: number;
succeeded: number;
failed: number;
};
export type ScanStatus = {
id: string;
started_at: string;
finished_at: string | null;
library?: ScanStageLibrary;
mbid_backfill?: ScanStageMbidBackfill;
cover_enrich?: ScanStageCoverEnrich;
error_message?: string;
in_flight: boolean;
};
export async function getScanStatus(): Promise<ScanStatus> {
return api.get<ScanStatus>('/api/admin/scan/status');
}
export type TriggerScanResp = { id?: string };
export async function triggerScan(): Promise<TriggerScanResp> {
return api.post<TriggerScanResp>('/api/admin/scan/run', {});
}
// Polls every 3s while a scan is in flight; falls back to a 30s stale-time
// when idle so the section auto-updates as workers complete each stage.
export function createScanStatusQuery() {
return createQuery({
queryKey: qk.scanStatus(),
queryFn: getScanStatus,
staleTime: 30_000,
refetchInterval: 3_000
});
}