From f4289c025cf38dbbc885a0f8b2154e39a4f84559 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 11 Aug 2026 17:03:26 +0800 Subject: [PATCH] fix(ui): active request card -> real-time QPS-style sparkline; remove pie slice outline - drawActiveChart: rolling request-rate buffer (window.qpsSeries) sampled ~every 2s from records timestamps, drawn as an area+fine; header shows current active requests + 'reqs / 3s' subtitle; refreshes on the existing 3s paintStats poll - status pie: remove the hard slice outline (no stroke), clean seamless slices - active card now consistent with others: big value + subtitle + chart - deployed (bak .bak.20260811n), server active --- internal/gateway/ui/index.html | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/internal/gateway/ui/index.html b/internal/gateway/ui/index.html index b6ff288..52b6f8d 100644 --- a/internal/gateway/ui/index.html +++ b/internal/gateway/ui/index.html @@ -767,7 +767,9 @@ async function paintStats() { const kpi = $('#kpi-row'); if (kpi) kpi.innerHTML = `
-
${t('kpiActive')} ${st.active || 0}
+
${t('kpiActive')}
+
${st.active || 0}
+
reqs / 3s
@@ -871,17 +873,37 @@ function dailyBars(ctx, counts, w, h, color) { }); } +// rolling real-time request-rate buffer (QPS-style sparkline) +window.qpsSeries = window.qpsSeries || { last: Date.now(), data: [] }; function drawActiveChart(st) { const cv = document.getElementById('cv-active'); if (!cv) return; const ctx = cv.getContext('2d'); const dpr = window.devicePixelRatio || 1; const parent = cv.parentElement, rect = parent.getBoundingClientRect(); - cv.width = rect.width * dpr; cv.height = 86 * dpr; ctx.scale(dpr, dpr); + const H = 86; + cv.width = rect.width * dpr; cv.height = H * dpr; ctx.scale(dpr, dpr); ctx.clearRect(0, 0, cv.width, cv.height); - // active count across records window (bucketed per minute) as a sparkline + // sample: requests started in the last 3s (approx QPS). Records carry unix-ms time. + const qs = window.qpsSeries; + const now = Date.now(); const recs = st.records || []; - const col = window.charts && window.charts.active || []; - sparkline(ctx, col, rect.width, 86, '#4fa3d8'); + const rate = recs.filter(r => r.time >= now - 3000).length; + if (now - qs.last >= 2000) { qs.last = now; qs.data.push(Math.max(rate, 0)); } + if (qs.data.length > 40) qs.data.shift(); + // grid baseline + ctx.strokeStyle = 'var(--line)'; ctx.lineWidth = 1; ctx.beginPath(); + ctx.moveTo(0, H - 1); ctx.lineTo(rect.width, H - 1); ctx.stroke(); + // area fill under the line + if (qs.data.length > 1) { + const max = Math.max.apply(null, qs.data) || 1; + const px = i => i / (qs.data.length - 1) * rect.width; + const py = v => 2 + (1 - v / max) * (H - 4); + ctx.beginPath(); + qs.data.forEach((v, i) => i ? ctx.lineTo(px(i), py(v)) : ctx.moveTo(px(i), py(v))); + ctx.lineTo(rect.width, H); ctx.lineTo(px(0), H); ctx.closePath(); + ctx.fillStyle = 'rgba(79,163,216,.18)'; ctx.fill(); + sparkline(ctx, qs.data, rect.width, H, '#4fa3d8'); + } } function drawReqsChart(st) { const cv = document.getElementById('cv-reqs'); if (!cv) return; @@ -955,7 +977,6 @@ function drawStatusChart(rows) { ctx.closePath(); ctx.fillStyle = palette[i % palette.length]; ctx.fill(); - ctx.strokeStyle = 'var(--card)'; ctx.lineWidth = 1.5; ctx.stroke(); a0 += sweep; }); // center label = total