From be1621027ab0e5e601be45eef8067c1a4270d3cb Mon Sep 17 00:00:00 2001 From: iceice400 Date: Sat, 6 Jun 2026 04:57:11 -0400 Subject: [PATCH 1/4] feat(topology): force-directed mesh graph from NEIGHBORINFO and TRACEROUTE Extend GET /api/analytics/topology with hours window, nodes, edges, and traceroute paths. Add Topology dashboard tab with D3 force graph; map overlay reads new response shape. --- frontend/css/topology.css | 121 +++++++++++++++ frontend/index.html | 24 +++ frontend/js/app.js | 3 +- frontend/js/components/node_map.js | 5 +- frontend/js/topology_tab.js | 203 +++++++++++++++++++++++++ frontend/sidebar/sidebar_controller.js | 3 + src/api/routes/analytics.py | 48 +----- src/storage/packet_repository.py | 172 +++++++++++++++++++++ tests/test_topology_api.py | 125 +++++++++++++++ 9 files changed, 659 insertions(+), 45 deletions(-) create mode 100644 frontend/css/topology.css create mode 100644 frontend/js/topology_tab.js create mode 100644 tests/test_topology_api.py diff --git a/frontend/css/topology.css b/frontend/css/topology.css new file mode 100644 index 00000000..b45cbd63 --- /dev/null +++ b/frontend/css/topology.css @@ -0,0 +1,121 @@ +.section[data-section="topology"] { + overflow-y: auto; +} + +.topology-panel { + padding: 1.25rem; + max-width: 1100px; + margin: 0 auto; +} + +.topology-panel__header { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; + margin-bottom: 0.75rem; +} + +.topology-panel__title { + margin: 0; + font-size: 1.1rem; + color: var(--text-primary, #f1f5f9); +} + +.topology-panel__desc { + margin: 0.25rem 0 0; + font-size: 0.78rem; + color: var(--text-secondary, #94a3b8); + max-width: 36rem; +} + +.topology-panel__controls { + display: inline-flex; + gap: 0.35rem; +} + +.topology-btn { + background: rgba(15, 23, 42, 0.8); + border: 1px solid rgba(51, 65, 85, 0.8); + color: var(--text-secondary, #94a3b8); + font-size: 0.72rem; + font-family: var(--font-mono); + padding: 0.25rem 0.55rem; + border-radius: 6px; + cursor: pointer; +} + +.topology-btn--active, +.topology-btn:hover { + color: var(--accent-cyan, #06b6d4); + border-color: rgba(6, 182, 212, 0.45); +} + +.topology-panel__meta { + display: flex; + gap: 1rem; + font-family: var(--font-mono); + font-size: 0.68rem; + color: #64748b; + margin-bottom: 0.5rem; +} + +.topology-panel__canvas-wrap { + position: relative; + background: var(--card-bg, #111827); + border: 1px solid var(--border, #1e293b); + border-radius: 12px; + min-height: 420px; + overflow: hidden; +} + +.topology-svg { + display: block; + width: 100%; + min-height: 420px; +} + +.topology-empty { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary, #94a3b8); + font-size: 0.85rem; + pointer-events: none; +} + +.topology-legend { + display: flex; + flex-wrap: wrap; + gap: 1rem; + margin-top: 0.75rem; + font-size: 0.68rem; + color: #64748b; +} + +.topology-swatch { + display: inline-block; + width: 10px; + height: 10px; + border-radius: 50%; + margin-right: 0.3rem; + vertical-align: middle; +} + +.topology-swatch--mt { background: #06b6d4; } +.topology-swatch--mc { background: #a855f7; } +.topology-swatch--weak { + background: transparent; + border: 1px dashed #64748b; + border-radius: 2px; + width: 14px; + height: 0; +} + +@media (max-width: 768px) { + .topology-panel { padding: 0.85rem; } + .topology-panel__header { flex-direction: column; } +} diff --git a/frontend/index.html b/frontend/index.html index 239398cc..ead39b7e 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -33,6 +33,7 @@ + @@ -102,6 +103,21 @@ Stats +