diff --git a/public/live.js b/public/live.js index 80d9fd7f..975bf0ac 100644 --- a/public/live.js +++ b/public/live.js @@ -54,7 +54,7 @@ function packetMatchesRegion(packets, obsMap, selected) { if (!selected || !selected.length) return true; if (!packets || !packets.length) return false; - const sel = selected.map(function (s) { return String(s).toUpperCase(); }); + const sel = selected.map(function(s) { return String(s).toUpperCase(); }); for (var i = 0; i < packets.length; i++) { var oid = packets[i] && packets[i].observer_id; if (oid == null) continue; @@ -195,7 +195,7 @@ // Wire up click handlers on corner buttons var btns = document.querySelectorAll('.panel-corner-btn[data-panel]'); for (var i = 0; i < btns.length; i++) { - btns[i].addEventListener('click', function (e) { + btns[i].addEventListener('click', function(e) { e.stopPropagation(); var panelId = this.getAttribute('data-panel'); onCornerClick(panelId); @@ -397,7 +397,7 @@ const pkts = data.packets || []; return expandToBufferEntriesAsync(pkts); }) - .then(function (replayEntries) { + .then(function(replayEntries) { if (gen !== VCR.replayGen) return; // stale async result — user changed mode if (replayEntries.length === 0) { vcrSetMode('PAUSED'); @@ -461,7 +461,7 @@ const filtered = pkts.filter(p => !existingIds.has(p.id)); return expandToBufferEntriesAsync(filtered); }) - .then(function (newEntries) { + .then(function(newEntries) { if (gen !== VCR.replayGen) return; // stale async result VCR.buffer = [].concat(newEntries, VCR.buffer); VCR.playhead = 0; @@ -470,7 +470,7 @@ startReplay(); updateTimeline(); }) - .catch(() => { }); + .catch(() => {}); } function startReplay() { @@ -488,7 +488,7 @@ } } const replayGroups = [...hashGroups.values()].sort((a, b) => a.ts - b.ts); - console.log('[vcr] ' + replayGroups.length + ' groups from ' + VCR.buffer.length + ' buffer entries. Top 3:', replayGroups.slice(0, 3).map(g => g.packets.length + ' obs')); + console.log('[vcr] ' + replayGroups.length + ' groups from ' + VCR.buffer.length + ' buffer entries. Top 3:', replayGroups.slice(0,3).map(g => g.packets.length + ' obs')); let groupIdx = 0; function tick() { @@ -531,7 +531,7 @@ .then(data => { const pkts = data.packets || []; if (pkts.length === 0) return false; - return expandToBufferEntriesAsync(pkts).then(function (newEntries) { + return expandToBufferEntriesAsync(pkts).then(function(newEntries) { if (gen !== VCR.replayGen) return false; // stale VCR.buffer = VCR.buffer.concat(newEntries); return true; @@ -579,7 +579,7 @@ clickablePaths.push(entry); pruneClickablePaths(Date.now()); let dismissTimer = null; - poly.on('click', function (e) { + poly.on('click', function(e) { if (dismissTimer) clearTimeout(dismissTimer); const html = buildClickablePathPopupHtml(typeName, color, hopNames, tsMs, hash); L.popup({ maxWidth: 280, className: 'path-info-popup' }) @@ -611,28 +611,28 @@ // 7-segment LCD renderer const SEG_MAP = { - '0': 0x7E, '1': 0x30, '2': 0x6D, '3': 0x79, '4': 0x33, '5': 0x5B, '6': 0x5F, '7': 0x70, - '8': 0x7F, '9': 0x7B, '-': 0x01, ':': 0x80, ' ': 0x00, 'P': 0x67, 'A': 0x77, 'U': 0x3E, - 'S': 0x5B, 'E': 0x4F, 'L': 0x0E, 'I': 0x30, 'V': 0x3E, '+': 0x01 + '0':0x7E,'1':0x30,'2':0x6D,'3':0x79,'4':0x33,'5':0x5B,'6':0x5F,'7':0x70, + '8':0x7F,'9':0x7B,'-':0x01,':':0x80,' ':0x00,'P':0x67,'A':0x77,'U':0x3E, + 'S':0x5B,'E':0x4F,'L':0x0E,'I':0x30,'V':0x3E,'+':0x01 }; function drawSegDigit(ctx, x, y, w, h, bits, color) { const t = Math.max(2, h * 0.12); // segment thickness const g = 1; // gap - const hw = w - 2*g, hh = (h - 3 * g) / 2; + const hw = w - 2*g, hh = (h - 3*g) / 2; ctx.fillStyle = color; // a=top, b=top-right, c=bot-right, d=bot, e=bot-left, f=top-left, g=mid - if (bits & 0x40) ctx.fillRect(x + g + t / 2, y, hw - t, t); // a - if (bits & 0x20) ctx.fillRect(x + w - t, y + g + t / 2, t, hh - t / 2); // b - if (bits & 0x10) ctx.fillRect(x + w - t, y + hh + 2 * g + t / 2, t, hh - t / 2);// c - if (bits & 0x08) ctx.fillRect(x + g + t / 2, y + h - t, hw - t, t); // d - if (bits & 0x04) ctx.fillRect(x, y + hh + 2 * g + t / 2, t, hh - t / 2); // e - if (bits & 0x02) ctx.fillRect(x, y + g + t / 2, t, hh - t / 2); // f - if (bits & 0x01) ctx.fillRect(x + g + t / 2, y + hh + g - t / 2, hw - t, t); // g + if (bits & 0x40) ctx.fillRect(x+g+t/2, y, hw-t, t); // a + if (bits & 0x20) ctx.fillRect(x+w-t, y+g+t/2, t, hh-t/2); // b + if (bits & 0x10) ctx.fillRect(x+w-t, y+hh+2*g+t/2, t, hh-t/2);// c + if (bits & 0x08) ctx.fillRect(x+g+t/2, y+h-t, hw-t, t); // d + if (bits & 0x04) ctx.fillRect(x, y+hh+2*g+t/2, t, hh-t/2); // e + if (bits & 0x02) ctx.fillRect(x, y+g+t/2, t, hh-t/2); // f + if (bits & 0x01) ctx.fillRect(x+g+t/2, y+hh+g-t/2, hw-t, t); // g // colon if (bits & 0x80) { const r = t * 0.6; - ctx.beginPath(); ctx.arc(x + w / 2, y + h * 0.33, r, 0, Math.PI * 2); ctx.fill(); - ctx.beginPath(); ctx.arc(x + w / 2, y + h * 0.67, r, 0, Math.PI * 2); ctx.fill(); + ctx.beginPath(); ctx.arc(x+w/2, y+h*0.33, r, 0, Math.PI*2); ctx.fill(); + ctx.beginPath(); ctx.arc(x+w/2, y+h*0.67, r, 0, Math.PI*2); ctx.fill(); } } function drawLcdText(text, color) { @@ -776,7 +776,7 @@ */ var VCR_CHUNK_SIZE = 200; function expandToBufferEntriesAsync(pkts) { - return new Promise(function (resolve) { + return new Promise(function(resolve) { var entries = []; var i = 0; function processChunk() { @@ -879,9 +879,9 @@ } else { const entry = { packets: [pkt], timer: setTimeout(() => { - const buffered = propagationBuffer.get(hash); - propagationBuffer.delete(hash); - if (buffered) renderPacketTree(buffered.packets); + const buffered = propagationBuffer.get(hash); + propagationBuffer.delete(hash); + if (buffered) renderPacketTree(buffered.packets); }, PROPAGATION_BUFFER_MS) }; propagationBuffer.set(hash, entry); @@ -911,7 +911,7 @@ VCR.timelineTimestamps = timestamps.map(t => new Date(t).getTime()); VCR.timelineFetchedScope = scopeMs; } - } catch (e) { /* ignore */ } + } catch(e) { /* ignore */ } } function updateTimeline() { @@ -1232,7 +1232,7 @@ const w = size.x + padX * 2; const h = size.y + padY * 2; - const dpr = window.devicePixelRatio || 1; + const dpr = window.devicePixelRatio || 1; // Updating width/height automatically clears the canvas animCanvas.width = w * dpr; @@ -1279,8 +1279,8 @@ function _liveResolveTile(dark) { if (!dark) return { url: TILE_LIGHT, attribution: '© OpenStreetMap © CartoDB', refUrl: null }; const reg = window.MC_TILE_PROVIDERS || {}; - const id = (typeof window.MC_getDarkTileProvider === 'function') ? window.MC_getDarkTileProvider() : 'carto-dark'; - const p = reg[id] || reg['carto-dark'] || {}; + const id = (typeof window.MC_getDarkTileProvider === 'function') ? window.MC_getDarkTileProvider() : 'carto-dark'; + const p = reg[id] || reg['carto-dark'] || {}; return { url: p.url || p.baseUrl || TILE_DARK, attribution: p.attribution || '© OpenStreetMap © CartoDB', @@ -1293,7 +1293,7 @@ if (tileLayer.options) tileLayer.options.attribution = r.attribution; if (dark && r.refUrl) { if (!_liveDarkRefLayer) { - _liveDarkRefLayer = L.tileLayer(r.refUrl, {maxZoom: 19, attribution: r.attribution}).addTo(map); + _liveDarkRefLayer = L.tileLayer(r.refUrl, { maxZoom: 19, attribution: r.attribution }).addTo(map); } else { _liveDarkRefLayer.setUrl(r.refUrl); } @@ -1308,21 +1308,21 @@ } } const _liveInitTile = _liveResolveTile(isDark); - let tileLayer = L.tileLayer(_liveInitTile.url, {maxZoom: 19, attribution: _liveInitTile.attribution}).addTo(map); + let tileLayer = L.tileLayer(_liveInitTile.url, { maxZoom: 19, attribution: _liveInitTile.attribution }).addTo(map); if (isDark && _liveInitTile.refUrl) { - _liveDarkRefLayer = L.tileLayer(_liveInitTile.refUrl, {maxZoom: 19, attribution: _liveInitTile.attribution}).addTo(map); + _liveDarkRefLayer = L.tileLayer(_liveInitTile.refUrl, { maxZoom: 19, attribution: _liveInitTile.attribution }).addTo(map); } if (typeof window.MC_applyTileFilter === 'function') window.MC_applyTileFilter(); // Swap tiles when theme changes - const _themeObs = new MutationObserver(function() { + const _themeObs = new MutationObserver(function () { const dark = document.documentElement.getAttribute('data-theme') === 'dark' || (document.documentElement.getAttribute('data-theme') !== 'light' && window.matchMedia('(prefers-color-scheme: dark)').matches); _liveSyncDarkTiles(dark); }); _themeObs.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] }); // #1420 — re-render on customizer change. - window.addEventListener('mc-tile-provider-changed', function() { + window.addEventListener('mc-tile-provider-changed', function () { const dark = document.documentElement.getAttribute('data-theme') === 'dark' || (document.documentElement.getAttribute('data-theme') !== 'light' && window.matchMedia('(prefers-color-scheme: dark)').matches); _liveSyncDarkTiles(dark); @@ -1348,7 +1348,7 @@ injectSVGFilters(); AreaFilter.init(document.getElementById('liveAreaFilter')); - AreaFilter.onChange(function() { loadNodes(); }); + AreaFilter.onChange(function () { loadNodes(); }); await loadNodes(); showHeatMap(); connectWS(); @@ -1420,7 +1420,7 @@ // (cmd/server/types.go ObserverListResponse) — NOT a top-level array. // Bug #1136: previously parsed as array → map empty → region filter // dropped every packet. - fetch('/api/observers').then(function (r) { return r.json(); }).then(function (data) { + fetch('/api/observers').then(function(r) { return r.json(); }).then(function(data) { setObserverIataMap(buildObserverIataMap(data)); }).catch(function() { /* leave map empty; filter will hide all when active */ }); RegionFilter.init(rfEl, { dropdown: true }); @@ -1587,7 +1587,7 @@ } }); - nodeFilterInput.addEventListener('blur', function() { + nodeFilterInput.addEventListener('blur', function () { // Slight delay so click on a suggestion can register first. setTimeout(hideDropdown, 150); }); @@ -1607,7 +1607,7 @@ } // Geo filter overlay - (async function() { + (async function () { try { const gf = await api('/config/geo-filter', { ttl: 3600 }); if (!gf || !gf.polygon || gf.polygon.length < 3) return; @@ -1617,7 +1617,7 @@ color: geoColor, weight: 2, opacity: 0.8, fillColor: geoColor, fillOpacity: 0.08 }); - const bufferPoly = gf.bufferKm > 0 ? (function() { + const bufferPoly = gf.bufferKm > 0 ? (function () { let cLat = 0, cLon = 0; gf.polygon.forEach(function (p) { cLat += p[0]; cLon += p[1]; }); cLat /= gf.polygon.length; cLon /= gf.polygon.length; @@ -1807,14 +1807,14 @@ var tog = document.getElementById(p.togId); if (body) body.removeAttribute('hidden'); if (root) { root.classList.remove('is-collapsed'); root.classList.remove('is-expanded'); } - if (tog) { tog.setAttribute('aria-expanded', 'true'); } + if (tog) { tog.setAttribute('aria-expanded', 'true'); } } } } pairs.forEach(function (p) { var tog = document.getElementById(p.togId); if (!tog) return; - tog.addEventListener('click', function() { + tog.addEventListener('click', function () { var root = document.getElementById(p.rootId); var nowExpanded = !(root && root.classList.contains('is-expanded')); setExpanded(p, nowExpanded); @@ -1890,9 +1890,9 @@ // Resize clamping (debounced) var resizeTimer = null; - window.addEventListener('resize', function() { + window.addEventListener('resize', function () { clearTimeout(resizeTimer); - resizeTimer = setTimeout(function() { dragMgr.handleResize(); }, 200); + resizeTimer = setTimeout(function () { dragMgr.handleResize(); }, 200); }); } @@ -1906,7 +1906,7 @@ const swatch = window.makeRoleMarkerSVG ? window.makeRoleMarkerSVG(role, color, 14) : ``; - li.innerHTML = ` ${(ROLE_LABELS[role] || role).replace(/s$/,'')}`; + li.innerHTML = ` ${(ROLE_LABELS[role] || role).replace(/s$/, '')}`; roleLegendList.appendChild(li); } } @@ -1948,7 +1948,7 @@ // Save/restore map view const savedView = localStorage.getItem('live-map-view'); if (savedView) { - try { const v = JSON.parse(savedView); map.setView([v.lat, v.lng], v.zoom); } catch { } + try { const v = JSON.parse(savedView); map.setView([v.lat, v.lng], v.zoom); } catch {} } map.on('moveend', () => { const c = map.getCenter(); @@ -1997,7 +1997,7 @@ item.setAttribute('role', 'menuitem'); item.setAttribute('data-scope', src.dataset.scope); item.textContent = src.textContent; - item.addEventListener('click', function() { + item.addEventListener('click', function () { src.click(); // delegate to original handler — keeps single source of truth menu.setAttribute('hidden', ''); moreBtn.setAttribute('aria-expanded', 'false'); @@ -2010,13 +2010,13 @@ e.stopPropagation(); var open = !menu.hasAttribute('hidden'); if (open) { menu.setAttribute('hidden', ''); moreBtn.setAttribute('aria-expanded', 'false'); } - else { menu.removeAttribute('hidden'); moreBtn.setAttribute('aria-expanded', 'true'); } + else { menu.removeAttribute('hidden'); moreBtn.setAttribute('aria-expanded', 'true'); } }); // Click outside closes the menu. document.addEventListener('click', function (e) { if (menu.hasAttribute('hidden')) return; if (e.target === moreBtn || moreBtn.contains(e.target) || - e.target === menu || menu.contains(e.target)) return; + e.target === menu || menu.contains(e.target)) return; menu.setAttribute('hidden', ''); moreBtn.setAttribute('aria-expanded', 'false'); }); @@ -2124,7 +2124,7 @@ // Refresh relative timestamps in feed every 10 seconds (#701) _feedTimestampInterval = setInterval(function() { - document.querySelectorAll('.feed-time[data-ts]').forEach(function (el) { + document.querySelectorAll('.feed-time[data-ts]').forEach(function(el) { el.innerHTML = formatLiveTimestampHtml(Number(el.dataset.ts)); }); }, 10000); @@ -2155,10 +2155,10 @@ _navCleanup.timeout = setTimeout(() => { topNav.classList.add('nav-autohide'); }, 4000); } }); - if (_navCleanup.pinned) { + if (_navCleanup.pinned) { pinBtn.classList.add('pinned'); pinBtn.setAttribute('aria-pressed', 'true'); - topNav.classList.remove('nav-autohide'); + topNav.classList.remove('nav-autohide'); } topNav.appendChild(pinBtn); } @@ -2216,7 +2216,7 @@ const observers = h.observers || []; const recent = h.recentPackets || []; const roleColor = ROLE_COLORS[n.role] || '#6b7280'; - const roleLabel = (ROLE_LABELS[n.role] || n.role || 'unknown').replace(/s$/,''); + const roleLabel = (ROLE_LABELS[n.role] || n.role || 'unknown').replace(/s$/, ''); const hasLoc = n.lat != null && n.lon != null; const lastSeen = formatLiveTimestampHtml(n.last_seen); const thresholds = window.getHealthThresholds ? getHealthThresholds(n.role) : { degradedMs: 3600000, silentMs: 86400000 }; @@ -2241,9 +2241,9 @@
${escapeHtml(n.default_scope)}`
- }${escapeHtml(n.default_scope)}`
+}` : ''}
${hasLoc ? `