diff --git a/apps/web/src/views/home.ejs b/apps/web/src/views/home.ejs index 5dd7e53..2504654 100644 --- a/apps/web/src/views/home.ejs +++ b/apps/web/src/views/home.ejs @@ -85,8 +85,8 @@ 'us-west': '#f59e0b', }; - // Per-monitor, per-region latency tracking for sparkline (mirrors SSR sparklineFromPings) - const sparkData = {}; + // Per-monitor tracking: regions = {region: [vals]}, timeline = [{region, val}] in arrival order + const sparkData = {}; // mid → { regions: {region: [vals]}, timeline: [{region}] } // Seed from SSR data-vals and data-region document.querySelectorAll('[data-monitor-id]').forEach(card => { const mid = card.dataset.monitorId; @@ -94,38 +94,35 @@ if (!svg) return; const region = svg.dataset.region || '__none__'; const vals = svg.dataset.vals ? svg.dataset.vals.split(',').map(Number) : []; - sparkData[mid] = {}; - sparkData[mid][region] = vals; + sparkData[mid] = { + regions: { [region]: vals }, + timeline: vals.map(() => region), + }; }); function getBestRegion(monitorId) { - const regions = sparkData[monitorId] || {}; - // Only consider regions that appear in the 3 most recent pings overall - // sparkData stores vals per region in chronological order; build a merged timeline - const timeline = []; - for (const [region, vals] of Object.entries(regions)) { - for (const val of vals) timeline.push({ region, val }); - } - if (!timeline.length) return { region: '__none__', latest: null }; + const data = sparkData[monitorId]; + if (!data || !data.timeline.length) return { region: '__none__', latest: null }; - const recentRegions = new Set(timeline.slice(-3).map(p => p.region)); + // Only consider regions present in the 3 most recent pings + const recentRegions = new Set(data.timeline.slice(-3)); let bestRegion = '__none__', bestAvg = Infinity; - for (const [region, vals] of Object.entries(regions)) { + for (const [region, vals] of Object.entries(data.regions)) { if (!recentRegions.has(region) || !vals.length) continue; const recent = vals.slice(-3); const avg = recent.reduce((a, b) => a + b, 0) / recent.length; if (avg < bestAvg) { bestAvg = avg; bestRegion = region; } } - const vals = regions[bestRegion] || []; + const vals = data.regions[bestRegion] || []; const latest = vals.length ? vals[vals.length - 1] : null; return { region: bestRegion, latest }; } function redrawSparkline(card, monitorId) { const { region: bestRegion } = getBestRegion(monitorId); - const vals = (sparkData[monitorId] || {})[bestRegion]; + const vals = (sparkData[monitorId]?.regions || {})[bestRegion]; if (!vals || !vals.length) return; const color = REGION_COLORS[bestRegion] || '#60a5fa'; const W = 120, H = 32; @@ -156,12 +153,13 @@ if (ping.latency_ms != null) { const mid = ping.monitor_id; const region = ping.region || '__none__'; - if (!sparkData[mid]) sparkData[mid] = {}; - if (!sparkData[mid][region]) sparkData[mid][region] = []; - sparkData[mid][region].push(ping.latency_ms); - if (sparkData[mid][region].length > 20) sparkData[mid][region].shift(); + if (!sparkData[mid]) sparkData[mid] = { regions: {}, timeline: [] }; + if (!sparkData[mid].regions[region]) sparkData[mid].regions[region] = []; + sparkData[mid].regions[region].push(ping.latency_ms); + sparkData[mid].timeline.push(region); + if (sparkData[mid].regions[region].length > 20) sparkData[mid].regions[region].shift(); + if (sparkData[mid].timeline.length > 40) sparkData[mid].timeline.shift(); redrawSparkline(card, mid); - // Show best region's latest latency const { latest } = getBestRegion(mid); if (latest != null) card.querySelector('.stat-latency').textContent = latest + 'ms'; }