From 9f8dfa1398478eaa2006434017076a77aeeecd2d Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Sun, 15 Feb 2026 23:01:27 -0300 Subject: [PATCH] feat: add costs page and enhance health provider status display Add dedicated Costs page combining Budget and Pricing tabs with a sidebar navigation layout. Improve Health page provider section with status legend (Healthy/Recovering/Down) and import AI_PROVIDERS constants for richer provider display. --- src/app/(dashboard)/dashboard/costs/page.js | 77 ++++++++++ src/app/(dashboard)/dashboard/health/page.js | 131 ++++++++++++++---- .../(dashboard)/dashboard/settings/page.js | 4 - src/app/(dashboard)/dashboard/usage/page.js | 7 +- src/app/api/monitoring/health/route.js | 13 +- src/shared/components/Sidebar.js | 1 + 6 files changed, 194 insertions(+), 39 deletions(-) create mode 100644 src/app/(dashboard)/dashboard/costs/page.js diff --git a/src/app/(dashboard)/dashboard/costs/page.js b/src/app/(dashboard)/dashboard/costs/page.js new file mode 100644 index 0000000000..98727033ef --- /dev/null +++ b/src/app/(dashboard)/dashboard/costs/page.js @@ -0,0 +1,77 @@ +"use client"; + +import { useState } from "react"; +import { cn } from "@/shared/utils/cn"; +import BudgetTab from "../usage/components/BudgetTab"; +import PricingTab from "../settings/components/PricingTab"; + +const sections = [ + { + id: "budget", + label: "Budget", + icon: "account_balance_wallet", + description: "Daily and monthly spend limits", + }, + { + id: "pricing", + label: "Pricing", + icon: "payments", + description: "Per-model cost configuration", + }, +]; + +export default function CostsPage() { + const [activeSection, setActiveSection] = useState("budget"); + + return ( +
+ {/* Header */} +
+

Costs

+

+ Budget limits and model pricing configuration +

+
+ + {/* Layout: sidebar + content */} +
+ {/* Sidebar */} + + + {/* Content */} +
+ {activeSection === "budget" && } + {activeSection === "pricing" && } +
+
+
+ ); +} diff --git a/src/app/(dashboard)/dashboard/health/page.js b/src/app/(dashboard)/dashboard/health/page.js index 4fc1ed5b85..fe8c7a3842 100644 --- a/src/app/(dashboard)/dashboard/health/page.js +++ b/src/app/(dashboard)/dashboard/health/page.js @@ -14,6 +14,7 @@ import { useState, useEffect, useCallback } from "react"; import { Card } from "@/shared/components"; +import { AI_PROVIDERS } from "@/shared/constants/providers"; function formatUptime(seconds) { const d = Math.floor(seconds / 86400); @@ -324,38 +325,120 @@ export default function HealthPage() { {/* Provider Health */} -

- - health_and_safety - - Provider Health -

+
+

+ + health_and_safety + + Provider Health +

+ {cbEntries.length > 0 && ( +
+ + Healthy + + + Recovering + + + Down + +
+ )} +
{cbEntries.length === 0 ? (

No circuit breaker data available. Make some requests first.

) : ( -
- {cbEntries.map(([provider, cb]) => { - const style = CB_COLORS[cb.state] || CB_COLORS.CLOSED; - return ( -
-
- {provider} - {style.label} + (() => { + const unhealthy = cbEntries.filter(([, cb]) => cb.state !== "CLOSED"); + const healthy = cbEntries.filter(([, cb]) => cb.state === "CLOSED"); + return ( +
+ {/* Unhealthy providers first */} + {unhealthy.length > 0 && ( +
+

+ Issues Detected +

+ {unhealthy.map(([provider, cb]) => { + const style = CB_COLORS[cb.state] || CB_COLORS.OPEN; + const providerInfo = AI_PROVIDERS[provider]; + const displayName = providerInfo?.name || provider; + return ( +
+
+ {providerInfo?.textIcon || provider.slice(0, 2).toUpperCase()} +
+
+
+ + {displayName} + + + {style.label} + +
+
+ {cb.failures} failure{cb.failures !== 1 ? "s" : ""} + {cb.lastFailure && ( + + ยท Last: {new Date(cb.lastFailure).toLocaleTimeString()} + + )} +
+
+
+ ); + })}
-
- Failures: {cb.failures || 0} - {cb.lastFailure && ( - - Last: {new Date(cb.lastFailure).toLocaleTimeString()} - + )} + + {/* Healthy providers in compact grid */} + {healthy.length > 0 && ( +
+ {unhealthy.length > 0 && ( +

+ Operational +

)} +
+ {healthy.map(([provider]) => { + const providerInfo = AI_PROVIDERS[provider]; + const displayName = providerInfo?.name || provider; + return ( +
+ + + {displayName} + +
+ ); + })} +
-
- ); - })} -
+ )} +
+ ); + })() )} diff --git a/src/app/(dashboard)/dashboard/settings/page.js b/src/app/(dashboard)/dashboard/settings/page.js index a06a001408..cf63a13834 100644 --- a/src/app/(dashboard)/dashboard/settings/page.js +++ b/src/app/(dashboard)/dashboard/settings/page.js @@ -11,7 +11,6 @@ import ProxyTab from "./components/ProxyTab"; import AppearanceTab from "./components/AppearanceTab"; import ThinkingBudgetTab from "./components/ThinkingBudgetTab"; import SystemPromptTab from "./components/SystemPromptTab"; -import PricingTab from "./components/PricingTab"; import ComplianceTab from "./components/ComplianceTab"; import CacheStatsCard from "./components/CacheStatsCard"; import ResilienceTab from "./components/ResilienceTab"; @@ -22,7 +21,6 @@ const tabs = [ { id: "security", label: "Security", icon: "shield" }, { id: "routing", label: "Routing", icon: "route" }, { id: "resilience", label: "Resilience", icon: "electrical_services" }, - { id: "pricing", label: "Pricing", icon: "payments" }, { id: "advanced", label: "Advanced", icon: "tune" }, { id: "compliance", label: "Compliance", icon: "policy" }, ]; @@ -88,8 +86,6 @@ export default function SettingsPage() {
)} - {activeTab === "pricing" && } - {activeTab === "resilience" && } {activeTab === "advanced" && ( diff --git a/src/app/(dashboard)/dashboard/usage/page.js b/src/app/(dashboard)/dashboard/usage/page.js index 68760f5887..b8d74006bc 100644 --- a/src/app/(dashboard)/dashboard/usage/page.js +++ b/src/app/(dashboard)/dashboard/usage/page.js @@ -5,19 +5,17 @@ import { RequestLoggerV2, ProxyLogger, CardSkeleton, SegmentedControl } from "@/ import ProviderLimits from "./components/ProviderLimits"; import SessionsTab from "./components/SessionsTab"; import RateLimitStatus from "./components/RateLimitStatus"; -import BudgetTab from "./components/BudgetTab"; export default function UsagePage() { - const [activeTab, setActiveTab] = useState("limits"); + const [activeTab, setActiveTab] = useState("logs"); return (
} {activeTab === "proxy-logs" && } - {activeTab === "budget" && }
); } diff --git a/src/app/api/monitoring/health/route.js b/src/app/api/monitoring/health/route.js index 2d0263914f..94cf9043b2 100644 --- a/src/app/api/monitoring/health/route.js +++ b/src/app/api/monitoring/health/route.js @@ -31,14 +31,15 @@ export async function GET() { platform: process.platform, }; - // Provider health summary + // Provider health summary (circuitBreakers is an Array of { name, state, ... }) const providerHealth = {}; - for (const [key, cb] of Object.entries(circuitBreakers)) { - providerHealth[key] = { + for (const cb of circuitBreakers) { + // Skip test circuit breakers (leftover from unit tests) + if (cb.name.startsWith("test-") || cb.name.startsWith("test_")) continue; + providerHealth[cb.name] = { state: cb.state, - failures: cb.failures, - lastFailure: cb.lastFailure, - nextRetry: cb.nextRetry, + failures: cb.failureCount || 0, + lastFailure: cb.lastFailureTime, }; } diff --git a/src/shared/components/Sidebar.js b/src/shared/components/Sidebar.js index 7d299312bd..0aecf12ee2 100644 --- a/src/shared/components/Sidebar.js +++ b/src/shared/components/Sidebar.js @@ -17,6 +17,7 @@ const navItems = [ { href: "/dashboard/providers", label: "Providers", icon: "dns" }, { href: "/dashboard/combos", label: "Combos", icon: "layers" }, { href: "/dashboard/usage", label: "Usage", icon: "bar_chart" }, + { href: "/dashboard/costs", label: "Costs", icon: "account_balance_wallet" }, { href: "/dashboard/analytics", label: "Analytics", icon: "analytics" }, { href: "/dashboard/health", label: "Health", icon: "health_and_safety" }, { href: "/dashboard/cli-tools", label: "CLI Tools", icon: "terminal" },