mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 02:45:39 -04:00
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:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user