import { useEffect, useMemo, useState } from 'react'; import { Bell } from 'lucide-react'; type FleetHealth = 'healthy' | 'degraded' | 'critical'; interface FleetMastheadProps { nodeCount: number; onlineCount: number; criticalCount: number; totalCpuPercent: number; worstCpu: { name: string; percent: number } | null; totalMemUsed: number; totalMemTotal: number; activeContainers: number; totalContainers: number; lastSyncAt: number | null; loading: boolean; } function formatBytes(bytes: number): string { if (!bytes || bytes <= 0) return '0 GiB'; return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GiB`; } function formatAgo(ms: number): string { const clamped = Math.max(0, ms); if (clamped < 60_000) return `${Math.round(clamped / 1000)}s`; if (clamped < 3_600_000) return `${Math.round(clamped / 60_000)}m`; return `${Math.round(clamped / 3_600_000)}h`; } function useTicker(intervalMs: number): number { const [now, setNow] = useState(() => Date.now()); useEffect(() => { const id = setInterval(() => setNow(Date.now()), intervalMs); return () => clearInterval(id); }, [intervalMs]); return now; } const healthConfig: Record = { healthy: { label: 'The fleet', dotClass: 'bg-success shadow-[0_0_0_3px_color-mix(in_oklch,var(--success)_20%,transparent)]', textClass: 'text-stat-value', railClass: 'bg-brand', tintClass: 'from-brand/[0.06] via-transparent to-transparent', }, degraded: { label: 'The fleet', dotClass: 'bg-warning shadow-[0_0_0_3px_color-mix(in_oklch,var(--warning)_22%,transparent)]', textClass: 'text-warning', railClass: 'bg-warning', tintClass: 'from-warning/[0.06] via-transparent to-transparent', }, critical: { label: 'The fleet', dotClass: 'bg-destructive shadow-[0_0_0_3px_color-mix(in_oklch,var(--destructive)_24%,transparent)]', textClass: 'text-destructive', railClass: 'bg-destructive', tintClass: 'from-destructive/[0.06] via-transparent to-transparent', }, }; export function FleetMasthead({ nodeCount, onlineCount, criticalCount, totalCpuPercent, worstCpu, totalMemUsed, totalMemTotal, activeContainers, totalContainers, lastSyncAt, loading, }: FleetMastheadProps) { const level: FleetHealth = useMemo(() => { if (criticalCount > 0) return 'critical'; if (onlineCount < nodeCount) return 'degraded'; return 'healthy'; }, [criticalCount, onlineCount, nodeCount]); const config = healthConfig[level]; const now = useTicker(1000); const offlineCount = Math.max(0, nodeCount - onlineCount); const reasons: string[] = []; if (offlineCount > 0) reasons.push(`${offlineCount} offline`); if (criticalCount > 0) reasons.push(`${criticalCount} critical`); const lastSyncLabel = loading ? 'syncing…' : lastSyncAt ? `last sync ${formatAgo(now - lastSyncAt)}` : 'no sync yet'; const metaLine = `${nodeCount} ${nodeCount === 1 ? 'node' : 'nodes'} · ${onlineCount} online · ${lastSyncLabel}`; const reasonsLine = reasons.join(' · '); const cpuTone = totalCpuPercent >= 80 ? 'warn' : 'value'; const memPercent = totalMemTotal > 0 ? (totalMemUsed / totalMemTotal) * 100 : 0; return (
0 ? `of ${formatBytes(totalMemTotal)} · ${memPercent.toFixed(0)}%` : undefined} tone="value" divider />
0 ? 'text-destructive' : 'text-stat-icon'}`} strokeWidth={1.5} /> 0 ? 'text-destructive' : 'text-stat-subtitle'}`} > {criticalCount} {criticalCount === 1 ? 'alert' : 'alerts'}
); } function StatTile({ label, value, sub, tone, divider, }: { label: string; value: string; sub?: string; tone: 'value' | 'warn'; divider?: boolean; }) { return (
{label} {value} {sub ? ( {sub} ) : null}
); }