Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/control-plane-ui/public/locales/en/pages.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@
"sectionLatencyHealthSubtitle": "Our overhead versus the upstream provider — by the busiest provider in the window.",
"latencyOurOverheadP95": "Our Overhead P95",
"latencyUpstreamTotalP95": "Upstream P95",
"latencyEndToEndP95": "End-to-End P95",
"source_all": "All",
"source_vk": "Virtual keys",
"source_proxy": "Proxy",
"source_agent": "Agent",
"sourcePickerLabel": "Traffic source",
"complianceWarningTitle": "No compliance hooks are active",
"complianceWarningBody": "No traffic in the selected window was covered by compliance hooks. Enable a hook under Compliance → Hooks & Policies to inspect and govern this traffic.",
"latencySlowestProvider": "Slowest Upstream Provider"
},
"providers": {
Expand Down
8 changes: 8 additions & 0 deletions packages/control-plane-ui/public/locales/es/pages.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@
"sectionLatencyHealthSubtitle": "Nuestro overhead frente al proveedor upstream — según el proveedor con más tráfico en la ventana.",
"latencyOurOverheadP95": "Nuestro overhead P95",
"latencyUpstreamTotalP95": "Upstream P95",
"latencyEndToEndP95": "Latencia total P95",
"source_all": "Todo",
"source_vk": "Claves virtuales",
"source_proxy": "Proxy",
"source_agent": "Agente",
"sourcePickerLabel": "Fuente de tráfico",
"complianceWarningTitle": "No hay hooks de cumplimiento activos",
"complianceWarningBody": "Ningún tráfico en el período seleccionado fue cubierto por hooks de cumplimiento. Active un hook en Cumplimiento → Hooks y políticas para inspeccionar y gobernar este tráfico.",
"latencySlowestProvider": "Proveedor upstream más lento"
},
"providers": {
Expand Down
8 changes: 8 additions & 0 deletions packages/control-plane-ui/public/locales/zh/pages.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@
"sectionLatencyHealthSubtitle": "对比我方开销与上游提供商耗时 — 取该时间窗口内流量最大的提供商。",
"latencyOurOverheadP95": "我方开销 P95",
"latencyUpstreamTotalP95": "上游 P95",
"latencyEndToEndP95": "端到端 P95",
"source_all": "全部",
"source_vk": "虚拟密钥",
"source_proxy": "代理",
"source_agent": "代理端",
"sourcePickerLabel": "流量来源",
"complianceWarningTitle": "未启用合规钩子",
"complianceWarningBody": "所选时间窗口内没有流量经过合规钩子处理。请在“合规 → 钩子与策略”中启用钩子,以检查和治理此流量。",
"latencySlowestProvider": "最慢的上游提供商"
},
"providers": {
Expand Down
8 changes: 8 additions & 0 deletions packages/control-plane-ui/src/i18n/locales/en/pages.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@
"sectionLatencyHealthSubtitle": "Our overhead versus the upstream provider — by the busiest provider in the window.",
"latencyOurOverheadP95": "Our Overhead P95",
"latencyUpstreamTotalP95": "Upstream P95",
"latencyEndToEndP95": "End-to-End P95",
"source_all": "All",
"source_vk": "Virtual keys",
"source_proxy": "Proxy",
"source_agent": "Agent",
"sourcePickerLabel": "Traffic source",
"complianceWarningTitle": "No compliance hooks are active",
"complianceWarningBody": "No traffic in the selected window was covered by compliance hooks. Enable a hook under Compliance → Hooks & Policies to inspect and govern this traffic.",
"latencySlowestProvider": "Slowest Upstream Provider"
},
"providers": {
Expand Down
8 changes: 8 additions & 0 deletions packages/control-plane-ui/src/i18n/locales/es/pages.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@
"sectionLatencyHealthSubtitle": "Nuestro overhead frente al proveedor upstream — según el proveedor con más tráfico en la ventana.",
"latencyOurOverheadP95": "Nuestro overhead P95",
"latencyUpstreamTotalP95": "Upstream P95",
"latencyEndToEndP95": "Latencia total P95",
"source_all": "Todo",
"source_vk": "Claves virtuales",
"source_proxy": "Proxy",
"source_agent": "Agente",
"sourcePickerLabel": "Fuente de tráfico",
"complianceWarningTitle": "No hay hooks de cumplimiento activos",
"complianceWarningBody": "Ningún tráfico en el período seleccionado fue cubierto por hooks de cumplimiento. Active un hook en Cumplimiento → Hooks y políticas para inspeccionar y gobernar este tráfico.",
"latencySlowestProvider": "Proveedor upstream más lento"
},
"providers": {
Expand Down
8 changes: 8 additions & 0 deletions packages/control-plane-ui/src/i18n/locales/zh/pages.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@
"sectionLatencyHealthSubtitle": "对比我方开销与上游提供商耗时 — 取该时间窗口内流量最大的提供商。",
"latencyOurOverheadP95": "我方开销 P95",
"latencyUpstreamTotalP95": "上游 P95",
"latencyEndToEndP95": "端到端 P95",
"source_all": "全部",
"source_vk": "虚拟密钥",
"source_proxy": "代理",
"source_agent": "代理端",
"sourcePickerLabel": "流量来源",
"complianceWarningTitle": "未启用合规钩子",
"complianceWarningBody": "所选时间窗口内没有流量经过合规钩子处理。请在“合规 → 钩子与策略”中启用钩子,以检查和治理此流量。",
"latencySlowestProvider": "最慢的上游提供商"
},
"providers": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,51 @@
box-shadow: none;
}

