mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-14 05:18:54 +00:00
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).
92 lines
4.6 KiB
Markdown
92 lines
4.6 KiB
Markdown
# 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](../README.md#admin) for what
|
|
the dashboard shows and how its API/auth works, and the
|
|
[Development section](../README.md#development) 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:
|
|
|
|
1. **Check `git diff webui/package.json`** for a re-added
|
|
`lucide-vue-next`/`@lucide/vue` dependency, and remove it
|
|
(`npm uninstall`) if nothing in `src/` imports from it.
|
|
2. **Check the new component's own file** for a
|
|
`lucide-vue-next`/`@lucide/vue` import and swap it for the
|
|
FontAwesome equivalent (see `src/components/ui/select/Select*.vue`
|
|
for the pattern already applied there).
|
|
3. **Diff `src/assets/main.css` anyway** before committing, in case a
|
|
future shadcn-vue release starts reading a different config key for
|
|
the same font-injection step.
|
|
|
|
## Local development
|
|
|
|
```sh
|
|
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 on `AdminUsageEntryView.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`
|
|
(shared `ColumnDef`s for every usage table).
|
|
- `src/types/api.ts` — TypeScript mirrors of admin.go's JSON response
|
|
shapes (`adminOverviewResponse`, `adminUsageResponse`,
|
|
`usageHistoryResponse`).
|
|
- `generate.mjs` — reads `dist/` after `vite build` and 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.
|