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.
This commit is contained in:
2026-08-21 09:21:41 +01:00
parent 47b22d2d7f
commit 655bbcbf2c
81 changed files with 9694 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
// Shared response shapes for the three /admin/api/* routes (admin.go). Kept
// as one module so every store/component imports the identical types rather
// than re-declaring them (vue.md: "share types once, import everywhere").
/** Mirrors Go's LimitsConfig (llmgateway.go) — every field is 0/omitted when unlimited. */
export interface LimitsConfig {
requestsPerMinute?: number
requestsPerDay?: number
tokensPerDay?: number
tokensPerMonth?: number
costPerDayUSD?: number
costPerMonthUSD?: number
}
export interface AdminProviderView {
name: string
type: string
baseUrl: string
lastRefresh: string
lastErr?: string
modelCount: number
}
export interface AdminRedisView {
lastErrAt: string
lastErr?: string
configured: boolean
}
export interface AdminCacheView {
ttl?: string
enabled: boolean
}
export interface AdminRetryView {
backoff?: string
enabled: boolean
attempts?: number
}
export interface AdminGroupView {
limits?: LimitsConfig
name: string
memberCount: number
}
export interface AdminAliasView {
alias: string
target: string
}
/** GET /admin/api/overview (admin.go: adminOverviewResponse). */
export interface AdminOverviewResponse {
version: string
providers: AdminProviderView[]
groups: AdminGroupView[]
aliases: AdminAliasView[]
redis: AdminRedisView
cache: AdminCacheView
retry: AdminRetryView
}
export interface AdminUsageEntryView {
limits?: LimitsConfig
kind: string
id: string
groupName?: string
requestsPerMinute: number
requestsPerDay: number
tokensInPerDay: number
tokensOutPerDay: number
tokensInPerMonth: number
tokensOutPerMonth: number
costPerDayMicroUsd: number
costPerMonthMicroUsd: number
storeDown?: boolean
}
/** GET /admin/api/usage (admin.go: adminUsageResponse). */
export interface AdminUsageResponse {
users: AdminUsageEntryView[]
groups: AdminUsageEntryView[]
total: AdminUsageEntryView
}
/** The four metric values GET /admin/api/usage/history accepts. */
export type HistoryMetric = 'req' | 'tokin' | 'tokout' | 'cost'
/** The three window values GET /admin/api/usage/history accepts. */
export type HistoryWindow = 'hour' | 'day' | 'month'
export interface UsageHistoryPoint {
bucket: string
value: number
}
/** GET /admin/api/usage/history (admin.go: usageHistoryResponse). */
export interface UsageHistoryResponse {
scope: string
metric: string
window: string
points: UsageHistoryPoint[]
}