feat(all): 任务引擎双阶段解耦、僵尸涡旋修复、动态 CPU 配额与前端详情页重构

将 TLUSTY/SYNSPEC 拆为各自独立的 enabled/policy/strategies 阶段,
以策略链自动弹栈取代单级 seed_step 布尔回退;定向修复 2026-08-02
僵尸任务涡旋事故;新增节点并发配额热调;前端详情页从 1412 行巨型
视图拆为薄控制器 + detail 子模块,并补齐工具层与单测。

引擎与调度(task_engine_decoupling_design.md)
- models.rs: 新增 StagePolicy / EngineStageConfig / TaskSpec 阶段字段、
  normalize_compat() 校正旧版在途消息策略链、failed_stage 归因
- scheduler.rs: resolve_dispatchable_chain 派发门控、
  trigger_strategy_fallback 按 failed_stage 精确弹栈;启动期
  force_recompute/skip_converged(默认)/skip_failed 三策略
- db.rs: tasks 表 +7 列持久化阶段配置;终态守卫
  (mark_grid_point_running 仅 pending/queued→running;
  record_task_report 拒绝迟到失败翻黑 converged);策略弹栈快照

僵尸涡旋修复(runbook-20260802-zombie-vortex-fix.md)
- 全链路跨库活性交叉校验:派发/claim/孤儿回收/回退统一查 MQ 队列活性,
  活则放行、死则清僵尸,结构性消除"每点重复派发"
- stop/重启卫生:清队列同步 delete_tasks_by_ids,杜绝遗留 pending 行
- report_task: 幂等吸收 + 409 区分迟到冗余结果,仅 state_changed 时回退
- MQ: NULL workflow_name 回填 __legacy__、requeue 后迟到上报被 403 竞态修复

动态 CPU 配额(dynamic_cpu_slots_design.md)
- admin.rs: POST /admin/nodes/:id/quota(Option<Option<i32>> 区分
  缺字段/显式 null);nodes 表 +admin_max_slots
- worker.rs: effective_max_slots = min(admin, physical),心跳下发原子生效

科学产物保全(tlusty_result_artifacts.md)
- runner.rs: SYNSPEC 启动前快照 fort.12/fort.14 → .bfac/.emflux 防覆盖
- 半失败点(大气收敛+光谱失败)改判 Failed 并写入 note;仅 SYNSPEC
  场景不再恒判失败;撤销归档 LRU 200 上限改为永久保留
- executor.rs: 透传 synspec_params 数值参数(此前固定 None)

前端(dashboard/)
- workflowDetail.js 1412→328 行,拆出 views/detail/{ctx,overview,
  pointsTable,parSets,pointPanel}.js,AbortController 治理监听/请求生命周期
- 删除 wfActions.js,新增 wfEnginePanel.js(双阶段三维配置编辑面板)
- 新增 utils/{errors,format,icons,polling,yamlStage}.js 纯函数模块
- 路由级动态 import 代码分割;节点配额三点菜单 + Modal 管理
- 首次引入 node:test 单测(format/polling/yamlStage/psCache,644 行)
- 系统性补齐 a11y:skip-link、ARIA、Tab 键盘漫游、toast 关闭、退出动画

文档与工具
- 新增 6 篇设计/调研:引擎解耦、动态配额、涡旋 runbook、
  光谱正确性分析、收敛判断、产物归档
