Files
DCTS/docs/workflow_detail_design.md
T
fmq 1bfa240cb0 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
2026-07-31 01:34:05 +08:00

388 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 工作流执行可视化 —— 功能重设计文档
> 目标:把"恒星大气网格工作流"面板从当前的 **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/SSE5s 轮询 + 详情弹窗作用域轮询足够,复杂度与收益不匹配。
- **不**给 `/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>