mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-14 05:18:54 +00:00
Add a "Filter users or groups" search input to UsageView, mirroring OverviewView's model search (same Input component, icon, and clear-button styling). Case-insensitive, trimmed substring match: - A user matches when its own name contains the query. - A group matches when its own name contains the query OR it has at least one matching member. Groups accordion shows only matching groups, sorted through the existing Groups toolbar unchanged. A group that matches only via a member auto-expands (using the same tested override semantics as Overview's provider search); a group matched by its own name is not force-expanded, since the match is already visible on its collapsed trigger row. The nested members table inside an expanded group shows every member when the group matched by name, and only the matching members when it matched via a member. The flat Users table filters to matching users, plus every member of a name-matched group. The Total card and ChartsView's chart rendering are untouched by the filter. ChartsView's scope picker (Select) gets the identical search input, filtering the user/group option list it offers. "Total" always stays in the list, and the currently selected scope is always kept in the list even if a new query would otherwise hide it, so an active chart is never torn down or re-labeled by narrowing the picker. Generalize lib/provider-expand.ts into lib/search-expand.ts: the same two-set (manuallyExpanded/manuallyCollapsed) auto-expand/override state machine now drives both Overview's providers and Usage's groups, keyed on generic string ids instead of provider names specifically. OverviewView.vue is updated to the renamed computeExpandedItems/toggleItemExpand exports; its own behavior is unchanged. The moved spec (search-expand.spec.ts) keeps all 9 original cases plus 2 new ones pinning the same semantics against non-provider (group-shaped) ids. Add lib/usage-search.ts with the shared user/group matching helpers (userMatches, groupNameMatches, membersOfGroup, matchingMembersOfGroup, groupMatches) so UsageView and ChartsView share one matching implementation, with its own vitest spec. UsageTable.vue gains an optional emptyMessage prop (threaded to DataTable) so UsageView can show a "no users match ..."/"no groups match ..." message under an active filter, matching Overview's own alias-search empty-state pattern. No backend/Go logic changes; filtering is client-side over the already-polled GET /admin/api/usage response.
95 lines
4.8 KiB
Markdown
95 lines
4.8 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`, `search-expand.ts` (search-aware
|
|
expand state, adapted to drive Accordion's v-model — shared by
|
|
OverviewView's providers and UsageView's groups), `usage-search.ts`
|
|
(shared user/group query-matching helpers, used by UsageView and
|
|
ChartsView), `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.
|