核心变更:
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
388 lines
25 KiB
Markdown
388 lines
25 KiB
Markdown
# 工作流执行可视化 —— 功能重设计文档
|
||
|
||
> 目标:把"恒星大气网格工作流"面板从当前的 **3 个动作(查看 YAML / 启动 / 删除)**
|
||
> 升级为完整的 **执行可观测台**:执行进度、逐网格点执行详情、收敛判定、
|
||
> 冷启动 vs 种子步进归因、参数空间冷启动成功图谱,以及失败点重试。
|
||
>
|
||
> 原则:**数据已经在库里大半,缺口主要是 HTTP 暴露 + 少量持久化 + 全新前端 UI。**
|
||
> 零新增前端依赖(不引入图表库),沿用现有 ESM + 原生 DOM + 设计令牌风格。
|
||
|
||
---
|
||
|
||
## 1. 现状盘点(带代码证据)
|
||
|
||
### 1.1 已经持久化、可直接用的数据
|
||
|
||
| 数据 | 位置 | 说明 |
|
||
|---|---|---|
|
||
| 网格点 6 维参数、`cno_sum`、`wave` | `grid_points` 表(`db.rs:246-263`) | 按 `workflow_name` 分区,复合唯一 `(workflow_name, name)` |
|
||
| 点状态 `pending/queued/running/converged/failed` | `grid_points.status` | `GridPointStatus`(`models.rs:235-243`) |
|
||
| **成功手段** `cold_run` / `seed_step` / `imported` | `grid_points.success_method` | 收敛时由成功任务的 `task_type` 回填(`db.rs:1097-1101`);失败点为 NULL |
|
||
| 重试次数 | `grid_points.attempt_count` | 每次回报 +1(`db.rs:1089-1095`) |
|
||
| 每次尝试的收敛指标 | `tasks.max_relc` | 收敛判据 `max_relc < chmax`(默认 0.001,`conv_check.rs:105-109`) |
|
||
| **每次尝试的种子来源** | `tasks.seed_point_name` | 派发时写入(`scheduler.rs:194-205`) |
|
||
| 每次尝试的方法 / 错误 / 节点 / 完成时间 | `tasks.task_type / error_message / node_id / completed_at` | 一个点多行(每次尝试一行),工作流删除前长期保留 |
|
||
| 全局种子库 | `seeds` 表 + `data/seeds/<name>/<name>.7` | 仅收敛且无 NaN 的点入库(`task.rs:187-207`) |
|
||
| **逐阶段完整诊断**(lte→nc→nl / seed_nc→nl) | `data/seeds/<name>/conv.json` | `ModelSummary`(`models.rs:376-391`):每阶段 `converged / best_max_relc / chmax / elapsed_sec`、总耗时、`synspec_sec`、所用种子。**成功与失败的点都会写**(`task.rs:178-184`) |
|
||
| 按工作流的统计聚合 | `db.get_grid_summary_stats(Some(wf))` | **已实现且有单测**(`db.rs:1582-1639`),但没有任何 HTTP 端点调用它 |
|
||
|
||
### 1.2 缺口(需要补的)
|
||
|
||
| # | 缺口 | 影响 | 补法 |
|
||
|---|---|---|---|
|
||
| G1 | 无逐点 HTTP 端点 | 前端拿不到任何点级数据 | 新增 `/workflows/:name/points` |
|
||
| G2 | 无按工作流的统计端点(`get_grid_summary_stats` 未暴露;`/api/status` 只有全局) | 卡片/详情无法显示每工作流进度 | 新增 `/workflows/:name/stats`;列表接口内联计数 |
|
||
| G3 | `queued` 被并进 `pending`(`db.rs:1596,1612`) | 无法区分"排队中"与"未入队" | 新端点拆开统计 |
|
||
| G4 | 单点耗时/迭代数未进库(`TaskReport.elapsed_sec` 收到即丢;`last_iter` 在 `StageSummary` 前被丢弃) | 列表无耗时列、无 ETA | 迁移补列 + runner 携带 `last_iter`(Phase 3) |
|
||
| G5 | 无进度时间序列 | 画不出"进度-时间"曲线 | 新表 `workflow_progress_snapshots`(Phase 3) |
|
||
| G6 | 失败点无法重试(start 只重置 `queued`,`db.rs:886-899`) | 失败点永久卡死 | 新增 retry 端点(复用 `reset_specific_grid_points_to_pending` 思路) |
|
||
| G7 | conv.json 不提供下载/查看 | 逐阶段诊断无法展示 | 点详情端点读盘解析返回 |
|
||
| G8 | 前端无任何进度/详情 UI;无 tab 组件 | —— | 全新 UI |
|
||
|
||
### 1.3 领域语义(设计依据)
|
||
|
||
- **冷启动 vs 种子步进不是用户选的,是调度器按"种子可用性"自动决定的**:
|
||
派发时 `find_best_seed_from_db` 命中(同族精确匹配优先,否则全局加权距离 ≤ 3.0,
|
||
`seed_finder.rs:13-38`)→ `SeedStep`(热启动链 `seed_nc→nl`),否则 `ColdRun`(`lte→nc→nl`)。
|
||
- **wave = 难度波次**:按 `cno_sum` 升序分组,"先易后难",让早期收敛点充当种子。
|
||
所以"哪些参数冷启动能成功" ≈ 低 cno_sum / 中低温区的点;高温 He-poor 区几乎全靠种子步进。
|
||
- **还有一次性失败回退**:冷启动发散且存在邻居种子时,自动以 `seed_step` 重投一次
|
||
(`scheduler.rs:270-350`,受 `seed_step_fallback` 配置与 `has_seed_step_attempt` 一次性闸门约束)。
|
||
因此 `success_method='seed_step'` 的点可能是"冷启动失败后被救回"的——该归因需从 `tasks` 多行历史还原。
|
||
- **第三类 `imported`**:历史 `run_grid.py` 结果导入(`db.rs:966-981`),UI 需单独呈现为"历史导入"。
|
||
- **收敛判据**:末次迭代最差深度点的最大相对修正 `max_relc < chmax`(默认 1e-3),
|
||
且大气 NaN 占比 ≤ 10%(`conv_check.rs:127-149`)。
|
||
|
||
---
|
||
|
||
## 2. 功能设计(信息架构)
|
||
|
||
### 2.1 工作流卡片(改造现有 `workflows.js` 卡片)
|
||
|
||
```
|
||
┌─────────────────────────────────────────────┐
|
||
│ sdB_cno [运行中] │ ← 现有:名称/描述/状态徽章
|
||
│ sdB 6维恒星大气模型计算网格 │
|
||
│ ▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░ 42% │ ← 新增:收敛进度条 + 百分比
|
||
│ 181/432 收敛 · 12 种子步进 · 3 失败 · 8 运行 │ ← 新增:一行计数摘要
|
||
│ [进入详情 →] [查看YAML] [暂停] [删除] │ ← 整卡头部可点进详情页
|
||
└─────────────────────────────────────────────┘
|
||
```
|
||
|
||
- 卡片名/头部区域是 `<a href="#/workflows/sdB_cno">`;操作按钮 `stopPropagation` 保持独立语义。
|
||
|
||
- 进度数据来自 **列表接口内联计数**(见 §3.2),不额外轮询。
|
||
- 状态徽章扩充:`initializing` 显示"初始化中"(warning 琥珀),`paused` 显示"已暂停"(secondary),
|
||
不再把三者都归为"闲置"。
|
||
|
||
### 2.2 工作流详情页(独立页面,hash 路由 `#/workflows/:name`)
|
||
|
||
> **信息架构决策**:详情内容量(三 Tab + 可过滤点表 + 热力矩阵 + 逐点诊断)远超模态框承载,
|
||
> 且需要深链/返回键/长时间监控驻留。因此采用**独立视图页面** + 零依赖 hash 路由,
|
||
> 而非宽模态。入口:卡片整卡或"详情"按钮 → `location.hash = '#/workflows/<name>'`。
|
||
|
||
**页面骨架**(顶部常驻"任务控制条",是全页最先映入眼帘的部分——直接呈现这个网格的实时生命体征):
|
||
|
||
```
|
||
← 返回控制台 sdB_cno [运行中] [启动/暂停] [重试失败] [查看YAML] [删除]
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░ ← 分段进度条:绿=收敛 红=失败 琥珀=运行 蓝=排队 灰=待定
|
||
432 网格点 · 181 收敛(42%) · 8 运行 · 40 排队 · 3 失败 · 冷启动 169 / 种子步进 12 · ETA ≈ 2h40m
|
||
```
|
||
|
||
- 分段进度条按状态占比着色拼接,5s 轮询下各段宽度此消彼长——进度是"看得见在动"的。
|
||
- 计数用 `tabular-num` 防跳动;数字变化可加轻量过渡。
|
||
- 工作流不存在(hash 指向已删除的名字)→ 空态卡 + 返回按钮。
|
||
|
||
**Tab 1 — 执行概览**
|
||
- 4 个 metric tile(复用 `.metric-card`):网格总数 / 已收敛 / 失败 / 运行中+排队。
|
||
- **方法归因**:冷启动收敛 X · 种子步进收敛 Y · 历史导入 Z · 失败 N(彩色计数行)。
|
||
- wave(难度波次)分布:每波次 `converged/total` 的小条形列表——直观看到"难度推进到哪了"。
|
||
- **最近动态流**:最近 12 条尝试回报(点名 / 方法徽章 / 结果 / max_relc / 节点 / 完成时间),
|
||
轮询下新条目从顶部淡入——数据就是 `points` 端点 `sort=last_completed_at&order=desc&limit=12`,零新增端点。
|
||
- ETA(Phase 3):按平均单点耗时 × 剩余点数 ÷ 集群空闲槽位估算。
|
||
|
||
**Tab 2 — 网格点明细**
|
||
- 过滤器:状态(全部/排队/运行/收敛/失败)、方法(全部/冷启动/种子步进/导入)、wave 下拉、点名搜索框。
|
||
- 表格(复用 `.data-table` + 关键行增量刷新,仿 `nodesTable.js`):
|
||
|
||
| 列 | 来源 |
|
||
|---|---|
|
||
| 点名(mono 省略,title 全文) | `name` |
|
||
| Teff / logg / logHe / CNO和 | 6 维参数 |
|
||
| wave | `wave` |
|
||
| 状态 | badge:收敛=online 绿 / 失败=danger 红 / 运行=warning 琥珀 / 排队=info 蓝 / 待定=secondary |
|
||
| 方法 | badge:冷启动=蓝(blueprint)/ 种子步进=紫(purple)/ 导入=secondary——**刻意区别于状态色** |
|
||
| max_relc | `tabular-num`,科学计数;失败点显示最后一次尝试值 |
|
||
| 尝试数 | `attempt_count`(>1 且收敛 = "被救回",加 ↻ 角标) |
|
||
| 耗时 | Phase 3 起有值;之前用 `completed_at-created_at` 粗估并标 ~ |
|
||
| 操作 | [查看] → 点详情 |
|
||
|
||
- 分页:`limit=100 + offset`,服务端排序(默认 wave→cno_sum→teff)。
|
||
- **刷新策略**:打开弹窗时拉一次;Tab 激活期间每 5s 自动刷新(可手动刷新按钮强制);
|
||
弹窗关闭即停。不污染全局轮询。
|
||
|
||
**Tab 3 — 收敛性分析(冷启动图谱)**
|
||
- 核心问题:"哪些参数范围内冷启动能成功"。
|
||
- 6 维空间降到 2D 热力矩阵:用户从下拉选 X 轴 / Y 轴(默认 X=Teff、Y=cno_sum);
|
||
其余维度若为多值则提供切片选择器(固定某维取值)。
|
||
- 单元格着色(CSS grid,无图表库):
|
||
- 🟩 冷启动收敛(`converged && method=cold_run`)
|
||
- 🟪 种子步进收敛(`seed_step`)
|
||
- ⬜ 历史导入(`imported`)
|
||
- 🟥 失败(`failed`)
|
||
- 🟨 运行/排队中
|
||
- ░ 未开始(`pending` 且从未尝试)
|
||
- 矩阵下方:图例 + 聚合结论条("冷启动成功率 62%(268/432);Teff≥60000 区间冷启动成功率 0%,全部由种子步进救回 41 个、失败 3 个")。
|
||
- 单元格 hover 显示点名与 max_relc;点击 → 同 Tab 2 的点详情。
|
||
|
||
### 2.3 网格点详情(右侧滑入面板 slide-over,不再模态套模态)
|
||
|
||
点表格/热力图/动态流中任一点名 → 右侧滑入 480px 面板(`.point-panel`,背景遮罩 + Esc/点击遮罩关闭,
|
||
复用焦点圈管理;独立页面上滑入面板比二级模态更轻、视线不跳转)。
|
||
|
||
- 头部:点名(mono)+ 状态/方法徽章 + [复制点名]。
|
||
- **尝试历史表**(`tasks` 多行):# / 方法 / 种子来源(点名,可点击跳查该点)/ max_relc / 结果 / 节点 / 完成时间 / 错误摘要。
|
||
这一张表直接还原"冷启动失败 → 种子步进救回"的完整剧情。
|
||
- **逐阶段链诊断**(读 `conv.json`):每阶段一张小卡:
|
||
`lte` ✅ grey start · `nc` ⚠️ max_relc=0.957(不要求收敛)· `nl` ✅ max_relc=6.9e-3 / 17 iter / 612s;
|
||
种子步进链则显示 `seed_nc → nl`,并标注种子点名与总耗时、synspec 耗时、NaN 标志。
|
||
- 失败点:突出显示最后一次 `error_message`(`code-pre-wrap`,textContent 渲染防 XSS)。
|
||
|
||
### 2.4 视图与操作逻辑
|
||
|
||
**路由**(新增 `src/router.js`,约 70 行,零依赖):
|
||
|
||
| hash | 视图 | 挂载动作 | 卸载动作 |
|
||
|---|---|---|---|
|
||
| `#/`(或空/未知) | 控制台首页(现有布局) | 渲染骨架 + `startPolling()` | 停全局轮询 |
|
||
| `#/workflows/:name` | 工作流详情页 | 渲染页面骨架 + 拉 stats/points + 启作用域轮询 | 停作用域轮询 |
|
||
|
||
- `window.addEventListener('hashchange', render)`;首次加载按当前 hash 渲染。
|
||
- **登录门禁不变**:无 token 时任何 hash 都先展示登录遮罩,登录成功后按 hash 进入对应视图。
|
||
- 全局 header/footer 保持常驻;视图内容渲染进 `#view-root` 容器(`index.html` 结构调整:
|
||
现有 `main.main-container` 内容收敛为首页视图模板)。
|
||
- 返回键/深链/刷新天然可用(hash 不触发整页刷新)。
|
||
|
||
**交互一览**:
|
||
|
||
| 操作 | 前端 | 后端 |
|
||
|---|---|---|
|
||
| 进详情页 | hash 切换 → 按需 fetch stats + points(第1页) | —— |
|
||
| 切 Tab | 委托 `data-wf-tab`;切到"明细/分析"时惰性加载 | —— |
|
||
| 过滤/翻页/排序 | change/click → 重新 fetch(带 query) | 端点支持参数 |
|
||
| 自动刷新 | 详情页挂载期间 5s 拉 stats(控制条+概览常驻刷新);明细 Tab 激活时同步刷新表格 | —— |
|
||
| 点详情面板 | 行内 [查看]/点名链接 → 拉 `points/:point` → 滑入面板;种子来源点名可跳查(换载面板内容) | —— |
|
||
| 重试失败点 | `showConfirm` → POST → toast → 刷新 stats/points | 新端点,仅 `running` 状态允许 |
|
||
| 返回首页 | "← 返回控制台"链接 / 浏览器返回 | —— |
|
||
| 首页卡片 | 整卡点击进详情;卡片上保留 启动/暂停/删除 快捷操作 + 查看YAML(仍为模态) | —— |
|
||
|
||
---
|
||
|
||
## 3. 后端设计
|
||
|
||
### 3.1 新增端点(全部挂在 `/api/workflows/:name/...` 下)
|
||
|
||
> 鉴权:`required_role` 里 `path.starts_with("/workflows/")` 已映射 Admin(`mod.rs:92-95`),
|
||
> 子路由**无需改鉴权矩阵**。统一返回 `{success, message, data}` 信封(与现有 workflow API 一致)。
|
||
> 点名参数复用现有字符集白名单校验(`task.rs:133-146` 同款),防路径穿越。
|
||
|
||
#### ① `GET /api/workflows/:name/stats` —— 每工作流进度(G2/G3)
|
||
|
||
复用并增强 `get_grid_summary_stats(Some(wf))`,**拆开 pending/queued**,补方法与波次维度:
|
||
|
||
```json
|
||
{
|
||
"success": true, "message": "ok",
|
||
"data": {
|
||
"name": "sdB_cno", "status": "running",
|
||
"total": 432,
|
||
"pending": 120, "queued": 40, "running": 8, "converged": 261, "failed": 3,
|
||
"cold_run_converged": 220, "seed_step_converged": 41, "imported_converged": 0,
|
||
"waves": [ {"wave": 0, "total": 108, "converged": 108, "failed": 0}, ... ],
|
||
"avg_point_sec": 740.0, "eta_sec": 9600
|
||
}
|
||
}
|
||
```
|
||
|
||
- `avg_point_sec` / `eta_sec`:Phase 3 有 `elapsed_sec` 列后精确计算;
|
||
之前用 `tasks.completed_at - tasks.created_at` 均值近似(含排队等待,偏大,标注近似)。
|
||
- SQL 走现有覆盖索引 `idx_grid_points_wf_status`,单次聚合扫描,5s 轮询无压力。
|
||
|
||
#### ② `GET /api/workflows/:name/points` —— 逐点列表(G1)
|
||
|
||
参数:`status`、`method`、`wave`、`q`(点名子串)、`sort`(`wave|teff|max_relc|attempts`,默认 wave)、
|
||
`order`、`limit`(≤500,默认 100)、`offset`。
|
||
|
||
```json
|
||
{
|
||
"success": true, "message": "ok",
|
||
"data": {
|
||
"total": 432,
|
||
"points": [
|
||
{
|
||
"name": "t60000_g5.0_he-2_c-4_n-4_o-4",
|
||
"teff": 60000, "logg": 5.0, "loghe": -2, "logc": -4, "logn": -4, "logo": -4,
|
||
"cno_sum": -12, "wave": 0,
|
||
"status": "converged", "success_method": "seed_step", "attempt_count": 2,
|
||
"last_max_relc": 0.00043, "last_task_type": "seed_step",
|
||
"seed_point_name": "t60000_g5.0_he2_c-4_n-4_o-4",
|
||
"node_id": "node-a1b2", "last_completed_at": "2026-07-30T11:12:00Z",
|
||
"elapsed_sec": 126.4
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
- "最近一次尝试"用关联子查询取 `tasks` 最新行:
|
||
|
||
```sql
|
||
SELECT gp.*, t.max_relc AS last_max_relc, t.task_type AS last_task_type,
|
||
t.seed_point_name, t.node_id, t.completed_at AS last_completed_at,
|
||
t.error_message AS last_error
|
||
FROM grid_points gp
|
||
LEFT JOIN tasks t ON t.task_id = (
|
||
SELECT t2.task_id FROM tasks t2
|
||
WHERE t2.point_name = gp.name AND t2.workflow_name = gp.workflow_name
|
||
ORDER BY t2.completed_at IS NULL, t2.completed_at DESC, t2.created_at DESC
|
||
LIMIT 1)
|
||
WHERE gp.workflow_name = ?1 [AND ...filters]
|
||
ORDER BY gp.wave, gp.cno_sum, gp.teff
|
||
LIMIT ? LIMIT OFFSET ?;
|
||
```
|
||
|
||
- 配套新索引:`CREATE INDEX idx_tasks_point_wf_time ON tasks(point_name, workflow_name, completed_at);`
|
||
(迁移中补;432 点规模下无索引也能跑,加索引是为大网格兜底。)
|
||
- 备选(若大网格性能不够):把 `last_*` 字段反规范化进 `grid_points`,在 `record_task_report` 里顺手写——
|
||
但**默认不做**,避免迁移面扩大;先用 JOIN,压测后再议。
|
||
|
||
#### ③ `GET /api/workflows/:name/points/:point` —— 单点详情 + 阶段诊断(G7)
|
||
|
||
```json
|
||
{
|
||
"success": true, "message": "ok",
|
||
"data": {
|
||
"point": { /* 同 ② 的单点字段 */ },
|
||
"attempts": [
|
||
{"task_id": "...", "task_type": "cold_run", "seed_point_name": null,
|
||
"status": "failed", "max_relc": 954000.0, "atmosphere_has_nan": true,
|
||
"node_id": "node-a1b2", "error_message": "nl stage diverged...",
|
||
"created_at": "...", "completed_at": "..."},
|
||
{"task_id": "...", "task_type": "seed_step", "seed_point_name": "t60000_...",
|
||
"status": "completed", "max_relc": 0.00043, ...}
|
||
],
|
||
"conv": { /* data/seeds/<name>/conv.json 解析后的 ModelSummary;文件不在则 null */ }
|
||
}
|
||
}
|
||
```
|
||
|
||
- `conv` 读盘路径严格拼为 `seeds_dir/<point>/conv.json`(单层目录,point 先过白名单 +
|
||
canonicalize 归属兜底),解析失败/不存在 → `null`(前端降级为"诊断文件不可用")。
|
||
**不**把整文件塞进列表接口(体积控制)。
|
||
|
||
#### ④ `POST /api/workflows/:name/points/retry` —— 失败点重试(G6)
|
||
|
||
请求体:`{"names": ["t80000_..."], "all_failed": true}`(二选一)。
|
||
- 前置:工作流必须 `running`(否则 400,提示"请先启动工作流"——避免重置后无调度器消费的僵尸态)。
|
||
- 逻辑:新 db 函数 `reset_failed_points(wf, names|all)`:
|
||
`UPDATE grid_points SET status='pending', success_method=NULL WHERE workflow_name=? AND status='failed' [AND name IN (...)]`,
|
||
随后立即触发一次 `schedule_pending_tasks()`(不必等 30s tick)。
|
||
- 重试后的方法仍由调度器按种子可用性自动决定(大概率 seed_step,因为此时种子池已更丰富)——
|
||
这与领域语义一致,前端文案如实说明:"重试的点将由调度器自动选择冷启动或种子步进"。
|
||
|
||
#### ⑤ 列表接口内联计数(G2,卡片进度条用)
|
||
|
||
`GET /api/workflows` 的 `WorkflowSummary` 追加:
|
||
|
||
```json
|
||
{"name": "...", "status": "...", "description": "...",
|
||
"created_at": "...", "updated_at": "...",
|
||
"stats": {"total": 432, "converged": 261, "failed": 3, "running": 8,
|
||
"cold_run_converged": 220, "seed_step_converged": 41}}
|
||
```
|
||
|
||
- 实现:`list_workflows` 里对 `grid_points` 做一次 `GROUP BY workflow_name` 聚合再 map 合并(**单条 SQL,无 N+1**)。
|
||
- 未启动(无网格点)的工作流 `stats: null`,前端不显示进度条。
|
||
|
||
### 3.2 持久化变更(Phase 3,最小迁移)
|
||
|
||
> Phase 1/2 **零迁移**——全部基于现有列。以下为 Phase 3 的可选增强:
|
||
|
||
1. `grid_points` 加列:`elapsed_sec REAL`(最近一次成功/失败尝试的墙钟耗时)。
|
||
在 `record_task_report` 写入——`TaskReport.elapsed_sec` 本来就在手(`models.rs:318`),当前被丢弃。
|
||
2. `StageSummary` 携带 `last_iter / worst_depth / n_depths`(`models.rs:364-373` 扩字段,
|
||
`runner.rs:302-317` 处 `ConvCheckResult` 已有值,只是没搬过去)→ conv.json 与点详情获得迭代数。
|
||
3. 新表 `workflow_progress_snapshots(workflow_name TEXT, ts DATETIME, pending INT, queued INT,
|
||
running INT, converged INT, failed INT)`:由 `main.rs:111-197` 的 30s 后台循环顺手写一行;
|
||
保留策略:仅当计数相对上次快照有变化才写;定期清理 24h 前的行。供"进度-时间"曲线与精确 ETA。
|
||
|
||
### 3.3 明确不做 / 保持现状
|
||
|
||
- **不**改 pull 调度模型、不改 wave 语义、不改种子匹配算法——只读取与展示。
|
||
- **不**引入 WebSocket/SSE:5s 轮询 + 详情弹窗作用域轮询足够,复杂度与收益不匹配。
|
||
- **不**给 `/api/seed/:name` 开 Admin 权限(它是 Node 角色的二进制通道);前端只需 conv.json 的 JSON 内容,走 ③ 即可。
|
||
- `seeds` 表/`.7` 文件不暴露列表下载(超出本次范围,且属 Node 域)。
|
||
|
||
### 3.4 安全清单(对照全局安全规约)
|
||
|
||
- 所有新端点经现有 `auth_middleware`(Admin 角色)+ 全局限流;无新增鉴权旁路。
|
||
- 点名/工作流名全部过字符集白名单(`[A-Za-z0-9._-]`);conv.json 读盘路径不接受 `..`/绝对路径。
|
||
- 无新增密钥;错误信息用现有 `AppError`(Internal 不回显细节,`error.rs:28-34`)。
|
||
- 前端所有服务端字符串经 `escapeHtml`(列表/表格)或 `textContent`(conv.json、错误文本)渲染。
|
||
- retry 端点为写操作:仅 Admin、受限于 running 状态、参数白名单,无注入面(参数化查询)。
|
||
|
||
---
|
||
|
||
## 4. 前端实现设计
|
||
|
||
### 4.1 新增/改动文件
|
||
|
||
| 文件 | 改动 |
|
||
|---|---|
|
||
| `src/router.js`(新) | hash 路由:`parseHash()` → `{view:'home'|'workflow', name?}`;`hashchange` 监听 + 首载渲染;视图挂载/卸载生命周期钩子(停/启作用域轮询);登录门禁判断 |
|
||
| `src/views/home.js`(新) | 现 `index.html` 中 `main.main-container` 的首页内容收敛为模板函数 + 挂载逻辑(metric 卡/节点表/工作流卡列表),`startPolling()` 在此触发 |
|
||
| `src/views/workflowDetail.js`(新) | 详情页:任务控制条(分段进度条/计数/ETA/操作)+ Tab 容器;概览(tiles/方法归因/wave 分布/最近动态流)、点表格(关键行签名 diff,仿 nodesTable)、过滤器、热力矩阵(CSS grid 单元格)、点详情滑入面板。所有插值 `escapeHtml`;作用域 5s 轮询自管 |
|
||
| `src/api.js` | 新增 `fetchWorkflowStatsApi(name)`、`fetchWorkflowPointsApi(name, params)`、`fetchPointDetailApi(name, point)`、`retryPointsApi(name, body)`;全部经 `apiFetch` + `encodeURIComponent`,返回 raw Response |
|
||
| `src/components/workflows.js` | 卡片加进度条/计数/状态徽章扩充;头部包成进详情的锚链接;读取列表内联 `stats`;空 stats 降级 |
|
||
| `src/main.js` | 瘦身为全局接线:登录/主题/登出/全局模态(create-wf / view-yaml / reissue / confirm)/ header 刷新;视图级委托移入各 view |
|
||
| `src/state.js` | 全局轮询仅服务首页;视图切换时由 router 停启 |
|
||
| `index.html` | `main` 收敛为 `<div id="view-root">`(视图由 JS 渲染);保留登录遮罩/header/footer/全局模态/toast;新增 `#point-panel` 滑入面板骨架 |
|
||
| `src/style.css` | 新组件:`.wf-status-strip`(控制条 + 分段进度条 `.seg-progress` 五色段)、`.wf-tabs`(tab 栏,双主题)、`.method-badge.cold/.seed/.imported`(蓝/紫/中性,**区别于状态色**)、`.heatmap`/`.heatmap-cell`(状态色填充 + hover)、`.stage-chip`(阶段诊断小卡)、`.point-panel`(右侧滑入 + 遮罩)、`.activity-feed`(动态流条目淡入);全部走设计令牌 + `prefers-reduced-motion` 降级 |
|
||
|
||
### 4.2 必须遵循的现有约定(探查结论)
|
||
|
||
1. 事件一律 `data-*` 委托,不挂逐按钮监听(`main.js:377-388` 模式)。
|
||
2. 服务端字符串进 `innerHTML` 前必过 `escapeHtml`;整块文本用 `textContent`。
|
||
3. 模态:静态骨架 + `setupFocusTrap`,close 按钮类 `modal-close`(否则 Escape 映射失效,`modal.js:21`)。
|
||
4. 破坏性操作先 `showConfirm`,反馈只走 `showToast`,变更后 `fetchAllData()` 立即刷新。
|
||
5. 轮询下渲染:行/卡 keyed by `data-*` + 签名 diff + 高频单元格 `textContent` 原位刷新(`nodesTable.js:34-54` 模式),空态写入做 trim 比较防抖。
|
||
6. 样式只用令牌;状态复用 `.status-badge` 变体;进度复用 `.progress-bar-track/-fill`;表格复用 `.data-table` + `.col-*` + `.tabular-num`。
|
||
7. 工作流标识是 `name`(无数字 id);后端状态含 `initializing`,前端要显式映射。
|
||
|
||
---
|
||
|
||
## 5. 分期实施计划
|
||
|
||
### Phase 1 — 进度可见(MVP,零迁移)
|
||
- 后端:`stats` 端点(拆 queued)+ `points` 端点(JOIN 最近尝试 + 新索引)+ 列表内联 `stats`。
|
||
- 前端:**hash 路由 + 视图化重构** + 卡片进度条/计数/徽章 + 工作流详情页(任务控制条 + 「概览」「网格点明细」两 Tab + 过滤器/分页 + 最近动态流)。
|
||
- 验收:深链 `#/workflows/sdB_cno` 打开详情页,能看到 432 点逐行状态、每点方法/max_relc/尝试数/种子来源,进度条随轮询而动。
|
||
|
||
### Phase 2 — 诊断与归因
|
||
- 后端:`points/:point` 端点(尝试历史 + conv.json 解析)+ `retry` 端点 + db `reset_failed_points`。
|
||
- 前端:点详情滑入面板(尝试历史 + 阶段链诊断 + 错误)+「收敛性分析」热力 Tab + 「重试失败点」按钮。
|
||
- 验收:能回答"这个点为什么失败/被谁救回",能看到冷启动成功参数区,能一键重试失败点。
|
||
|
||
### Phase 3 — 时序与精算(可选增强)
|
||
- 后端:迁移(`elapsed_sec` 列、`StageSummary.last_iter`)+ `workflow_progress_snapshots` + ETA 精算。
|
||
- 前端:概览 ETA 精确化 + 进度-时间 sparkline + 点表耗时列去"~"。
|
||
|
||
---
|
||
|
||
## 6. 待确认的开放问题
|
||
|
||
1. Phase 范围:三期全做,还是先交付 Phase 1+2?
|
||
2. 「重试失败点」是否需要(它会触发真实计算任务)?默认仅 running 时可用。
|
||
3. 热力矩阵默认轴 Teff × cno_sum 是否符合你的物理直觉?(sdB_cno 当前网格 logg/logHe/CNO 各只有单值,实际是 Teff 一维——多值网格下矩阵才有意义。)
|
||
4. conv.json 之外是否还需要在点详情里提供逐阶段 `.err`/`fort.9` 原始文件下载(目前节点归档 `data/result` 有 LRU 200 上限,服务端只有 conv.json)?
|
||
</content> |