核心变更:
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
92 lines
4.5 KiB
JavaScript
92 lines
4.5 KiB
JavaScript
import { JSDOM } from 'jsdom';
|
|
import fs from 'node:fs';
|
|
|
|
const html = fs.readFileSync('index.html', 'utf8');
|
|
const dom = new JSDOM(html, {
|
|
url: 'http://localhost:3000/#/workflows/sdB_cno',
|
|
runScripts: 'outside-only',
|
|
pretendToBeVisual: true,
|
|
});
|
|
const { window } = dom;
|
|
global.window = window;
|
|
global.document = window.document;
|
|
global.location = window.location;
|
|
global.localStorage = window.localStorage;
|
|
global.matchMedia =
|
|
window.matchMedia?.bind(window) ?? (() => ({ matches: false, addEventListener() {} }));
|
|
window.localStorage.setItem('dcts_admin_token', 'test-token');
|
|
|
|
const mkPoint = (teff, logc, status, method, attempts = 1, relc = 0.0005) => ({
|
|
name: `t${teff}_g5.0_he-2_c${logc}_n-4_o-4`,
|
|
teff, logg: 5.0, loghe: -2, logc, logn: -4, logo: -4,
|
|
cno_sum: logc - 8, wave: 0,
|
|
status, success_method: method, attempt_count: attempts,
|
|
last_max_relc: relc, last_task_type: method, seed_point_name: method === 'seed_step' ? 't20000_g5.0_he-2_c-4_n-4_o-4' : null,
|
|
node_id: 'node-a1', last_completed_at: status === 'converged' || status === 'failed' ? '2026-07-30 10:00:00' : null,
|
|
last_error: status === 'failed' ? 'nl stage diverged' : null,
|
|
});
|
|
|
|
const POINTS = [
|
|
mkPoint(20000, -4, 'converged', 'cold_run'),
|
|
mkPoint(40000, -4, 'converged', 'seed_step', 2),
|
|
mkPoint(60000, -4, 'failed', null, 2, 9.5e5),
|
|
mkPoint(20000, -3, 'running', null),
|
|
];
|
|
|
|
global.fetch = window.fetch = async (url) => {
|
|
const u = String(url);
|
|
if (u.includes('/stats')) {
|
|
return new Response(JSON.stringify({
|
|
success: true, message: 'ok',
|
|
data: { name: 'sdB_cno', status: 'running', total: 4, pending: 0, queued: 0, running: 1, converged: 2, failed: 1, cold_run_converged: 1, seed_step_converged: 1, imported_converged: 0, waves: [{ wave: 0, total: 4, converged: 2, failed: 1 }], avg_point_sec: 600, eta_sec: 600 },
|
|
}), { status: 200 });
|
|
}
|
|
if (u.includes('/points')) {
|
|
let pts = [...POINTS];
|
|
const q = new URL(u, 'http://localhost');
|
|
if (q.searchParams.get('status')) pts = pts.filter(p => p.status === q.searchParams.get('status'));
|
|
if (q.searchParams.get('method')) pts = pts.filter(p => p.success_method === q.searchParams.get('method'));
|
|
return new Response(JSON.stringify({ success: true, message: 'ok', data: { total: pts.length, points: pts } }), { status: 200 });
|
|
}
|
|
return new Response('{}', { status: 404 });
|
|
};
|
|
|
|
const { mountWorkflowDetail } = await import('./src/views/workflowDetail.js');
|
|
const tick = () => new Promise((r) => setTimeout(r, 30));
|
|
|
|
mountWorkflowDetail('sdB_cno');
|
|
await tick(); await tick();
|
|
|
|
// Tab2:点表
|
|
document.querySelector('[data-wf-tab="points"]').click();
|
|
await tick(); await tick();
|
|
console.log('points rows:', document.querySelectorAll('#points-table-body tr[data-point-row]').length === 4 ? 'OK' : 'MISSING');
|
|
console.log('rescued marker:', document.querySelector('#points-table-body')?.textContent.includes('↻') ? 'OK' : 'MISSING');
|
|
|
|
// 状态过滤 → 仅 failed
|
|
document.querySelector('[data-wf-filter="status"]').value = 'failed';
|
|
document.querySelector('[data-wf-filter="status"]').dispatchEvent(new window.Event('change', { bubbles: true }));
|
|
await tick();
|
|
console.log('status filter:', document.querySelectorAll('#points-table-body tr[data-point-row]').length === 1 ? 'OK' : 'WRONG');
|
|
|
|
// Tab3:热力图
|
|
document.querySelector('[data-wf-tab="analysis"]').click();
|
|
await tick(); await tick();
|
|
// logc 非轴且多值 → 切片选择器应出现并默认固定 logc=-4(3 点成格,c-3 点被切掉)
|
|
const sliceSel = document.querySelector('[data-heat-slice="logc"]');
|
|
console.log('slice selector:', sliceSel ? 'OK' : 'MISSING');
|
|
const cells = document.querySelectorAll('.heatmap-cell');
|
|
console.log('heatmap cells (sliced):', cells.length === 3 ? 'OK' : `GOT ${cells.length}`);
|
|
console.log('cold cell:', document.querySelector('.heatmap-cell.hc-cold') ? 'OK' : 'MISSING');
|
|
console.log('seed cell:', document.querySelector('.heatmap-cell.hc-seed') ? 'OK' : 'MISSING');
|
|
console.log('failed cell:', document.querySelector('.heatmap-cell.hc-failed') ? 'OK' : 'MISSING');
|
|
console.log('conclusion:', document.getElementById('heat-conclusion')?.textContent.includes('冷启动成功') ? 'OK' : 'MISSING');
|
|
|
|
// 切片切到 logc=-3 → 只剩 running 点一格
|
|
sliceSel.value = '-3';
|
|
sliceSel.dispatchEvent(new window.Event('change', { bubbles: true }));
|
|
await tick();
|
|
console.log('slice switch:', document.querySelectorAll('.heatmap-cell').length === 1 && document.querySelector('.heatmap-cell.hc-running') ? 'OK' : 'WRONG');
|
|
|
|
process.exit(0);
|