docs: enterprise refactor plan with ralph specs

This commit is contained in:
debian
2026-03-04 16:17:03 -05:00
parent 4c92712d20
commit f8191133c8
204 changed files with 32722 additions and 422 deletions

View File

@@ -0,0 +1,74 @@
/**
* useApi — fetch helper with error handling.
*/
const BASE = '/api';
export async function apiFetch<T>(path: string, options?: RequestInit): Promise<T> {
const res = await fetch(`${BASE}${path}`, {
headers: { 'Content-Type': 'application/json' },
...options,
});
if (!res.ok) {
const body = await res.json().catch(() => ({ error: res.statusText }));
throw new Error((body as { error?: string }).error ?? res.statusText);
}
return res.json() as Promise<T>;
}
export const api = {
getSessions: () => apiFetch<import('../types').Session[]>('/sessions'),
getSession: (id: string) => apiFetch<import('../types').Session>(`/sessions/${id}`),
createSession: (body: { url: string; seed: number; config?: Partial<import('../types').ExplorationConfig> }) =>
apiFetch<{ sessionId: string; status: string; startedAt: string }>('/sessions', {
method: 'POST',
body: JSON.stringify(body),
}),
stopSession: (id: string) =>
apiFetch<{ stopped: boolean }>(`/sessions/${id}`, { method: 'DELETE' }),
getSessionPerformance: (id: string) =>
apiFetch<import('../types').PerformanceMetrics[]>(`/sessions/${id}/performance`),
getAnomalies: (params?: { sessionId?: string; severity?: string; type?: string }) => {
const qs = new URLSearchParams();
if (params?.sessionId) qs.set('sessionId', params.sessionId);
if (params?.severity) qs.set('severity', params.severity);
if (params?.type) qs.set('type', params.type);
const q = qs.toString() ? `?${qs.toString()}` : '';
return apiFetch<import('../types').AnomalySummary[]>(`/anomalies${q}`);
},
getAnomaly: (id: string) => apiFetch<import('../types').Anomaly>(`/anomalies/${id}`),
replayAnomaly: (id: string) =>
apiFetch<{ replayId: string; status: string }>(`/anomalies/${id}/replay`, { method: 'POST' }),
enrichAnomaly: (id: string) =>
apiFetch<{ status: string; anomalyId: string }>(`/anomalies/${id}/enrich`, { method: 'POST' }),
getStats: () => apiFetch<import('../types').Stats>('/stats'),
getConfig: () => apiFetch<import('../types').ServerConfig>('/config'),
patchConfig: (body: Partial<import('../types').ServerConfig>) =>
apiFetch<import('../types').ServerConfig>('/config', { method: 'PATCH', body: JSON.stringify(body) }),
// Schedules
getSchedules: () => apiFetch<import('../types').Schedule[]>('/schedules'),
createSchedule: (body: { name: string; url: string; cronExpression: string; config?: object; enabled?: boolean }) =>
apiFetch<import('../types').Schedule>('/schedules', { method: 'POST', body: JSON.stringify(body) }),
patchSchedule: (id: string, body: Partial<{ name: string; url: string; cronExpression: string; enabled: boolean }>) =>
apiFetch<import('../types').Schedule>(`/schedules/${id}`, { method: 'PATCH', body: JSON.stringify(body) }),
deleteSchedule: (id: string) =>
apiFetch<void>(`/schedules/${id}`, { method: 'DELETE' }),
// Visual regression
getVisualComparisons: (params?: { sessionId?: string; status?: string }) => {
const qs = new URLSearchParams();
if (params?.sessionId) qs.set('sessionId', params.sessionId);
if (params?.status) qs.set('status', params.status);
const q = qs.toString() ? `?${qs.toString()}` : '';
return apiFetch<import('../types').VisualComparison[]>(`/visual/comparisons${q}`);
},
approveBaseline: (comparisonId: string) =>
apiFetch<{ baselineId: string; status: string }>(`/visual/baselines/${comparisonId}/approve`, { method: 'POST' }),
rejectBaseline: (comparisonId: string) =>
apiFetch<{ status: string }>(`/visual/baselines/${comparisonId}/reject`, { method: 'POST' }),
approveAllBaselines: (sessionId?: string) =>
apiFetch<{ approved: number }>('/visual/baselines/approve-all', { method: 'POST', body: JSON.stringify({ sessionId }) }),
};

View File

@@ -0,0 +1,40 @@
/**
* useSocket — reusable socket.io-client connection.
*/
import { useEffect, useRef, useCallback } from 'react';
import { io, Socket } from 'socket.io-client';
export type SocketHandler = (event: string, data: unknown) => void;
export function useSocket(onEvent: SocketHandler) {
const socketRef = useRef<Socket | null>(null);
const handlerRef = useRef<SocketHandler>(onEvent);
handlerRef.current = onEvent;
useEffect(() => {
const socket = io({ path: '/socket.io' });
socketRef.current = socket;
const events = [
'session:started',
'state:discovered',
'action:executed',
'anomaly:detected',
'session:completed',
'session:error',
];
events.forEach((evt) => {
socket.on(evt, (data: unknown) => handlerRef.current(evt, data));
});
return () => { socket.disconnect(); };
}, []);
const emit = useCallback((event: string, data: unknown) => {
socketRef.current?.emit(event, data);
}, []);
return { emit };
}