diff --git a/public/analytics.js b/public/analytics.js index 32d9665d..49a7b909 100644 --- a/public/analytics.js +++ b/public/analytics.js @@ -1349,29 +1349,52 @@ } // Top hops leaderboard - html += `

πŸ† Top 20 Longest Hops

`; + html += `

πŸ† Top 20 Longest Hops

#FromToDistance (km)TypeSNRPacket
`; const top20 = data.topHops.slice(0, 20); top20.forEach((h, i) => { const fromLink = h.fromPk ? `${esc(h.fromName)}` : esc(h.fromName || '?'); const toLink = h.toPk ? `${esc(h.toName)}` : esc(h.toName || '?'); const snr = h.snr != null ? h.snr + ' dB' : 'β€”'; const pktLink = h.hash ? `${esc(h.hash.slice(0, 12))}…` : 'β€”'; - html += ``; + const mapBtn = h.fromPk && h.toPk ? `` : ''; + html += ``; }); html += `
#FromToDistance (km)TypeSNRPacket
${i+1}${fromLink}${toLink}${h.dist}${esc(h.type)}${snr}${pktLink}
${i+1}${fromLink}${toLink}${h.dist}${esc(h.type)}${snr}${pktLink}${mapBtn}
`; // Top paths if (data.topPaths.length) { - html += `

πŸ›€οΈ Top 10 Longest Multi-Hop Paths

`; + html += `

πŸ›€οΈ Top 10 Longest Multi-Hop Paths

#Total Distance (km)HopsRoutePacket
`; data.topPaths.slice(0, 10).forEach((p, i) => { const route = p.hops.map(h => esc(h.fromName)).concat(esc(p.hops[p.hops.length-1].toName)).join(' β†’ '); const pktLink = p.hash ? `${esc(p.hash.slice(0, 12))}…` : 'β€”'; - html += ``; + // Collect all unique pubkeys in path order + const pathPks = []; + p.hops.forEach(h => { if (h.fromPk && !pathPks.includes(h.fromPk)) pathPks.push(h.fromPk); }); + if (p.hops.length && p.hops[p.hops.length-1].toPk) { const last = p.hops[p.hops.length-1].toPk; if (!pathPks.includes(last)) pathPks.push(last); } + const mapBtn = pathPks.length >= 2 ? `` : ''; + html += ``; }); html += `
#Total Distance (km)HopsRoutePacket
${i+1}${p.totalDist}${p.hopCount}${route}${pktLink}
${i+1}${p.totalDist}${p.hopCount}${route}${pktLink}${mapBtn}
`; } el.innerHTML = html; + + // Wire up map buttons + el.querySelectorAll('.dist-map-hop').forEach(btn => { + btn.addEventListener('click', () => { + sessionStorage.setItem('map-route-hops', JSON.stringify({ hops: [btn.dataset.from, btn.dataset.to] })); + window.location.hash = '#/map?route=1'; + }); + }); + el.querySelectorAll('.dist-map-path').forEach(btn => { + btn.addEventListener('click', () => { + try { + const hops = JSON.parse(btn.dataset.hops); + sessionStorage.setItem('map-route-hops', JSON.stringify({ hops })); + window.location.hash = '#/map?route=1'; + } catch {} + }); + }); } catch (e) { el.innerHTML = `
Failed to load distance analytics: ${esc(e.message)}
`; } diff --git a/public/index.html b/public/index.html index e5502570..fb7cd35f 100644 --- a/public/index.html +++ b/public/index.html @@ -88,11 +88,11 @@ - + - + diff --git a/server.js b/server.js index 6ee8e02a..fdbd4130 100644 --- a/server.js +++ b/server.js @@ -1734,7 +1734,7 @@ app.get('/api/analytics/distance', (req, res) => { allHops.push(hop); catDists[type].push(dist); pathDist += dist; - pathHops.push({ fromName: a.name, toName: b.name, dist: hop.dist }); + pathHops.push({ fromName: a.name, fromPk: a.public_key, toName: b.name, toPk: b.public_key, dist: hop.dist }); if (hourBucket) { if (!distByHour[hourBucket]) distByHour[hourBucket] = [];