feat(webui): surface discovery health state in the Providers tab

Add healthState and openUntil to AdminProviderView, and a formatUntil
helper alongside the existing formatAgo/formatTimestamp pair, rendering
a future ISO timestamp as "in Ns".

ProvidersView.vue shows a Badge next to a provider's other status
indicators, destructive for open and secondary for half-open, using the
existing shadcn-vue Badge component rather than a hand-rolled
indicator. A closed provider, the healthy common case, renders no badge
at all, matching the existing convention for the per-model rate badge.
The expanded accordion content adds a matching detail row.

This was the brief's stated motivation for the admin surfacing work: a
broken provider looked identical to a working one in the dashboard.
That gap was previously closed at the API layer only; this closes it in
the UI clients actually see.

Regenerates admin_assets_gen.go from the rebuilt webui via make
admin-ui, per this repository's own convention of committing the
generated asset alongside its source change.
This commit is contained in:
2026-08-22 22:57:19 +01:00
parent e82dabb1dc
commit 8a3f9e7d00
4 changed files with 88 additions and 7 deletions
+18
View File
@@ -156,6 +156,24 @@ export function formatAgo(iso: string | undefined): string {
return ` (${secs}s ago)`
}
/**
* formatUntil renders a FUTURE ISO timestamp as "in Ns" — the discovery
* circuit breaker's openUntil (feat/provider-health, admin.go's
* adminProviderView.OpenUntil) is the only caller — or "" for the unset
* zero-time sentinel, an unparseable value, or a time already in the
* past (a stale poll reading an openUntil the breaker has since moved
* past; the next 5s refresh corrects it, so this just omits the detail
* rather than showing a negative/nonsensical countdown).
*/
export function formatUntil(iso: string | undefined): string {
if (!iso || iso === ZERO_TIME) return ''
const then = new Date(iso).getTime()
if (Number.isNaN(then)) return ''
const secs = Math.round((then - Date.now()) / 1000)
if (secs <= 0) return ''
return `in ${secs}s`
}
/** formatTimestamp renders an ISO timestamp for display, or "never" for the unset zero-time sentinel. */
export function formatTimestamp(iso: string | undefined): string {
if (!iso || iso === ZERO_TIME) return 'never'