From 2295751e18ec2e190560aa925de9d8215aa885c2 Mon Sep 17 00:00:00 2001 From: Pablo Revilla Date: Wed, 27 Aug 2025 14:17:46 -0700 Subject: [PATCH] Added the traceroute and neighbours to the map --- meshview/templates/map.html | 120 ++++++++++++++++++++---------------- 1 file changed, 66 insertions(+), 54 deletions(-) diff --git a/meshview/templates/map.html b/meshview/templates/map.html index 560947c..d17c3fb 100644 --- a/meshview/templates/map.html +++ b/meshview/templates/map.html @@ -57,22 +57,6 @@ L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { var markers = {}; var markerById = {}; -var nodeIndex = {}; -var bounds = L.latLngBounds(); -var channels = new Set(); -var edgeLayer = L.layerGroup().addTo(map); - -const portMap = { - 1: "Text", - 67: "Telemetry", - 3: "Position", - 70: "Traceroute", - 4: "Node Info", - 71: "Neighbour Info", - 73: "Map Report" -}; - -// --- Nodes data --- var nodes = [ {% for node in nodes %} { @@ -91,7 +75,30 @@ var nodes = [ {% endfor %} ]; -// --- Color palette --- +const portMap = { + 1: "Text", + 67: "Telemetry", + 3: "Position", + 70: "Traceroute", + 4: "Node Info", + 71: "Neighbour Info", + 73: "Map Report" +}; + +function timeAgo(date) { + var now = new Date(); + var diff = now - new Date(date); + var seconds = Math.floor(diff / 1000); + var minutes = Math.floor(seconds / 60); + var hours = Math.floor(minutes / 60); + var days = Math.floor(hours / 24); + + if (days > 0) return days + "d"; + if (hours > 0) return hours + "h"; + if (minutes > 0) return minutes + "m"; + return seconds + "s"; +} + const palette = [ "#e6194b","#4363d8","#f58231","#911eb4","#46f0f0","#f032e6","#bcf60c","#fabebe", "#008080","#e6beff","#9a6324","#fffac8","#800000","#aaffc3","#808000","#ffd8b1","#000075","#808080" @@ -106,16 +113,18 @@ function hashToColor(str) { return color; } -// --- Plot nodes --- -nodes.forEach(function(node) { - if (node.lat != null && node.long != null) { - let category = node.channel; - let isRouter = node.isRouter; - channels.add(category); +// Plot nodes +var bounds = L.latLngBounds(); +var channels = new Set(); +nodes.forEach(function(node) { + if (node.lat !== null && node.long !== null) { + let category = node.channel; + channels.add(category); let color = hashToColor(category); + let markerOptions = { - radius: isRouter ? 9 : 7, + radius: node.isRouter ? 9 : 7, color: "white", fillColor: color, fillOpacity: 1, @@ -128,35 +137,33 @@ nodes.forEach(function(node) { Model: ${node.hw_model}
Role: ${node.role}
`; - if (node.last_update) popupContent += `Last seen: ${node.last_update}
`; + if (node.last_update) popupContent += `Last seen: ${timeAgo(node.last_update)}
`; if (node.firmware) popupContent += `Firmware: ${node.firmware}
`; var marker = L.circleMarker([node.lat, node.long], markerOptions).addTo(map); marker.nodeId = node.id; markerById[node.id] = marker; - marker.on('click', function(e) { - e.originalEvent.stopPropagation(); - edgeLayer.clearLayers(); - onNodeClick(node); + marker.on('click', function() { + marker.bindPopup(popupContent).openPopup(); + setTimeout(() => marker.closePopup(), 3000); }); if (!markers[category]) markers[category] = []; - markers[category].push({ marker, isRouter }); + markers[category].push({ marker, isRouter: node.isRouter }); - nodeIndex[node.id] = [node.lat, node.long]; bounds.extend(marker.getLatLng()); } }); -// --- Fit bounds --- +// Fit map bounds var bayAreaBounds = [ [{{ site_config["site"]["map_top_left_lat"] }}, {{ site_config["site"]["map_top_left_lon"] }}], [{{ site_config["site"]["map_bottom_right_lat"] }}, {{ site_config["site"]["map_bottom_right_lon"] }}] ]; map.fitBounds(bayAreaBounds); -// --- Filters --- +// Channel filters let filterContainer = document.getElementById("filter-container"); channels.forEach(channel => { let filterId = `filter-${channel.replace(/\s+/g, '-').toLowerCase()}`; @@ -178,12 +185,15 @@ function updateMarkers() { }); }); } + document.querySelectorAll(".filter-checkbox").forEach(input => { input.addEventListener("change", updateMarkers); }); -// --- Load edges --- +// --- Edges --- +var edgeLayer = L.layerGroup().addTo(map); var edgesData = null; + function loadEdges(callback) { if (edgesData) callback(edgesData); else { @@ -194,22 +204,20 @@ function loadEdges(callback) { } } -// --- On node click --- function onNodeClick(node) { console.log(`Clicked node ${node.long_name}: lat=${node.lat}, long=${node.long}`); loadEdges(edges => { edgeLayer.clearLayers(); edges.forEach(edge => { + // Only draw edges connected to the clicked node if (edge.from !== node.id && edge.to !== node.id) return; let fromNode = nodes.find(n => n.id === edge.from); let toNode = nodes.find(n => n.id === edge.to); - // Validate coordinates and skip zeroes + // Only draw if lat/long are not 0 if (fromNode && toNode && - fromNode.lat != null && fromNode.long != null && - toNode.lat != null && toNode.long != null && fromNode.lat !== 0 && fromNode.long !== 0 && toNode.lat !== 0 && toNode.long !== 0) { @@ -230,13 +238,30 @@ function onNodeClick(node) { }); } -// --- Click empty space hides edges --- -map.on('click', function() { - edgeLayer.clearLayers(); + +// Attach edge click events +nodes.forEach(node => { + if (node.lat != null && node.long != null) { + let marker = markerById[node.id]; + if (marker) marker.on('click', () => onNodeClick(node)); + } }); // --- Blinking nodes --- +var lastFetchTime = null; const activeBlinks = new Map(); + +function fetchLatestPacket() { + fetch(`/api/packets?limit=1&since=1970-01-01T00:00:00Z`) + .then(res => res.json()) + .then(data => { + if (data.packets && data.packets.length > 0) lastFetchTime = data.packets[0].import_time; + else lastFetchTime = new Date().toISOString(); + console.log("Starting from:", lastFetchTime); + }) + .catch(err => console.error("Error fetching latest packet:", err)); +} + function blinkNode(marker, longName, portnum) { if (activeBlinks.has(marker)) { clearInterval(activeBlinks.get(marker)); @@ -274,18 +299,6 @@ function blinkNode(marker, longName, portnum) { activeBlinks.set(marker, interval); } -var lastFetchTime = null; -function fetchLatestPacket() { - fetch(`/api/packets?limit=1&since=1970-01-01T00:00:00Z`) - .then(res => res.json()) - .then(data => { - if (data.packets && data.packets.length > 0) lastFetchTime = data.packets[0].import_time; - else lastFetchTime = new Date().toISOString(); - console.log("Starting from:", lastFetchTime); - }) - .catch(err => console.error("Error fetching latest packet:", err)); -} - function fetchNewPackets() { if (!lastFetchTime) return; fetch(`/api/packets?since=${lastFetchTime}`) @@ -309,6 +322,5 @@ function fetchNewPackets() { fetchLatestPacket(); setInterval(fetchNewPackets, 1000); - {% endblock %}