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:
1 parent
c73537d44c
commit
0be7d7fea2
36 files changed
+4551
-882
No files matched your search
@@ -0,0 +1,172 @@
|
||||
import { z } from 'zod';
|
||||
import { os } from '@orpc/server';
|
||||
import {
|
||||
getRecentTrafficLogs,
|
||||
getTrafficLogStats,
|
||||
exportTrafficLogs,
|
||||
getParityCounters,
|
||||
getCircuitBreakerStatus,
|
||||
getProxyMetrics,
|
||||
getRecentRequests,
|
||||
generateIdeConfig,
|
||||
} from './handler';
|
||||
|
||||
const TrafficLogEntrySchema = z.object({
|
||||
timestamp: z.string(),
|
||||
direction: z.string(),
|
||||
requestId: z.string(),
|
||||
endpoint: z.string(),
|
||||
method: z.string().optional(),
|
||||
status: z.number().optional(),
|
||||
headers: z.record(z.string(), z.unknown()).optional(),
|
||||
body: z.unknown().optional(),
|
||||
chunk: z.unknown().optional(),
|
||||
durationMs: z.number().optional(),
|
||||
error: z
|
||||
.object({
|
||||
message: z.string(),
|
||||
stack: z.string().optional(),
|
||||
status: z.number().optional(),
|
||||
body: z.unknown().optional(),
|
||||
})
|
||||
.optional(),
|
||||
metadata: z.record(z.string(), z.unknown()).optional(),
|
||||
});
|
||||
|
||||
const TrafficLogsResponseSchema = z.object({
|
||||
success: z.boolean(),
|
||||
data: z.array(TrafficLogEntrySchema).optional(),
|
||||
error: z.string().optional(),
|
||||
});
|
||||
|
||||
const TrafficLogStatsResponseSchema = z.object({
|
||||
success: z.boolean(),
|
||||
data: z
|
||||
.object({
|
||||
byDirection: z.record(z.string(), z.number()),
|
||||
byEndpoint: z.record(z.string(), z.number()),
|
||||
total: z.number(),
|
||||
})
|
||||
.optional(),
|
||||
error: z.string().optional(),
|
||||
});
|
||||
|
||||
const ExportTrafficLogsResponseSchema = z.object({
|
||||
success: z.boolean(),
|
||||
data: z.string().optional(),
|
||||
error: z.string().optional(),
|
||||
});
|
||||
|
||||
const ParityCountersResponseSchema = z.object({
|
||||
success: z.boolean(),
|
||||
data: z
|
||||
.object({
|
||||
totalRequests: z.number(),
|
||||
matchedRequests: z.number(),
|
||||
parityViolations: z.number(),
|
||||
lastUpdated: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
error: z.string().optional(),
|
||||
});
|
||||
|
||||
const CircuitBreakerStatusResponseSchema = z.object({
|
||||
success: z.boolean(),
|
||||
data: z
|
||||
.object({
|
||||
states: z.record(
|
||||
z.string(),
|
||||
z.object({
|
||||
state: z.string(),
|
||||
failures: z.number(),
|
||||
lastFailure: z.string().nullable(),
|
||||
}),
|
||||
),
|
||||
globalEnabled: z.boolean(),
|
||||
})
|
||||
.optional(),
|
||||
error: z.string().optional(),
|
||||
});
|
||||
|
||||
const ProxyMetricsResponseSchema = z.object({
|
||||
success: z.boolean(),
|
||||
data: z
|
||||
.object({
|
||||
uptimeSeconds: z.number(),
|
||||
totalRequests: z.number(),
|
||||
activeConnections: z.number(),
|
||||
avgLatency: z.number(),
|
||||
errorRate: z.number(),
|
||||
requestsPerMinute: z.number(),
|
||||
})
|
||||
.optional(),
|
||||
error: z.string().optional(),
|
||||
});
|
||||
|
||||
const RecentRequestsResponseSchema = z.object({
|
||||
success: z.boolean(),
|
||||
data: z.array(z.unknown()).optional(),
|
||||
error: z.string().optional(),
|
||||
});
|
||||
|
||||
const IdeConfigResponseSchema = z.object({
|
||||
success: z.boolean(),
|
||||
data: z
|
||||
.object({
|
||||
name: z.string(),
|
||||
settingsPath: z.string(),
|
||||
proxySetting: z.string(),
|
||||
apiKeySetting: z.string(),
|
||||
instructions: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
error: z.string().optional(),
|
||||
});
|
||||
|
||||
export const proxyAdvancedRouter = os.router({
|
||||
getRecentTrafficLogs: os
|
||||
.input(
|
||||
z.object({
|
||||
limit: z.number().int().min(1).max(1000).optional(),
|
||||
direction: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.output(TrafficLogsResponseSchema)
|
||||
.handler(async ({ input }) => {
|
||||
return getRecentTrafficLogs(input.limit, input.direction);
|
||||
}),
|
||||
|
||||
getTrafficLogStats: os.output(TrafficLogStatsResponseSchema).handler(async () => {
|
||||
return getTrafficLogStats();
|
||||
}),
|
||||
|
||||
exportTrafficLogs: os
|
||||
.input(z.object({ format: z.enum(['json', 'csv']) }))
|
||||
.output(ExportTrafficLogsResponseSchema)
|
||||
.handler(async ({ input }) => {
|
||||
return exportTrafficLogs(input.format);
|
||||
}),
|
||||
|
||||
getParityCounters: os.output(ParityCountersResponseSchema).handler(async () => {
|
||||
return getParityCounters();
|
||||
}),
|
||||
|
||||
getCircuitBreakerStatus: os.output(CircuitBreakerStatusResponseSchema).handler(async () => {
|
||||
return getCircuitBreakerStatus();
|
||||
}),
|
||||
|
||||
getProxyMetrics: os.output(ProxyMetricsResponseSchema).handler(async () => {
|
||||
return getProxyMetrics();
|
||||
}),
|
||||
|
||||
getRecentRequests: os.output(RecentRequestsResponseSchema).handler(async () => {
|
||||
return getRecentRequests();
|
||||
}),
|
||||
|
||||
generateIdeConfig: os
|
||||
.input(z.object({ ide: z.string() }))
|
||||
.output(IdeConfigResponseSchema)
|
||||
.handler(async ({ input }) => {
|
||||
return generateIdeConfig(input.ide);
|
||||
}),
|
||||
});
|
||||
Reference in new issue
Block a user