diff --git a/apix-admin/src/main/resources/META-INF/resources/admin.js b/apix-admin/src/main/resources/META-INF/resources/admin.js index 9999279..e69d8a9 100644 --- a/apix-admin/src/main/resources/META-INF/resources/admin.js +++ b/apix-admin/src/main/resources/META-INF/resources/admin.js @@ -128,3 +128,114 @@ }); } })(); + +// ── Sandbox live rate chart (sandbox drill-down only) ────────────────────── +(function () { + const el = document.getElementById('rate-chart'); + if (!el || !window.__D || !__D.sandboxId) return; + + const INTERVAL = 5000; + const BUCKETS = 72; // 6 min at 5s + const rateLimit = __D.ratePerMinute || 1; + const sid = __D.sandboxId; + + const buf = new Array(BUCKETS).fill(0); + let lastTotal = null; + + const ML = 36; // left margin for Y-axis labels + const MB = 18; // bottom margin for X-axis labels + const W = el.parentElement.clientWidth || 900; + const H = 96; + const CH = H - MB; // chart height (plot area) + + el.setAttribute('viewBox', '0 0 ' + W + ' ' + H); + el.style.width = '100%'; + + const yS = d3.scaleLinear().domain([0, rateLimit * 1.1]).range([CH, 0]); + const xS = d3.scaleLinear().domain([0, BUCKETS - 1]).range([ML, W]); + + const areaFn = d3.area() + .x(function (_, i) { return xS(i); }) + .y0(CH) + .y1(function (d) { return yS(d); }) + .curve(d3.curveCatmullRom.alpha(0.5)); + + const svg = d3.select(el); + + // Y-axis: 0 / 50 / 100% grid lines + labels + [0, 50, 100].forEach(function (pct) { + const y = yS(rateLimit * pct / 100); + 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 + time labels + svg.append('line') + .attr('x1', ML).attr('x2', W) + .attr('y1', CH).attr('y2', CH) + .attr('class', 'axis-line'); + + [ + { bucket: 0, label: '-6m', anchor: 'start' }, + { bucket: BUCKETS / 2, label: '-3m', anchor: 'middle' }, + { bucket: BUCKETS - 1, label: 'now', anchor: 'end' } + ].forEach(function (t) { + svg.append('text') + .attr('x', xS(t.bucket)) + .attr('y', H - 4) + .attr('text-anchor', t.anchor) + .attr('class', 'axis-label') + .text(t.label); + }); + + // 100% limit line (red dashed) + var ly = yS(rateLimit); + svg.append('line') + .attr('x1', ML).attr('x2', W) + .attr('y1', ly).attr('y2', ly) + .attr('class', 'rate-limit-line'); + + var path = svg.append('path').attr('class', 'rate-area'); + + function fmt(n) { + return n >= 1e6 ? (n / 1e6).toFixed(1) + 'M' + : n >= 1e3 ? (n / 1e3).toFixed(1) + 'k' + : String(n); + } + + function render(rpm) { + buf.push(rpm); + buf.shift(); + path.attr('d', areaFn(buf)); + var pct = rateLimit > 0 ? (rpm / rateLimit * 100).toFixed(1) : '0.0'; + var txt = fmt(rpm) + ' req/min (' + pct + '%)'; + var elNow = document.getElementById('rate-now'); + var elLive = document.getElementById('rate-live-val'); + if (elNow) elNow.textContent = fmt(rpm); + if (elLive) elLive.textContent = txt; + } + + async function poll() { + try { + var r = await fetch('/sandbox/' + sid + '/poll'); + if (!r.ok) return; + var d = await r.json(); + var total = Number(d.totalRequests) || 0; + if (lastTotal !== null) { + var delta = Math.max(0, total - lastTotal); + render(Math.round(delta * (60000 / INTERVAL))); + } + lastTotal = total; + } catch (_) {} + } + + poll(); + setInterval(poll, INTERVAL); +})(); diff --git a/apix-admin/src/main/resources/templates/DashboardResource/global.html b/apix-admin/src/main/resources/templates/DashboardResource/global.html index c9a9554..bf4cfbd 100644 --- a/apix-admin/src/main/resources/templates/DashboardResource/global.html +++ b/apix-admin/src/main/resources/templates/DashboardResource/global.html @@ -106,6 +106,6 @@ nav { - + diff --git a/apix-admin/src/main/resources/templates/DashboardResource/sandbox.html b/apix-admin/src/main/resources/templates/DashboardResource/sandbox.html index 81af3fa..d423b0f 100644 --- a/apix-admin/src/main/resources/templates/DashboardResource/sandbox.html +++ b/apix-admin/src/main/resources/templates/DashboardResource/sandbox.html @@ -151,117 +151,6 @@ button.promote:hover { background: #2ea043; } - - +