import { useEffect, useState } from 'react'; import { apiFetch } from '@/lib/api'; import { SystemSheet, SheetSection } from '@/components/ui/system-sheet'; import { RefreshCw } from 'lucide-react'; import { formatTimeAgo } from '@/lib/relativeTime'; import type { MeshNodeDiagnostic } from '@/types/mesh'; interface Props { open: boolean; onOpenChange: (open: boolean) => void; nodeId: number | null; nodeName: string | null; } function bytesFmt(n: number): string { if (n < 1024) return `${n}B`; if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)}KB`; return `${(n / (1024 * 1024)).toFixed(1)}MB`; } function ageFmt(ms: number): string { if (ms < 1000) return `${ms}ms`; if (ms < 60_000) return `${Math.floor(ms / 1000)}s`; return `${Math.floor(ms / 60_000)}m`; } export function MeshDiagnosticsSheet({ open, onOpenChange, nodeId, nodeName }: Props) { const [diag, setDiag] = useState(null); const [loading, setLoading] = useState(false); const [updatedAt, setUpdatedAt] = useState(null); const refresh = async () => { if (nodeId == null) return; setLoading(true); try { const res = await apiFetch(`/mesh/nodes/${nodeId}/diagnostic`, { localOnly: true }); if (res.ok) { setDiag(await res.json()); setUpdatedAt(Date.now()); } } finally { setLoading(false); } }; useEffect(() => { if (open) void refresh(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [open, nodeId]); const forwarderLabel = diag ? (diag.forwarder.listening ? `forwarder listening (${diag.forwarder.listenerCount})` : 'forwarder idle') : 'forwarder ?'; const pilotLabel = diag ? (diag.pilot.connected ? 'pilot connected' : 'pilot disconnected') : 'pilot ?'; const streamsLabel = `${diag?.activeStreams.length ?? 0} streams`; const aliasesLabel = `${diag?.aliasCache.length ?? 0} aliases`; const meta = `${forwarderLabel} · ${pilotLabel} · ${streamsLabel} · ${aliasesLabel}`; const footerContext = updatedAt ? `Updated ${formatTimeAgo(updatedAt)}` : (loading ? 'Loading…' : 'Never updated'); return ( { void refresh(); }, disabled: loading, }} footerContext={footerContext} size="md" >
Forwarder
{diag?.forwarder.listening ? `listening on ${diag.forwarder.listenerCount} port${diag.forwarder.listenerCount === 1 ? '' : 's'}` : 'idle'}
Pilot tunnel
{diag?.pilot.connected ? 'connected' : 'disconnected'}
Buffered
{diag ? bytesFmt(diag.pilot.bufferedAmount) : '-'}
Last seen
{diag?.pilot.lastSeen ? new Date(diag.pilot.lastSeen).toLocaleTimeString() : '-'}

Mesh runs in-process on each node; no separate container.

{(!diag || diag.activeStreams.length === 0) && (
No active streams.
)}
{diag?.activeStreams.map((s) => (
#{s.streamId} {s.alias ?? ''} in {bytesFmt(s.bytesIn)} / out {bytesFmt(s.bytesOut)} · {ageFmt(s.ageMs)}
))}
{(!diag || diag.aliasCache.length === 0) && (
No aliases registered.
)}
{diag?.aliasCache.map((a) => (
{a.host} node #{a.targetNodeId}:{a.port}
))}
); }