From 7426f6c71885b28dd8314aefed1ec09595d39148 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sat, 6 Jun 2026 21:52:20 -0400 Subject: [PATCH] feat(web): remove scan-schedule admin UI (scheduler retired) Co-Authored-By: Claude Opus 4.8 (1M context) --- web/src/lib/api/admin.scan-schedule.test.ts | 58 ------- web/src/lib/api/admin.ts | 37 ----- web/src/lib/api/queries.ts | 1 - web/src/routes/admin/+page.svelte | 160 +------------------- web/src/routes/admin/admin.test.ts | 24 +-- 5 files changed, 2 insertions(+), 278 deletions(-) delete mode 100644 web/src/lib/api/admin.scan-schedule.test.ts diff --git a/web/src/lib/api/admin.scan-schedule.test.ts b/web/src/lib/api/admin.scan-schedule.test.ts deleted file mode 100644 index 86001b5c..00000000 --- a/web/src/lib/api/admin.scan-schedule.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { getScanSchedule, updateScanSchedule, type ScanSchedule } from './admin'; - -vi.mock('./client', () => ({ - api: { get: vi.fn(), post: vi.fn(), patch: vi.fn() } -})); - -import { api } from './client'; - -describe('admin scan-schedule API', () => { - beforeEach(() => vi.clearAllMocks()); - - it('getScanSchedule GETs the correct path', async () => { - const sample: ScanSchedule = { - mode: 'daily', - interval_hours: null, - time_of_day: '03:00', - weekly_day: null, - next_scheduled_at: '2026-05-07T03:00:00Z' - }; - (api.get as unknown as ReturnType).mockResolvedValueOnce(sample); - const got = await getScanSchedule(); - expect(api.get).toHaveBeenCalledWith('/api/admin/scan/schedule'); - expect(got).toEqual(sample); - }); - - it('updateScanSchedule PATCHes with the right body', async () => { - const sample: ScanSchedule = { - mode: 'interval', - interval_hours: 6, - time_of_day: null, - weekly_day: null, - next_scheduled_at: '2026-05-06T20:00:00Z' - }; - (api.patch as unknown as ReturnType).mockResolvedValueOnce(sample); - const got = await updateScanSchedule({ mode: 'interval', interval_hours: 6 }); - expect(api.patch).toHaveBeenCalledWith('/api/admin/scan/schedule', { - mode: 'interval', - interval_hours: 6 - }); - expect(got.mode).toBe('interval'); - expect(got.interval_hours).toBe(6); - }); - - it('updateScanSchedule with mode=off sends just mode', async () => { - const sample: ScanSchedule = { - mode: 'off', - interval_hours: null, - time_of_day: null, - weekly_day: null, - next_scheduled_at: null - }; - (api.patch as unknown as ReturnType).mockResolvedValueOnce(sample); - const got = await updateScanSchedule({ mode: 'off' }); - expect(api.patch).toHaveBeenCalledWith('/api/admin/scan/schedule', { mode: 'off' }); - expect(got.next_scheduled_at).toBe(null); - }); -}); diff --git a/web/src/lib/api/admin.ts b/web/src/lib/api/admin.ts index 96e621dc..9533a9e9 100644 --- a/web/src/lib/api/admin.ts +++ b/web/src/lib/api/admin.ts @@ -383,43 +383,6 @@ export function createCoverProvidersQuery() { }); } -// Scan schedule ----------------------------------------------------------- - -export type ScanScheduleMode = 'off' | 'interval' | 'daily' | 'weekly'; - -export type ScanSchedule = { - mode: ScanScheduleMode; - interval_hours: number | null; - time_of_day: string | null; // 'HH:MM' - weekly_day: number | null; // 1..7 (Mon..Sun, ISO 8601) - next_scheduled_at: string | null; // RFC3339 ISO timestamp; null when mode='off' -}; - -export type ScanScheduleUpdate = { - mode: ScanScheduleMode; - interval_hours?: number | null; - time_of_day?: string | null; - weekly_day?: number | null; -}; - -export async function getScanSchedule(): Promise { - return api.get('/api/admin/scan/schedule'); -} - -export async function updateScanSchedule(patch: ScanScheduleUpdate): Promise { - return api.patch('/api/admin/scan/schedule', patch); -} - -// Settings rarely change in operator time. 60s staleTime; refetches -// on window focus + after any PATCH invalidation — no polling. -export function createScanScheduleQuery() { - return createQuery({ - queryKey: qk.scanSchedule(), - queryFn: getScanSchedule, - staleTime: 60_000 - }); -} - // Admin user-management ------------------------------------------------------ export type AdminUser = { diff --git a/web/src/lib/api/queries.ts b/web/src/lib/api/queries.ts index 37dafb5c..50f9ad9e 100644 --- a/web/src/lib/api/queries.ts +++ b/web/src/lib/api/queries.ts @@ -46,7 +46,6 @@ export const qk = { adminPlaybackErrors: (resolved?: boolean) => ['adminPlaybackErrors', { resolved: resolved ?? false }] as const, scanStatus: () => ['scanStatus'] as const, - scanSchedule: () => ['scanSchedule'] as const, coverage: () => ['coverage'] as const, coverProviders: () => ['coverProviders'] as const, adminUsers: () => ['adminUsers'] as const, diff --git a/web/src/routes/admin/+page.svelte b/web/src/routes/admin/+page.svelte index 54718b28..0ca34f8d 100644 --- a/web/src/routes/admin/+page.svelte +++ b/web/src/routes/admin/+page.svelte @@ -10,8 +10,6 @@ createAdminQuarantineQuery, createScanStatusQuery, createCoverageQuery, - createScanScheduleQuery, - updateScanSchedule, approveRequest, rejectRequest, resolveQuarantine, @@ -19,9 +17,7 @@ deleteQuarantineViaLidarr, refetchMissingCovers, triggerScan, - researchMissingArt, - type ScanSchedule, - type ScanScheduleMode + researchMissingArt } from '$lib/api/admin'; import { qk } from '$lib/api/queries'; import { errCode } from '$lib/api/errors'; @@ -211,89 +207,6 @@ return new Date(iso).toLocaleString(); } - // ---- Scan schedule ---- - const scheduleStore = $derived(createScanScheduleQuery()); - const scheduleQ = $derived($scheduleStore); - const schedule = $derived(scheduleQ.data); - - let scheduleLocal = $state<{ - mode: ScanScheduleMode; - interval_hours: number; - time_of_day: string; - weekly_day: number; - }>({ - mode: 'off', - interval_hours: 6, - time_of_day: '03:00', - weekly_day: 7, - }); - let scheduleSaving = $state(false); - - $effect(() => { - if (schedule) { - scheduleLocal = { - mode: schedule.mode, - interval_hours: schedule.interval_hours ?? 6, - time_of_day: schedule.time_of_day ?? '03:00', - weekly_day: schedule.weekly_day ?? 7, - }; - } - }); - - function scheduleHasChanges(): boolean { - if (!schedule) return false; - if (scheduleLocal.mode !== schedule.mode) return true; - switch (scheduleLocal.mode) { - case 'interval': - return scheduleLocal.interval_hours !== (schedule.interval_hours ?? -1); - case 'daily': - return scheduleLocal.time_of_day !== (schedule.time_of_day ?? ''); - case 'weekly': - return scheduleLocal.time_of_day !== (schedule.time_of_day ?? '') - || scheduleLocal.weekly_day !== (schedule.weekly_day ?? -1); - default: - return false; - } - } - - async function onSaveSchedule() { - scheduleSaving = true; - try { - const patch: { mode: ScanScheduleMode; interval_hours?: number; time_of_day?: string; weekly_day?: number } = { - mode: scheduleLocal.mode, - }; - if (scheduleLocal.mode === 'interval') patch.interval_hours = scheduleLocal.interval_hours; - if (scheduleLocal.mode === 'daily' || scheduleLocal.mode === 'weekly') patch.time_of_day = scheduleLocal.time_of_day; - if (scheduleLocal.mode === 'weekly') patch.weekly_day = scheduleLocal.weekly_day; - await updateScanSchedule(patch); - await client.invalidateQueries({ queryKey: qk.scanSchedule() }); - } catch (e) { - pushToast(`Schedule save failed: ${errCode(e)}`, 'error'); - } finally { - scheduleSaving = false; - } - } - - function formatRelative(iso: string): string { - const target = new Date(iso); - const now = new Date(); - const diffMs = target.getTime() - now.getTime(); - const absHours = Math.abs(diffMs) / 3_600_000; - let rel: string; - if (absHours < 1) { - const mins = Math.round(Math.abs(diffMs) / 60_000); - rel = diffMs >= 0 ? `in ${mins} min` : `${mins} min ago`; - } else if (absHours < 48) { - const h = Math.round(absHours); - rel = diffMs >= 0 ? `in ${h} hour${h === 1 ? '' : 's'}` : `${h} hour${h === 1 ? '' : 's'} ago`; - } else { - const days = Math.round(absHours / 24); - rel = diffMs >= 0 ? `in ${days} days` : `${days} days ago`; - } - const local = target.toLocaleString(); - return `${rel} (${local})`; - } - // ---- Cover art bulk refetch ---- let bulkBusy = $state(false); let bulkResult = $state(null); @@ -517,77 +430,6 @@ {/if} - -
-
-

