mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-15 05:29:28 +00:00
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:
@@ -21,7 +21,7 @@ import {
|
||||
CardTitle,
|
||||
} from '@/components/ui/card'
|
||||
import { useSearchQuery } from '@/composables/useSearchQuery'
|
||||
import { formatAgo, formatContextWindow, refreshDetailLabel, refreshLabel, routableModelId } from '@/lib/format'
|
||||
import { formatAgo, formatContextWindow, formatUntil, refreshDetailLabel, refreshLabel, routableModelId } from '@/lib/format'
|
||||
import { isModelDegraded } from '@/lib/provider-rate'
|
||||
import { type ExpandState, clearExpandOverrides, computeExpandedItems, toggleItemExpand } from '@/lib/search-expand'
|
||||
import { useDashboardStore } from '@/stores/dashboard'
|
||||
@@ -169,6 +169,31 @@ function modelMetaFor(p: AdminProviderView, model: string): AdminModelMetaView {
|
||||
return p.modelMeta?.[model] ?? ZERO_MODEL_META
|
||||
}
|
||||
|
||||
// --- discovery circuit breaker (feat/provider-health) ---
|
||||
//
|
||||
// Badge shown ONLY for 'open'/'half-open' — mirroring modelIsDegraded's
|
||||
// own "no badge when healthy" convention above: a 'closed' provider (the
|
||||
// common case) renders no breaker badge at all, exactly like a
|
||||
// non-degraded model gets no ProviderRateBadge.
|
||||
|
||||
/** healthBadgeVariant maps healthState to the shadcn-vue Badge variant: destructive (backing off) for 'open', secondary (in progress) for 'half-open', undefined (no badge) for 'closed'. */
|
||||
function healthBadgeVariant(state: AdminProviderView['healthState']): 'destructive' | 'secondary' | undefined {
|
||||
switch (state) {
|
||||
case 'open':
|
||||
return 'destructive'
|
||||
case 'half-open':
|
||||
return 'secondary'
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
/** healthBadgeLabel renders the breaker badge's text: "open (in Ns)" while backing off (formatUntil omits the parenthetical once openUntil has passed or is unset), or "half-open" while probing. */
|
||||
function healthBadgeLabel(p: AdminProviderView): string {
|
||||
const until = formatUntil(p.openUntil)
|
||||
return until ? `${p.healthState} (${until})` : p.healthState
|
||||
}
|
||||
|
||||
// --- model aliases (sortable DataTable, operator directive) ---
|
||||
//
|
||||
// The alias id gets the ModelChip copy treatment (an alias name IS the
|
||||
@@ -295,6 +320,21 @@ const aliasEmptyMessage = computed(() =>
|
||||
:attempts-minute="p.attemptsMinute"
|
||||
:failures-minute="p.failuresMinute"
|
||||
/>
|
||||
<!--
|
||||
Discovery circuit breaker (feat/provider-health): no
|
||||
badge at all while 'closed' (the healthy, common case),
|
||||
mirroring modelIsDegraded's own convention for the
|
||||
per-model rate badge above.
|
||||
-->
|
||||
<Badge
|
||||
v-if="p.healthState !== 'closed'"
|
||||
as="span"
|
||||
:variant="healthBadgeVariant(p.healthState)"
|
||||
class="font-normal"
|
||||
:title="p.healthState === 'open' ? 'discovery is backing off after repeated failures' : 'a discovery probe is deciding whether to recover'"
|
||||
>
|
||||
{{ healthBadgeLabel(p) }}
|
||||
</Badge>
|
||||
<FontAwesomeIcon
|
||||
v-if="p.lastErr"
|
||||
:icon="faTriangleExclamation"
|
||||
@@ -314,6 +354,10 @@ const aliasEmptyMessage = computed(() =>
|
||||
<dt class="text-xs text-muted-foreground">Last refresh</dt>
|
||||
<dd>{{ refreshDetailLabel(p.discoveryEnabled, p.lastRefresh) }}</dd>
|
||||
</div>
|
||||
<div v-if="p.healthState !== 'closed'">
|
||||
<dt class="text-xs text-muted-foreground">Discovery health</dt>
|
||||
<dd class="text-destructive">{{ healthBadgeLabel(p) }}</dd>
|
||||
</div>
|
||||
<div v-if="p.lastErr">
|
||||
<dt class="text-xs text-muted-foreground">Last error</dt>
|
||||
<dd class="text-destructive">{{ p.lastErr }}</dd>
|
||||
|
||||
Reference in New Issue
Block a user