mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-08-14 09:09:58 +00:00
feat: deep-linkable sections on node detail page
Added ids: node-stats, node-observers, fullPathsSection, node-packets. Use ?section=<id> to scroll to any section on load. e.g. #/nodes/<pubkey>?section=node-packets Variable hash size badge and analytics links updated to use ?section=.
This commit is contained in:
+1
-1
@@ -811,7 +811,7 @@
|
||||
}).join(' ');
|
||||
const stripe = i % 2 === 1 ? 'background:var(--row-stripe)' : '';
|
||||
return `<tr style="${stripe}">
|
||||
<td><a href="#/nodes/${encodeURIComponent(n.public_key)}?highlight=hashsize" style="font-weight:600;color:var(--accent)">${esc(n.name || n.public_key.slice(0, 12))}</a></td>
|
||||
<td><a href="#/nodes/${encodeURIComponent(n.public_key)}?section=node-packets" style="font-weight:600;color:var(--accent)">${esc(n.name || n.public_key.slice(0, 12))}</a></td>
|
||||
<td><span class="badge" style="background:${roleColor}20;color:${roleColor}">${n.role}</span></td>
|
||||
<td><code style="font-family:var(--mono);font-weight:700">${prefix}</code> <span class="text-muted">(${n.hash_size || '?'}B)</span></td>
|
||||
<td>${sizeBadges}</td>
|
||||
|
||||
+12
-9
@@ -119,7 +119,7 @@
|
||||
body.innerHTML = `
|
||||
<div class="node-full-card" style="padding:12px 16px;margin-bottom:8px">
|
||||
<div class="node-detail-name" style="font-size:20px">${escapeHtml(n.name || '(unnamed)')}</div>
|
||||
<div style="margin:4px 0 6px"><span class="badge" style="background:${roleColor}20;color:${roleColor}">${n.role}</span> ${n.hash_size ? `<span class="badge" style="background:var(--nav-bg);color:var(--nav-text);font-family:var(--mono)">${n.public_key.slice(0, n.hash_size * 2).toUpperCase()}</span>` : ''} ${n.hash_size_inconsistent ? `<a href="#/nodes/${encodeURIComponent(n.public_key)}?highlight=hashsize" class="badge" style="background:var(--status-yellow);color:#000;font-size:10px;cursor:pointer;text-decoration:none">⚠️ variable hash size</a>` : ''} ${statusLabel}</div>
|
||||
<div style="margin:4px 0 6px"><span class="badge" style="background:${roleColor}20;color:${roleColor}">${n.role}</span> ${n.hash_size ? `<span class="badge" style="background:var(--nav-bg);color:var(--nav-text);font-family:var(--mono)">${n.public_key.slice(0, n.hash_size * 2).toUpperCase()}</span>` : ''} ${n.hash_size_inconsistent ? `<a href="#/nodes/${encodeURIComponent(n.public_key)}?section=node-packets" class="badge" style="background:var(--status-yellow);color:#000;font-size:10px;cursor:pointer;text-decoration:none">⚠️ variable hash size</a>` : ''} ${statusLabel}</div>
|
||||
${n.hash_size_inconsistent ? `<div style="font-size:11px;color:var(--text-muted);margin:-2px 0 6px;padding:6px 10px;background:var(--surface-2);border-radius:4px;border-left:3px solid var(--status-yellow)">Adverts show varying hash sizes (<strong>${(n.hash_sizes_seen||[]).join('-byte, ')}-byte</strong>). This is a <a href="https://github.com/meshcore-dev/MeshCore/commit/fcfdc5f" target="_blank" style="color:var(--accent)">known bug</a> where automatic adverts ignore the configured multibyte path setting. Fixed in <a href="https://github.com/meshcore-dev/MeshCore/releases/tag/repeater-v1.14.1" target="_blank" style="color:var(--accent)">repeater v1.14.1</a>.</div>` : ''}
|
||||
<div class="node-detail-key mono" style="font-size:11px;word-break:break-all;margin-bottom:6px">${n.public_key}</div>
|
||||
<div>
|
||||
@@ -136,7 +136,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="node-stats-table">
|
||||
<table class="node-stats-table" id="node-stats">
|
||||
<tr><td>Last Heard</td><td>${lastHeard ? timeAgo(lastHeard) : (n.last_seen ? timeAgo(n.last_seen) : '—')}</td></tr>
|
||||
<tr><td>First Seen</td><td>${n.first_seen ? new Date(n.first_seen).toLocaleString() : '—'}</td></tr>
|
||||
<tr><td>Total Packets</td><td>${stats.totalTransmissions || stats.totalPackets || n.advert_count || 0}${stats.totalObservations && stats.totalObservations !== (stats.totalTransmissions || stats.totalPackets) ? ' <span class="text-muted" style="font-size:0.85em">(seen ' + stats.totalObservations + '×)</span>' : ''}</td></tr>
|
||||
@@ -147,7 +147,7 @@
|
||||
<tr><td>Hash Prefix</td><td>${n.hash_size ? '<code style="font-family:var(--mono);font-weight:700">' + n.public_key.slice(0, n.hash_size * 2).toUpperCase() + '</code> (' + n.hash_size + '-byte)' : 'Unknown'}${n.hash_size_inconsistent ? ' <span style="color:var(--status-yellow);cursor:help" title="Seen: ' + (n.hash_sizes_seen || []).join(', ') + '-byte">⚠️ varies</span>' : ''}</td></tr>
|
||||
</table>
|
||||
|
||||
${observers.length ? `<div class="node-full-card">
|
||||
${observers.length ? `<div class="node-full-card" id="node-observers">
|
||||
${(() => { const regions = [...new Set(observers.map(o => o.iata).filter(Boolean))]; return regions.length ? `<div style="margin-bottom:8px"><strong>Regions:</strong> ${regions.map(r => '<span class="badge" style="margin:0 2px">' + escapeHtml(r) + '</span>').join(' ')}</div>` : ''; })()}
|
||||
<h4>Heard By (${observers.length} observer${observers.length > 1 ? 's' : ''})</h4>
|
||||
<table class="data-table" style="font-size:12px">
|
||||
@@ -169,7 +169,7 @@
|
||||
<div id="fullPathsContent"><div class="text-muted" style="padding:8px"><span class="spinner"></span> Loading paths…</div></div>
|
||||
</div>
|
||||
|
||||
<div class="node-full-card">
|
||||
<div class="node-full-card" id="node-packets">
|
||||
<h4>Recent Packets (${adverts.length})</h4>
|
||||
<div class="node-activity-list">
|
||||
${adverts.length ? adverts.map(p => {
|
||||
@@ -219,10 +219,13 @@
|
||||
}).catch(() => {});
|
||||
});
|
||||
|
||||
// Auto-scroll to adverts if highlight=hashsize
|
||||
if (location.hash.includes('highlight=hashsize')) {
|
||||
const recentSection = body.querySelector('.node-activity-list');
|
||||
if (recentSection) setTimeout(() => recentSection.scrollIntoView({ behavior: 'smooth', block: 'start' }), 300);
|
||||
// Deep-link scroll: ?section=node-packets or ?section=node-packets
|
||||
const hashParams = location.hash.split('?')[1] || '';
|
||||
const urlParams = new URLSearchParams(hashParams);
|
||||
const scrollTarget = urlParams.get('section') || (urlParams.has('highlight') ? 'node-packets' : null);
|
||||
if (scrollTarget) {
|
||||
const targetEl = document.getElementById(scrollTarget);
|
||||
if (targetEl) setTimeout(() => targetEl.scrollIntoView({ behavior: 'smooth', block: 'start' }), 300);
|
||||
}
|
||||
|
||||
// QR code for full-screen view
|
||||
@@ -519,7 +522,7 @@
|
||||
panel.innerHTML = `
|
||||
<div class="node-detail">
|
||||
<div class="node-detail-name">${escapeHtml(n.name || '(unnamed)')}</div>
|
||||
<div class="node-detail-role"><span class="badge" style="background:${roleColor}20;color:${roleColor}">${n.role}</span> ${n.hash_size ? `<span class="badge" style="background:var(--nav-bg);color:var(--nav-text);font-family:var(--mono)">${n.public_key.slice(0, n.hash_size * 2).toUpperCase()}</span>` : ''} ${n.hash_size_inconsistent ? `<a href="#/nodes/${encodeURIComponent(n.public_key)}?highlight=hashsize" class="badge" style="background:var(--status-yellow);color:#000;font-size:10px;cursor:pointer;text-decoration:none">⚠️ variable hash size</a>` : ''} ${statusLabel}
|
||||
<div class="node-detail-role"><span class="badge" style="background:${roleColor}20;color:${roleColor}">${n.role}</span> ${n.hash_size ? `<span class="badge" style="background:var(--nav-bg);color:var(--nav-text);font-family:var(--mono)">${n.public_key.slice(0, n.hash_size * 2).toUpperCase()}</span>` : ''} ${n.hash_size_inconsistent ? `<a href="#/nodes/${encodeURIComponent(n.public_key)}?section=node-packets" class="badge" style="background:var(--status-yellow);color:#000;font-size:10px;cursor:pointer;text-decoration:none">⚠️ variable hash size</a>` : ''} ${statusLabel}
|
||||
<a href="#/nodes/${encodeURIComponent(n.public_key)}" class="btn-primary" style="display:inline-block;text-decoration:none;font-size:11px;padding:2px 8px;margin-left:8px">🔍 Details</a>
|
||||
<a href="#/nodes/${encodeURIComponent(n.public_key)}/analytics" class="btn-primary" style="display:inline-block;margin-left:4px;text-decoration:none;font-size:11px;padding:2px 8px">📊 Analytics</a>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user