diff --git a/app/(pages)/settings/_components/system-health-section.tsx b/app/(pages)/settings/_components/system-health-section.tsx index e8bf138..33282b4 100644 --- a/app/(pages)/settings/_components/system-health-section.tsx +++ b/app/(pages)/settings/_components/system-health-section.tsx @@ -161,22 +161,7 @@ export function SystemHealthCards() { - - refresh()} - disabled={isValidating} - /> - } - > - {isValidating ? : } - - Refresh - + @@ -273,24 +258,31 @@ export function SystemHealthCards() { {/* ── Card 2: Background Jobs ── */} - + {/* ── Card 3: Storage ── */} -
-
- -
-
- Storage - - Image cache and backup disk usage - +
+
+
+ +
+
+ Storage + + Image cache and backup disk usage + +
+
@@ -360,12 +352,41 @@ export function SystemHealthCards() { ); } +function RefreshButton({ + isValidating, + onRefresh, +}: { + isValidating: boolean; + onRefresh: () => void; +}) { + return ( + + + } + > + {isValidating ? : } + + Refresh + + ); +} + /** Background Jobs card with table layout and manual trigger */ function BackgroundJobsCard({ jobs, + isValidating, onRefresh, }: { jobs: SystemHealthData["jobs"]; + isValidating: boolean; onRefresh: () => void; }) { const [triggeringJob, setTriggeringJob] = useState(null); @@ -392,24 +413,39 @@ function BackgroundJobsCard({ } }; - const healthyCount = jobs.filter((j) => j.lastStatus === "success").length; + const sortedJobs = [...jobs].sort((a, b) => { + // Disabled jobs go to the bottom + if (a.disabled !== b.disabled) return a.disabled ? 1 : -1; + // Active jobs sorted by next run time (soonest first, null last) + if (!a.nextRunAt && !b.nextRunAt) return 0; + if (!a.nextRunAt) return 1; + if (!b.nextRunAt) return -1; + return new Date(a.nextRunAt).getTime() - new Date(b.nextRunAt).getTime(); + }); + const activeJobs = jobs.filter((j) => !j.disabled); + const healthyCount = activeJobs.filter( + (j) => j.lastStatus === "success", + ).length; return ( -
-
- -
-
- Background jobs - - {healthyCount} of {jobs.length} jobs healthy - +
+
+
+ +
+
+ Background jobs + + {healthyCount} of {activeJobs.length} jobs healthy + +
+
@@ -434,7 +470,7 @@ function BackgroundJobsCard({ - {jobs.map((job) => { + {sortedJobs.map((job) => { const isTriggering = triggeringJob === job.jobName; const isRunning = job.isCurrentlyRunning || isTriggering; @@ -446,10 +482,12 @@ function BackgroundJobsCard({ {/* Job name + status */}
- {isRunning ? ( + {job.disabled ? ( + + ) : isRunning ? ( ) : job.lastStatus === null ? ( - + ) : job.lastStatus === "success" ? ( ) : ( @@ -553,7 +591,7 @@ function BackgroundJobsCard({ size="icon" aria-label="Trigger job" className="size-6" - disabled={isRunning} + disabled={isRunning || job.disabled} onClick={() => handleTrigger(job.jobName)} /> } diff --git a/lib/services/system-health.ts b/lib/services/system-health.ts index e725f54..999b634 100644 --- a/lib/services/system-health.ts +++ b/lib/services/system-health.ts @@ -5,6 +5,8 @@ import { db } from "@/lib/db/client"; import { cronRuns, episodes, titles, user } from "@/lib/db/schema"; import { listBackups } from "@/lib/services/backup"; import { imageCacheEnabled } from "@/lib/services/image-cache"; +import { getSetting } from "@/lib/services/settings"; +import { isUpdateCheckEnabled } from "@/lib/services/update-check"; const DATA_DIR = process.env.DATA_DIR || "./data"; const DATABASE_URL = @@ -39,6 +41,7 @@ export interface SystemHealthData { lastStatus: "running" | "success" | "error" | null; lastError: string | null; isCurrentlyRunning: boolean; + disabled: boolean; }[]; imageCache: { enabled: boolean; @@ -175,15 +178,21 @@ function getJobsHealth(): SystemHealthData["jobs"] { lastDurationMs = latest.finishedAt.getTime() - latest.startedAt.getTime(); } + const disabled = + (jobName === "scheduledBackup" && + getSetting("scheduledBackups") !== "true") || + (jobName === "updateCheck" && !isUpdateCheckEnabled()); + return { jobName, - cronPattern: schedule?.pattern ?? null, - nextRunAt: schedule?.nextRunAt ?? null, + cronPattern: disabled ? null : (schedule?.pattern ?? null), + nextRunAt: disabled ? null : (schedule?.nextRunAt ?? null), lastRunAt: latest?.startedAt?.toISOString() ?? null, lastDurationMs, lastStatus: (latest?.status as "running" | "success" | "error") ?? null, lastError: latest?.errorMessage ?? null, isCurrentlyRunning, + disabled, }; }); }