From 62943f62929f6f83e43b71e13b6c16da6a7d1e87 Mon Sep 17 00:00:00 2001 From: Jack Kingsman Date: Tue, 3 Mar 2026 11:54:54 -0800 Subject: [PATCH] Add why-active reason to visualizer nodes --- .../src/components/PacketVisualizer3D.tsx | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/frontend/src/components/PacketVisualizer3D.tsx b/frontend/src/components/PacketVisualizer3D.tsx index 3ab2c56..f9de3a5 100644 --- a/frontend/src/components/PacketVisualizer3D.tsx +++ b/frontend/src/components/PacketVisualizer3D.tsx @@ -56,6 +56,7 @@ interface GraphNode extends SimulationNodeDatum3D { type: NodeType; isAmbiguous: boolean; lastActivity: number; + lastActivityReason?: string; lastSeen?: number | null; probableIdentity?: string | null; ambiguousNames?: string[]; @@ -110,6 +111,15 @@ function arraysEqual(a: string[], b: string[]): boolean { return true; } +function formatRelativeTime(timestamp: number): string { + const seconds = Math.floor((Date.now() - timestamp) / 1000); + if (seconds < 5) return 'just now'; + if (seconds < 60) return `${seconds}s ago`; + const minutes = Math.floor(seconds / 60); + const secs = seconds % 60; + return secs > 0 ? `${minutes}m ${secs}s ago` : `${minutes}m ago`; +} + // ============================================================================= // DATA LAYER HOOK (3D variant) // ============================================================================= @@ -725,6 +735,15 @@ function useVisualizerData3D({ const path = buildPath(parsed, packet, myPrefix); if (path.length < 2) continue; + // Tag each node with why it's considered active + const label = getPacketLabel(parsed.payloadType); + for (let i = 0; i < path.length; i++) { + const n = nodesRef.current.get(path[i]); + if (n && n.id !== 'self') { + n.lastActivityReason = i === 0 ? `${label} source` : `Relayed ${label}`; + } + } + for (let i = 0; i < path.length - 1; i++) { if (path[i] !== path[i + 1]) { addLink(path[i], path[i + 1]); @@ -1909,6 +1928,12 @@ export function PacketVisualizer3D({ {node.ambiguousNames.join(', ')} )} + {pinnedNodeId && node.type !== 'self' && ( +
+
Last active: {formatRelativeTime(node.lastActivity)}
+ {node.lastActivityReason &&
Reason: {node.lastActivityReason}
} +
+ )} {neighbors.length > 0 && (
Traffic exchanged with: