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
+79
View File
@@ -0,0 +1,79 @@
"use client";
import { motion } from "motion/react";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
const colors = {
ok: { bg: "bg-green-500", shadow: "0 0 0 0px rgba(74,222,128,0.5)" },
error: { bg: "bg-destructive", shadow: "0 0 0 0px rgba(248,113,113,0.5)" },
warn: { bg: "bg-amber-500", shadow: "0 0 0 0px rgba(251,191,36,0.5)" },
inactive: { bg: "bg-muted-foreground/30", shadow: "" },
};
const pulseColors = {
ok: "0 0 0 4px rgba(74,222,128,0)",
error: "0 0 0 4px rgba(248,113,113,0)",
warn: "0 0 0 4px rgba(251,191,36,0)",
};
const defaultLabels: Record<string, string> = {
ok: "Healthy",
error: "Error",
warn: "Warning",
inactive: "Inactive",
};
/** Small colored status dot with a pulsing ring for active states. */
export function StatusDot({
status,
label,
className,
}: {
status: "ok" | "error" | "warn" | "inactive";
/** Tooltip text. Defaults to a label derived from the status. */
label?: string;
className?: string;
}) {
const { bg, shadow } = colors[status];
const pulse = status !== "inactive";
const tooltipText = label ?? defaultLabels[status];
const dotEl = pulse ? (
<motion.span
className={cn(
"inline-block h-2 w-2 shrink-0 rounded-full",
bg,
className,
)}
animate={{
boxShadow: [shadow, pulseColors[status as keyof typeof pulseColors]],
}}
transition={{
duration: 1.2,
repeat: Number.POSITIVE_INFINITY,
ease: "easeOut",
repeatDelay: 0.3,
}}
/>
) : (
<span
className={cn(
"inline-block h-2 w-2 shrink-0 rounded-full",
bg,
className,
)}
/>
);
return (
<Tooltip>
<TooltipTrigger className="cursor-default">{dotEl}</TooltipTrigger>
<TooltipContent>{tooltipText}</TooltipContent>
</Tooltip>
);
}