fix: match client-side sparkline behavior to SSR region-aware rendering
This commit is contained in:
parent
6beb7f8039
commit
688245b0c2
|
|
@ -1,4 +1,4 @@
|
||||||
export function sparkline(values: number[], width = 120, height = 32, color = '#60a5fa'): string {
|
export function sparkline(values: number[], width = 120, height = 32, color = '#60a5fa', region = '__none__'): string {
|
||||||
if (!values.length) return '';
|
if (!values.length) return '';
|
||||||
const max = Math.max(...values, 1);
|
const max = Math.max(...values, 1);
|
||||||
const min = Math.min(...values, 0);
|
const min = Math.min(...values, 0);
|
||||||
|
|
@ -9,7 +9,7 @@ export function sparkline(values: number[], width = 120, height = 32, color = '#
|
||||||
const y = height - ((v - min) / range) * (height - 4) - 2;
|
const y = height - ((v - min) / range) * (height - 4) - 2;
|
||||||
return `${x},${y}`;
|
return `${x},${y}`;
|
||||||
}).join(' ');
|
}).join(' ');
|
||||||
return `<svg width="${width}" height="${height}" class="inline-block" data-vals="${values.join(',')}"><polyline points="${points}" fill="none" stroke="${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
|
return `<svg width="${width}" height="${height}" class="inline-block" data-vals="${values.join(',')}" data-region="${region}"><polyline points="${points}" fill="none" stroke="${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Given pings with region+latency, pick the region with the lowest avg latency
|
// Given pings with region+latency, pick the region with the lowest avg latency
|
||||||
|
|
@ -42,5 +42,5 @@ export function sparklineFromPings(pings: Array<{latency_ms?: number|null, regio
|
||||||
}
|
}
|
||||||
|
|
||||||
const color = COLORS[bestRegion] || '#60a5fa';
|
const color = COLORS[bestRegion] || '#60a5fa';
|
||||||
return sparkline(byRegion[bestRegion], width, height, color);
|
return sparkline(byRegion[bestRegion], width, height, color, bestRegion);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,50 @@
|
||||||
} catch {}
|
} catch {}
|
||||||
}, 30000);
|
}, 30000);
|
||||||
|
|
||||||
|
const REGION_COLORS = {
|
||||||
|
'eu-central': '#3b82f6',
|
||||||
|
'us-east': '#10b981',
|
||||||
|
'us-west': '#f59e0b',
|
||||||
|
'ap-southeast': '#a78bfa',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Per-monitor, per-region latency tracking for sparkline (mirrors SSR sparklineFromPings)
|
||||||
|
const sparkData = {};
|
||||||
|
// Seed from SSR data-vals and data-region
|
||||||
|
document.querySelectorAll('[data-monitor-id]').forEach(card => {
|
||||||
|
const mid = card.dataset.monitorId;
|
||||||
|
const svg = card.querySelector('.stat-sparkline svg');
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
|
||||||
|
function redrawSparkline(card, monitorId) {
|
||||||
|
const regions = sparkData[monitorId] || {};
|
||||||
|
// Pick region with lowest average latency (same as SSR)
|
||||||
|
let bestRegion = '__none__', bestAvg = Infinity;
|
||||||
|
for (const [region, vals] of Object.entries(regions)) {
|
||||||
|
if (!vals.length) continue;
|
||||||
|
const avg = vals.reduce((a, b) => a + b, 0) / vals.length;
|
||||||
|
if (avg < bestAvg) { bestAvg = avg; bestRegion = region; }
|
||||||
|
}
|
||||||
|
const vals = regions[bestRegion];
|
||||||
|
if (!vals || !vals.length) return;
|
||||||
|
const color = REGION_COLORS[bestRegion] || '#60a5fa';
|
||||||
|
const W = 120, H = 32;
|
||||||
|
const max = Math.max(...vals, 1);
|
||||||
|
const min = Math.min(...vals, 0);
|
||||||
|
const range = max - min || 1;
|
||||||
|
const step = W / Math.max(vals.length - 1, 1);
|
||||||
|
const points = vals.map((v, i) => `${i * step},${H - ((v - min) / range) * (H - 4) - 2}`).join(' ');
|
||||||
|
const sparkEl = card.querySelector('.stat-sparkline');
|
||||||
|
if (sparkEl) {
|
||||||
|
sparkEl.innerHTML = `<svg width="${W}" height="${H}" class="inline-block" data-vals="${vals.join(',')}" data-region="${bestRegion}"><polyline points="${points}" fill="none" stroke="${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SSE: on each ping, update text fields and redraw sparkline in place
|
// SSE: on each ping, update text fields and redraw sparkline in place
|
||||||
watchAccount((ping) => {
|
watchAccount((ping) => {
|
||||||
const card = document.querySelector(`[data-monitor-id="${ping.monitor_id}"]`);
|
const card = document.querySelector(`[data-monitor-id="${ping.monitor_id}"]`);
|
||||||
|
|
@ -89,30 +133,15 @@
|
||||||
if (ping.latency_ms != null) card.querySelector('.stat-latency').textContent = ping.latency_ms + 'ms';
|
if (ping.latency_ms != null) card.querySelector('.stat-latency').textContent = ping.latency_ms + 'ms';
|
||||||
card.querySelector('.stat-last').innerHTML = timeAgo(ping.checked_at);
|
card.querySelector('.stat-last').innerHTML = timeAgo(ping.checked_at);
|
||||||
|
|
||||||
// Sparkline — append point to existing polyline, drop oldest, no refetch
|
// Update sparkline data per region, then redraw picking best region
|
||||||
if (ping.latency_ms != null) {
|
if (ping.latency_ms != null) {
|
||||||
const sparkEl = card.querySelector('.stat-sparkline');
|
const mid = ping.monitor_id;
|
||||||
const polyline = sparkEl?.querySelector('polyline');
|
const region = ping.region || '__none__';
|
||||||
if (polyline) {
|
if (!sparkData[mid]) sparkData[mid] = {};
|
||||||
const pts = polyline.getAttribute('points').trim().split(' ').filter(Boolean);
|
if (!sparkData[mid][region]) sparkData[mid][region] = [];
|
||||||
const W = 120, H = 32;
|
sparkData[mid][region].push(ping.latency_ms);
|
||||||
// Parse existing points
|
if (sparkData[mid][region].length > 20) sparkData[mid][region].shift();
|
||||||
let coords = pts.map(p => p.split(',').map(Number));
|
redrawSparkline(card, mid);
|
||||||
// Extract just y-values (latencies are encoded in y relative to scale)
|
|
||||||
// Easier: maintain a data attr on the svg with the raw values
|
|
||||||
const svg = sparkEl.querySelector('svg');
|
|
||||||
let vals = svg?.dataset.vals ? svg.dataset.vals.split(',').map(Number) : [];
|
|
||||||
vals.push(ping.latency_ms);
|
|
||||||
if (vals.length > 20) vals.shift();
|
|
||||||
if (svg) svg.dataset.vals = vals.join(',');
|
|
||||||
// Redraw polyline in place
|
|
||||||
const max = Math.max(...vals, 1);
|
|
||||||
const min = Math.min(...vals, 0);
|
|
||||||
const range = max - min || 1;
|
|
||||||
const step = W / Math.max(vals.length - 1, 1);
|
|
||||||
const newPts = vals.map((v, i) => `${i * step},${H - ((v - min) / range) * (H - 4) - 2}`).join(' ');
|
|
||||||
polyline.setAttribute('points', newPts);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue