feat(all): 源精度命名体系、工作流可观测台、节点停用管理与白名单归档
核心变更:
1. GridAxisValue 源精度命名
- 新增 GridAxisValue 类型,携带 f64 数值 + YAML 源书写文本(Deref 透明兼容算术)
- config.rs 绕过 serde_yaml 归一化,逐 token 捕获轴值原文(logg: 5.0 → g5.0)
- runner/executor/scheduler 全链路改用 DB TEXT 列权威 point_name,
修复 REAL 列回读丢精度导致的 model_name 错配
2. 工作流执行可观测台
- 新增 stats/progress/points 三组 API(进度时间序列、经验速率 ETA、
停滞预警、逐点明细分页、收敛性热力图数据)
- 新增 workflow_progress_snapshots 表 + tasks/grid_points 耗时列
- runner 携带 last_iter/worst_depth/n_depths 进 conv.json
- 前端新增 hash 路由、工作流详情页(概览/网格点/收敛分析三 Tab)、YAML 编辑器
3. 节点停用/启用管理
- 新增 disabled 状态 + disable/enable API;停用节点保持心跳但停止分发,
worker 空闲待命而非退出;移除 revoke API,token 失效统一走重发覆盖;
移除 host_name 字段
4. 白名单结果归档
- 新增 result_filter 模块,只归档有语义产物,丢弃 Tlusty 中间单元(~2MB/模型)
- executor 原子写入归档 + 200 点 LRU 上限
5. 历史数据导入
- sync_seeds 重写为 import_results:经 /admin/import_seed 标记 converged +
按新版命名迁移产物树
6. 部署与目录重规划
- data/results→seeds、data/archive→result + migrate_data_dirs.sh
- deploy.sh 增强(SSH 复用、Profile、远程 env);Dockerfile 瘦身
7. 文档同步更新 api/database/architecture/deployment
This commit is contained in:
+68
-11
@@ -25,19 +25,30 @@ export function escapeHtml(s) {
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
// 统一 fetch 包装:自动注入 Authorization 头,401 时清除 token 并刷新
|
||||
export async function apiFetch(url, opts = {}) {
|
||||
// 统一 fetch 包装:自动注入 Authorization 头,支持 10s 超时处理与 401 处理
|
||||
export async function apiFetch(url, opts = {}, timeoutMs = 10000) {
|
||||
const token = getAdminToken();
|
||||
const headers = { ...(opts.headers || {}) };
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
const res = await fetch(url, { ...opts, headers });
|
||||
if (res.status === 401) {
|
||||
clearAdminToken();
|
||||
location.reload();
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
||||
const signal = opts.signal || controller.signal;
|
||||
|
||||
try {
|
||||
const res = await fetch(url, { ...opts, headers, signal });
|
||||
clearTimeout(timeoutId);
|
||||
if (res.status === 401) {
|
||||
clearAdminToken();
|
||||
location.reload();
|
||||
}
|
||||
return res;
|
||||
} catch (err) {
|
||||
clearTimeout(timeoutId);
|
||||
throw err;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// API 请求接口定义
|
||||
@@ -76,6 +87,15 @@ export async function createWorkflow(workflowData) {
|
||||
});
|
||||
}
|
||||
|
||||
/** 更新既有工作流配置(PUT)。服务端拒绝 running/initializing 状态覆写(400)。 */
|
||||
export async function updateWorkflowApi(name, workflowData) {
|
||||
return apiFetch(`/api/workflows/${encodeURIComponent(name)}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(workflowData),
|
||||
});
|
||||
}
|
||||
|
||||
export async function startWorkflowApi(name) {
|
||||
return apiFetch(`/api/workflows/${encodeURIComponent(name)}/start`, { method: 'POST' });
|
||||
}
|
||||
@@ -92,6 +112,39 @@ export async function getWorkflowDetailApi(name) {
|
||||
return apiFetch(`/api/workflows/${encodeURIComponent(name)}`);
|
||||
}
|
||||
|
||||
// ===== 工作流执行观测 API(详情页数据源) =====
|
||||
// 均返回 raw Response,调用方检查 res.ok && json.success;
|
||||
// opts 可传 { signal } 用于视图卸载时中断在途请求。
|
||||
|
||||
export async function fetchWorkflowStatsApi(name, opts = {}) {
|
||||
return apiFetch(`/api/workflows/${encodeURIComponent(name)}/stats`, opts);
|
||||
}
|
||||
|
||||
export async function fetchWorkflowPointsApi(name, params = {}, opts = {}) {
|
||||
const qs = new URLSearchParams();
|
||||
Object.entries(params).forEach(([k, v]) => {
|
||||
if (v != null && v !== '') qs.set(k, v);
|
||||
});
|
||||
const s = qs.toString();
|
||||
return apiFetch(`/api/workflows/${encodeURIComponent(name)}/points${s ? `?${s}` : ''}`, opts);
|
||||
}
|
||||
|
||||
export async function fetchPointDetailApi(name, point, opts = {}) {
|
||||
return apiFetch(
|
||||
`/api/workflows/${encodeURIComponent(name)}/points/${encodeURIComponent(point)}`,
|
||||
opts
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchWorkflowProgressApi(name, params = {}, opts = {}) {
|
||||
const qs = new URLSearchParams();
|
||||
Object.entries(params).forEach(([k, v]) => {
|
||||
if (v != null && v !== '') qs.set(k, v);
|
||||
});
|
||||
const s = qs.toString();
|
||||
return apiFetch(`/api/workflows/${encodeURIComponent(name)}/progress${s ? `?${s}` : ''}`, opts);
|
||||
}
|
||||
|
||||
export async function approveNodeApi(nodeId) {
|
||||
return apiFetch(`/api/admin/nodes/${encodeURIComponent(nodeId)}/approve`, { method: 'POST' });
|
||||
}
|
||||
@@ -100,10 +153,14 @@ export async function rejectNodeApi(nodeId) {
|
||||
return apiFetch(`/api/admin/nodes/${encodeURIComponent(nodeId)}/reject`, { method: 'POST' });
|
||||
}
|
||||
|
||||
export async function revokeNodeApi(nodeId) {
|
||||
return apiFetch(`/api/admin/nodes/${encodeURIComponent(nodeId)}/revoke`, { method: 'POST' });
|
||||
}
|
||||
|
||||
export async function reissueNodeApi(nodeId) {
|
||||
return apiFetch(`/api/admin/nodes/${encodeURIComponent(nodeId)}/reissue`, { method: 'POST' });
|
||||
}
|
||||
|
||||
export async function disableNodeApi(nodeId) {
|
||||
return apiFetch(`/api/admin/nodes/${encodeURIComponent(nodeId)}/disable`, { method: 'POST' });
|
||||
}
|
||||
|
||||
export async function enableNodeApi(nodeId) {
|
||||
return apiFetch(`/api/admin/nodes/${encodeURIComponent(nodeId)}/enable`, { method: 'POST' });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user