mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-15 05:29:28 +00:00
fix(webui): accessible warn token, day-window model badges, review minors
Review round (v0.22) — MUST-1, SHOULD-2's frontend half, and every folded minor that touched webui/. MUST-1 (contrast): the degraded badge's amber previously reused --color-chart-tokens-out, tuned for a chart mark, not body text — measured ~2.22:1 against --card in light mode, a WCAG 1.4.3 failure on exactly the "something's wrong" tier. A new semantic --status-warn token pair (main.css, with a @theme inline mapping) replaces it: #a35c00 light (~5.06:1), #f0ac1f dark (~8.97:1, already passing, redefined anyway so every semantic-color token's dark value lives in one place). ProviderRateBadge.vue's degraded tier now uses border-status-warn/40 text-status-warn. SHOULD-2 (frontend half — see the backend commit for admin.go/limits.go): AdminModelRateView drops attemptsMinute/failuresMinute (the backend no longer sends them at all). ProviderRateBadge.vue's attempts-minute/ failures-minute props become optional; their absence (not a passed- through 0) triggers a new dayRateTitle fallback (lib/provider-rate.ts) — a real "0 attempts this minute" and "no minute data fetched at all" read identically as 0 otherwise, which would misreport a model badge as having live-minute data it never measured. Folded minors: - formatRatePercent floors instead of rounds — only a true 100% rate (zero failures) ever renders "100%"; 99.6% now shows "99%", not a round-tripped "100%" that would overstate it. - providerSuccessRate NaN-guards both inputs (Number.isFinite). - ProvidersView's modelRateFor uses `p.modelRates?.[model]` — a stale client against an older server build could otherwise throw rather than fall back, matching its own doc comment's stated intent. - ProviderRateBadge's title is now also its aria-label — title alone is mouse-only; a keyboard/screen-reader user previously got none of the minute/day detail. - The accordion's "Last refresh" DETAIL row (not just the trigger's compact text) now shares refreshLabel's three-state logic via a new refreshDetailLabel (lib/format.ts) — it previously called formatTimestamp directly, so a discovery-off provider's expanded row still said "never" beside a trigger that correctly said "discovery off". - providerModelScopeID's Go doc comment (limits.go, backend commit) cross-references configNamePattern for why the "/" join is collision- safe even though a model id may itself contain "/". - useTabHash.ts documents its two intentional URL-normalization quirks: case-sensitive hash matching, and an invalid/absent initial hash normalizing only in memory, not in the address bar, until the next tab change.
This commit is contained in:
@@ -18,6 +18,20 @@ import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
* validTabs. Matching is a plain string comparison: a tab value is
|
||||
* already a fixed lowercase identifier (App.vue's TabsTrigger value=
|
||||
* strings), never re-cased anywhere in this pipeline.
|
||||
*
|
||||
* Two intentional URL-normalization quirks (folded review minors, v0.22
|
||||
* review round), both worth naming rather than leaving implicit:
|
||||
*
|
||||
* 1. Case sensitivity: "#Providers" does NOT match "providers" — it falls
|
||||
* back to defaultTab like any other unrecognized hash, rather than
|
||||
* being normalized case-insensitively. A URL a person typed or edited
|
||||
* by hand with different casing is treated as invalid, not corrected.
|
||||
* 2. An invalid or absent hash is normalized only in memory (this
|
||||
* function's own return value), never in the address bar itself:
|
||||
* useTabHash's initial read below never calls history.replaceState —
|
||||
* only a LATER tab change does (see its own doc comment) — so loading
|
||||
* "#bogus" shows the Providers tab while the address bar still reads
|
||||
* "#bogus" until the reader switches tabs at least once.
|
||||
*/
|
||||
export function hashToTab<T extends string>(hash: string, validTabs: readonly T[], defaultTab: T): T {
|
||||
const raw = hash.startsWith('#') ? hash.slice(1) : hash
|
||||
|
||||
Reference in New Issue
Block a user