Scan schedule

-

- Run a library scan automatically on a recurring cadence. Manual triggers - and boot scans are unaffected. -

-
- - {#if schedule} -
- - - - {#if scheduleLocal.mode === 'interval'} - -
- - hours -
- {/if} - - {#if scheduleLocal.mode === 'daily' || scheduleLocal.mode === 'weekly'} - - - {/if} - - {#if scheduleLocal.mode === 'weekly'} - - - {/if} -
- -
- - {#if schedule.next_scheduled_at} - - Next scan: {formatRelative(schedule.next_scheduled_at)} - - {:else if scheduleLocal.mode === 'off'} - Schedule is off. - {/if} -
- {:else} -

Loading…

- {/if} -
-

Cover art

diff --git a/web/src/routes/admin/admin.test.ts b/web/src/routes/admin/admin.test.ts index a0272596..35f4dfdf 100644 --- a/web/src/routes/admin/admin.test.ts +++ b/web/src/routes/admin/admin.test.ts @@ -42,21 +42,7 @@ vi.mock('$lib/api/admin', async () => { deleteQuarantineViaLidarr: vi.fn().mockResolvedValue({}), triggerScan: vi.fn().mockResolvedValue({}), refetchMissingCovers: vi.fn().mockResolvedValue({ started: true }), - researchMissingArt: vi.fn().mockResolvedValue({ version: 1 }), - createScanScheduleQuery: vi.fn(() => - readable({ - data: { - mode: 'off', - interval_hours: null, - time_of_day: null, - weekly_day: null, - next_scheduled_at: null, - }, - isPending: false, - isError: false - }) - ), - updateScanSchedule: vi.fn().mockResolvedValue({}) + researchMissingArt: vi.fn().mockResolvedValue({ version: 1 }) }; }); @@ -250,14 +236,6 @@ describe('admin Overview page', () => { ); }); - test('renders the scan-schedule section with mode=off default', () => { - setupOverview(); - render(OverviewPage); - expect(screen.getByText('Scan schedule')).toBeInTheDocument(); - const select = screen.getByLabelText('Mode') as HTMLSelectElement; - expect(select.value).toBe('off'); - }); - test('renders Re-search missing art button and invokes on click', async () => { setupOverview(); render(OverviewPage);