mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-14 05:18:54 +00:00
The Usage tab shows a bar for each configured limit. It turns amber at 80% and red at 100%. The tab also shows rejected requests per day, a projected month-end cost with budget headroom, and CSV export for users and groups. Names link to Charts with the matching scope. The Charts tab ranks models over the full 24h, 30d or 12mo window. A provider filter is applied on the server, and the ranking can be exported as CSV. A new Events tab lists recent failures, filtered by kind and user. The selected tab, filters and scope live in the URL hash. Polling stops while the page is hidden. Views show which replica answered, and a banner lists configuration warnings. CSV cells that start with a formula character are neutralised. Links sit outside the accordion buttons, so both controls keep their accessible names. admin_assets_gen.go is regenerated. Claude-Session: https://claude.ai/code/session_01V5jYJNxpDFdJkyyExpADrv
225 lines
6.7 KiB
TypeScript
225 lines
6.7 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import { createVisibilityPoller, type VisibilityDocumentLike } from './polling'
|
|
|
|
/**
|
|
* createFakeDoc builds a minimal VisibilityDocumentLike test double: a
|
|
* mutable `hidden` flag the test flips directly, a single
|
|
* visibilitychange listener slot (real Document supports many, but this
|
|
* module only ever registers one), and `fire()`/`hasListener()` helpers
|
|
* so a test can drive and inspect it without any real DOM.
|
|
*/
|
|
function createFakeDoc() {
|
|
let hiddenValue = false
|
|
let listener: (() => void) | undefined
|
|
const doc: VisibilityDocumentLike = {
|
|
get hidden() {
|
|
return hiddenValue
|
|
},
|
|
addEventListener(_type, l) {
|
|
listener = l
|
|
},
|
|
removeEventListener(_type, l) {
|
|
if (listener === l) listener = undefined
|
|
},
|
|
}
|
|
return {
|
|
doc,
|
|
setHidden: (v: boolean) => {
|
|
hiddenValue = v
|
|
},
|
|
fire: () => listener?.(),
|
|
hasListener: () => listener !== undefined,
|
|
}
|
|
}
|
|
|
|
describe('createVisibilityPoller', () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers()
|
|
})
|
|
afterEach(() => {
|
|
vi.useRealTimers()
|
|
})
|
|
|
|
it('ticks immediately on start, before the first interval elapses', () => {
|
|
const tick = vi.fn()
|
|
const poller = createVisibilityPoller({ intervalMs: 1000, tick, doc: createFakeDoc().doc })
|
|
|
|
poller.start()
|
|
|
|
expect(tick).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('ticks again every intervalMs while running', () => {
|
|
const tick = vi.fn()
|
|
const poller = createVisibilityPoller({ intervalMs: 1000, tick, doc: createFakeDoc().doc })
|
|
|
|
poller.start()
|
|
vi.advanceTimersByTime(3000)
|
|
|
|
expect(tick).toHaveBeenCalledTimes(4) // immediate + 3 interval ticks
|
|
})
|
|
|
|
it('start() is idempotent — calling it twice does not double-schedule', () => {
|
|
const tick = vi.fn()
|
|
const poller = createVisibilityPoller({ intervalMs: 1000, tick, doc: createFakeDoc().doc })
|
|
|
|
poller.start()
|
|
poller.start()
|
|
vi.advanceTimersByTime(1000)
|
|
|
|
expect(tick).toHaveBeenCalledTimes(2) // one immediate + one interval tick, not two of either
|
|
})
|
|
|
|
it('stop() clears the interval — no further ticks', () => {
|
|
const tick = vi.fn()
|
|
const poller = createVisibilityPoller({ intervalMs: 1000, tick, doc: createFakeDoc().doc })
|
|
|
|
poller.start()
|
|
poller.stop()
|
|
vi.advanceTimersByTime(5000)
|
|
|
|
expect(tick).toHaveBeenCalledTimes(1) // only the immediate tick from start()
|
|
})
|
|
|
|
it('stop() is idempotent — safe to call before start() or twice in a row', () => {
|
|
const tick = vi.fn()
|
|
const poller = createVisibilityPoller({ intervalMs: 1000, tick, doc: createFakeDoc().doc })
|
|
|
|
expect(() => {
|
|
poller.stop()
|
|
poller.start()
|
|
poller.stop()
|
|
poller.stop()
|
|
}).not.toThrow()
|
|
expect(tick).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('pauses polling while the document is hidden', () => {
|
|
const tick = vi.fn()
|
|
const fake = createFakeDoc()
|
|
const poller = createVisibilityPoller({ intervalMs: 1000, tick, doc: fake.doc })
|
|
|
|
poller.start()
|
|
tick.mockClear()
|
|
fake.setHidden(true)
|
|
fake.fire()
|
|
vi.advanceTimersByTime(5000)
|
|
|
|
expect(tick).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('catches up immediately when the document becomes visible again, then resumes the interval', () => {
|
|
const tick = vi.fn()
|
|
const fake = createFakeDoc()
|
|
const poller = createVisibilityPoller({ intervalMs: 1000, tick, doc: fake.doc })
|
|
|
|
poller.start()
|
|
fake.setHidden(true)
|
|
fake.fire()
|
|
tick.mockClear()
|
|
|
|
fake.setHidden(false)
|
|
fake.fire()
|
|
expect(tick).toHaveBeenCalledTimes(1) // the catch-up tick, not waiting out intervalMs
|
|
|
|
vi.advanceTimersByTime(1000)
|
|
expect(tick).toHaveBeenCalledTimes(2) // interval resumed after the catch-up
|
|
})
|
|
|
|
it('stop() removes the visibilitychange listener', () => {
|
|
const tick = vi.fn()
|
|
const fake = createFakeDoc()
|
|
const poller = createVisibilityPoller({ intervalMs: 1000, tick, doc: fake.doc })
|
|
|
|
poller.start()
|
|
expect(fake.hasListener()).toBe(true)
|
|
poller.stop()
|
|
|
|
expect(fake.hasListener()).toBe(false)
|
|
})
|
|
|
|
it('falls back to a plain interval with no visibility awareness when no document is available (vitest\'s node environment, no explicit doc passed)', () => {
|
|
const tick = vi.fn()
|
|
const poller = createVisibilityPoller({ intervalMs: 1000, tick })
|
|
|
|
poller.start()
|
|
vi.advanceTimersByTime(2000)
|
|
|
|
expect(tick).toHaveBeenCalledTimes(3) // immediate + 2 interval ticks — never throws for lack of `document`
|
|
poller.stop()
|
|
})
|
|
// P13: a poller started while the document is already hidden (e.g. this
|
|
// admin panel loaded in a background tab) must not tick until the reader
|
|
// actually looks at it — the pre-fix version ticked immediately and
|
|
// scheduled a background interval regardless of visibility.
|
|
it('does not tick immediately when the document is hidden at start', () => {
|
|
const tick = vi.fn()
|
|
const fake = createFakeDoc()
|
|
fake.setHidden(true)
|
|
const poller = createVisibilityPoller({ intervalMs: 1000, tick, doc: fake.doc })
|
|
|
|
poller.start()
|
|
|
|
expect(tick).not.toHaveBeenCalled()
|
|
poller.stop()
|
|
})
|
|
|
|
it('does not schedule a background interval while started hidden', () => {
|
|
const tick = vi.fn()
|
|
const fake = createFakeDoc()
|
|
fake.setHidden(true)
|
|
const poller = createVisibilityPoller({ intervalMs: 1000, tick, doc: fake.doc })
|
|
|
|
poller.start()
|
|
vi.advanceTimersByTime(10_000)
|
|
|
|
expect(tick).not.toHaveBeenCalled()
|
|
poller.stop()
|
|
})
|
|
|
|
it('ticks for the first time once the document becomes visible, having started hidden', () => {
|
|
const tick = vi.fn()
|
|
const fake = createFakeDoc()
|
|
fake.setHidden(true)
|
|
const poller = createVisibilityPoller({ intervalMs: 1000, tick, doc: fake.doc })
|
|
|
|
poller.start()
|
|
fake.setHidden(false)
|
|
fake.fire()
|
|
|
|
expect(tick).toHaveBeenCalledTimes(1)
|
|
|
|
vi.advanceTimersByTime(1000)
|
|
expect(tick).toHaveBeenCalledTimes(2) // interval resumed after the deferred first tick
|
|
poller.stop()
|
|
})
|
|
|
|
it('start() while hidden is still idempotent — a second call does not schedule a second listener/timer', () => {
|
|
const tick = vi.fn()
|
|
const fake = createFakeDoc()
|
|
fake.setHidden(true)
|
|
const poller = createVisibilityPoller({ intervalMs: 1000, tick, doc: fake.doc })
|
|
|
|
poller.start()
|
|
poller.start()
|
|
fake.setHidden(false)
|
|
fake.fire()
|
|
|
|
expect(tick).toHaveBeenCalledTimes(1) // one catch-up tick, not two
|
|
poller.stop()
|
|
})
|
|
|
|
it('ticks immediately when the document is already visible at start (unchanged, non-hidden case)', () => {
|
|
const tick = vi.fn()
|
|
const fake = createFakeDoc()
|
|
fake.setHidden(false)
|
|
const poller = createVisibilityPoller({ intervalMs: 1000, tick, doc: fake.doc })
|
|
|
|
poller.start()
|
|
|
|
expect(tick).toHaveBeenCalledTimes(1)
|
|
poller.stop()
|
|
})
|
|
})
|