/* Hero controls row — source picker + time-window picker, wraps on narrow. */
.heroControls {
display: flex;
gap: var(--g-space-2);
align-items: center;
flex-wrap: wrap;
justify-content: flex-end;
}

/* Compliance-coverage warning banner — shown when no traffic in the window
* was covered by compliance hooks. */
.complianceWarning {
display: flex;
align-items: center;
gap: var(--g-space-3);
padding: var(--g-space-3) var(--g-space-4);
border: 1px solid color-mix(in srgb, var(--color-warning) 40%, transparent);
background: color-mix(in srgb, var(--color-warning) 12%, transparent);
border-radius: var(--g-radius-md);
}

.complianceWarningIcon {
flex-shrink: 0;
font-size: var(--g-font-size-lg);
color: var(--color-warning);
line-height: 1;
}

.complianceWarningText {
display: flex;
flex-direction: column;
gap: 2px;
}

.complianceWarningTitle {
font-weight: var(--g-font-weight-semibold);
color: var(--color-text);
font-size: var(--g-font-size-sm);
}

.complianceWarningBody {
color: var(--color-text-muted);
font-size: var(--g-font-size-xs);
}

/* ── Time window picker (Dashboard PageHeader action) ───────────────────── */

.windowPicker {
Expand Down
39 changes: 32 additions & 7 deletions packages/control-plane-ui/src/pages/dashboard/DashboardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { analyticsApi, providerApi } from '@/api/services';
import type { CacheROISummary } from '@/api/services/overview/analytics';
import { proxyApi } from '../../api/services/infrastructure/misc/proxy';
import styles from './DashboardPage.module.css';
import { WINDOW_MS, type TimeWindow } from './dashboardShared';
import { WINDOW_MS, type TimeWindow, type TrafficSource, sourceToRollupParam, sourceToLatencyParam } from './dashboardShared';
import { HeroSection } from './HeroSection';
import { HealthCards } from './HealthCards';
import { LatencyHealth } from './LatencyHealth';
Expand All @@ -26,22 +26,30 @@ export function DashboardPage() {
/* ── Time window state ──────────────────────────────────────────────── */

const [timeWindow, setTimeWindow] = useState<TimeWindow>('30d');
const [source, setSource] = useState<TrafficSource>('all');

const { startTime, endTime } = useMemo(() => {
const end = new Date();
const start = new Date(end.getTime() - WINDOW_MS[timeWindow]);
return { startTime: start.toISOString(), endTime: end.toISOString() };
}, [timeWindow]);

// The source selector maps to two different backend vocabularies: the
// rollup endpoints (summary, by-provider) take vk|proxy|agent; latency-phases
// takes ai-gateway|compliance-proxy|agent. Sparkline/cacheROI have no source
// dimension, so trend charts stay cross-source (noted in the UI copy).
const rollupSourceParam = sourceToRollupParam(source);
const latencySource = sourceToLatencyParam(source);

/* ── Data fetching ──────────────────────────────────────────────────── */

const { data: summary, loading, error, refetch } = useApi<AnalyticsSummary>(
() => analyticsApi.summary({ startTime, endTime }),
['admin', 'analytics', 'summary', timeWindow],
() => analyticsApi.summary({ startTime, endTime, ...rollupSourceParam }),
['admin', 'analytics', 'summary', timeWindow, source],
);
const { data: providers } = useApi<{ data: ProviderBreakdown[] }>(
() => analyticsApi.byProvider({ startTime, endTime }),
['admin', 'analytics', 'by-provider', timeWindow],
() => analyticsApi.byProvider({ startTime, endTime, ...rollupSourceParam }),
['admin', 'analytics', 'by-provider', timeWindow, source],
);
const { data: providerList } = useApi<{ data: Provider[] }>(
() => providerApi.list({ ...ADMIN_LIST_FULL_PAGE_PARAMS }),
Expand All @@ -60,8 +68,8 @@ export function DashboardPage() {
// Aggregating by `provider` lets us surface the slowest provider in a
// standalone callout without a second round-trip.
const { data: latencyPhases } = useApi(
() => analyticsApi.latencyPhases({ groupBy: 'provider', start: startTime, end: endTime }),
['admin', 'analytics', 'latency-phases', 'dashboard', timeWindow],
() => analyticsApi.latencyPhases({ groupBy: 'provider', start: startTime, end: endTime, source: latencySource }),
['admin', 'analytics', 'latency-phases', 'dashboard', timeWindow, source],
);

/* ── Compliance Proxy ───────────────────────────────────────────────── */
Expand Down Expand Up @@ -173,13 +181,20 @@ export function DashboardPage() {

const windowLabel = t(`pages:dashboard.win${timeWindow}` as never);

// Surface a warning when the proxy is reachable but nothing in the window
// was covered by compliance hooks — a 0% figure otherwise reads as a metric,
// not the governance gap it actually is.
const showComplianceWarning = proxyReachable === true && proxyTotalRequests > 0 && proxyCoveragePercent === 0;

/* ── Render ─────────────────────────────────────────────────────────── */

return (
<Stack gap="lg">
<HeroSection
timeWindow={timeWindow}
setTimeWindow={setTimeWindow}
source={source}
setSource={setSource}
animRequests={animRequests}
vkRequests={vkRequests}
proxyTotalRequests={proxyTotalRequests}
Expand All @@ -190,6 +205,16 @@ export function DashboardPage() {
windowLabel={windowLabel}
/>

{showComplianceWarning && (
<div className={styles.complianceWarning} role="alert">
<span className={styles.complianceWarningIcon} aria-hidden="true">⚠</span>
<span className={styles.complianceWarningText}>
<span className={styles.complianceWarningTitle}>{t('pages:dashboard.complianceWarningTitle')}</span>
<span className={styles.complianceWarningBody}>{t('pages:dashboard.complianceWarningBody')}</span>
</span>
</div>
)}

<HealthCards
sparkData={sparkData}
animRequests={animRequests}
Expand Down
42 changes: 30 additions & 12 deletions packages/control-plane-ui/src/pages/dashboard/HeroSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import clsx from 'clsx';
import { AnimatedNumber } from '@/components/ui';
import { formatTokens, formatUsd } from '@/lib/format';
import styles from './DashboardPage.module.css';
import { WINDOW_OPTIONS, type TimeWindow } from './dashboardShared';
import { WINDOW_OPTIONS, SOURCE_OPTIONS, type TimeWindow, type TrafficSource } from './dashboardShared';

interface HeroSectionProps {
timeWindow: TimeWindow;
setTimeWindow: (w: TimeWindow) => void;
source: TrafficSource;
setSource: (s: TrafficSource) => void;
animRequests: number;
vkRequests: number;
proxyTotalRequests: number;
Expand All @@ -21,6 +23,8 @@ interface HeroSectionProps {
export function HeroSection({
timeWindow,
setTimeWindow,
source,
setSource,
animRequests,
vkRequests,
proxyTotalRequests,
Expand All @@ -46,17 +50,31 @@ export function HeroSection({
<h1 className={styles.heroTitle}>{t('pages:dashboard.title')}</h1>
<p className={styles.heroSubtitle}>{t('pages:dashboard.heroSubtitle')}</p>
</div>
<div className={styles.windowPicker}>
{WINDOW_OPTIONS.map((w) => (
<button
key={w}
type="button"
className={clsx(styles.windowBtn, w === timeWindow && styles.windowBtnActive)}
onClick={() => setTimeWindow(w)}
>
{t(`pages:dashboard.win${w}` as never)}
</button>
))}
<div className={styles.heroControls}>
<div className={styles.windowPicker} role="group" aria-label={t('pages:dashboard.sourcePickerLabel', 'Traffic source')}>
{SOURCE_OPTIONS.map((s) => (
<button
key={s}
type="button"
className={clsx(styles.windowBtn, s === source && styles.windowBtnActive)}
onClick={() => setSource(s)}
>
{t(`pages:dashboard.source_${s}` as never)}
</button>
))}
</div>
<div className={styles.windowPicker}>
{WINDOW_OPTIONS.map((w) => (
<button
key={w}
type="button"
className={clsx(styles.windowBtn, w === timeWindow && styles.windowBtnActive)}
onClick={() => setTimeWindow(w)}
>
{t(`pages:dashboard.win${w}` as never)}
</button>
))}
</div>
</div>
</div>
<div className={styles.heroStats}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function LatencyHealth({ latencyPhases, navigate }: LatencyHealthProps) {
{t('pages:dashboard.sectionLatencyHealthSubtitle', 'Our overhead versus the upstream provider — by the busiest provider in the window.')}
</p>
</div>
<Grid columns={3} gap="md">
<Grid columns={4} gap="md">
<Card padding="lg" className={styles.metricCard}>
<div className={styles.metricLabelRow}>{t('pages:dashboard.latencyOurOverheadP95', 'Our Overhead P95')}</div>
<div className={styles.metricValueRow}>
Expand All @@ -45,6 +45,13 @@ export function LatencyHealth({ latencyPhases, navigate }: LatencyHealthProps) {
</div>
<span className={styles.metricSubtitle}>{top.groupLabel}</span>
</Card>
<Card padding="lg" className={styles.metricCard}>
<div className={styles.metricLabelRow}>{t('pages:dashboard.latencyEndToEndP95', 'End-to-End P95')}</div>
<div className={styles.metricValueRow}>
<span className={styles.metricValue}>{fmtMs(top.totalP95Ms)}</span>
</div>
<span className={styles.metricSubtitle}>{top.groupLabel}</span>
</Card>
<Card padding="lg" className={clsx(styles.metricCard, styles.clickable)} onClick={() => navigate('/analytics')}>
<div className={styles.metricLabelRow}>{t('pages:dashboard.latencySlowestProvider', 'Slowest Upstream Provider')}</div>
<div className={styles.metricValueRow}>
Expand Down
27 changes: 27 additions & 0 deletions packages/control-plane-ui/src/pages/dashboard/dashboardShared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,33 @@ export const WINDOW_MS: Record<TimeWindow, number> = {

export const WINDOW_OPTIONS: TimeWindow[] = ['1h', '1d', '7d', '30d'];

/* ── Traffic source ─────────────────────────────────────────────────────── */

// The live traffic sources the analytics endpoints can filter by. NOT a
// benchmark/environment dimension — synthetic benchmark runs live on the
// separate Benchmarks surface, and there is no environment column in the
// analytics rollups, so neither is offered here (would be a dead control).
export type TrafficSource = 'all' | 'vk' | 'proxy' | 'agent';

export const SOURCE_OPTIONS: TrafficSource[] = ['all', 'vk', 'proxy', 'agent'];

// Rollup endpoints (summary, by-provider) accept ?source=vk|proxy|agent
// (empty = all) per sourceSubDimension() in analytics_rollup.go.
export function sourceToRollupParam(s: TrafficSource): Record<string, string> {
return s === 'all' ? {} : { source: s };
}

// latency-phases accepts ?source=ai-gateway|compliance-proxy|agent|all per
// analytics_latency.go — a different vocabulary from the rollup endpoints.
export function sourceToLatencyParam(s: TrafficSource): 'all' | 'ai-gateway' | 'compliance-proxy' | 'agent' {
switch (s) {
case 'vk': return 'ai-gateway';
case 'proxy': return 'compliance-proxy';
case 'agent': return 'agent';
default: return 'all';
}
}

/* ── Info icon ──────────────────────────────────────────────────────────── */

export function InfoIcon({ description }: { description: string }) {
Expand Down
Loading