Overhaul system health section with job trigger controls and live timestamps

Rebuild the background jobs card as a sortable table showing each job's
schedule, last run time (live-updating via a new useTimeAgo hook), last
duration, and a manual trigger button backed by a new POST
/api/admin/jobs/trigger route. Extract StatusDot into a shared
component. Add cronToHuman() to display schedule patterns as readable
strings (e.g. "Every 6h", "Daily at 03:00"). Replace static
formatDistanceToNow calls throughout the health section with a
LiveTimeAgo component that refreshes every 30 seconds. Also swap a
handful of section icons for better visual matches across settings cards.
This commit is contained in:
2026-03-05 11:52:37 -05:00
parent b474d07d97
commit 107eb9a9e7
18 changed files with 2698 additions and 260 deletions
+14 -2
View File
@@ -32,6 +32,8 @@ export interface SystemHealthData {
};
jobs: {
jobName: string;
cronPattern: string | null;
nextRunAt: string | null;
lastRunAt: string | null;
lastDurationMs: number | null;
lastStatus: "running" | "success" | "error" | null;
@@ -143,6 +145,12 @@ async function getTmdbHealth(): Promise<SystemHealthData["tmdb"]> {
}
function getJobsHealth(): SystemHealthData["jobs"] {
// Lazy-import to avoid circular dependency issues at module level
const { getJobSchedules } =
require("@/lib/cron") as typeof import("@/lib/cron");
const schedules = getJobSchedules();
const scheduleMap = new Map(schedules.map((s) => [s.jobName, s]));
return JOB_NAMES.map((jobName) => {
const latest = db
.select()
@@ -153,14 +161,18 @@ function getJobsHealth(): SystemHealthData["jobs"] {
.get();
const isCurrentlyRunning = latest?.status === "running";
const schedule = scheduleMap.get(jobName);
let lastDurationMs: number | null = null;
if (latest?.finishedAt && latest.startedAt) {
// Prefer the in-memory durationMs column; fall back to timestamp diff
let lastDurationMs: number | null = latest?.durationMs ?? null;
if (lastDurationMs === null && latest?.finishedAt && latest.startedAt) {
lastDurationMs = latest.finishedAt.getTime() - latest.startedAt.getTime();
}
return {
jobName,
cronPattern: schedule?.pattern ?? null,
nextRunAt: schedule?.nextRunAt ?? null,
lastRunAt: latest?.startedAt?.toISOString() ?? null,
lastDurationMs,
lastStatus: (latest?.status as "running" | "success" | "error") ?? null,