Files
traefik-llmgateway/webui/README.md
T
lukaszraczylo d6b9879813 feat(webui): user and group search filter on usage and charts views
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.
2026-08-21 12:16:57 +01:00

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.