feat(admin): add time/% axes to live rate chart
Deploy to Production / deploy (push) Successful in 3m20s
Deploy to Production / deploy (push) Successful in 3m20s
Y-axis shows 0/50/100% of rate limit with grid lines. X-axis shows -6m / -3m / now time labels. Live label also shows current utilisation percentage. Chart height increased to 96px to accommodate axes. Cache-busted admin.js to v4. Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
This commit is contained in:
@@ -64,7 +64,7 @@ button.promote:hover { background: #2ea043; }
|
|||||||
}
|
}
|
||||||
.agent-blink { fill: #3d8bfd; opacity:0; animation: blink-pulse 2.8s ease-in-out infinite; }
|
.agent-blink { fill: #3d8bfd; opacity:0; animation: blink-pulse 2.8s ease-in-out infinite; }
|
||||||
|
|
||||||
#rate-wrap { background: #060d18; padding: 0 1.5rem 1rem; border-bottom: 1px solid #21262d; }
|
#rate-wrap { background: #060d18; padding: 0 1.5rem 0.5rem; border-bottom: 1px solid #21262d; }
|
||||||
.rate-header { display: flex; align-items: baseline; gap: 0.75rem; padding: 0.6rem 0 0.4rem; }
|
.rate-header { display: flex; align-items: baseline; gap: 0.75rem; padding: 0.6rem 0 0.4rem; }
|
||||||
.rate-title { font-size: 0.65rem; color: #484f58; text-transform: uppercase; letter-spacing: 0.06em; }
|
.rate-title { font-size: 0.65rem; color: #484f58; text-transform: uppercase; letter-spacing: 0.06em; }
|
||||||
.rate-live { font-size: 0.75rem; color: #388bfd; }
|
.rate-live { font-size: 0.75rem; color: #388bfd; }
|
||||||
@@ -72,6 +72,8 @@ button.promote:hover { background: #2ea043; }
|
|||||||
#rate-chart { display: block; width: 100%; overflow: visible; }
|
#rate-chart { display: block; width: 100%; overflow: visible; }
|
||||||
.rate-area { fill: #1f6feb33; stroke: #388bfd; stroke-width: 1.5; }
|
.rate-area { fill: #1f6feb33; stroke: #388bfd; stroke-width: 1.5; }
|
||||||
.rate-limit-line { stroke: #f85149; stroke-width: 0.8; stroke-dasharray: 4 3; opacity: 0.5; }
|
.rate-limit-line { stroke: #f85149; stroke-width: 0.8; stroke-dasharray: 4 3; opacity: 0.5; }
|
||||||
|
.axis-label { font-size: 9px; fill: #484f58; font-family: 'SF Mono','Consolas','Fira Code',monospace; }
|
||||||
|
.axis-line { stroke: #21262d; stroke-width: 0.5; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -124,7 +126,7 @@ button.promote:hover { background: #2ea043; }
|
|||||||
<span class="rate-live" id="rate-live-val">— req/min</span>
|
<span class="rate-live" id="rate-live-val">— req/min</span>
|
||||||
<span class="rate-limit-label">limit: {dashboard.ratePerMinute}/min ──</span>
|
<span class="rate-limit-label">limit: {dashboard.ratePerMinute}/min ──</span>
|
||||||
</div>
|
</div>
|
||||||
<svg id="rate-chart" height="64"></svg>
|
<svg id="rate-chart" height="96"></svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="map-wrap"><svg id="world-map"></svg></div>
|
<div id="map-wrap"><svg id="world-map"></svg></div>
|
||||||
@@ -149,7 +151,7 @@ button.promote:hover { background: #2ea043; }
|
|||||||
<script>var __D = {dataJson.raw};</script>
|
<script>var __D = {dataJson.raw};</script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/topojson-client@3/dist/topojson-client.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/topojson-client@3/dist/topojson-client.min.js"></script>
|
||||||
<script src="/admin.js?v=3"></script>
|
<script src="/admin.js?v=4"></script>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
const INTERVAL = 5000;
|
const INTERVAL = 5000;
|
||||||
@@ -159,32 +161,75 @@ button.promote:hover { background: #2ea043; }
|
|||||||
|
|
||||||
const buf = new Array(BUCKETS).fill(0);
|
const buf = new Array(BUCKETS).fill(0);
|
||||||
let lastTotal = null;
|
let lastTotal = null;
|
||||||
|
let tickCount = 0;
|
||||||
|
|
||||||
const el = document.getElementById('rate-chart');
|
const el = document.getElementById('rate-chart');
|
||||||
const W = el.parentElement.clientWidth || 900;
|
const ML = 36; // left margin for Y-axis labels
|
||||||
const H = 64;
|
const MB = 18; // bottom margin for X-axis labels
|
||||||
|
const W = (el.parentElement.clientWidth || 900);
|
||||||
|
const H = 96;
|
||||||
|
const CW = W - ML; // chart width
|
||||||
|
const CH = H - MB; // chart height
|
||||||
el.setAttribute('viewBox', '0 0 ' + W + ' ' + H);
|
el.setAttribute('viewBox', '0 0 ' + W + ' ' + H);
|
||||||
el.style.width = '100%';
|
el.style.width = '100%';
|
||||||
|
|
||||||
const xS = d3.scaleLinear().domain([0, BUCKETS - 1]).range([0, W]);
|
// Y scale: 0–110% of rate limit, mapped to pixel height
|
||||||
const yS = d3.scaleLinear().domain([0, Math.max(rateLimit * 1.1, 1)]).range([H, 0]);
|
const yS = d3.scaleLinear().domain([0, rateLimit * 1.1]).range([CH, 0]);
|
||||||
|
// X scale: bucket index to pixel x (within chart area)
|
||||||
|
const xS = d3.scaleLinear().domain([0, BUCKETS - 1]).range([ML, W]);
|
||||||
|
|
||||||
const areaFn = d3.area()
|
const areaFn = d3.area()
|
||||||
.x(function(_, i) { return xS(i); })
|
.x(function(_, i) { return xS(i); })
|
||||||
.y0(H)
|
.y0(CH)
|
||||||
.y1(function(d) { return yS(d); })
|
.y1(function(d) { return yS(d); })
|
||||||
.curve(d3.curveCatmullRom.alpha(0.5));
|
.curve(d3.curveCatmullRom.alpha(0.5));
|
||||||
|
|
||||||
const svg = d3.select(el);
|
const svg = d3.select(el);
|
||||||
const path = svg.append('path').attr('class', 'rate-area');
|
|
||||||
|
|
||||||
// Red dashed limit line
|
// Y-axis grid lines + % labels (0%, 50%, 100%)
|
||||||
|
[0, 50, 100].forEach(function(pct) {
|
||||||
|
const rpm = rateLimit * pct / 100;
|
||||||
|
const y = yS(rpm);
|
||||||
|
svg.append('line')
|
||||||
|
.attr('x1', ML).attr('x2', W)
|
||||||
|
.attr('y1', y).attr('y2', y)
|
||||||
|
.attr('class', 'axis-line');
|
||||||
|
svg.append('text')
|
||||||
|
.attr('x', ML - 4).attr('y', y + 3)
|
||||||
|
.attr('text-anchor', 'end')
|
||||||
|
.attr('class', 'axis-label')
|
||||||
|
.text(pct + '%');
|
||||||
|
});
|
||||||
|
|
||||||
|
// X-axis baseline
|
||||||
|
svg.append('line')
|
||||||
|
.attr('x1', ML).attr('x2', W)
|
||||||
|
.attr('y1', CH).attr('y2', CH)
|
||||||
|
.attr('class', 'axis-line');
|
||||||
|
|
||||||
|
// X-axis time labels: -6m, -3m, now
|
||||||
|
[
|
||||||
|
{ bucket: 0, label: '-6m' },
|
||||||
|
{ bucket: BUCKETS / 2, label: '-3m' },
|
||||||
|
{ bucket: BUCKETS - 1, label: 'now' }
|
||||||
|
].forEach(function(t) {
|
||||||
|
svg.append('text')
|
||||||
|
.attr('x', xS(t.bucket))
|
||||||
|
.attr('y', H - 4)
|
||||||
|
.attr('text-anchor', t.bucket === 0 ? 'start' : t.bucket === BUCKETS - 1 ? 'end' : 'middle')
|
||||||
|
.attr('class', 'axis-label')
|
||||||
|
.text(t.label);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Red dashed limit line (100%)
|
||||||
const ly = yS(rateLimit);
|
const ly = yS(rateLimit);
|
||||||
svg.append('line')
|
svg.append('line')
|
||||||
.attr('x1', 0).attr('x2', W)
|
.attr('x1', ML).attr('x2', W)
|
||||||
.attr('y1', ly).attr('y2', ly)
|
.attr('y1', ly).attr('y2', ly)
|
||||||
.attr('class', 'rate-limit-line');
|
.attr('class', 'rate-limit-line');
|
||||||
|
|
||||||
|
const path = svg.append('path').attr('class', 'rate-area');
|
||||||
|
|
||||||
function fmt(n) {
|
function fmt(n) {
|
||||||
return n >= 1e6 ? (n/1e6).toFixed(1)+'M' : n >= 1e3 ? (n/1e3).toFixed(1)+'k' : String(n);
|
return n >= 1e6 ? (n/1e6).toFixed(1)+'M' : n >= 1e3 ? (n/1e3).toFixed(1)+'k' : String(n);
|
||||||
}
|
}
|
||||||
@@ -193,8 +238,9 @@ button.promote:hover { background: #2ea043; }
|
|||||||
buf.push(rpm);
|
buf.push(rpm);
|
||||||
buf.shift();
|
buf.shift();
|
||||||
path.attr('d', areaFn(buf));
|
path.attr('d', areaFn(buf));
|
||||||
const txt = fmt(rpm) + ' req/min';
|
const pct = rateLimit > 0 ? (rpm / rateLimit * 100).toFixed(1) : '0.0';
|
||||||
document.getElementById('rate-now').textContent = fmt(rpm);
|
const txt = fmt(rpm) + ' req/min (' + pct + '%)';
|
||||||
|
document.getElementById('rate-now').textContent = fmt(rpm);
|
||||||
document.getElementById('rate-live-val').textContent = txt;
|
document.getElementById('rate-live-val').textContent = txt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,6 +255,7 @@ button.promote:hover { background: #2ea043; }
|
|||||||
render(Math.round(delta * (60000 / INTERVAL)));
|
render(Math.round(delta * (60000 / INTERVAL)));
|
||||||
}
|
}
|
||||||
lastTotal = total;
|
lastTotal = total;
|
||||||
|
tickCount++;
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user