import { useEffect, useMemo, useState } from 'react'; import { Bell } from 'lucide-react'; import { CursorProvider, Cursor, CursorContainer, CursorFollow, } from '@/components/animate-ui/primitives/animate/cursor'; import type { Stats, SystemStats, NotificationItem, HealthLevel } from './types'; interface HealthStatusBarProps { stats: Stats; systemStats: SystemStats | null; notifications: NotificationItem[]; activeNodeName: string; nodeCount: number; lastSyncAt: number | null; } interface HealthResult { level: HealthLevel; reasons: string[]; } function deriveHealth(stats: Stats, systemStats: SystemStats | null, notifications: NotificationItem[]): HealthResult { const cpu = parseFloat(systemStats?.cpu.usage || '0'); const ram = parseFloat(systemStats?.memory.usagePercent || '0'); const disk = parseFloat(systemStats?.disk?.usagePercent || '0'); const unreadErrors = notifications.filter(n => !n.is_read && n.level === 'error').length; const reasons: string[] = []; if (cpu >= 80) reasons.push(`CPU ${cpu.toFixed(0)}%`); if (ram >= 80) reasons.push(`RAM ${ram.toFixed(0)}%`); if (disk >= 80) reasons.push(`Disk ${disk.toFixed(0)}%`); if (stats.exited > 0) reasons.push(`${stats.exited} exited`); if (unreadErrors > 0) reasons.push(`${unreadErrors} unread ${unreadErrors === 1 ? 'error' : 'errors'}`); if (cpu >= 90 || ram >= 90 || disk >= 90 || (stats.exited > 0 && unreadErrors > 0)) { return { level: 'critical', reasons }; } if (cpu >= 80 || ram >= 80 || disk >= 80 || stats.exited > 0 || unreadErrors > 0) { return { level: 'degraded', reasons }; } return { level: 'healthy', reasons: ['All systems nominal'] }; } const healthConfig: Record = { healthy: { label: 'Healthy', 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: 'Degraded', 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: 'Critical', 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', }, }; function formatGib(bytes: number): string { 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; } export function HealthStatusBar({ stats, systemStats, notifications, activeNodeName, nodeCount, lastSyncAt, }: HealthStatusBarProps) { const { level, reasons } = useMemo( () => deriveHealth(stats, systemStats, notifications), [stats, systemStats, notifications], ); const config = healthConfig[level]; const now = useTicker(1000); const unreadAlerts = notifications.filter(n => !n.is_read).length; const running = `${stats.active}/${stats.total}`; const cpuLabel = systemStats ? `${parseFloat(systemStats.cpu.usage).toFixed(0)}%` : '--'; const memLabel = systemStats ? formatGib(systemStats.memory.used) : '--'; const lastSyncLabel = lastSyncAt ? `last sync ${formatAgo(now - lastSyncAt)}` : 'connecting…'; const metaLine = `${activeNodeName} · ${nodeCount} ${nodeCount === 1 ? 'node' : 'nodes'} · ${lastSyncLabel}`; const reasonsLine = reasons.join(' · '); return (
{/* State column */}
{/* Stats column */}
{stats.managed} managed · {stats.unmanaged} external {stats.exited > 0 ? ( <> · {stats.exited} exited ) : null}
= 80 ? 'warn' : 'value'} divider />
{/* Right column */}
0 ? 'text-warning' : 'text-stat-icon'}`} strokeWidth={1.5} /> 0 ? 'text-warning' : 'text-stat-subtitle'}`} > {unreadAlerts} {unreadAlerts === 1 ? 'alert' : 'alerts'}
); } function StatTile({ label, value, tone, divider, }: { label: string; value: string; tone: 'value' | 'warn'; divider?: boolean; }) { return (
{label} {value}
); }