feat(server,dashboard): 引入多工作流数据隔离、安全中间件与前端 ESM 模块化重构
- server: 实现按 workflow_name 的多工作流数据隔离与旧数据库平滑迁移机制 - server: 新增 API Key 认证(auth)、限流中间件(rate_limit)与运维备份接口(admin) - server: 统一 AppError 错误处理体系,重构调度器 scheduler 支持工作流级重置与抢占 - node: 节点 ID 缺失时自动生成随机 UUID,原生支持 `docker compose --scale node=N` 动态扩容 - dashboard: 前端模块化重构(state/api/components),升级 CSS 变量设计系统与 Toast 通知 - docker/docs: 更新 /healthz 健康检查、部署脚本 IP 配置及数据库设计文档
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
/* DCTS Dashboard Application State & Polling Scheduler */
|
||||
|
||||
import { fetchClusterStatus, fetchWorkflowsList, fetchNodesList } from './api.js';
|
||||
import { renderNodesTable } from './components/nodesTable.js';
|
||||
import { renderWorkflows } from './components/workflows.js';
|
||||
|
||||
let isPolling = false;
|
||||
let pollTimer = null;
|
||||
|
||||
export function isAppPolling() {
|
||||
return isPolling;
|
||||
}
|
||||
|
||||
export function startPolling() {
|
||||
if (isPolling) return;
|
||||
isPolling = true;
|
||||
scheduleFetchData();
|
||||
}
|
||||
|
||||
export function stopPolling() {
|
||||
isPolling = false;
|
||||
if (pollTimer) {
|
||||
clearTimeout(pollTimer);
|
||||
pollTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function scheduleFetchData() {
|
||||
if (pollTimer) clearTimeout(pollTimer);
|
||||
await fetchAllData();
|
||||
if (isPolling) {
|
||||
pollTimer = setTimeout(scheduleFetchData, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchAllData() {
|
||||
await Promise.all([
|
||||
fetchStatus(),
|
||||
fetchWorkflows(),
|
||||
fetchNodes(),
|
||||
]);
|
||||
}
|
||||
|
||||
export async function fetchStatus() {
|
||||
try {
|
||||
const data = await fetchClusterStatus();
|
||||
updateUI(data);
|
||||
updateServerStatus(true);
|
||||
} catch (err) {
|
||||
console.warn('获取 DCTS 状态失败:', err);
|
||||
updateServerStatus(false);
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchWorkflows() {
|
||||
try {
|
||||
const json = await fetchWorkflowsList();
|
||||
if (json.success && json.data) {
|
||||
renderWorkflows(json.data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('获取工作流列表失败:', err);
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchNodes() {
|
||||
try {
|
||||
const json = await fetchNodesList();
|
||||
if (json.success && Array.isArray(json.data)) {
|
||||
renderNodesTable(json.data);
|
||||
return;
|
||||
}
|
||||
const data = await fetchClusterStatus();
|
||||
if (data.nodes) {
|
||||
renderNodesTable(data.nodes);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('获取计算节点列表失败:', err);
|
||||
}
|
||||
}
|
||||
|
||||
export function updateServerStatus(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');
|
||||
if (valActiveNodes) valActiveNodes.textContent = activeNodesCount;
|
||||
if (valTotalSlots) {
|
||||
valTotalSlots.textContent = `${activeSlots} / ${maxSlots} CPU 槽位占用`;
|
||||
}
|
||||
|
||||
const stats = data.grid_stats || {};
|
||||
const pending = stats.pending ?? data.pending_points ?? 0;
|
||||
const running = stats.running ?? data.running_points ?? 0;
|
||||
const converged = stats.converged ?? data.converged_points ?? 0;
|
||||
const failed = stats.failed ?? data.failed_points ?? 0;
|
||||
const completed = converged + 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');
|
||||
if (valCompletedTasks) valCompletedTasks.textContent = completed.toLocaleString();
|
||||
if (valCompletionRate) {
|
||||
valCompletionRate.textContent = `${converged.toLocaleString()} 收敛 / ${failed.toLocaleString()} 未收敛`;
|
||||
}
|
||||
|
||||
const valTotalWorkflows = document.getElementById('val-total-workflows');
|
||||
if (valTotalWorkflows && data.total_workflows !== undefined) {
|
||||
valTotalWorkflows.textContent = data.total_workflows;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user