Add a Health column to the MCP servers and A2A agents tables in TargetsView. An unhealthy target renders a destructive badge with the age of its last check and carries the failure count, source, and scrubbed last error in the title. An unknown target renders an outline badge and, when it has failed but never succeeded, the same failure details in its title. A healthy target renders muted text with its last observed latency and no badge, matching the providers' convention. Add AdminTargetHealthView to the API types with the wire contract the Go side emits, a bare formatElapsedAgo helper next to formatAgo, and column-cell tests for all three states. Regenerate the baked admin assets. Claude-Session: https://claude.ai/code/session_01EebV5jyWvYocByNQ3oss1w
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(Providers/Usage, 5s poll),history(Charts, fetch-on-selection + 30s refresh).src/components/—AuthGate,ProvidersView(the "Providers" tab — named for its UI label; it still fetches GET /admin/api/overview, whose own name is unchanged API surface),UsageView,ChartsView/UsageChart,UsageTable,DataTable(the shared sortable-table wrapper — shadcn-vue's DataTable pattern,@tanstack/ vue-table, generic over row type),SearchInput(the shared search-box — icon + shadcnInput+ shadcnButtonclear button — every search filter in the panel renders);ui/holds the shadcn-vue primitives this panel actually uses (Accordion, Card, Table, Tabs, Button, Input, Alert, Select, Badge) — nothing installed and unused. Providers 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/composables/—useThemeColors(Chart.js axis/grid/legend colors off the current design tokens),useSearchQuery(the sharedquery/normalized/hasQuerystate everySearchInputpairs with).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 ProvidersView's providers and UsageView's groups),usage-search.ts(shared user/group query-matching helpers, used by UsageView and ChartsView),usage-columns.ts(sharedColumnDefs for every usage table).src/types/api.ts— TypeScript mirrors of admin.go's JSON response shapes (adminOverviewResponse,adminUsageResponse,usageHistoryResponse) — these keep their backend names; only the frontend tab label/component changed (seeProvidersViewabove).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.