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).
4.6 KiB
Admin panel (webui)
Source for the traefik-llmgateway read-only admin dashboard: Vue 3 + TypeScript + Pinia + Tailwind v4 + shadcn-vue + Chart.js, built with Vite.
See the repo root README.md's Admin section for what
the dashboard shows and how its API/auth works, and the
Development section for make admin-ui — the
only way this directory's output (../admin_assets_gen.go) is meant to be
regenerated. Nothing here runs at request time: the plugin serves the
built, baked-in output, never webui/ itself.
Warning: shadcn-vue add/init regressions
components.json has no iconLibrary or font key. Do not add them
back. Their omission is the fix, not an oversight — read on before you
"helpfully" restore either one.
Earlier revisions of this file set "iconLibrary": "lucide" and
"font": "inter", both wrong for this project (FontAwesome only, per
the vue-web-development house skill; no external font at all). Both
npx shadcn-vue init and npx shadcn-vue add <component> read those two
fields and, when set, rewrite src/assets/main.css — hit twice in this
project's own history, init then a later add, both re-adding a
@import url('https://fonts.googleapis.com/...') line and switching the
dark-mode @custom-variant back to a .dark-class toggle.
The actual shadcn-vue config schema
(node_modules/shadcn-vue/dist/schema/index.js's rawConfigSchema)
declares both fields as z.string().optional() — not an enum, contrary
to an earlier, incorrect claim in this project's own review notes that
the CLI schema forced one of five fixed values. The CLI's init --font/
--icon-library flags do offer five fixed choices each, but that is a
flag-parsing constraint, not a components.json schema constraint.
Deleting both keys leaves them undefined after parse, a valid state:
verified empirically, running npx shadcn-vue add badge -o -y against a
components.json with both keys removed left src/assets/main.css
byte-for-byte unchanged.
That verification also surfaced a narrower, remaining trap: the same run
still added @lucide/vue back to package.json's dependencies (Badge
itself does not use it — some components' registry entries list an icon
package regardless of iconLibrary). So after any future shadcn-vue add/init call:
- Check
git diff webui/package.jsonfor a re-addedlucide-vue-next/@lucide/vuedependency, and remove it (npm uninstall) if nothing insrc/imports from it. - Check the new component's own file for a
lucide-vue-next/@lucide/vueimport and swap it for the FontAwesome equivalent (seesrc/components/ui/select/Select*.vuefor the pattern already applied there). - Diff
src/assets/main.cssanyway before committing, in case a future shadcn-vue release starts reading a different config key for the same font-injection step.
Local development
npm ci
npm run dev # Vite dev server — /admin/api/* calls need a real
# gateway to fetch from; point one at the same origin or
# accept the "refresh failed" state while iterating on
# layout/styling alone.
npm run build # what `make admin-ui` runs, plus webui/generate.mjs
Structure
src/stores/— Pinia:auth(the sessionStorage key gate),dashboard(Overview/Usage, 5s poll),history(Charts, fetch-on-selection + 30s refresh).src/components/—AuthGate,OverviewView,UsageView,ChartsView/UsageChart,UsageTable,DataTable(the shared sortable-table wrapper — shadcn-vue's DataTable pattern,@tanstack/ vue-table, generic over row type);ui/holds the shadcn-vue primitives this panel actually uses (Accordion, Card, Table, Tabs, Button, Input, Alert, Select, Badge) — nothing installed and unused. Providers (Overview) and Groups (Usage) render as a real Accordion — expanding a group shows its member users' own usage rows, a client-side join onAdminUsageEntryView.groupName.src/lib/—api.ts(the/admin/api/*fetch wrapper),format.ts,key-storage.ts,chart-setup.ts,provider-expand.ts(search-aware expand state, adapted to drive Accordion's v-model),usage-columns.ts(sharedColumnDefs for every usage table).src/types/api.ts— TypeScript mirrors of admin.go's JSON response shapes (adminOverviewResponse,adminUsageResponse,usageHistoryResponse).generate.mjs— readsdist/aftervite buildand writes../admin_assets_gen.go; see its own header comment for why every asset is base64-encoded rather than embedded as a raw string literal.