diff --git a/web/src/routes/(app)/admin/scraper/+page.svelte b/web/src/routes/(app)/admin/scraper/+page.svelte index bf8c3c3..318a928 100644 --- a/web/src/routes/(app)/admin/scraper/+page.svelte +++ b/web/src/routes/(app)/admin/scraper/+page.svelte @@ -185,11 +185,8 @@ function successRateColor(rate: number): string { } /** Muted class for zero/default values, foreground for interesting ones. */ -function emphasisClass(value: number, zeroIsDefault = true): string { - if (zeroIsDefault) { - return value === 0 ? "text-muted-foreground" : "text-foreground"; - } - return value === 1 ? "text-muted-foreground" : "text-foreground"; +function emphasisClass(value: number): string { + return value === 0 ? "text-muted-foreground" : "text-foreground"; } function xAxisFormat(period: ScraperPeriod) { @@ -212,64 +209,64 @@ function handleSortingChange(updater: Updater) { sorting = typeof updater === "function" ? updater(sorting) : updater; } - const columns: ColumnDef[] = [ - { - id: "subject", - accessorKey: "subject", - header: "Subject", - enableSorting: true, - sortingFn: (a, b) => a.original.subject.localeCompare(b.original.subject), +const columns: ColumnDef[] = [ + { + id: "subject", + accessorKey: "subject", + header: "Subject", + enableSorting: true, + sortingFn: (a, b) => a.original.subject.localeCompare(b.original.subject), + }, + { + id: "status", + accessorFn: (row) => row.scheduleState, + header: "Scrape in", + enableSorting: true, + sortingFn: (a, b) => { + const order: Record = { eligible: 0, cooldown: 1, paused: 2, read_only: 3 }; + const sa = order[a.original.scheduleState] ?? 4; + const sb = order[b.original.scheduleState] ?? 4; + if (sa !== sb) return sa - sb; + return (a.original.cooldownRemainingSecs ?? Infinity) - (b.original.cooldownRemainingSecs ?? Infinity); }, - { - id: "status", - accessorFn: (row) => row.scheduleState, - header: "Status", - enableSorting: true, - sortingFn: (a, b) => { - const order: Record = { eligible: 0, cooldown: 1, paused: 2, read_only: 3 }; - const sa = order[a.original.scheduleState] ?? 4; - const sb = order[b.original.scheduleState] ?? 4; - if (sa !== sb) return sa - sb; - return (a.original.cooldownRemainingSecs ?? Infinity) - (b.original.cooldownRemainingSecs ?? Infinity); - }, - }, - { - id: "interval", - accessorFn: (row) => row.currentIntervalSecs * row.timeMultiplier, - header: "Interval", - enableSorting: true, - }, - { - id: "lastScraped", - accessorKey: "lastScraped", - header: "Last Scraped", - enableSorting: true, - }, - { - id: "changeRate", - accessorKey: "avgChangeRatio", - header: "Change %", - enableSorting: true, - }, - { - id: "zeros", - accessorKey: "consecutiveZeroChanges", - header: "Zeros", - enableSorting: true, - }, - { - id: "runs", - accessorKey: "recentRuns", - header: "Runs", - enableSorting: true, - }, - { - id: "fails", - accessorKey: "recentFailures", - header: "Fails", - enableSorting: true, - }, - ]; + }, + { + id: "interval", + accessorFn: (row) => row.currentIntervalSecs * row.timeMultiplier, + header: "Interval", + enableSorting: true, + }, + { + id: "lastScraped", + accessorKey: "lastScraped", + header: "Last Scraped", + enableSorting: true, + }, + { + id: "changeRate", + accessorKey: "avgChangeRatio", + header: "Change %", + enableSorting: true, + }, + { + id: "zeros", + accessorKey: "consecutiveZeroChanges", + header: "Zeros", + enableSorting: true, + }, + { + id: "runs", + accessorKey: "recentRuns", + header: "Runs", + enableSorting: true, + }, + { + id: "fails", + accessorKey: "recentFailures", + header: "Fails", + enableSorting: true, + }, +]; const table = createSvelteTable({ get data() { @@ -288,18 +285,19 @@ const table = createSvelteTable({ enableSortingRemoval: true, }); - const skeletonWidths: Record = { - subject: "w-24", - status: "w-20", - interval: "w-14", - lastScraped: "w-20", - changeRate: "w-12", - zeros: "w-8", - runs: "w-8", - fails: "w-8", - }; +const skeletonWidths: Record = { + subject: "w-24", + status: "w-20", + interval: "w-14", + lastScraped: "w-20", + changeRate: "w-12", + zeros: "w-8", + runs: "w-8", + fails: "w-8", +}; const columnCount = columns.length; +const detailGridCols = "grid-cols-[7fr_5fr_3fr_4fr_4fr_3fr_4fr_minmax(6rem,1fr)]"; // --- Lifecycle --- @@ -361,9 +359,6 @@ $effect(() => {

Total Scrapes

{formatNumber(stats.totalScrapes)}

-

- {formatNumber(stats.successfulScrapes)} ok / {formatNumber(stats.failedScrapes)} failed -

Success Rate

@@ -558,13 +553,13 @@ $effect(() => { Subjects ({subjects.length})
- +
{#each table.getHeaderGroups() as headerGroup} {#each headerGroup.headers as header}
{
-
- {#if detailLoading} -

Loading results...

- {:else if subjectDetail && subjectDetail.results.length > 0} -
- - - - - - - - - - - - - - +
+
+
+ +
+
Time
+
Duration
+
Status
+
Fetched
+
Changed
+
%
+
Audits
+
Error
+
+ +
+ {#if detailLoading} + {#each Array(8) as _} +
+
+
+
+
+
+
+
+
+
+ {/each} + {:else if subjectDetail && subjectDetail.results.length > 0} {#each subjectDetail.results as result (result.id)} {@const detailRel = relativeTime(new Date(result.completedAt), now)} -
- - - - - - - - - + +
+ {#if !result.success && result.errorMessage} + + {result.errorMessage} + + {:else} + {"\u2014"} + {/if} +
+ {/each} - -
TimeDurationStatusFetchedChangedUnchangedAuditsError
+
+
- {detailRel.text === "now" ? "just now" : detailRel.text} + {detailRel.text === "now" ? "just now" : detailRel.text} -
{formatDurationMs(result.durationMs)} + +
{formatDurationMs(result.durationMs)}
+
{#if result.success} ok {:else} fail {/if} -
+ +
{result.coursesFetched ?? "\u2014"} -
+ +
{result.coursesChanged ?? "\u2014"} -
- {result.coursesUnchanged ?? "\u2014"} - + +
+ {#if result.coursesFetched != null && result.coursesFetched > 0 && result.coursesChanged != null} + {(result.coursesChanged / result.coursesFetched * 100).toFixed(1)}% + {:else} + {"\u2014"} + {/if} +
+
{result.auditsGenerated ?? "\u2014"} -
- {result.errorMessage ?? ""} -
+ {:else} +
No recent results.
+ {/if} +
+
- {:else} -

No recent results.

- {/if}