/* Swactor Dashboard — Shared JavaScript */ // ── Mode Detection ───────────────────────────────────────────── var DASHBOARD_MODE = (document.querySelector('meta[name="dashboard-mode"]') || {}).content || 'live'; var isReplay = (DASHBOARD_MODE === 'replay'); // ── Shared DOM References ────────────────────────────────────── var statusDot = document.getElementById('statusDot'); var uptimeLabel = document.getElementById('uptimeLabel'); var replayBadge = document.getElementById('replayBadge'); var replaySpeed = document.getElementById('replaySpeed'); var replayPct = document.getElementById('replayPct'); var progressBarWrap = document.getElementById('progressBarWrap'); var progressFill = document.getElementById('progressFill'); // ── Replay UI Setup ──────────────────────────────────────────── if (isReplay) { if (replayBadge) replayBadge.className = 'replay-badge visible'; if (progressBarWrap) progressBarWrap.className = 'progress-bar-wrap visible'; if (statusDot) statusDot.className = 'status-dot replaying'; if (uptimeLabel) uptimeLabel.style.display = 'none'; } // ── Uptime Tracking ──────────────────────────────────────────── var lastUptimeMs = null; var lastStatsTime = null; function updateUptime() { if (isReplay || !uptimeLabel) return; var up = lastUptimeMs; if (up !== null && lastStatsTime !== null) { up += (Date.now() - lastStatsTime); } if (up === null) { uptimeLabel.textContent = ''; return; } var s = Math.floor(up / 1000); var d = Math.floor(s / 86400); var h = Math.floor((s % 86400) / 3600); var m = Math.floor((s % 3600) / 60); var sec = s % 60; var parts = []; if (d > 0) parts.push(d + 'd'); if (h > 0 || d > 0) parts.push(h + 'h'); parts.push(m + 'm'); parts.push(sec + 's'); uptimeLabel.textContent = parts.join(' '); } if (uptimeLabel) setInterval(updateUptime, 1000); function trackUptime(data) { if (typeof data.uptime_ms === 'number') { lastUptimeMs = data.uptime_ms; lastStatsTime = Date.now(); } } // ── Status Dot Management ────────────────────────────────────── function setStatus(s) { if (!statusDot) return; if (s === 'done') { statusDot.className = 'status-dot done'; if (isReplay) { if (replayPct) replayPct.textContent = '100%'; if (progressFill) progressFill.style.width = '100%'; } } else if (s === 'disconnected') { statusDot.className = 'status-dot disconnected'; } else { statusDot.className = isReplay ? 'status-dot replaying' : 'status-dot'; } } // ── Utility Functions ────────────────────────────────────────── function escapeHtml(s) { if (!s) return ''; return s.replace(/&/g,'&').replace(//g,'>'); } function addrToHex(addr) { var bytes = Array.isArray(addr) ? addr : Object.values(addr); var hex = ''; for (var j = 0; j < Math.min(8, bytes.length); j++) { hex += ('0' + bytes[j].toString(16)).slice(-2); } return hex + '\u2026'; } function addrToFullHex(addr) { var bytes = Array.isArray(addr) ? addr : Object.values(addr); var hex = ''; for (var j = 0; j < bytes.length; j++) { hex += ('0' + bytes[j].toString(16)).slice(-2); } return hex; } function shortTypeName(full) { if (!full) return ''; var parts = full.split('::'); return parts[parts.length - 1]; } // ── Color Palette ────────────────────────────────────────────── var colors = ['#4caf50','#2196f3','#ff9800','#f44336','#9c27b0','#00bcd4','#ffeb3b','#e91e63']; function colorWithAlpha(hex, alpha) { var r = parseInt(hex.slice(1, 3), 16); var g = parseInt(hex.slice(3, 5), 16); var b = parseInt(hex.slice(5, 7), 16); return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')'; } // ── SSE Connection Factory ───────────────────────────────────── // // Usage: // var es = createSSE({ // stats: function(data) { ... }, // activity: function(data) { ... }, // distribution: function(data) { ... } // }); // function createSSE(handlers) { var es = new EventSource('/events'); // Wire page-specific handlers Object.keys(handlers).forEach(function(event) { es.addEventListener(event, function(e) { try { handlers[event](JSON.parse(e.data)); } catch(err) { console.error(event + ' parse error', err); } }); }); // Common replay handlers es.addEventListener('replay_meta', function(e) { try { var meta = JSON.parse(e.data); if (replaySpeed) { replaySpeed.textContent = meta.speed + 'x'; replaySpeed.style.display = 'inline'; } if (replayPct) { replayPct.style.display = 'inline'; replayPct.textContent = '0%'; } } catch(err) { console.error('replay_meta parse error', err); } }); es.addEventListener('replay_progress', function(e) { try { var data = JSON.parse(e.data); var pct = Math.round(data.progress * 100); if (replayPct) replayPct.textContent = pct + '%'; if (progressFill) progressFill.style.width = pct + '%'; } catch(err) { console.error('replay_progress parse error', err); } }); es.addEventListener('done', function() { setStatus('done'); es.close(); }); es.onerror = function() { setStatus('disconnected'); }; es.onopen = function() { setStatus('connected'); }; return es; }