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
+34
View File
@@ -0,0 +1,34 @@
import { formatDistanceToNowStrict } from "date-fns";
import { useEffect, useEffectEvent, useState } from "react";
function toTimestamp(date: string | Date | null | undefined): number | null {
if (!date) return null;
const t = date instanceof Date ? date.getTime() : new Date(date).getTime();
return Number.isFinite(t) ? t : null;
}
export function useTimeAgo(
date: string | Date | null | undefined,
{ intervalMs = 1_000, addSuffix = true, fallback = "" } = {},
): string {
const ts = toTimestamp(date);
const [text, setText] = useState(() =>
ts === null ? fallback : formatDistanceToNowStrict(ts, { addSuffix }),
);
const tick = useEffectEvent(() => {
const next =
ts === null ? fallback : formatDistanceToNowStrict(ts, { addSuffix });
if (next !== text) setText(next);
});
useEffect(() => {
tick();
if (ts === null) return;
const id = setInterval(tick, intervalMs);
return () => clearInterval(id);
}, [ts, intervalMs]); // tick is NOT listed — that's the point
return text;
}