Files
traefik-llmgateway/webui
lukaszraczylo 24809ed25a fix(webui): shared SearchInput/useSearchQuery, correct expand rationale
Address review of the user/group search filter (feat/user-search):

- Correct the "group name match does not auto-expand" rationale in
  UsageView.vue and lib/search-expand.ts. The prior comment claimed
  Overview has no provider-name search — false: OverviewView's
  providerMatches checks each model's full ROUTABLE id
  (`${provider}/${model}`, lib/format.ts's routableModelId), so a
  provider-name query does match and auto-expand there. The actual
  reason Usage withholds auto-expand from a name-only match: a group's
  name is already legible on its collapsed trigger row, so expanding
  every name match too would dump full member rosters for no reason;
  a member-only match has no such shortcut and still needs the reveal.
  Behavior is unchanged — only the (now accurate) justification.

- Extract components/SearchInput.vue and composables/useSearchQuery.ts.
  OverviewView's model search, UsageView's user/group search, and
  ChartsView's scope-picker search were three ~22-line copies of the
  same icon+Input+clear-button markup, plus three copies of the
  query/normalized/hasQuery/clear quartet. All three views now consume
  the shared component/composable. SearchInput's clear button is a real
  shadcn Button (variant="ghost", size="icon-xs" — 24x24px) instead of a
  hand-rolled <button>, fixing a WCAG 2.2 SC 2.5.8 hit-target/
  focus-visible gap once instead of three times.

- ChartsView's search input: w-48 (clipped the 22-char placeholder) to
  w-56, matching the sibling SelectTrigger's width.

- UsageView.vue's visibleMembers now calls the exported, tested
  matchingMembersOfGroup helper instead of re-implementing the same
  filter inline with userMatches.

- Reworded the "shared with ChartsView.vue" claims on membersOfGroup
  (usage-search.ts) and in UsageView.vue's visibleMembers doc comment:
  ChartsView never calls membersOfGroup directly, only indirectly via
  groupMatches -> matchingMembersOfGroup.

- UsageView's groups/users empty-state messages now follow Overview's
  aliasEmptyMessage three-way pattern ("none configured" vs "no
  groups/users match ..." vs "none"), distinguishing nothing-configured
  from nothing-matches instead of collapsing both to the same text.

Gate unchanged: npm run build + npm test + vue-tsc, make admin-ui
(separate regen commit), go test ./... -count=1, gofmt/vet, make
yaegi-check.
2026-08-21 12:17:04 +01:00
..
2026-08-21 09:21:41 +01:00
2026-08-21 09:21:41 +01:00
2026-08-21 09:21:41 +01:00

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:

  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

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 ColumnDefs 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.