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
This commit is contained in:
root
2026-08-11 17:03:26 +08:00
parent ac0caf9e0c
commit f4289c025c

View File

@ -767,7 +767,9 @@ async function paintStats() {
const kpi = $('#kpi-row'); const kpi = $('#kpi-row');
if (kpi) kpi.innerHTML = ` if (kpi) kpi.innerHTML = `
<div class="kpi${statsKeyF ? ' kpi-exit' : ''}" title="${statsKeyF ? t('exitKeyView') : ''}" onclick="${statsKeyF ? `renderKeyF('${escAttr(statsKeyF)}')` : ''}"> <div class="kpi${statsKeyF ? ' kpi-exit' : ''}" title="${statsKeyF ? t('exitKeyView') : ''}" onclick="${statsKeyF ? `renderKeyF('${escAttr(statsKeyF)}')` : ''}">
<div class="k-hdr"><span class="k-lab">${t('kpiActive')} <span class="dot"></span></span><span class="okc k-sub">${st.active || 0}</span></div> <div class="k-hdr"><span class="k-lab">${t('kpiActive')} <span class="dot"></span></span></div>
<div class="k-val">${st.active || 0}</div>
<div class="k-sub">reqs / 3s</div>
<div class="k-chart"><canvas id="cv-active"></canvas></div> <div class="k-chart"><canvas id="cv-active"></canvas></div>
</div> </div>
<div class="kpi"> <div class="kpi">
@ -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) { function drawActiveChart(st) {
const cv = document.getElementById('cv-active'); if (!cv) return; const cv = document.getElementById('cv-active'); if (!cv) return;
const ctx = cv.getContext('2d'); const ctx = cv.getContext('2d');
const dpr = window.devicePixelRatio || 1; const dpr = window.devicePixelRatio || 1;
const parent = cv.parentElement, rect = parent.getBoundingClientRect(); 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); 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 recs = st.records || [];
const col = window.charts && window.charts.active || []; const rate = recs.filter(r => r.time >= now - 3000).length;
sparkline(ctx, col, rect.width, 86, '#4fa3d8'); 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) { function drawReqsChart(st) {
const cv = document.getElementById('cv-reqs'); if (!cv) return; const cv = document.getElementById('cv-reqs'); if (!cv) return;
@ -955,7 +977,6 @@ function drawStatusChart(rows) {
ctx.closePath(); ctx.closePath();
ctx.fillStyle = palette[i % palette.length]; ctx.fillStyle = palette[i % palette.length];
ctx.fill(); ctx.fill();
ctx.strokeStyle = 'var(--card)'; ctx.lineWidth = 1.5; ctx.stroke();
a0 += sweep; a0 += sweep;
}); });
// center label = total // center label = total