import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import { Link } from 'wouter'; import { StorageBar } from './StorageBar'; /// Human-oriented node card for the FleetHealth landing. /// Shows: overall health traffic-light, storage bars, mount state, /// cache hit rate, next scheduled job. No hex, no primitives. export function NodeCard({ node }) { const health = healthOf(node); const border = { ok: 'border-emerald-700 hover:border-emerald-500', warn: 'border-amber-700 hover:border-amber-500', err: 'border-red-700 hover:border-red-500', idle: 'border-slate-700 hover:border-slate-500', }[health]; const dot = { ok: 'bg-emerald-400', warn: 'bg-amber-400', err: 'bg-red-400', idle: 'bg-slate-500', }[health]; return (_jsx(Link, { href: `/nodes/${node.node_name}`, children: _jsxs("a", { className: [ 'block rounded-lg bg-slate-900 border transition-colors', 'p-5 space-y-4', border, ].join(' '), children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: `inline-block w-2.5 h-2.5 rounded-full ${dot}` }), _jsx("span", { className: "text-lg font-semibold text-slate-100", children: node.node_name })] }), _jsx("span", { className: "text-xs text-slate-500 font-mono", children: node.zone || '—' })] }), !node.online && (_jsx("div", { className: "text-sm text-red-400 break-words", children: node.error ?? 'offline' })), node.online && (_jsxs(_Fragment, { children: [node.filesystem && (_jsx(StorageBar, { label: "disk", used: node.filesystem.used_bytes, total: node.filesystem.total_bytes })), node.hot && node.hot.max_bytes > 0 && (_jsx(StorageBar, { label: "hot tier", used: node.hot.used_bytes, total: node.hot.max_bytes, pinned: node.hot.pinned_bytes ?? undefined })), _jsxs("div", { className: "grid grid-cols-2 gap-y-1 text-sm", children: [_jsx("span", { className: "text-slate-500", children: "mount" }), _jsx("span", { className: "text-right font-mono text-xs", children: node.mount?.active ? (_jsx("span", { className: "text-emerald-300", children: "\u2713 mounted" })) : (_jsx("span", { className: "text-slate-500", children: "not mounted" })) }), _jsx("span", { className: "text-slate-500", children: "cache hit rate" }), _jsx("span", { className: "text-right font-mono text-xs", children: node.cache && node.cache.hits + node.cache.misses > 0 ? `${Math.round(node.cache.hit_rate * 100)}%` : _jsx("span", { className: "text-slate-500", children: "idle" }) }), _jsx("span", { className: "text-slate-500", children: "next scheduled job" }), _jsx("span", { className: "text-right font-mono text-xs", children: nextTimer(node) })] })] }))] }) })); } function healthOf(n) { if (!n.online) return 'err'; const fsPct = n.filesystem ? n.filesystem.used_bytes / Math.max(n.filesystem.total_bytes, 1) : 0; const anyFailed = n.timers.some((t) => t.last_result && t.last_result !== 'success'); if (fsPct > 0.9 || anyFailed) return 'err'; if (fsPct > 0.75) return 'warn'; if (n.mount && !n.mount.active) return 'warn'; return 'ok'; } function nextTimer(n) { const next = n.timers .filter((t) => t.next_fire_unix) .sort((a, b) => (a.next_fire_unix ?? 0) - (b.next_fire_unix ?? 0))[0]; if (!next?.next_fire_unix) return _jsx("span", { className: "text-slate-500", children: "\u2014" }); const label = next.unit .replace(/^clawstor-/, '') .replace(/\.timer$/, ''); const now = Math.floor(Date.now() / 1000); const diff = next.next_fire_unix - now; const when = diff < 3600 ? `in ${Math.max(0, Math.floor(diff / 60))}m` : diff < 86400 ? `in ${Math.floor(diff / 3600)}h` : `in ${Math.floor(diff / 86400)}d`; return (_jsxs("span", { children: [_jsx("span", { className: "text-slate-300", children: label }), ' ', _jsx("span", { className: "text-slate-500", children: when })] })); }