/* 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/catch,rejected 仅在极异常情况;以第一项(状态)成败为准。 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 从列表单独写入,避免与此处双源覆盖造成数字抖动 }