/* WorkerPulse — single worker heartbeat tile.
*
* State derives from:
* • last activity is fresher than the worker's expected cadence → ok / pending
* • last activity stale → idle (worker quiet, not necessarily broken)
* • any failures in the last hour → warn (degraded)
* • all 1h runs failing → error
*
* Per-worker freshness thresholds:
* • Analyzer — 5 min (poll every 2s, gets work intermittently)
* • RMM Overshell — 10 min
* • Sync scheduler — 60 min (cron-driven; quietest worker) */
'use client';
import { Card, CardContent } from '@/components/ui/card';
import { StatusLight, type StatusLightState } from '@/components/ui/status-light';
interface WorkerSnapshot {
name: string;
lastActivity: string | null;
inFlight: number;
oneHour: { success: number; failure: number };
}
interface WorkerPulseProps {
worker: WorkerSnapshot;
/** Stale threshold in minutes; varies per worker. */
freshnessMinutes?: number;
}
function relTime(iso: string | null): string {
if (!iso) return 'never';
const ms = Date.now() - new Date(iso).getTime();
if (ms < 0) return 'just now';
const min = Math.floor(ms / 60000);
if (min < 1) return 'just now';
if (min < 60) return `${min} min ago`;
const hr = Math.floor(min / 60);
if (hr < 48) return `${hr} h ago`;
const day = Math.floor(hr / 24);
return `${day} d ago`;
}
export function WorkerPulse({ worker, freshnessMinutes = 30 }: WorkerPulseProps) {
const { lastActivity, inFlight, oneHour } = worker;
const fresh =
lastActivity != null &&
Date.now() - new Date(lastActivity).getTime() < freshnessMinutes * 60_000;
let state: StatusLightState;
let stateLabel: string;
if (oneHour.failure > 0 && oneHour.success === 0) {
state = 'error';
stateLabel = 'failing';
} else if (oneHour.failure > 0) {
state = 'warn';
stateLabel = 'degraded';
} else if (inFlight > 0) {
state = 'pending';
stateLabel = 'in flight';
} else if (fresh) {
state = 'ok';
stateLabel = 'ok';
} else {
state = 'idle';
stateLabel = 'idle';
}
return (
{worker.name} {stateLabel}
Last activity {relTime(lastActivity)}
{value}
{label}