diff --git a/public/nodes.js b/public/nodes.js index 2547541..fd69754 100644 --- a/public/nodes.js +++ b/public/nodes.js @@ -628,34 +628,9 @@ headerSelector: '#fullNeighborsHeader' }); - // #690 — Clock Skew detail section - (async function loadClockSkew() { - var container = document.getElementById('node-clock-skew'); - if (!container) return; - try { - var cs = await api('/nodes/' + encodeURIComponent(n.public_key) + '/clock-skew', { ttl: 30000 }); - if (!cs || !cs.severity) return; - container.style.display = ''; - var severityColor = SKEW_SEVERITY_COLORS[cs.severity] || 'var(--text-muted)'; - var severityLabel = SKEW_SEVERITY_LABELS[cs.severity] || cs.severity; - var driftHtml = cs.driftPerDaySec ? '
Drift: ' + formatDrift(cs.driftPerDaySec) + '
' : ''; - var sparkHtml = renderSkewSparkline(cs.samples, 200, 32); - var skewDisplay = cs.severity === 'no_clock' - ? 'No Clock' - : '' + formatSkew(cs.medianSkewSec) + ''; - container.innerHTML = - '

⏰ Clock Skew

' + - '
' + - skewDisplay + - renderSkewBadge(cs.severity, cs.medianSkewSec) + - (cs.calibrated ? ' ✓ calibrated' : '') + - '
' + - driftHtml + - (sparkHtml ? '
' + sparkHtml + '
Skew over time (' + (cs.samples || []).length + ' samples)
' : ''); - } catch (e) { - // Non-fatal — section stays hidden - } - })(); + // #690 — Clock Skew detail section (full-screen view) + loadClockSkewInto(document.getElementById('node-clock-skew'), n.public_key); + // Affinity debug panel — show if debugAffinity is enabled (function loadAffinityDebug() { @@ -812,6 +787,36 @@ let _allNodes = null; // cached full node list let _fleetSkew = null; // cached clock skew map: pubkey → {severity, medianSkewSec, ...} + /** + * Fetch per-node clock skew and render into the given container element. + * Shared between the full-screen detail page and the side panel (#813, #690). + * No-op if the container is missing, the API errors, or the response lacks severity. + */ + async function loadClockSkewInto(container, pubkey) { + if (!container) return; + try { + var cs = await api('/nodes/' + encodeURIComponent(pubkey) + '/clock-skew', { ttl: 30000 }); + if (!cs || !cs.severity) return; + container.style.display = ''; + var driftHtml = cs.driftPerDaySec ? '
Drift: ' + formatDrift(cs.driftPerDaySec) + '
' : ''; + var sparkHtml = renderSkewSparkline(cs.samples, 200, 32); + var skewDisplay = cs.severity === 'no_clock' + ? 'No Clock' + : '' + formatSkew(cs.medianSkewSec) + ''; + container.innerHTML = + '

⏰ Clock Skew

' + + '
' + + skewDisplay + + renderSkewBadge(cs.severity, cs.medianSkewSec) + + (cs.calibrated ? ' ✓ calibrated' : '') + + '
' + + driftHtml + + (sparkHtml ? '
' + sparkHtml + '
Skew over time (' + (cs.samples || []).length + ' samples)
' : ''); + } catch (e) { + // Non-fatal — section stays hidden + } + } + /** Fetch fleet clock skew once, return map keyed by pubkey */ async function getFleetSkew() { if (_fleetSkew) return _fleetSkew; @@ -1194,6 +1199,8 @@ + + ${observers.length ? `
${(() => { const regions = [...new Set(observers.map(o => o.iata).filter(Boolean))]; return regions.length ? `
Regions: ${regions.join(', ')}
` : ''; })()}

Heard By (${observers.length} observer${observers.length > 1 ? 's' : ''})

@@ -1287,6 +1294,10 @@ viewAllPubkey: n.public_key }); + // #813 — Clock Skew section in side panel (mirrors full-screen view) + loadClockSkewInto(document.getElementById('node-clock-skew'), n.public_key); + + // Fetch paths through this node api('/nodes/' + encodeURIComponent(n.public_key) + '/paths', { ttl: CLIENT_TTL.nodeDetail }).then(pathData => { const el = document.getElementById('pathsContent');