diff --git a/src/app/(dashboard)/dashboard/combos/page.js b/src/app/(dashboard)/dashboard/combos/page.js index 3b25f25b75..0846dafbe8 100644 --- a/src/app/(dashboard)/dashboard/combos/page.js +++ b/src/app/(dashboard)/dashboard/combos/page.js @@ -9,6 +9,7 @@ import { CardSkeleton, ModelSelectModal, ProxyConfigModal, + EmptyState, } from "@/shared/components"; import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard"; @@ -189,20 +190,13 @@ export default function CombosPage() { {/* Combos List */} {combos.length === 0 ? ( - -
-
- layers -
-

No combos yet

-

- Create model combos with weighted routing and fallback support -

- -
-
+ setShowCreateModal(true)} + /> ) : (
{combos.map((combo) => ( diff --git a/src/app/(dashboard)/dashboard/settings/components/CacheStatsCard.js b/src/app/(dashboard)/dashboard/settings/components/CacheStatsCard.js new file mode 100644 index 0000000000..f6d9eaea8f --- /dev/null +++ b/src/app/(dashboard)/dashboard/settings/components/CacheStatsCard.js @@ -0,0 +1,71 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Card } from "@/shared/components"; + +export default function CacheStatsCard() { + const [cache, setCache] = useState(null); + const [flushing, setFlushing] = useState(false); + + const fetchStats = () => { + fetch("/api/cache/stats") + .then((r) => r.json()) + .then(setCache) + .catch(() => {}); + }; + + useEffect(fetchStats, []); + + const handleFlush = async () => { + setFlushing(true); + try { + await fetch("/api/cache/stats", { method: "DELETE" }); + fetchStats(); + } finally { + setFlushing(false); + } + }; + + return ( + +
+

+ cached + Prompt Cache +

+ +
+ + {cache ? ( +
+
+

Size

+

+ {cache.size}/{cache.maxSize} +

+
+
+

Hit Rate

+

{cache.hitRate?.toFixed(1) ?? 0}%

+
+
+

Hits

+

{cache.hits ?? 0}

+
+
+

Evictions

+

{cache.evictions ?? 0}

+
+
+ ) : ( +

Loading cache stats…

+ )} +
+ ); +} diff --git a/src/app/(dashboard)/dashboard/settings/components/ComplianceTab.js b/src/app/(dashboard)/dashboard/settings/components/ComplianceTab.js new file mode 100644 index 0000000000..e47fc5166c --- /dev/null +++ b/src/app/(dashboard)/dashboard/settings/components/ComplianceTab.js @@ -0,0 +1,83 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Card, DataTable } from "@/shared/components"; + +export default function ComplianceTab() { + const [logs, setLogs] = useState([]); + const [loading, setLoading] = useState(true); + const [filter, setFilter] = useState(""); + + useEffect(() => { + fetch("/api/compliance/audit-log?limit=100") + .then((r) => r.json()) + .then((data) => { + setLogs(Array.isArray(data) ? data : data.logs || []); + setLoading(false); + }) + .catch(() => setLoading(false)); + }, []); + + const filtered = filter + ? logs.filter( + (l) => + l.action?.toLowerCase().includes(filter.toLowerCase()) || + l.actor?.toLowerCase().includes(filter.toLowerCase()) + ) + : logs; + + return ( + +
+

+ policy + Audit Log +

+ setFilter(e.target.value)} + className="px-3 py-1.5 text-sm rounded-lg bg-black/5 dark:bg-white/5 border border-border text-text-main placeholder:text-text-muted focus:outline-none focus:ring-1 focus:ring-accent" + /> +
+ + {loading ? ( +

Loading audit log…

+ ) : filtered.length === 0 ? ( +

No audit events found.

+ ) : ( +
+ + + + + + + + + + + {filtered.map((log, i) => ( + + + + + + + ))} + +
TimeActionActorDetails
+ {log.timestamp ? new Date(log.timestamp).toLocaleString() : "—"} + + + {log.action || "—"} + + {log.actor || "system"} + {log.details ? JSON.stringify(log.details) : "—"} +
+
+ )} +
+ ); +} diff --git a/src/app/(dashboard)/dashboard/settings/page.js b/src/app/(dashboard)/dashboard/settings/page.js index fcb37e40b4..91e1b25968 100644 --- a/src/app/(dashboard)/dashboard/settings/page.js +++ b/src/app/(dashboard)/dashboard/settings/page.js @@ -12,6 +12,8 @@ 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"; const tabs = [ { id: "general", label: "General", icon: "settings" }, @@ -20,6 +22,7 @@ const tabs = [ { id: "routing", label: "Routing", icon: "route" }, { id: "pricing", label: "Pricing", icon: "payments" }, { id: "advanced", label: "Advanced", icon: "tune" }, + { id: "compliance", label: "Compliance", icon: "policy" }, ]; export default function SettingsPage() { @@ -85,7 +88,14 @@ export default function SettingsPage() { {activeTab === "pricing" && } - {activeTab === "advanced" && } + {activeTab === "advanced" && ( +
+ + +
+ )} + + {activeTab === "compliance" && }
{/* App Info */} diff --git a/src/app/(dashboard)/dashboard/usage/components/BudgetTelemetryCards.js b/src/app/(dashboard)/dashboard/usage/components/BudgetTelemetryCards.js new file mode 100644 index 0000000000..e3fc300d3a --- /dev/null +++ b/src/app/(dashboard)/dashboard/usage/components/BudgetTelemetryCards.js @@ -0,0 +1,115 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Card } from "@/shared/components"; + +export default function BudgetTelemetryCards() { + const [telemetry, setTelemetry] = useState(null); + const [cache, setCache] = useState(null); + const [policies, setPolicies] = useState(null); + + useEffect(() => { + Promise.allSettled([ + fetch("/api/telemetry/summary").then((r) => r.json()), + fetch("/api/cache/stats").then((r) => r.json()), + fetch("/api/policies").then((r) => r.json()), + ]).then(([t, c, p]) => { + if (t.status === "fulfilled") setTelemetry(t.value); + if (c.status === "fulfilled") setCache(c.value); + if (p.status === "fulfilled") setPolicies(p.value); + }); + }, []); + + const fmt = (ms) => (ms != null ? `${Math.round(ms)}ms` : "—"); + + return ( +
+ {/* Latency Card */} + +

+ speed + Latency +

+ {telemetry ? ( +
+
+ p50 + {fmt(telemetry.p50)} +
+
+ p95 + {fmt(telemetry.p95)} +
+
+ p99 + {fmt(telemetry.p99)} +
+
+ Total requests + {telemetry.totalRequests ?? 0} +
+
+ ) : ( +

No data yet

+ )} +
+ + {/* Cache Card */} + +

+ cached + Prompt Cache +

+ {cache ? ( +
+
+ Entries + + {cache.size}/{cache.maxSize} + +
+
+ Hit Rate + {cache.hitRate?.toFixed(1) ?? 0}% +
+
+ Hits / Misses + + {cache.hits ?? 0} / {cache.misses ?? 0} + +
+
+ ) : ( +

No data yet

+ )} +
+ + {/* System Health Card */} + +

+ monitor_heart + System Health +

+ {policies ? ( +
+
+ Circuit Breakers + {policies.circuitBreakers?.length ?? 0} active +
+
+ Locked IPs + {policies.lockedIdentifiers?.length ?? 0} +
+ {policies.circuitBreakers?.some((cb) => cb.state === "OPEN") && ( +
+ âš  Open circuit breakers detected +
+ )} +
+ ) : ( +

No data yet

+ )} +
+
+ ); +} diff --git a/src/app/(dashboard)/dashboard/usage/page.js b/src/app/(dashboard)/dashboard/usage/page.js index f9d78dca44..d693f306da 100644 --- a/src/app/(dashboard)/dashboard/usage/page.js +++ b/src/app/(dashboard)/dashboard/usage/page.js @@ -12,6 +12,8 @@ import ProviderLimits from "./components/ProviderLimits"; import SessionsTab from "./components/SessionsTab"; import RateLimitStatus from "./components/RateLimitStatus"; +import BudgetTelemetryCards from "./components/BudgetTelemetryCards"; + export default function UsagePage() { const [activeTab, setActiveTab] = useState("overview"); @@ -33,6 +35,7 @@ export default function UsagePage() { {activeTab === "overview" && ( }> + )} {activeTab === "logs" && }