From c4453fbb31a1256d0cf2690e1f761c9854000272 Mon Sep 17 00:00:00 2001 From: Pablo Revilla Date: Wed, 24 Dec 2025 10:54:09 -0800 Subject: [PATCH] Modify packet.html to sort by hop count. --- meshview/templates/packet.html | 198 +++++++++++++++++++-------------- 1 file changed, 116 insertions(+), 82 deletions(-) diff --git a/meshview/templates/packet.html b/meshview/templates/packet.html index 8cf795e..9b9a3b7 100644 --- a/meshview/templates/packet.html +++ b/meshview/templates/packet.html @@ -380,103 +380,137 @@ document.addEventListener("DOMContentLoaded", async () => { } /* --------------------------------------------- - Load packets_seen - ----------------------------------------------*/ - const seenRes = await fetch(`/api/packets_seen/${packetId}`); - const seenData = await seenRes.json(); - const seenList = seenData.seen ?? []; + Load packets_seen +----------------------------------------------*/ +const seenRes = await fetch(`/api/packets_seen/${packetId}`); +const seenData = await seenRes.json(); +const seenList = seenData.seen ?? []; - const seenSorted = seenList.slice().sort((a,b)=>{ - return (b.hop_start ?? -999) - (a.hop_start ?? -999); - }); +/* --------------------------------------------- + Sort by hop count (highest first) +----------------------------------------------*/ +const seenSorted = seenList.slice().sort((a,b)=>{ + const ha = (a.hop_start ?? 0) - (a.hop_limit ?? 0); + const hb = (b.hop_start ?? 0) - (b.hop_limit ?? 0); + return hb - ha; +}); - if (seenSorted.length){ - seenContainer.classList.remove("d-none"); - seenCountSpan.textContent = `(${seenSorted.length})`; - } +if (seenSorted.length){ + seenContainer.classList.remove("d-none"); + seenCountSpan.textContent = `(${seenSorted.length})`; +} - /* --------------------------------------------- - Render gateway table + map markers - ----------------------------------------------*/ - seenTableBody.innerHTML = seenSorted.map(s=>{ - const node = nodeLookup[s.node_id]; - const label = node?.long_name || s.node_id; +/* --------------------------------------------- + GROUP BY HOP COUNT +----------------------------------------------*/ +const hopGroups = {}; - const timeStr = s.import_time_us - ? new Date(s.import_time_us/1000).toLocaleTimeString() - : "—"; +seenSorted.forEach(s => { + const hopValue = Math.max( + 0, + (s.hop_start ?? 0) - (s.hop_limit ?? 0) + ); + if (!hopGroups[hopValue]) hopGroups[hopValue] = []; + hopGroups[hopValue].push(s); +}); - if (node?.last_lat && node.last_long){ - const rlat = node.last_lat/1e7; - const rlon = node.last_long/1e7; - allBounds.push([rlat, rlon]); +/* --------------------------------------------- + Render grouped gateway table + map markers +----------------------------------------------*/ +seenTableBody.innerHTML = Object.keys(hopGroups) + .sort((a,b) => Number(a) - Number(b)) // 0 hop first + .map(hopKey => { - const hopValue = (s.hop_start ?? 0) - (s.hop_limit ?? 0); - const color = hopColor(hopValue); + const hopLabel = + hopKey === "0" + ? (packetTranslations.direct || "Direct (0 hops)") + : `${hopKey} ${packetTranslations.hops || "hops"}`; - const marker = L.marker([rlat,rlon],{ - icon: L.divIcon({ - html: ` -
${hopValue}
`, - className: "", - iconSize:[24,24], - iconAnchor:[12,12] - }) - }).addTo(map); + const rows = hopGroups[hopKey].map(s => { + const node = nodeLookup[s.node_id]; + const label = node?.long_name || s.node_id; - let distKm = null, distMi = null; - if (srcLat && srcLon){ - distKm = haversine(srcLat, srcLon, rlat, rlon); - distMi = distKm * 0.621371; + const timeStr = s.import_time_us + ? new Date(s.import_time_us/1000).toLocaleTimeString() + : "—"; + + /* ---------------- MAP MARKERS (UNCHANGED) ---------------- */ + if (node?.last_lat && node.last_long){ + const rlat = node.last_lat/1e7; + const rlon = node.last_long/1e7; + allBounds.push([rlat, rlon]); + + const color = hopColor(hopKey); + + const marker = L.marker([rlat,rlon],{ + icon: L.divIcon({ + html: ` +
${hopKey}
`, + className: "", + iconSize:[24,24], + iconAnchor:[12,12] + }) + }).addTo(map); + + marker.bindPopup(` +
+ ${label}
+ Node ID: + ${s.node_id}
+ HW: ${node?.hw_model ?? "—"}
+ Channel: ${s.channel ?? "—"}

+ + Signal
+ RSSI: ${s.rx_rssi ?? "—"}
+ SNR: ${s.rx_snr ?? "—"}

+ + Hops: ${hopKey} +
+ `); } - marker.bindPopup(` -
- ${label}
- ${packetTranslations.node_id_short || "Node ID"}: - ${s.node_id}
- HW: ${node?.hw_model ?? "—"}
- ${packetTranslations.channel || "Channel"}: ${s.channel ?? "—"}

- - ${packetTranslations.signal || "Signal"}
- RSSI: ${s.rx_rssi ?? "—"}
- SNR: ${s.rx_snr ?? "—"}

- - ${packetTranslations.hops || "Hops"}: ${hopValue}
- - ${packetTranslations.distance || "Distance"}:
- ${ - distKm - ? `${distKm.toFixed(2)} km (${distMi.toFixed(2)} mi)` - : "—" - } -
- `); - } + return ` + + ${label} + ${s.rx_rssi ?? "—"} + ${s.rx_snr ?? "—"} + ${hopKey} + ${s.channel ?? "—"} + ${timeStr} + + `; + }).join(""); return ` - ${label} - ${s.rx_rssi ?? "—"} - ${s.rx_snr ?? "—"} - ${s.hop_start ?? "—"} → ${s.hop_limit ?? "—"} - ${s.channel ?? "—"} - ${timeStr} - `; + + 🔁 ${hopLabel} (${hopGroups[hopKey].length}) + + + ${rows} + `; }).join(""); + /* --------------------------------------------- Fit map around all markers ----------------------------------------------*/