Component-first admin-panel rework (operator directive), replacing
hand-rolled patterns with the actual shadcn-vue library components
rather than working around them.
- Providers (OverviewView) and Groups (UsageView) now render as a real
shadcn-vue Accordion: trigger is the summary line, content holds the
detail (base URL, last refresh/error, model chips for providers;
full stat grid + member users for groups) — the earlier hand-rolled
second-<TableRow> toggle is retired. lib/provider-expand.ts's tested
two-set expand-state module is unchanged; only the template now
adapts it to Accordion's type="multiple" v-model via a writable
computed instead of driving a plain v-if.
- Usage view: expanding a group shows its member users' own usage rows
— a client-side join on AdminUsageEntryView.groupName, which admin.go
already sets per user (verified via admin_test.go's existing
TestAdminUsage_MathAgainstSeededCounters; no backend change needed).
- New DataTable.vue: the shadcn-vue DataTable pattern
(@tanstack/vue-table, pinned to ^8.21.3 — the version whose API
matches the documented pattern; npm's default "latest" resolves to
v9, a breaking rewrite with a different API) factored into one
reusable, generic component so every real table (users usage,
aliases, a group's nested member list) gets clickable sortable
headers with asc/desc indicators for free. usage-columns.ts holds
the shared ColumnDef set; UsageTable.vue now delegates to DataTable.
Groups themselves cannot be a literal <table> (Accordion wraps
content in a <div>, illegal inside <tbody>), so their own
sortability is driven by the same columns headlessly, surfaced as a
small header-button toolbar above the Accordion.
- Aliases table: alias id gets the ModelChip copy treatment (the alias
name is the routable string); target stays plain text.
- App.vue: dropped the max-w-6xl container — the panel now uses the
full viewport width with responsive gutters (px-4 sm:px-6 lg:px-8).
Table.vue already wraps every table in its own overflow-x-auto
container, so a wide table still scrolls itself, never the page.
shadcn-vue add accordion re-added @lucide/vue and required swapping
its Chevron icons for FontAwesome (webui/README.md's own documented
trap); components.json's iconLibrary/font keys stayed absent
(verified main.css byte-identical after the add).
Browser-verified against the real integration stack (real /admin over
the Yaegi plugin, strict CSP incl. style-src 'self'): provider/group
accordion expand, search auto-expand through the new Accordion
adapter, sort toggles on aliases/users/groups-toolbar/nested-member
tables, alias copy-to-clipboard, full-width at 1920px. Zero console
errors (one unrelated MetaMask extension log).
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.