Appearance
状态管理与实时通信
1. 状态管理策略
本项目采用 React 内置状态管理(useState + useEffect),未引入 Redux/Zustand 等外部状态管理库。原因:
- 页面间数据独立,无跨页面共享状态的需求
- 组件层级浅(最多 3 层),无 prop drilling 问题
- 数据获取模式统一(页面级 fetch → 本地 state),自定义 Hook 已封装复用逻辑
2. 自定义 Hooks
2.1 usePolling — 通用轮询 Hook
文件:hooks/usePolling.ts
功能:以固定间隔轮询指定 API 函数,返回最新数据。
签名:
typescript
function usePolling<T>(
fetcher: () => Promise<T>,
intervalMs: number,
enabled: boolean
): {
data: T | null;
loading: boolean;
error: string | null;
refetch: () => void;
}参数:
fetcher— 返回 Promise 的数据获取函数intervalMs— 轮询间隔(毫秒)enabled— 是否启用轮询
行为:
- 组件挂载时立即执行一次
fetcher() - 若
enabled=true,以intervalMs间隔持续调用fetcher() - 组件卸载时自动清除定时器
refetch()可手动触发立即刷新
使用场景:
HealthCheck页面:每 15 秒轮询GET /health/nodes
示例:
typescript
const { data, loading, error, refetch } = usePolling<NodeHealthResponse>(
fetchNodeHealth,
15_000, // 15 秒
autoRefresh // 由 checkbox 控制
);2.2 useRunPolling — 运行状态轮询 Hook
文件:hooks/useRunPolling.ts
功能:针对运行实时状态设计的专用轮询 Hook,返回结构化的运行状态数据。
签名:
typescript
function useRunPolling(options: {
runId: string | null;
interval?: number;
enabled?: boolean;
}): {
run: Run | null;
currentStep: number;
totalSteps: number;
latestMetrics: MetricItem | null;
loading: boolean;
error: string | null;
}参数:
runId— 要监控的 Run ID(null 时不轮询)interval— 轮询间隔,默认 1000msenabled— 是否启用,默认 true
内部逻辑:
- 调用
GET /runs/{runId}/status获取RunStatusResponse - 解构返回的
run、current_step、total_steps、latest_metrics - 运行进入终态(finished/stopped/failed)后自动停止轮询
使用场景:
LiveMonitor页面:1 秒间隔轮询正在运行的实例
示例:
typescript
const { run, currentStep, totalSteps, latestMetrics, loading, error } =
useRunPolling({
runId: selectedRunId,
interval: 1000,
enabled: !!selectedRunId,
});2.3 useWebSocket — WebSocket 连接 Hook
文件:hooks/useWebSocket.ts
功能:管理 WebSocket 连接的生命周期,接收实时事件推送。
签名:
typescript
function useWebSocket(options: {
url: string | null;
onMessage?: (event: RunEventEnvelope) => void;
enabled?: boolean;
}): {
connected: boolean;
error: string | null;
send: (data: unknown) => void;
}参数:
url— WebSocket URL(null 时不连接),格式:/ws/runs/{runId}onMessage— 收到消息时的回调enabled— 是否启用连接
内部逻辑:
url非空且enabled=true时建立 WebSocket 连接- 收到消息后解析 JSON 为
RunEventEnvelope并调用onMessage - 连接断开时尝试重连(指数退避)
- 组件卸载时自动关闭连接
消息格式(与后端 RunEventEnvelope 对应):
typescript
interface RunEventEnvelope {
type: "state" | "step" | "schedule" | "metrics" | "sync" | "artifacts";
run_id: string;
ts_ms: number;
payload: Record<string, unknown>;
}当前使用状态:
- Hook 已实现并可用
LiveMonitor页面当前选择使用useRunPolling(HTTP 轮询)而非 WebSocket- WebSocket 方案保留为后续性能优化路径
3. 数据流转图
3.1 页面初始化流程
页面组件 mount
│
├── useEffect(() => { fetchXxx().then(setData) }, [])
│ │
│ └── api/client.ts → fetch('/endpoint')
│ │
│ └── Vite proxy → http://localhost:8000/endpoint
│ │
│ └── FastAPI route handler
│ │
│ └── JSON response
│ │
│ setData(response) → re-render
│
└── 组件渲染(使用 state 数据)3.2 实时轮询流程(useRunPolling)
LiveMonitor mount
│
├── useRunPolling({ runId, interval: 1000 })
│ │
│ ├── 立即执行 fetchRunStatus(runId)
│ │ └── GET /runs/{runId}/status → RunStatusResponse
│ │ └── 更新 run / currentStep / latestMetrics
│ │
│ └── setInterval(fetchRunStatus, 1000)
│ │
│ ├── 每秒获取最新状态
│ │
│ └── if (终态) → clearInterval(自动停止)
│
└── 组件渲染(实时更新指标卡片)3.3 WebSocket 流程(可选)
组件 mount
│
├── useWebSocket({ url: '/ws/runs/{runId}' })
│ │
│ ├── new WebSocket(url)
│ │ └── 连接建立
│ │ │
│ │ ├── 服务端推送 state 事件
│ │ ├── 服务端推送 metrics 事件
│ │ └── 后续持续推送 step/schedule/metrics 事件
│ │
│ └── ws.onmessage → JSON.parse → onMessage(envelope)
│ └── 组件根据 type 更新对应 state
│
└── 组件卸载 → ws.close()4. API 客户端封装
文件:api/client.ts
所有后端 HTTP 调用集中在此文件,统一处理请求构造和错误转换。
主要函数
| 函数 | 请求 | 返回类型 |
|---|---|---|
fetchHealth() | GET /health | HealthResponse |
fetchNodeHealth() | GET /health/nodes | NodeHealthResponse |
fetchConfig() | GET /config | ConfigResponse |
fetchStrategies() | GET /strategies | StrategyListResponse |
fetchScenariosMerged() | GET /scenarios | ScenarioMergedListResponse |
fetchScenarioFull(id) | GET /scenarios/{id} | ScenarioFullResponse |
createScenario(data) | POST /scenarios | ScenarioFullResponse |
updateScenario(id, data) | PUT /scenarios/{id} | ScenarioFullResponse |
deleteScenario(id) | DELETE /scenarios/{id} | void |
exportScenario(id) | POST /scenarios/{id}/export | ExportResponse |
fetchRuns(params) | GET /runs | RunListResponse |
createRun(data) | POST /runs | RunItem |
startRun(id) | POST /runs/{id}/start | RunItem |
stopRun(id) | POST /runs/{id}/stop | RunItem |
fetchRunDetail(id) | GET /runs/{id} | RunDetailResponse |
fetchRunStatus(id) | GET /runs/{id}/status | RunStatusResponse |
fetchRunMetrics(id) | GET /runs/{id}/metrics | RunMetricsResponse |
fetchRunTraces(id, params) | GET /runs/{id}/traces | RunTracesResponse |
fetchRunArtifacts(id) | GET /runs/{id}/artifacts | RunArtifactsResponse |
getArtifactDownloadUrl(runId, filename) | — | string(URL 构造) |
runLocationInit() | POST /config/location-init/run | LocationInitRunResponse |
错误处理
typescript
async function api<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(path, init);
if (!res.ok) {
const body = await res.json().catch(() => null);
throw new Error(body?.detail ?? `HTTP ${res.status}`);
}
return res.json();
}所有 API 函数共用 api<T>() 封装:
- 非 2xx 响应统一转换为
Error,消息取detail字段 - 网络异常透传
TypeError - 页面组件在
.catch()中处理错误(设置 error state 或忽略)
5. TypeScript 类型定义
5.1 types/run.ts
定义与运行相关的核心类型,与后端 schemas.py 中的 Pydantic 模型一一对应:
| 前端类型 | 后端模型 | 说明 |
|---|---|---|
Run | RunItem | 运行基本信息 |
RunSummaryItem | RunSummaryItem | 运行摘要统计 |
MetricItem | MetricItem | 步进指标 |
TraceItem | TraceItem | 通信追踪记录 |
ArtifactFileItem | ArtifactFileItem | 产物文件 |
HealthResponse | HealthResponse | 全局健康检查 |
NodeHealthResponse | NodeHealthResponse | 节点健康检查 |
HealthNodeCheck | HealthNodeCheck | 单节点健康 |
ConfigResponse | ConfigResponse | 系统配置 |
ConfigNodeEndpoint | ConfigNodeEndpoint | 节点端点 |
StrategyItem | StrategyItem | 策略信息 |
5.2 types/scenario.ts
定义场景管理相关的类型:
| 前端类型 | 后端模型 | 说明 |
|---|---|---|
ScenarioDbItem | ScenarioDbItem | 场景列表项 |
ScenarioFullResponse | ScenarioFullResponse | 场景完整信息 |
ScenarioNodeItem | ScenarioNodeItem | 场景节点 |
ScenarioTrafficItem | ScenarioTrafficItem | 流量配置 |
ScenarioRunConfig | ScenarioRunConfig | 运行参数 |
LocationInitRunResponse | LocationInitRunResponse | 位置初始化结果 |
6. 连接状态管理
HTTP 轮询连接状态
useRunPolling 通过组合判断推导连接状态:
typescript
const isConnected = !!selectedRunId && !error && !loading;在 LiveMonitor 页面以圆点指示器展示:
- 绿色
轮询中:正常获取数据 - 橙色
等待选择...:未选择运行 - 红色
请求失败:API 调用异常
WebSocket 连接状态
useWebSocket 维护 connected: boolean 状态:
- 连接建立:
ws.onopen→setConnected(true) - 连接断开:
ws.onclose→setConnected(false) - 错误发生:
ws.onerror→setError(message)
7. 性能考量
- 轮询频率:
useRunPolling默认 1 秒间隔,对后端产生 1 QPS 负载 - 自动停止:运行进入终态后轮询自动停止,避免无用请求
- 条件加载:
RunDetail页仅在运行结束后才加载 metrics/traces/artifacts - 分页:traces 查询默认 20 条/页,避免大量数据传输
- 组件卸载清理:所有定时器和 WebSocket 连接在组件 unmount 时自动清理