mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-14 05:18:54 +00:00
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.
60 lines
2.1 KiB
Vue
60 lines
2.1 KiB
Vue
<script setup lang="ts">
|
|
import { faKey } from '@fortawesome/free-solid-svg-icons'
|
|
import { ref } from 'vue'
|
|
|
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
import { Input } from '@/components/ui/input'
|
|
import { useAuthStore } from '@/stores/auth'
|
|
import { useDashboardStore } from '@/stores/dashboard'
|
|
|
|
const auth = useAuthStore()
|
|
const dashboard = useDashboardStore()
|
|
const keyInput = ref('')
|
|
|
|
function submit(): void {
|
|
const value = keyInput.value
|
|
keyInput.value = ''
|
|
auth.submit(value)
|
|
// Fetch immediately rather than waiting for the next 5s poll tick
|
|
// (dashboard.startPolling's own interval, already running from App.vue's
|
|
// onMounted) — otherwise a freshly authenticated dashboard can sit blank
|
|
// for up to 5s after key entry.
|
|
void dashboard.refresh()
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="mx-auto flex min-h-[70vh] max-w-md flex-col justify-center px-4">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle class="flex items-center gap-2">
|
|
<FontAwesomeIcon :icon="faKey" class="size-4 text-primary" />
|
|
Admin key required
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Enter an admin API key. The browser keeps it only in this tab's session storage: it is
|
|
never written to disk and is sent only to this page's own /admin/api/* requests.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent class="flex flex-col gap-4">
|
|
<form class="flex flex-col gap-3" @submit.prevent="submit">
|
|
<Input
|
|
v-model="keyInput"
|
|
type="password"
|
|
autocomplete="off"
|
|
placeholder="API key"
|
|
aria-label="Admin API key"
|
|
/>
|
|
<Button type="submit" class="w-full">Continue</Button>
|
|
</form>
|
|
<Alert v-if="auth.error" variant="destructive">
|
|
<AlertTitle>Authentication failed</AlertTitle>
|
|
<AlertDescription>{{ auth.error }}</AlertDescription>
|
|
</Alert>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</template>
|