mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-13 04:58:50 +00:00
A sidebar replaces the flat tabs with seven pages, and one filter bar sets the time range, comparison period and scope for all of them. - Home: spend against budget, forecast, request rate, error rate, open breakers, top users and models, recent events and warnings. - Spend: spend over time with comparison, budget burn-down, an attribution drill-down from group to user to model, pricing health and cost avoided by free models. - Consumers: a directory with last seen and limit bars, an access matrix, and a detail page per user. - Models: the catalog with billing prices, fleet latency and error rates, and provider health. - Reliability: error, timeout, failover, 429 and 402 trends, events, and latency per provider. - Config: the redacted config and a change helper that emits YAML or users.json snippets to paste into git. - MCP & Agents: callers per target. Page state lives in the URL hash, and old tab links map to the new pages. The replaced views are removed. admin_assets_gen.go is regenerated. Claude-Session: https://claude.ai/code/session_01V5jYJNxpDFdJkyyExpADrv
92 lines
4.6 KiB
TypeScript
92 lines
4.6 KiB
TypeScript
// Pure math behind the Home page's KpiTile.vue tiles (redesign-plan.md
|
|
// section 3.4) — kept out of the component/page the same way lib/
|
|
// provider-rate.ts and lib/usage-bars.ts keep their own tier math out of
|
|
// their components, so vitest's node-environment config exercises it
|
|
// directly, no component mount required. Deliberately reuses those two
|
|
// modules' existing thresholds (BAR_AMBER_THRESHOLD/BAR_RED_THRESHOLD,
|
|
// PROVIDER_RATE_AMBER_THRESHOLD/PROVIDER_RATE_DESTRUCTIVE_THRESHOLD)
|
|
// rather than declaring a third, independent set of budget/error-rate
|
|
// boundaries for the same two underlying concepts (a used/limit ratio,
|
|
// a success rate) the rest of this panel already colors consistently.
|
|
import { providerRateStatus, providerSuccessRate } from '@/lib/provider-rate'
|
|
import { MICROS_PER_USD, ratioTier } from '@/lib/usage-bars'
|
|
import type { AdminGroupView, AdminProviderView } from '@/types/api'
|
|
|
|
/** KpiTier is KpiTile.vue's own three-color scale — 'ok' (normal), 'warn' (approaching a limit / minor degradation), 'critical' (over a limit / severe degradation). */
|
|
export type KpiTier = 'ok' | 'warn' | 'critical'
|
|
|
|
/**
|
|
* sumGroupBudgetMicros sums every configured group's costPerMonthUSD
|
|
* limit, converted to micro-USD (Q2, redesign-plan.md DECISIONS: "Home
|
|
* budget baseline = sum of group costPerMonthUSD"). A group with no
|
|
* costPerMonthUSD configured (0 or undefined) contributes nothing — the
|
|
* caller reads a 0 total as "no budget set" (budgetRatio below), never a
|
|
* genuine zero-dollar budget.
|
|
*/
|
|
export function sumGroupBudgetMicros(groups: AdminGroupView[]): number {
|
|
let total = 0
|
|
for (const group of groups) {
|
|
const limitUsd = group.limits?.costPerMonthUSD
|
|
if (limitUsd && limitUsd > 0) total += Math.round(limitUsd * MICROS_PER_USD)
|
|
}
|
|
return total
|
|
}
|
|
|
|
/** budgetRatio is `spentMicros / budgetMicros`, or null when budgetMicros is 0 or negative ("no budget set" — Q2) — the fleet has nothing configured to compare month-to-date spend against. */
|
|
export function budgetRatio(spentMicros: number, budgetMicros: number): number | null {
|
|
if (budgetMicros <= 0) return null
|
|
return spentMicros / budgetMicros
|
|
}
|
|
|
|
/** budgetTier classifies a budgetRatio result into the Home spend tile's KpiTier, reusing lib/usage-bars.ts's own ratioTier thresholds (0.8 amber, 1.0 red) so a budget tile and a per-scope UsageBar agree on what "approaching the limit" means. A null ratio (no budget set) reads as 'ok' — there is nothing to warn about. */
|
|
export function budgetTier(ratio: number | null): KpiTier {
|
|
if (ratio === null) return 'ok'
|
|
switch (ratioTier(ratio)) {
|
|
case 'normal':
|
|
return 'ok'
|
|
case 'amber':
|
|
return 'warn'
|
|
case 'red':
|
|
return 'critical'
|
|
}
|
|
}
|
|
|
|
/** openBreakerCount counts providers whose discovery circuit breaker is not 'closed' (AdminProviderView.healthState) — the Home "open breakers" tile's own count, per-replica like the field it reads (see AdminProviderView.healthState's own doc comment). */
|
|
export function openBreakerCount(providers: AdminProviderView[]): number {
|
|
return providers.filter((p) => p.healthState !== 'closed').length
|
|
}
|
|
|
|
/** FleetErrorRate is the Home error-rate tile's fleet-wide reading: summed minute-window attempts/failures across every provider, and the resulting rate. */
|
|
export interface FleetErrorRate {
|
|
/** failures/attempts as a 0-1 fraction, or null when there were no attempts in the last minute (never a fabricated 0%). */
|
|
rate: number | null
|
|
attempts: number
|
|
failures: number
|
|
}
|
|
|
|
/** fleetErrorRate sums every provider's attemptsMinute/failuresMinute (AdminProviderView — the SAME per-replica, in-process minute counters ProviderRateBadge.vue's own minuteRateTitle reads) into one fleet-wide error rate. */
|
|
export function fleetErrorRate(providers: AdminProviderView[]): FleetErrorRate {
|
|
let attempts = 0
|
|
let failures = 0
|
|
for (const p of providers) {
|
|
attempts += p.attemptsMinute
|
|
failures += p.failuresMinute
|
|
}
|
|
const success = providerSuccessRate(attempts, failures)
|
|
return { rate: success === null ? null : 1 - success, attempts, failures }
|
|
}
|
|
|
|
/** errorRateTier classifies a fleetErrorRate() result into the Home error-rate tile's KpiTier, reusing lib/provider-rate.ts's own success-rate thresholds (99% amber, 90% red) inverted for an error-rate reading — the same boundaries the Providers tab's own badges already use. */
|
|
export function errorRateTier(rate: number | null): KpiTier {
|
|
if (rate === null) return 'ok'
|
|
switch (providerRateStatus(1 - rate)) {
|
|
case 'no-traffic':
|
|
case 'healthy':
|
|
return 'ok'
|
|
case 'degraded':
|
|
return 'warn'
|
|
case 'severe':
|
|
return 'critical'
|
|
}
|
|
}
|