feat(proxy): implement metrics persistence, traffic logs DB, and advanced monitoring

- Add SQLite schema for proxy metrics and traffic logs
- Implement ProxyMetricsRepo with historical aggregates
- Add TrafficLogsRepo for DB-backed traffic log queries
- Create proxy-advanced IPC handlers (metrics, traffic logs, circuit breaker, parity)
- Add ProxyMetricsService with connection snapshots and cleanup
- Add ProxyReplayService and ProxyIdeConfigService
- Implement MonitorTab and ToolsTab for advanced proxy features
- Add TrafficLogTable with sorting and filtering
- Fix i18n keys for proxy metrics
- Fix generateIdeConfig type mapping
- Update proxy routes with advanced tab integration
This commit is contained in:
Gemini Nexus Bot committed 2026-04-23 00:57:01 -03:00
1 parent c73537d44c
commit 0be7d7fea2
36 files changed
+4551 -882

No files matched your search

+623 -415
View File
File diff suppressed because it is too large. Load diff
+144 -116
View File
@@ -2,19 +2,8 @@ import { useMemo, useState } from 'react';
import { createFileRoute } from '@tanstack/react-router';
import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import {
BarChart,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
PieChart,
Pie,
Cell,
Legend,
} from 'recharts';
import { ResponsiveBar } from '@nivo/bar';
import { ResponsivePie } from '@nivo/pie';
import {
Select,
SelectContent,
@@ -27,7 +16,15 @@ import { ipc } from '@/ipc/manager';
import { useCloudAccounts } from '@/hooks/useCloudAccounts';
import { StatCard } from '@/components/usage/StatCard';
import { ChartCard } from '@/components/usage/ChartCard';
import { CustomTooltip } from '@/components/usage/CustomTooltip';
import {
COLOR_PROMPT,
COLOR_COMPLETION,
COLOR_TOTAL,
PIE_COLORS,
BarTooltip,
PieTooltip,
} from '@/components/usage/NivoTooltip';
import { nivoTheme } from '@/lib/nivo-theme';
import {
Activity,
ArrowUpRight,
@@ -38,23 +35,6 @@ import {
Zap,
} from 'lucide-react';
/* ------------------------------------------------------------------ */
/* Color tokens — muted, dark-background-friendly palette */
/* ------------------------------------------------------------------ */
const COLOR_PROMPT = '#5b9bd5';
const COLOR_COMPLETION = '#70ad47';
const COLOR_TOTAL = '#a5a5a5';
const PIE_COLORS = [
'#5b9bd5',
'#ed7d31',
'#70ad47',
'#a5a5a5',
'#ffc000',
'#4472c4',
'#91d1c2',
'#c55a5a',
];
/* ------------------------------------------------------------------ */
/* Types */
/* ------------------------------------------------------------------ */
@@ -108,6 +88,13 @@ function formatBucketLabel(bucket: string, range: TimeRange): string {
return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
}
function barColor(bar: any): string {
if (bar.id === 'Prompt') return COLOR_PROMPT;
if (bar.id === 'Completion') return COLOR_COMPLETION;
if (bar.id === 'Total') return COLOR_TOTAL;
return '#a5a5a5';
}
/* ------------------------------------------------------------------ */
/* Main Component */
/* ------------------------------------------------------------------ */
@@ -201,7 +188,7 @@ function UsagePage() {
const pieData = useMemo(() => {
return (modelData || []).map((d) => ({
name: d.model,
id: d.model,
value: d.totalTokens,
}));
}, [modelData]);
@@ -215,6 +202,10 @@ function UsagePage() {
}));
}, [hourlyData]);
const hourlyTickValues = useMemo(() => {
return hourlyChartData.filter((_, i) => i % 3 === 0).map((d) => d.name);
}, [hourlyChartData]);
const isDailyEmpty = !dailyLoading && !dailyError && (!dailyData || dailyData.length === 0);
const isModelEmpty = !modelLoading && !modelError && (!modelData || modelData.length === 0);
const isHourlyEmpty = !hourlyLoading && !hourlyError && (!hourlyData || hourlyData.length === 0);
@@ -343,35 +334,52 @@ function UsagePage() {
errorMessage={t('usage.loadFailed')}
retryLabel={t('usage.retry')}
>
<ResponsiveContainer width="100%" height={280}>
<BarChart data={chartData} barCategoryGap="20%">
<CartesianGrid
strokeDasharray="3 3"
stroke="rgba(255,255,255,0.06)"
vertical={false}
/>
<XAxis
dataKey="name"
tick={{ fill: '#a1a1aa', fontSize: 11 }}
axisLine={{ stroke: 'rgba(255,255,255,0.1)' }}
tickLine={false}
/>
<YAxis
tick={{ fill: '#a1a1aa', fontSize: 11 }}
axisLine={false}
tickLine={false}
tickFormatter={(v: number) => (v >= 1000 ? `${(v / 1000).toFixed(1)}k` : `${v}`)}
/>
<Tooltip content={<CustomTooltip />} />
<Legend
wrapperStyle={{ fontSize: 12, paddingTop: 8 }}
iconType="circle"
iconSize={8}
/>
<Bar dataKey="Prompt" fill={COLOR_PROMPT} radius={[2, 2, 0, 0]} />
<Bar dataKey="Completion" fill={COLOR_COMPLETION} radius={[2, 2, 0, 0]} />
</BarChart>
</ResponsiveContainer>
<div className="h-[280px]">
<ResponsiveBar
data={chartData}
keys={['Prompt', 'Completion']}
indexBy="name"
groupMode="grouped"
theme={nivoTheme}
colors={barColor}
margin={{ top: 10, right: 10, bottom: 40, left: 50 }}
padding={0.2}
innerPadding={2}
borderRadius={2}
enableGridY={true}
enableGridX={false}
gridYValues={5}
axisBottom={{
tickSize: 0,
tickPadding: 5,
tickRotation: 0,
}}
axisLeft={{
tickSize: 0,
tickPadding: 5,
tickRotation: 0,
format: (v: number) => (v >= 1000 ? `${(v / 1000).toFixed(1)}k` : `${v}`),
}}
tooltip={BarTooltip}
legends={[
{
dataFrom: 'keys',
anchor: 'bottom',
direction: 'row',
justify: false,
translateX: 0,
translateY: 30,
itemsSpacing: 16,
itemWidth: 80,
itemHeight: 16,
itemDirection: 'left-to-right',
symbolSize: 8,
symbolShape: 'circle',
},
]}
animate={true}
/>
</div>
</ChartCard>
{/* Model Usage Donut Chart */}
@@ -386,30 +394,34 @@ function UsagePage() {
errorMessage={t('usage.loadFailed')}
retryLabel={t('usage.retry')}
>
<ResponsiveContainer width="100%" height={280}>
<PieChart>
<Pie
data={pieData}
cx="50%"
cy="50%"
innerRadius={70}
outerRadius={100}
paddingAngle={3}
dataKey="value"
stroke="none"
>
{pieData.map((_, index) => (
<Cell key={`cell-${index}`} fill={PIE_COLORS[index % PIE_COLORS.length]} />
))}
</Pie>
<Tooltip content={<CustomTooltip />} />
<Legend
wrapperStyle={{ fontSize: 12, paddingTop: 8 }}
iconType="circle"
iconSize={8}
/>
</PieChart>
</ResponsiveContainer>
<div className="h-[280px]">
<ResponsivePie
data={pieData}
innerRadius={0.55}
padAngle={2}
colors={PIE_COLORS}
borderWidth={0}
theme={nivoTheme}
margin={{ top: 10, right: 10, bottom: 40, left: 10 }}
tooltip={PieTooltip}
legends={[
{
anchor: 'bottom',
direction: 'row',
justify: false,
translateX: 0,
translateY: 30,
itemsSpacing: 16,
itemWidth: 80,
itemHeight: 16,
itemDirection: 'left-to-right',
symbolSize: 8,
symbolShape: 'circle',
},
]}
animate={true}
/>
</div>
</ChartCard>
</div>
@@ -426,37 +438,53 @@ function UsagePage() {
errorMessage={t('usage.loadFailed')}
retryLabel={t('usage.retry')}
>
<ResponsiveContainer width="100%" height={280}>
<BarChart data={hourlyChartData} barCategoryGap="20%">
<CartesianGrid
strokeDasharray="3 3"
stroke="rgba(255,255,255,0.06)"
vertical={false}
/>
<XAxis
dataKey="name"
tick={{ fill: '#a1a1aa', fontSize: 11 }}
axisLine={{ stroke: 'rgba(255,255,255,0.1)' }}
tickLine={false}
interval={2}
/>
<YAxis
tick={{ fill: '#a1a1aa', fontSize: 11 }}
axisLine={false}
tickLine={false}
tickFormatter={(v: number) => (v >= 1000 ? `${(v / 1000).toFixed(1)}k` : `${v}`)}
/>
<Tooltip content={<CustomTooltip />} />
<Legend
wrapperStyle={{ fontSize: 12, paddingTop: 8 }}
iconType="circle"
iconSize={8}
/>
<Bar dataKey="Prompt" fill={COLOR_PROMPT} radius={[2, 2, 0, 0]} />
<Bar dataKey="Completion" fill={COLOR_COMPLETION} radius={[2, 2, 0, 0]} />
<Bar dataKey="Total" fill={COLOR_TOTAL} radius={[2, 2, 0, 0]} />
</BarChart>
</ResponsiveContainer>
<div className="h-[280px]">
<ResponsiveBar
data={hourlyChartData}
keys={['Prompt', 'Completion', 'Total']}
indexBy="name"
groupMode="grouped"
theme={nivoTheme}
colors={barColor}
margin={{ top: 10, right: 10, bottom: 40, left: 50 }}
padding={0.2}
innerPadding={2}
borderRadius={2}
enableGridY={true}
enableGridX={false}
gridYValues={5}
axisBottom={{
tickSize: 0,
tickPadding: 5,
tickRotation: 0,
tickValues: hourlyTickValues,
}}
axisLeft={{
tickSize: 0,
tickPadding: 5,
tickRotation: 0,
format: (v: number) => (v >= 1000 ? `${(v / 1000).toFixed(1)}k` : `${v}`),
}}
tooltip={BarTooltip}
legends={[
{
dataFrom: 'keys',
anchor: 'bottom',
direction: 'row',
justify: false,
translateX: 0,
translateY: 30,
itemsSpacing: 16,
itemWidth: 80,
itemHeight: 16,
itemDirection: 'left-to-right',
symbolSize: 8,
symbolShape: 'circle',
},
]}
animate={true}
/>
</div>
</ChartCard>
)}
</div>