Commit Graph

16 Commits

Author SHA1 Message Date
Carsten Rehfeld 4d980d7fc3 fix(admin): single update path per poll — no competing D3 transitions
Deploy to Production / deploy (push) Successful in 3m15s
Removed separate setInterval for buf flush. Flush + redraw now happen
together inside poll() — one atomic step, one D3 transition(950ms).
Two independent 1 s timers were firing out of phase and interrupting
each other's transitions, causing flicker and apparent 2 s cadence.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-17 01:49:37 +02:00
Carsten Rehfeld f895bcf0cf feat(admin): 1 s chart buckets + smooth D3 transition
Deploy to Production / deploy (push) Successful in 3m14s
- Bucket size: 1 s (was 5 s) → chart scrolls every second, feels live
- Window: 72 s visible, x-axis labels -72s / -36s / now
- D3 transition(900ms, easeLinear) on path redraw → continuous motion
- Sliding-window rate calculation unchanged (still 5 s window for rpm)

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-17 01:45:08 +02:00
Carsten Rehfeld a4a5a8e3f2 fix(admin): sliding-window rate — no sawtooth drop at bucket boundaries
Deploy to Production / deploy (push) Successful in 3m20s
Replace fixed-window accDelta (resets to 0 every 5 s) with a sliding
window: recentPolls[] holds {t, delta} entries; liveRpm() sums all
entries within the last 5 s and extrapolates to rpm. Old entries age
out naturally so the rate decays smoothly with no hard reset jump.
Chart and label now update continuously without the 0-drop artifact.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-17 01:37:55 +02:00
Carsten Rehfeld fa473d2b61 feat(admin): chart redraws every 1 s — live bucket at right edge
Deploy to Production / deploy (push) Successful in 3m12s
Separate chart refresh from bucket flush:
- 5 s flush commits completed bucket into buf, resets accumulator
- Every poll (1 s): redraw path as buf[1..] + current live bucket
  so the rightmost bar visually builds up in real time
- Label and chart now both update at 1 s cadence

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-17 01:26:08 +02:00
Carsten Rehfeld 435e1bda42 fix(admin): smooth live rate label — window-accumulated rate, not instantaneous
Deploy to Production / deploy (push) Successful in 3m16s
With 1 s polls and batch traffic, delta * 60 flickers between 0 and 300.
Fix: live label shows accDelta / elapsed_in_window * 60000 — the rate
builds up continuously within each 5 s bucket. First 200 ms after flush
carries the previous window rate to avoid a zero jump.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-17 01:20:15 +02:00
Carsten Rehfeld de597f1d8b fix(admin): clip rate chart at 100% — SVG clipPath + data cap
Deploy to Production / deploy (push) Successful in 3m13s
Two-layer fix:
- SVG clipPath on plot area: path physically cannot escape chart bounds
- Chart flush caps rpm at rateLimit: timing drift (two batches in one
  5 s window) no longer causes visual overshoot
Live label still shows true req/min and % including values > 100%.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-17 01:12:10 +02:00
Carsten Rehfeld 4908d5d6e0 feat(admin): adaptive poll rate — 1 s active, 5 s idle after 15 s no traffic
Deploy to Production / deploy (push) Successful in 3m16s
Admin sandbox drill-down polls at 1 s when traffic is flowing (live label
updates feel instant), drops back to 5 s after 15 s of inactivity.
Chart bucket remains fixed at 5 s so x-axis labels stay correct; deltas
accumulate between buckets independently of poll rate.

Tier-dependent rate reserved for future sandbox-owner dashboard.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-17 00:52:42 +02:00
Carsten Rehfeld 0ee76ac535 fix(admin): serialize poll response as JSON string via ObjectMapper
Deploy to Production / deploy (push) Successful in 3m20s
Response.ok(Map.of(...)).build() with class-level @Produces(TEXT_HTML)
caused Quarkus to call Map.toString() instead of the Jackson writer,
producing {key=value} output that r.json() can't parse. Explicitly
serialize with ObjectMapper and set Content-Type to application/json.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-17 00:35:06 +02:00
Carsten Rehfeld 6bc4059922 fix(admin): move rate chart out of Qute template into admin.js
Deploy to Production / deploy (push) Successful in 3m26s
Qute processes all {...} in .html files including <script> blocks.
JS function bodies like function(d) { return y(d); } were silently
eaten, causing a syntax error that killed the entire chart script.

