Commit Graph
4 Commits
Author SHA1 Message Date
lukaszraczylo 5408b8e895 fix(webui): address auth-gate review findings
Explicit refocus. Add a watch(() => auth.error, ...) that calls the
same focusKeyInput() helper onMounted already used. Previously,
refocus-after-failure only worked through an undocumented chain
(auth.submit() optimistically flips isAuthenticated, App.vue unmounts
AuthGate, a 401 calls auth.reject(), isAuthenticated flips back,
AuthGate remounts, onMounted refires). That chain still exists and
still drives the shake retrigger; a comment above the Input now says
so explicitly, so a future change to optimistic auth does not break
either behavior silently.

Security disclosure order. Move the three-item checklist above the
form, so the claims sit in the reading path before the key field
instead of below the submit button. Add a visually hidden `<h2
id="authgate-security-heading">` and tie the `<ul>` to it via
aria-labelledby, giving screen-reader users a label the list did not
have before. Wording and text-xs sizing are unchanged.

Invalid-state wiring. Add :aria-invalid and :aria-describedby to the
Input, pointing at a new id="authgate-error" on the Alert, closing the
loop between the input and its own error message.

Typography hierarchy. CardTitle grows to text-lg font-semibold; the
"LLM Gateway" wordmark drops to a muted-foreground text-xs eyebrow, so
the title now reads as the dominant line instead of losing to the
wordmark's former text-sm font-semibold.

Drop text-base from the Input's class list — Input.vue already sets
text-base (overridden by its own md:text-sm at >=768px); the local
copy did nothing but restate the default. h-10/pr-10 stay.
2026-08-21 13:03:01 +01:00
lukaszraczylo 49c9325c9f feat(webui): redesign admin key gate
Add a circular key-icon badge and a small "LLM Gateway" wordmark, so the
screen reads as this product's door and not a generic prompt.

Restructure the dense security paragraph into a short description plus a
three-item muted checklist. Each claim keeps its original strength: the
key stays in this tab's session storage, the browser never writes it to
disk, and requests go only to this page's own /admin/api/* routes.

Add a show/hide toggle for the key input. It reuses the ghost
icon-button composition SearchInput.vue already uses for its clear
button. Autofocus the input on mount. Disable submit while the trimmed
input is empty.

Add a brief shake on the key input when auth.error is set. The
animation respects prefers-reduced-motion and lives in a scoped
<style> block local to this file — the first one in webui/src/components,
added because no Tailwind-core or tw-animate-css keyframe reads as
"rejected".

Extend the header in App.vue with a GitHub repository link, per an
operator scope addition mid-task: an icon-only ghost button that opens
github.com/lukaszraczylo/traefik-llmgateway in a new tab. The link sits
in App.vue's header, above the gate/dashboard switch, so it renders on
every screen including the auth gate. Installs
@fortawesome/free-brands-svg-icons, pinned like the other fontawesome
packages, for the faGithub icon.

auth.submit() semantics, dashboard.refresh() timing, and the backend
stay unchanged.
2026-08-21 12:47:31 +01:00
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
lukaszraczylo 655bbcbf2c feat(webui): vue admin panel source
Vue 3 + TypeScript + Pinia + Tailwind v4 + shadcn-vue + Chart.js admin
panel source under webui/, built with Vite. Views: Overview (providers,
redis/cache/retry status, model aliases), Usage (per-user/per-group/total
tables with tokensIn/tokensOut/cost + limits), Charts (scope selector,
req/tokens(stacked in-out)/cost metric tabs, 24h/30d/12mo window
switcher, fed by GET /admin/api/usage/history). Pinia stores: auth
(sessionStorage key gate, x-api-key header, 401/403 re-prompt),
dashboard (5s poll), history (fetch-on-selection + 30s refresh).

Design: deliberate neutral warm-neutral palette with a signature teal
accent (no default Inter/purple-gradient/glassmorphism), real
typographic hierarchy via the platform UI/mono font stacks (zero
external assets - no Google Fonts), dark+light via prefers-color-scheme
(no class toggle, no theme switcher). Charts: zero-based bars, honest
axes, Okabe-Ito colorblind-safe series, tokens-in/out as a
distinguishable stacked pair.

vite.config.ts: base '/admin/' (matches admin.go's route layout) and
build.modulePreload.polyfill:false (no inline bootstrap script, so the
tightened admin CSP needs no 'unsafe-inline'). Code-split: Chart.js
(ChartsView) loads lazily, not in the initial bundle.

generate.mjs reads dist/ after `vite build` and writes
../admin_assets_gen.go (not committed by this change) - base64-encodes
every asset, decoded once at package-var init; documented rationale in
the script's own header comment.

Hygiene: webui/go.mod walls webui/ off from the repo root's `go build`/
`vet`/`test ./...` (npm's node_modules bundles a stray .go file that
would otherwise be swept in); tools/yaegi-check/main.go excludes webui/
from its GOPATH copy for the same reason. webui/ export-ignored
(.gitattributes) from release source tarballs; webui/node_modules/
gitignored. tsconfig.app.json/tsconfig.node.json's JSONC comments
dropped (TS itself is fine with either; the global pre-commit JSON
validator is not) - no config values changed.
2026-08-21 09:21:41 +01:00