- PIPELINE/design/api/database 等协同重写为分布式 C/S 架构口径
- scripts/fetch_results.sh 跨节点产物备份;import_results 按 cno 升序导入
- workflows/sdB_cno.yaml: 新增 tlusty/synspec_stage 配置块,修正 wstart 笔误
This commit is contained in:
fmq
2026-08-04 23:40:52 +08:00
parent c8fd24b120
commit cd370d88e7
74 changed files with 13386 additions and 3033 deletions
+100 -137
View File
@@ -6,7 +6,9 @@
<title>DCTS — 分布式恒星大气网格计算控制台</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&display=swap" rel="stylesheet">
<!-- 字重精简:Inter 去掉未使用的 300,Space Grotesk 去掉未使用的 500(仅 600/700 用于标题),
减少 2 个字体文件下载。display=swap 避免 FOIT。 -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Space+Grotesk:wght@600;700&display=swap" rel="stylesheet">
<script>
/* 首帧前落主题,避免 prefers-color-scheme 先绘制导致深/浅闪变 (FOUC) */
(function () {
@@ -20,18 +22,25 @@
<link rel="stylesheet" href="/src/style.css" />
</head>
<body>
<a href="#view-root" class="skip-link">跳到主内容</a>
<div id="app">
<!-- ===== 登录遮罩(鉴权) ===== -->
<div id="login-overlay" class="login-overlay">
<div id="login-overlay" class="login-overlay" role="dialog" aria-modal="true" aria-labelledby="login-title" aria-describedby="login-subtitle">
<div class="login-card card">
<div class="login-header">
<h2>DCTS 控制台登录</h2>
<p>请输入管理员密码以访问管理控制台</p>
<h2 id="login-title">DCTS 控制台登录</h2>
<p id="login-subtitle">请输入管理员密码以访问管理控制台</p>
</div>
<form id="form-login" class="stack-col stack-col-md">
<div class="form-group">
<label for="input-admin-token">管理员密码</label>
<input type="password" id="input-admin-token" class="form-input" placeholder="输入管理员密码..." required autocomplete="current-password" />
<div class="input-with-toggle">
<input type="password" id="input-admin-token" class="form-input" placeholder="输入管理员密码..." required autocomplete="current-password" />
<button type="button" class="btn-toggle-password" aria-label="显示密码" title="显示/隐藏密码">
<svg class="icon-eye-open" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
<svg class="icon-eye-closed hidden" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94"/><path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
</button>
</div>
</div>
<div id="login-error-msg" class="login-error-msg alert-error hidden"></div>
<button type="submit" id="btn-submit-login" class="btn btn-primary">登录</button>
@@ -111,133 +120,6 @@
<div id="view-root"></div>
</main>
<!-- 首页视图模板:router 挂载时克隆(见 src/views/home.js),ID 为渲染锚点,保持原样 -->
<template id="view-home-template">
<!-- 核心指标卡片 -->
<section class="metrics-grid">
<div class="metric-card card">
<div class="metric-header">
<span class="metric-title">在线计算节点</span>
<div class="metric-icon nodes-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="2" width="20" height="8" rx="2" ry="2"/>
<rect x="2" y="14" width="20" height="8" rx="2" ry="2"/>
<line x1="6" y1="6" x2="6.01" y2="6"/>
<line x1="6" y1="18" x2="6.01" y2="18"/>
</svg>
</div>
</div>
<div class="metric-body">
<span class="metric-value" id="val-active-nodes">0</span>
<span class="metric-sub" id="val-total-slots">0 / 0 CPU 槽位占用</span>
<div class="progress-bar-track" aria-hidden="true">
<div class="progress-bar-fill" id="slots-progress-fill"></div>
</div>
</div>
</div>
<div class="metric-card card">
<div class="metric-header">
<span class="metric-title">待计算网格点</span>
<div class="metric-icon pending-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<polyline points="12 6 12 12 16 14"/>
</svg>
</div>
</div>
<div class="metric-body">
<span class="metric-value" id="val-pending-tasks">0</span>
<span class="metric-sub" id="val-running-tasks">0 个任务计算中</span>
</div>
</div>
<div class="metric-card card">
<div class="metric-header">
<span class="metric-title">已计算网格模型</span>
<div class="metric-icon success-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
<polyline points="22 4 12 14.01 9 11.01"/>
</svg>
</div>
</div>
<div class="metric-body">
<span class="metric-value" id="val-completed-tasks">0</span>
<span class="metric-sub" id="val-completion-rate">网格总数: 0</span>
<div class="progress-bar-track" aria-hidden="true">
<div class="progress-bar-fill progress-fill-emerald" id="converged-progress-fill"></div>
</div>
</div>
</div>
<div class="metric-card card">
<div class="metric-header">
<span class="metric-title">计算工作流</span>
<div class="metric-icon workflow-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polygon points="12 2 2 7 12 12 22 7 12 2"/>
<polyline points="2 17 12 22 22 17"/>
<polyline points="2 12 12 17 22 12"/>
</svg>
</div>
</div>
<div class="metric-body">
<span class="metric-value" id="val-total-workflows">0</span>
<span class="metric-sub" id="val-active-wf">系统已就绪</span>
</div>
</div>
</section>
<!-- 主要内容双栏 -->
<div class="content-grid">
<!-- 左侧:计算节点集群与凭据管理 (统一全功能控制大表) -->
<div class="panel card">
<div class="panel-header">
<h2>计算节点集群与凭据管理</h2>
<div class="row-wrap" style="align-items: center; gap: 0.5rem;">
<span class="badge" id="node-count-badge">0 个节点</span>
<button id="btn-refresh-nodes" class="btn btn-secondary btn-sm">刷新</button>
</div>
</div>
<div class="panel-body table-responsive">
<table class="data-table">
<thead>
<tr>
<th class="col-node-id">节点 ID</th>
<th class="col-slots">CPU 槽位</th>
<th class="col-usage">CPU/内存</th>
<th class="col-status">节点状态</th>
<th class="col-token">Token 凭据</th>
<th class="col-heartbeat">心跳时间</th>
<th class="col-actions">管理操作</th>
</tr>
</thead>
<tbody id="nodes-table-body">
<tr>
<td colspan="7" class="empty-cell">正在连接服务端获取计算节点...</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 右侧:工作流管理 -->
<div class="stack-col stack-col-lg">
<!-- 工作流面板 -->
<div class="panel card">
<div class="panel-header">
<h2>恒星大气网格工作流</h2>
</div>
<div class="panel-body">
<div id="workflows-list" class="workflows-container">
<div class="empty-cell">正在获取工作流配置...</div>
</div>
</div>
</div>
</div>
</div>
</template>
<!-- 创建 / 编辑工作流 模态弹窗 -->
<div id="modal-create-wf" class="modal-backdrop hidden" role="dialog" aria-modal="true" aria-labelledby="modal-title">
@@ -250,8 +132,12 @@
</div>
<form id="form-create-wf" class="stack-col stack-col-md">
<div class="form-group">
<label for="input-wf-name">工作流名称 (Identifier)</label>
<input type="text" id="input-wf-name" class="form-input" placeholder="例如: sdB_cno" required />
<label for="input-wf-name">工作流名称 (Identifier) <span class="req-mark" aria-hidden="true">*</span></label>
<input type="text" id="input-wf-name" class="form-input" placeholder="例如: sdB_cno" required
pattern="[A-Za-z0-9._\-]+" maxlength="64" autocomplete="off"
aria-describedby="wf-name-hint" />
<small id="wf-name-hint" class="field-hint">仅支持字母、数字、点、下划线与连字符,最长 64 字符</small>
<small id="wf-name-error" class="field-error hidden" role="alert"></small>
</div>
<div class="form-group">
<label for="input-wf-desc">描述信息</label>
@@ -259,10 +145,11 @@
</div>
<div class="form-group">
<div class="row-wrap" style="justify-content: space-between; align-items: center;">
<label for="input-wf-yaml">YAML 配置内容 (GridConfig)</label>
<label for="input-wf-yaml">YAML 配置内容 (GridConfig) <span class="req-mark" aria-hidden="true">*</span></label>
<button type="button" id="btn-load-preset-yaml" class="btn btn-secondary btn-sm">填入默认示例</button>
</div>
<textarea id="input-wf-yaml" class="form-textarea" required placeholder="请粘贴工作流 YAML 参数网格定义..."></textarea>
<textarea id="input-wf-yaml" class="form-textarea" required placeholder="请粘贴工作流 YAML 参数网格定义..." aria-describedby="wf-yaml-hint"></textarea>
<small id="wf-yaml-hint" class="field-hint">定义 grid 参数网格与 chain 计算链,保存前服务端会校验 YAML 结构</small>
</div>
<div class="modal-footer">
<button type="button" id="cancel-create-wf" class="btn btn-secondary">取消</button>
@@ -304,6 +191,10 @@
<div class="modal-footer yaml-editor-footer">
<span id="yaml-editor-meta" class="yaml-editor-meta tabular-num"></span>
<span id="yaml-editor-lock" class="yaml-editor-lock hidden"></span>
<button type="button" id="btn-export-yaml" class="btn btn-secondary btn-sm" title="下载导出当前 YAML 配置文件">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
导出配置
</button>
<button type="button" id="btn-edit-yaml" class="btn btn-primary btn-sm">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></svg>
编辑
@@ -340,13 +231,85 @@
</div>
</div>
<!-- 节点详情与管理模态弹窗(三点菜单入口:配额调整 / 启停 / 重发 Token) -->
<div id="modal-node-manage" class="modal-backdrop hidden" role="dialog" aria-modal="true" aria-labelledby="node-manage-title">
<div class="modal-card">
<div class="modal-header">
<h3 id="node-manage-title">节点详情与管理</h3>
<button id="node-manage-close-btn" class="modal-close" aria-label="关闭">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
</div>
<div class="modal-body">
<!-- 基础信息 + 槽位展示 -->
<div id="node-manage-overview" class="node-manage-section">
<div class="form-group">
<label>节点 ID</label>
<div id="node-manage-id" class="node-manage-value mono"></div>
</div>
<div class="node-manage-grid">
<div class="form-group">
<label>节点状态</label>
<div id="node-manage-status"></div>
</div>
<div class="form-group">
<label>CPU 槽位 (占用/配额/最大)</label>
<div id="node-manage-slots" class="node-manage-value tabular-num"></div>
</div>
<div class="form-group">
<label>CPU / 内存</label>
<div id="node-manage-usage" class="node-manage-value tabular-num"></div>
</div>
<div class="form-group">
<label>心跳时间</label>
<div id="node-manage-heartbeat" class="node-manage-value"></div>
</div>
</div>
</div>
<!-- 修改配额 (CPU 核数) -->
<div class="node-manage-section">
<h4 class="node-manage-section-title">并发槽位配额</h4>
<p class="text-hint-mb">限制该节点最大并发领用的任务数(CPU 核数)。正在运行的任务不会被中断,等其结束后自然回落到新配额以下;设为 0 可暂停接收新任务,留空清除限制恢复物理上限。</p>
<div class="row-wrap" style="gap: 0.5rem; align-items: flex-end;">
<div class="form-group" style="flex: 1; min-width: 120px;">
<label for="node-manage-quota-input">配额上限</label>
<input type="number" id="node-manage-quota-input" class="form-input" min="0" step="1" placeholder="留空 = 无限制" />
</div>
<button type="button" id="btn-node-save-quota" class="btn btn-primary btn-sm">保存配额</button>
<button type="button" id="btn-node-clear-quota" class="btn btn-secondary btn-sm">清除配额</button>
</div>
</div>
<!-- 节点调度控制 -->
<div class="node-manage-section">
<h4 class="node-manage-section-title">调度控制</h4>
<div class="row-wrap" style="gap: 0.5rem;">
<button type="button" id="btn-node-disable" class="btn btn-secondary btn-sm">停用节点(暂停分发)</button>
<button type="button" id="btn-node-enable" class="btn btn-primary btn-sm">启用节点(恢复分发)</button>
</div>
</div>
<!-- 安全管理 -->
<div class="node-manage-section">
<h4 class="node-manage-section-title">安全管理</h4>
<p class="text-hint-mb">重发后旧 Token 立即失效,该节点下次心跳/领用将 401 退出。请随后把新 Token 同步到节点。</p>
<button type="button" id="btn-node-reissue" class="btn btn-secondary btn-sm">重新颁发专属 Token</button>
</div>
</div>
<div class="modal-footer">
<button type="button" id="btn-close-node-manage" class="btn btn-secondary">关闭</button>
</div>
</div>
</div>
<!-- 页脚 -->
<footer class="footer">
<p>DCTS 分布式恒星大气计算系统 &copy; 2026 TLUSTY/SYNSPEC Project</p>
</footer>
<!-- ===== 全局 Toast 提示容器 ===== -->
<div id="toast-container" class="toast-container"></div>
<div id="toast-container" class="toast-container" aria-live="polite" aria-atomic="true"></div>
<!-- ===== 通用自定义 Confirm 模态弹窗 ===== -->
<div id="modal-confirm" class="modal-backdrop hidden" role="dialog" aria-modal="true" aria-labelledby="confirm-modal-title" aria-describedby="confirm-modal-msg">