Moving the rate chart IIFE into admin.js (static, Qute-never-touches)
fixes the root cause. Axes (Y: 0/50/100%, X: -6m/-3m/now) and
live % label now render correctly.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-17 00:29:16 +02:00
Carsten Rehfeld 00e4731bde feat(admin): add time/% axes to live rate chart
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>
2026-05-17 00:21:22 +02:00
Carsten Rehfeld 0d4f7934f7 fix(admin): bust admin.js browser cache with ?v=3, disable immutable static cache
Deploy to Production / deploy (push) Successful in 3m16s
Quarkus defaults to Cache-Control: immutable, max-age=86400 for static files.
The immutable flag means browsers ignore hard-refresh and serve stale JS for 24h.
Fix: version query string on admin.js URLs, max-age=300 (5min), clear immutable-paths.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-16 23:53:21 +02:00
Carsten Rehfeld c7bfa73d2f fix(admin): blinks + live rate chart on sandbox drill-down
Deploy to Production / deploy (push) Successful in 3m14s
Blink bug: agent visits were gated behind registrarLat != null — sandboxes
without a declared location showed no blinks at all. Now always enters the
sandbox branch when __D.sandboxId is present; registrar star still conditional.

Rate chart: rolling D3 area chart (72 buckets x 5s = 6min window) polls
GET /sandbox/{uuid}/poll every 5s, computes delta→rpm, renders with
CatmullRom curve. Red dashed limit line. Live value shown in stat box and
chart header. Poll endpoint added to DashboardResource (JSON, no HTML).

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-16 22:29:37 +02:00
Carsten Rehfeld 51e210ab68 fix(admin): pre-compute pagination in Java, remove arithmetic from Qute template
Deploy to Production / deploy (push) Successful in 3m12s
Qute cannot evaluate + and * as arithmetic operators at runtime even with
requireTypeSafeExpressions=false — it tries to look them up as map keys.
Pre-compute hasPrev/hasNext/displayPage/prevPage/nextPage in the resource.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-16 21:55:18 +02:00
Carsten Rehfeld 60dfcd58f6 fix(admin): use getRequestUri().getPath() in auth filter to avoid //login double-slash
Deploy to Production / deploy (push) Successful in 3m13s
Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-16 21:42:18 +02:00
Carsten Rehfeld ad19c8f20f fix(admin): relax Qute type-safety for arithmetic in list.html, fix JS template literals
Deploy to Production / deploy (push) Successful in 3m14s
requireTypeSafeExpressions=false allows {page+1} and (page+1)*size
in pagination conditions. JS template literals with ${days} replaced
with string concatenation to avoid Qute consuming the {days} token.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-16 21:30:29 +02:00
Carsten Rehfeld 82a379e536 feat(admin): apix-admin UI at admin.api-index.org
Deploy to Production / deploy (push) Failing after 47s
Internal maintainer dashboard: global stats, sandbox list with
tier-filter and pagination, sandbox drill-down with D3 world map,
tier-promotion form. API-key auth via HttpOnly cookie. Port 8084,
rolling a/b deploy alongside the existing service pairs.

- apix-common: AdminSandboxSummary, AdminSandboxListResponse, AdminStatsResponse DTOs
- apix-registry: AdminResource (4 endpoints), SandboxService.listAllSandboxes + getGlobalStats
- apix-registry: SandboxResource GET /{uuid}/dashboard endpoint (was missing — fixes portal 500)
- apix-portal: RegistryClient path corrected to /dashboard
- apix-admin: new Quarkus module — auth filter, registry REST client, 3 resources, 6 Qute templates, admin.js
- infra: Dockerfile.admin, docker-compose admin-a/b services, Caddyfile admin.api-index.org block
- scripts: deploy-bluegreen.sh extended for admin-a/b

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-16 21:18:48 +02:00