/* 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}

0 ? 'error' : 'default'} />

Last activity {relTime(lastActivity)}

); } function Stat({ label, value, tone = 'default', }: { label: string; value: number; tone?: 'default' | 'error'; }) { return (

{value}

{label}

); }