Files
DCTS/dashboard/src/state.js
T
fmq d16b3d3cdc feat(all): 数据库模块化拆分与版本化迁移、任务引擎命名体系收敛、物理输出校验加固与用户配置接通
- server/db: 拆 4929 行 db.rs 单体为 db/ 目录,migrations.rs 引入 PRAGMA user_version
    版本化迁移运行器(M1~M13)
  - 任务引擎 Phase 6/7b/7c 改名收敛:EngineStageConfig→PhaseConfig、StagePolicy→ResumePolicy、
    Converged→Completed、删除 task_type 列、success_method 拆 tlusty_/synspec_ 双列、
    新增 tlusty_status/synspec_status 半失败阶段守卫
  - 科学正确性加固:conv_check 任意行 NaN/Inf/溢出判无效(0 行容忍)、新增 spec_is_valid
    校验 SYNSPEC 脏谱、itek_history 逐次迭代全量保真、fmt_abn powf 溢出饱和
  - 用户配置真正接通:tlusty_chain/tlusty_input 由死字段经 调度器→TaskSpec→executor→runner
    透传生效;config 加载期 validate + deny_unknown_fields + 解析失败记 warn
  - 调度修复:H1 活锁(pending_strategies 跳过已失败策略)、种子查找错误不再静默降级冷启动
  - dashboard: 阶段配置面板 tlusty_stage/synspec_stage、"已完成"标签、迭代诊断展示
  - docs: 新增 database_refactor_design.md,同步 database/api/PIPELINE/workflow_detail
2026-08-06 20:51:21 +08:00

147 lines
5.4 KiB
JavaScript
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.
/* DCTS Dashboard Application State & Polling Scheduler
*
* 首页全局轮询:5s 基准、指数退避上限 60s、Tab 切入后台自动暂停——
* 均由 utils/polling.js 的 createPoller 统一承担(与详情页作用域轮询共用一套实现)。
*/
import { fetchClusterStatus, fetchWorkflowsList, fetchNodesList } from './api.js';
import { renderNodesTable } from './components/nodesTable.js';
import { renderWorkflows } from './components/workflows.js';
import { createPoller } from './utils/polling.js';
import { logError } from './utils/errors.js';
let serverOnline = false;
// 全局轮询:fetchAllData 返回布尔(以 cluster 状态能否获取为整体健康判定)。
const poller = createPoller(fetchAllData, { baseMs: 5000, maxMs: 60000 });
export function isAppPolling() {
return poller.isRunning();
}
export function startPolling() {
poller.start();
}
export function stopPolling() {
poller.stop();
}
/** 手动立即触发一次全量拉取(兼容旧调用方:header 刷新 / 建工作流后 / wfActions 已改由
* isAppPolling 守卫,本函数保留给需要无条件刷新的调用点)。 */
export function scheduleFetchData() {
poller.triggerNow();
}
// 返回主状态拉取是否成功(用于轮询退避计数)。三项并行拉取,
// 以 cluster 状态能否成功获取作为整体健康判定。
export async function fetchAllData() {
const results = await Promise.allSettled([
fetchStatus(),
fetchWorkflows(),
fetchNodes(),
]);
// fetchStatus 内部已 try/catchrejected 仅在极异常情况;以第一项(状态)成败为准。
return results[0].status === 'fulfilled' && serverOnline;
}
export async function fetchStatus() {
try {
const data = await fetchClusterStatus();
updateUI(data);
updateServerStatus(true);
} catch (err) {
logError(err, '获取 DCTS 状态失败');
updateServerStatus(false);
}
}
export async function fetchWorkflows() {
try {
const json = await fetchWorkflowsList();
if (json.success && json.data) {
renderWorkflows(json.data);
}
} catch (err) {
logError(err, '获取工作流列表失败');
}
}
export async function fetchNodes() {
try {
const json = await fetchNodesList();
if (json.success && Array.isArray(json.data)) {
renderNodesTable(json.data);
}
} catch (err) {
logError(err, '获取计算节点列表失败');
}
}
export function updateServerStatus(online) {
serverOnline = !!online;
const box = document.getElementById('status-indicator-box');
const text = document.getElementById('server-status-text');
if (!box || !text) return;
if (online) {
box.className = 'status-indicator online';
text.textContent = '服务端正常运行';
} else {
box.className = 'status-indicator offline';
text.textContent = '服务端连接中断';
}
}
export function updateUI(data) {
if (!data) return;
const activeNodesCount = data.nodes_online ?? data.active_nodes_count ?? (data.nodes ? data.nodes.length : 0);
const activeSlots = data.total_active_slots ?? data.occupied_slots ?? 0;
const maxSlots = data.total_max_slots ?? 0;
const valActiveNodes = document.getElementById('val-active-nodes');
const valTotalSlots = document.getElementById('val-total-slots');
const slotsProgressFill = document.getElementById('slots-progress-fill');
if (valActiveNodes) valActiveNodes.textContent = activeNodesCount;
if (valTotalSlots) {
valTotalSlots.textContent = `${activeSlots} / ${maxSlots} CPU 槽位占用`;
}
if (slotsProgressFill) {
const pct = maxSlots > 0 ? Math.min(100, Math.max(0, Math.round((activeSlots / maxSlots) * 100))) : 0;
slotsProgressFill.style.width = `${pct}%`;
}
const stats = data.grid_stats || {};
// 后端已将 queued 从 pending 拆出(详情页需区分"未入队/排队中");
// 首页"待计算网格点"保持传统合并口径 = pending + queued。
const pending = (stats.pending ?? 0) + (stats.queued ?? 0);
const running = stats.running ?? 0;
// 7c 改名:后端权威键为 stats.completed(原 converged)。doneOk=成功完成,failed=失败,
// doneTotal=两者合计(所有已终止点)。
const doneOk = stats.completed ?? 0;
const failed = stats.failed ?? 0;
const doneTotal = doneOk + failed;
const valPendingTasks = document.getElementById('val-pending-tasks');
const valRunningTasks = document.getElementById('val-running-tasks');
if (valPendingTasks) valPendingTasks.textContent = pending.toLocaleString();
if (valRunningTasks) {
valRunningTasks.textContent = `${running.toLocaleString()} 个任务计算中`;
}
const valCompletedTasks = document.getElementById('val-completed-tasks');
const valCompletionRate = document.getElementById('val-completion-rate');
const convergedProgressFill = document.getElementById('converged-progress-fill');
if (valCompletedTasks) valCompletedTasks.textContent = doneTotal.toLocaleString();
if (valCompletionRate) {
valCompletionRate.textContent = `${doneOk.toLocaleString()} 完成 / ${failed.toLocaleString()} 未完成`;
}
if (convergedProgressFill) {
const totalModels = pending + running + doneTotal;
const pct = totalModels > 0 ? Math.min(100, Math.max(0, Math.round((doneOk / totalModels) * 100))) : 0;
convergedProgressFill.style.width = `${pct}%`;
}
// 工作流总数由 renderWorkflows 从列表单独写入,避免与此处双源覆盖造成数字抖动
}