Commit Graph
4 Commits
Author SHA1 Message Date
lukaszraczylo 4261efc41b feat(webui): add limit bars, forecasts, events, exports and URL state
The Usage tab shows a bar for each configured limit. It turns amber at
80% and red at 100%. The tab also shows rejected requests per day, a
projected month-end cost with budget headroom, and CSV export for users
and groups. Names link to Charts with the matching scope.

The Charts tab ranks models over the full 24h, 30d or 12mo window. A
provider filter is applied on the server, and the ranking can be
exported as CSV. A new Events tab lists recent failures, filtered by
kind and user. The selected tab, filters and scope live in the URL
hash. Polling stops while the page is hidden. Views show which replica
answered, and a banner lists configuration warnings.

CSV cells that start with a formula character are neutralised. Links
sit outside the accordion buttons, so both controls keep their
accessible names. admin_assets_gen.go is regenerated.

Claude-Session: https://claude.ai/code/session_01V5jYJNxpDFdJkyyExpADrv
2026-09-23 13:09:57 +01:00
lukaszraczylo 227200316d fix(webui): keep charts consistent with the current selection
Chart and ranking fetches carry a request id, and a superseded response
is dropped. Changing a selection clears the old data at once, so old
numbers never show under new labels or units. The auto-refresh tick
skips while a fetch is still in flight. "Loading..." shows only until
the first result for a selection arrives. Dashboard polls do not
overlap.

Users in several groups are counted and searched in every group. The
Models tab labels its windows as the current UTC bucket. Hour labels
are marked UTC. The Members column sorts numerically. Target access
shows "All groups" for null and "No groups" for []. The UI shows the
server's own error message. Cost axis ticks use the currency
formatter. npm run dev proxies /admin/api. Nine unused shadcn
components are removed.

admin_assets_gen.go is regenerated from the new build.

Claude-Session: https://claude.ai/code/session_01V5jYJNxpDFdJkyyExpADrv
2026-09-23 09:44:32 +01:00
lukaszraczylo abbf5d0967 fix(webui): accessible warn token, day-window model badges, review minors
Review round (v0.22) — MUST-1, SHOULD-2's frontend half, and every
folded minor that touched webui/.

MUST-1 (contrast): the degraded badge's amber previously reused
--color-chart-tokens-out, tuned for a chart mark, not body text —
measured ~2.22:1 against --card in light mode, a WCAG 1.4.3 failure on
exactly the "something's wrong" tier. A new semantic --status-warn token
pair (main.css, with a @theme inline mapping) replaces it: #a35c00 light
(~5.06:1), #f0ac1f dark (~8.97:1, already passing, redefined anyway so
every semantic-color token's dark value lives in one place).
ProviderRateBadge.vue's degraded tier now uses border-status-warn/40
text-status-warn.

SHOULD-2 (frontend half — see the backend commit for admin.go/limits.go):
AdminModelRateView drops attemptsMinute/failuresMinute (the backend no
longer sends them at all). ProviderRateBadge.vue's attempts-minute/
failures-minute props become optional; their absence (not a passed-
through 0) triggers a new dayRateTitle fallback (lib/provider-rate.ts) —
a real "0 attempts this minute" and "no minute data fetched at all" read
identically as 0 otherwise, which would misreport a model badge as having
live-minute data it never measured.

Folded minors:
- formatRatePercent floors instead of rounds — only a true 100% rate
  (zero failures) ever renders "100%"; 99.6% now shows "99%", not a
  round-tripped "100%" that would overstate it.
- providerSuccessRate NaN-guards both inputs (Number.isFinite).
- ProvidersView's modelRateFor uses `p.modelRates?.[model]` — a stale
  client against an older server build could otherwise throw rather than
  fall back, matching its own doc comment's stated intent.
- ProviderRateBadge's title is now also its aria-label — title alone is
  mouse-only; a keyboard/screen-reader user previously got none of the
  minute/day detail.
- The accordion's "Last refresh" DETAIL row (not just the trigger's
  compact text) now shares refreshLabel's three-state logic via a new
  refreshDetailLabel (lib/format.ts) — it previously called
  formatTimestamp directly, so a discovery-off provider's expanded row
  still said "never" beside a trigger that correctly said "discovery
  off".
- providerModelScopeID's Go doc comment (limits.go, backend commit)
  cross-references configNamePattern for why the "/" join is collision-
  safe even though a model id may itself contain "/".
- useTabHash.ts documents its two intentional URL-normalization quirks:
  case-sensitive hash matching, and an invalid/absent initial hash
  normalizing only in memory, not in the address bar, until the next tab
  change.
2026-08-22 00:00:48 +01:00
lukaszraczylo 655bbcbf2c feat(webui): vue admin panel source
Vue 3 + TypeScript + Pinia + Tailwind v4 + shadcn-vue + Chart.js admin
panel source under webui/, built with Vite. Views: Overview (providers,
redis/cache/retry status, model aliases), Usage (per-user/per-group/total
tables with tokensIn/tokensOut/cost + limits), Charts (scope selector,
req/tokens(stacked in-out)/cost metric tabs, 24h/30d/12mo window
switcher, fed by GET /admin/api/usage/history). Pinia stores: auth
(sessionStorage key gate, x-api-key header, 401/403 re-prompt),
dashboard (5s poll), history (fetch-on-selection + 30s refresh).

Design: deliberate neutral warm-neutral palette with a signature teal
accent (no default Inter/purple-gradient/glassmorphism), real
typographic hierarchy via the platform UI/mono font stacks (zero
external assets - no Google Fonts), dark+light via prefers-color-scheme
(no class toggle, no theme switcher). Charts: zero-based bars, honest
axes, Okabe-Ito colorblind-safe series, tokens-in/out as a
distinguishable stacked pair.

vite.config.ts: base '/admin/' (matches admin.go's route layout) and
build.modulePreload.polyfill:false (no inline bootstrap script, so the
tightened admin CSP needs no 'unsafe-inline'). Code-split: Chart.js
(ChartsView) loads lazily, not in the initial bundle.

generate.mjs reads dist/ after `vite build` and writes
../admin_assets_gen.go (not committed by this change) - base64-encodes
every asset, decoded once at package-var init; documented rationale in
the script's own header comment.

Hygiene: webui/go.mod walls webui/ off from the repo root's `go build`/
`vet`/`test ./...` (npm's node_modules bundles a stray .go file that
would otherwise be swept in); tools/yaegi-check/main.go excludes webui/
from its GOPATH copy for the same reason. webui/ export-ignored
(.gitattributes) from release source tarballs; webui/node_modules/
gitignored. tsconfig.app.json/tsconfig.node.json's JSONC comments
dropped (TS itself is fine with either; the global pre-commit JSON
validator is not) - no config values changed.
2026-08-21 09:21:41 +01:00