diff --git a/apps/web/src/views/detail.ejs b/apps/web/src/views/detail.ejs index 5ac029a..5df0887 100644 --- a/apps/web/src/views/detail.ejs +++ b/apps/web/src/views/detail.ejs @@ -208,6 +208,19 @@ return; } + // Compute a shared x-position per run_id (avg checked_at of all pings in the run) + // so all regions in the same run align vertically on the chart + const runTimes = {}; + for (const p of data) { + const rid = p.run_id || p.checked_at; // fallback for pings without run_id + if (!runTimes[rid]) runTimes[rid] = []; + runTimes[rid].push(new Date(p.checked_at).getTime()); + } + const runAvgTime = {}; + for (const [rid, times] of Object.entries(runTimes)) { + runAvgTime[rid] = times.reduce((a, b) => a + b, 0) / times.length; + } + // Group by region const byRegion = {}; for (const p of data) { @@ -219,12 +232,13 @@ const allLat = data.map(p => p.latency_ms); const yMin = Math.min(...allLat), yMax = Math.max(...allLat); const yRange = yMax - yMin || 1; - const allT = data.map(p => new Date(p.checked_at).getTime()); - const tMin = Math.min(...allT), tMax = Math.max(...allT); + const avgTimes = Object.values(runAvgTime); + const tMin = Math.min(...avgTimes), tMax = Math.max(...avgTimes); const tRange = tMax - tMin || 1; function toX(t) { return pad.left + ((t - tMin) / tRange) * cW; } function toY(v) { return pad.top + cH - ((v - yMin) / yRange) * cH; } + function pingX(p) { return toX(runAvgTime[p.run_id || p.checked_at]); } // Grid lines ctx.strokeStyle = 'rgba(75,85,99,0.3)'; ctx.lineWidth = 0.5; @@ -239,10 +253,10 @@ for (const region of regions) { const color = REGION_COLORS[region] || '#6b7280'; const rPings = byRegion[region].slice().sort((a, b) => - new Date(a.checked_at).getTime() - new Date(b.checked_at).getTime() + pingX(a) - pingX(b) ); const pts = rPings.map(p => ({ - x: toX(new Date(p.checked_at).getTime()), + x: pingX(p), y: toY(p.latency_ms), ping: p }));