Files
traefik-llmgateway/webui/README.md
T
lukaszraczylo b98416d1e9 fix(webui): version display, CSP guards, config traps, bucket-aligned stacking
Review fix round (4 Important + 2 folded):

1. Version display: OverviewView.vue was missing the muted version line
   the old vanilla-JS page rendered (types/api.ts already declared
   overview.version) - added.

2. CSP invariant now guarded twice: webui/generate.mjs's new
   assertNoInlineAssets fails the build if dist/index.html contains an
   inline <script> (no src= attribute) or any <style> tag - the exact
   condition the tightened admin CSP (script-src/style-src 'self', no
   'unsafe-inline') depends on. admin_test.go's new
   TestAdminIndexHTML_NoInlineAssets re-asserts the identical invariant
   server-side, against the decoded adminIndexHTML admin.go actually
   serves, so a regression is caught even if admin_assets_gen.go were
   regenerated with a modified copy of generate.mjs.

3. webui/components.json's iconLibrary:"lucide"/font:"inter" fields
   cannot be fixed in place - checked shadcn-vue's own schema
   (PRESET_ICON_LIBRARIES/PRESET_FONTS in the installed package): no
   "none"/fontawesome/system choice exists for either, and JSON (unlike
   the tsconfig.*.json files touched in the previous commit) has no
   comment syntax to warn inline. Documented instead: webui/README.md
   gained a loud warning section naming the exact regression (already
   hit twice this branch - a re-added Google Fonts import, a re-added
   lucide icon import) and the two-step fix to apply after any future
   shadcn-vue add/init call.

4. Real-CSP verification: the browser pass earlier in this branch ran
   under vite preview, which sends no CSP header at all. New
   integration/integration_test.go: TestAdminAssetCSP fetches GET
   /admin against the real Traefik+Yaegi pipeline, extracts the asset
   URL from its <script src="..."> tag, fetches that asset, and asserts
   both responses carry the exact adminCSP string byte-for-byte.
   admin.go's serveAdminAsset now also sets Content-Security-Policy
   (previously JSON/page routes only) so this holds for a direct
   navigation to an asset URL, not just as a same-origin sub-resource of
   GET /admin; admin_test.go's TestAdminAsset_Serving updated to match.

5. (folded) UsageChart.vue stacked the "tokens" tab's two series
   (tokin/tokout - two separate HTTP requests, not one atomic call) by
   positional index. A window rollover landing between the two requests
   could leave one series one bucket ahead, mis-stacking that refresh.
   Now aligns both series by their bucket key (a sorted union of every
   active series' buckets - the fixed-width, zero-padded bucket strings
   sort chronologically by construction), with a missing bucket in one
   series reading as 0 rather than shifting the whole series.

6. (folded) AuthGate.vue now triggers an immediate dashboard.refresh()
   on successful key submit, instead of waiting up to 5s for the next
   poll tick - the dashboard no longer sits blank right after auth.

Rebuilt via make admin-ui; admin_assets_gen.go regenerated.
2026-08-21 09:43:24 +01:00

3.5 KiB

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's iconLibrary and font fields are locked to "lucide" and "inter" — not this project's real choices (this project uses FontAwesome only, per vue-web-development house skill, and no external font at all). The shadcn-vue CLI's schema does not accept "none"/"fontawesome"/"system" for either field (checked directly: node_modules/shadcn-vue/dist/preset-*.js's PRESET_ICON_LIBRARIES and PRESET_FONTS arrays — five fixed choices each, no escape hatch), and JSON supports no comment syntax to warn inline (the repo's own pre-commit JSON validator rejects JSONC comments — see tsconfig.*.json history). This warning is the only place the trap could be recorded.

Both npx shadcn-vue init and npx shadcn-vue add <component> read these fields and silently rewrite src/assets/main.css — hit twice in this project's own history: init first, then a follow-up add call, both re-added a @import url('https://fonts.googleapis.com/...') line and switched the dark-mode @custom-variant back to a .dark-class toggle. If you ever run either command again:

  1. Diff src/assets/main.css before committing. Remove any re-added @import url('https://fonts.googleapis...') line (external font, blocked by the admin CSP's style-src 'self' anyway) and restore the @custom-variant dark removal (see the file's own header comment for why: prefers-color-scheme, not a class toggle).
  2. Check the new component 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).

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; ui/ holds the shadcn-vue primitives this panel actually uses (Card, Table, Tabs, Button, Input, Alert, Select, Badge) — nothing installed and unused.
  • src/lib/ — api.ts (the /admin/api/* fetch wrapper), format.ts, key-storage.ts, chart-setup.ts.
  • 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.