feat(platform): add source readiness operations view

This commit is contained in:
lingniu
2026-07-04 23:29:31 +08:00
parent bf106a9730
commit 44c7748958
9 changed files with 536 additions and 4 deletions

View File

@@ -287,6 +287,47 @@ test('onlineVehicleStatuses reads paged online vehicle status rows', async () =>
expect(result.items[0].offlineDurationMinutes).toBe(18);
});
test('sourceReadiness reads ops source readiness plan', async () => {
const fetchMock = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({
data: {
totalVehicles: 1033,
onlineVehicles: 208,
kafkaLag: 0,
activeConnections: 178,
redisOnlineKeys: 258,
platformRelease: 'platform-source-test',
sources: [{
protocol: 'GB32960',
role: '整车与氢能实时数据主来源',
online: 73,
total: 340,
onlineRate: 21.47,
missingVehicles: 693,
severity: 'warning',
status: '覆盖不足',
evidence: '在线 73/340',
action: '核对平台转发清单',
acceptance: '缺失来源车辆下降',
vehiclesHash: '#/vehicles?missingProtocol=GB32960',
realtimeHash: '#/realtime?protocol=GB32960',
historyHash: '#/history-query?protocol=GB32960&tab=raw',
alertHash: '#/alert-events?protocol=GB32960'
}]
},
traceId: 'trace-test',
timestamp: 1783094400000
})
} as Response);
const result = await api.sourceReadiness();
expect(fetchMock).toHaveBeenCalledWith('/api/ops/source-readiness', undefined);
expect(result.platformRelease).toBe('platform-source-test');
expect(result.sources[0].protocol).toBe('GB32960');
});
test('api errors include backend message, detail, and trace id', async () => {
vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: false,

View File

@@ -14,6 +14,7 @@ import type {
QualityIssueRow,
RawFrameRow,
RealtimeLocationRow,
SourceReadinessPlan,
VehicleRealtimeRow,
VehicleCoverageRow,
VehicleCoverageSummary,
@@ -117,5 +118,6 @@ export const api = {
alertEvents: (params = new URLSearchParams()) => request<Page<QualityIssueRow>>(`/api/alert-events?${params.toString()}`),
alertEventNotificationPlan: (params = new URLSearchParams()) => request<QualityNotificationPlan>(`/api/alert-events/notification-plan?${params.toString()}`),
reverseGeocode: (params = new URLSearchParams()) => request<MapReverseGeocode>(`/api/map/reverse-geocode?${params.toString()}`),
opsHealth: () => request<OpsHealth>('/api/ops/health')
opsHealth: () => request<OpsHealth>('/api/ops/health'),
sourceReadiness: () => request<SourceReadinessPlan>('/api/ops/source-readiness')
};

View File

@@ -359,6 +359,34 @@ export interface OpsHealth {
runtime: RuntimeInfo;
}
export interface SourceReadinessPlan {
totalVehicles: number;
onlineVehicles: number;
kafkaLag: number | null;
activeConnections: number | null;
redisOnlineKeys: number | null;
platformRelease: string;
sources: SourceReadinessRow[];
}
export interface SourceReadinessRow {
protocol: string;
role: string;
online: number;
total: number;
onlineRate: number;
missingVehicles: number;
severity: 'ok' | 'warning' | 'error' | string;
status: string;
evidence: string;
action: string;
acceptance: string;
vehiclesHash: string;
realtimeHash: string;
historyHash: string;
alertHash: string;
}
export interface CapacityMetrics {
activeConnections: number;
kafkaLag: number;