mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-17 05:37:43 +00:00
fix(webui): keep charts consistent with the current selection
Chart and ranking fetches carry a request id, and a superseded response is dropped. Changing a selection clears the old data at once, so old numbers never show under new labels or units. The auto-refresh tick skips while a fetch is still in flight. "Loading..." shows only until the first result for a selection arrives. Dashboard polls do not overlap. Users in several groups are counted and searched in every group. The Models tab labels its windows as the current UTC bucket. Hour labels are marked UTC. The Members column sorts numerically. Target access shows "All groups" for null and "No groups" for []. The UI shows the server's own error message. Cost axis ticks use the currency formatter. npm run dev proxies /admin/api. Nine unused shadcn components are removed. admin_assets_gen.go is regenerated from the new build. Claude-Session: https://claude.ai/code/session_01V5jYJNxpDFdJkyyExpADrv
This commit is contained in:
@@ -25,12 +25,33 @@ export const useDashboardStore = defineStore('dashboard', {
|
||||
lastUpdated: null as Date | null,
|
||||
error: '',
|
||||
timer: undefined as ReturnType<typeof setInterval> | undefined,
|
||||
/**
|
||||
* refreshing guards against overlapping polls (review finding): the 5s
|
||||
* setInterval has no in-flight check on its own, so a round trip that
|
||||
* takes longer than POLL_MS (a slow Redis behind /admin/api/overview,
|
||||
* say) would otherwise stack a second refresh() on top of the first —
|
||||
* two requests per section in flight at once, free to resolve in
|
||||
* either order and briefly show older data after the newer one already
|
||||
* landed. A tick that finds a refresh still in flight simply skips —
|
||||
* the next tick (or the already-in-flight one finishing) catches up,
|
||||
* so the dashboard never resorts to comparing response ordering.
|
||||
*/
|
||||
refreshing: false,
|
||||
}),
|
||||
actions: {
|
||||
async refresh(): Promise<void> {
|
||||
const auth = useAuthStore()
|
||||
if (!auth.isAuthenticated) return
|
||||
|
||||
if (this.refreshing) return
|
||||
this.refreshing = true
|
||||
try {
|
||||
await this.doRefresh()
|
||||
} finally {
|
||||
this.refreshing = false
|
||||
}
|
||||
},
|
||||
/** doRefresh is refresh()'s actual body, split out so the in-flight guard above wraps it in one place rather than every early-return branch below needing its own `finally`. */
|
||||
async doRefresh(): Promise<void> {
|
||||
const [overviewResult, usageResult, targetsResult] = await Promise.allSettled([
|
||||
adminFetch<AdminOverviewResponse>('/admin/api/overview'),
|
||||
adminFetch<AdminUsageResponse>('/admin/api/usage'),
|
||||
@@ -80,10 +101,5 @@ export const useDashboardStore = defineStore('dashboard', {
|
||||
void this.refresh()
|
||||
this.timer = setInterval(() => void this.refresh(), POLL_MS)
|
||||
},
|
||||
stopPolling(): void {
|
||||
if (this.timer === undefined) return
|
||||
clearInterval(this.timer)
|
||||
this.timer = undefined
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user