Color-code per-host avg label and clamp graph Y-axis to 1000ms

The avg latency text below each host's big number is now color-coded
using the same thresholds as the main figure. The sparkline Y-axis
stays 0-1000ms — values between 1000-1500ms pin to the top of the
chart but still show their real value in the latency display.
This commit is contained in:
Jeffrey Paul 2026-02-23 00:26:00 +07:00
parent 55fb63bec1
commit 14764a79ad

View File

@ -3,14 +3,17 @@ import "./styles.css";
// --- Configuration ----------------------------------------------------------- // --- Configuration -----------------------------------------------------------
// Timing, axis labels, and display constants. Latency above maxLatency is // Timing, axis labels, and display constants. Latency above maxLatency is
// clamped to "unreachable". The history buffer holds maxHistoryPoints // clamped to "unreachable". The sparkline Y-axis is capped at
// samples (historyDuration / updateInterval). // graphMaxLatency — values above it pin to the top of the chart but still
// display their real value in the latency figure. The history buffer holds
// maxHistoryPoints samples (historyDuration / updateInterval).
const CONFIG = Object.freeze({ const CONFIG = Object.freeze({
updateInterval: 2000, updateInterval: 2000,
historyDuration: 300, historyDuration: 300,
requestTimeout: 1500, requestTimeout: 1500,
maxLatency: 1500, maxLatency: 1500,
yAxisTicks: [0, 250, 500, 750, 1000, 1500], graphMaxLatency: 1000,
yAxisTicks: [0, 250, 500, 750, 1000],
xAxisTicks: [0, 60, 120, 180, 240, 300], xAxisTicks: [0, 60, 120, 180, 240, 300],
canvasHeight: 80, canvasHeight: 80,
get maxHistoryPoints() { get maxHistoryPoints() {
@ -240,7 +243,11 @@ class SparklineRenderer {
const len = history.length; const len = history.length;
const pw = cw / (CONFIG.maxHistoryPoints - 1); const pw = cw / (CONFIG.maxHistoryPoints - 1);
const getX = (i) => m.left + cw - (len - 1 - i) * pw; const getX = (i) => m.left + cw - (len - 1 - i) * pw;
const getY = (lat) => m.top + ch - (lat / CONFIG.maxLatency) * ch; const getY = (lat) =>
m.top +
ch -
(Math.min(lat, CONFIG.graphMaxLatency) / CONFIG.graphMaxLatency) *
ch;
SparklineRenderer._drawErrors(ctx, history, getX, m.top, ch); SparklineRenderer._drawErrors(ctx, history, getX, m.top, ch);
SparklineRenderer._drawLine(ctx, history, getX, getY); SparklineRenderer._drawLine(ctx, history, getX, getY);
@ -252,7 +259,7 @@ class SparklineRenderer {
ctx.textAlign = "right"; ctx.textAlign = "right";
ctx.textBaseline = "middle"; ctx.textBaseline = "middle";
for (const tick of CONFIG.yAxisTicks) { for (const tick of CONFIG.yAxisTicks) {
const y = m.top + ch - (tick / CONFIG.maxLatency) * ch; const y = m.top + ch - (tick / CONFIG.graphMaxLatency) * ch;
ctx.strokeStyle = "rgba(255,255,255,0.1)"; ctx.strokeStyle = "rgba(255,255,255,0.1)";
ctx.lineWidth = 1; ctx.lineWidth = 1;
ctx.beginPath(); ctx.beginPath();
@ -461,7 +468,7 @@ function updateHostRow(host, index) {
const avg = host.averageLatency(); const avg = host.averageLatency();
if (host.status === "online" && avg !== null) { if (host.status === "online" && avg !== null) {
statusEl.textContent = `avg: ${avg}ms`; statusEl.textContent = `avg: ${avg}ms`;
statusEl.className = "status-text text-xs text-gray-400 mt-1"; statusEl.className = `status-text text-xs mt-1 ${latencyClass(avg, "online")}`;
} else if (host.status === "offline") { } else if (host.status === "offline") {
statusEl.textContent = "unreachable"; statusEl.textContent = "unreachable";
statusEl.className = "status-text text-xs text-red-400 mt-1"; statusEl.className = "status-text text-xs text-red-400 mt-1";