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,122 @@
|
||||
/* DCTS Dashboard Nodes Table Component (增量 DOM 更新与 Skeleton) */
|
||||
|
||||
import { escapeHtml } from '../api.js';
|
||||
|
||||
export function renderNodesTableSkeleton() {
|
||||
const tbody = document.getElementById('nodes-table-body');
|
||||
if (!tbody) return;
|
||||
|
||||
const skeletonRows = Array.from({ length: 4 }).map(() => `
|
||||
<tr class="skeleton-row">
|
||||
<td><div class="skeleton-shimmer skeleton-text" style="width: 110px;"></div></td>
|
||||
<td><div class="skeleton-shimmer skeleton-text" style="width: 90px;"></div></td>
|
||||
<td><div class="skeleton-shimmer skeleton-text" style="width: 60px;"></div></td>
|
||||
<td><div class="skeleton-shimmer skeleton-text" style="width: 80px;"></div></td>
|
||||
<td><div class="skeleton-shimmer skeleton-badge"></div></td>
|
||||
<td><div class="skeleton-shimmer skeleton-badge"></div></td>
|
||||
<td><div class="skeleton-shimmer skeleton-text" style="width: 70px;"></div></td>
|
||||
<td><div class="skeleton-shimmer skeleton-text" style="width: 60px;"></div></td>
|
||||
</tr>
|
||||
`).join('');
|
||||
|
||||
tbody.innerHTML = skeletonRows;
|
||||
}
|
||||
|
||||
export function renderNodesTable(nodes) {
|
||||
const tbody = document.getElementById('nodes-table-body');
|
||||
const countBadge = document.getElementById('node-count-badge');
|
||||
|
||||
if (countBadge) {
|
||||
countBadge.textContent = `${nodes ? nodes.length : 0} 个节点`;
|
||||
}
|
||||
if (!tbody) return;
|
||||
|
||||
if (!nodes || nodes.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="8" class="empty-cell">
|
||||
暂无计算节点记录。启动 DCTS Node Worker 提交注册申请后,在此审批授权接入集群。
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建新 HTML
|
||||
const html = nodes.map(node => {
|
||||
const nodeId = escapeHtml(node.node_id || node.id || 'N/A');
|
||||
const hostName = escapeHtml(node.host_name || node.host || '127.0.0.1');
|
||||
|
||||
const isPending = node.status === 'pending_approval';
|
||||
const isOnline = node.status === 'online' || node.status === 'active';
|
||||
|
||||
let statusBadge;
|
||||
if (isPending) {
|
||||
statusBadge = '<span class="status-badge warning"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="vertical-align: -1px; margin-right: 3px;"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>待审批</span>';
|
||||
} else if (isOnline) {
|
||||
statusBadge = '<span class="status-badge online">在线</span>';
|
||||
} else {
|
||||
statusBadge = '<span class="status-badge offline">离线</span>';
|
||||
}
|
||||
|
||||
let tokenBadge;
|
||||
if (isPending) {
|
||||
tokenBadge = '<span class="status-badge secondary">待授权</span>';
|
||||
} else if (node.token_status === 'active') {
|
||||
tokenBadge = '<span class="status-badge online">有效</span>';
|
||||
} else if (node.token_status === 'revoked') {
|
||||
tokenBadge = '<span class="status-badge offline">已吊销</span>';
|
||||
} else {
|
||||
tokenBadge = '<span class="status-badge secondary">无凭据</span>';
|
||||
}
|
||||
|
||||
const lastHeartbeat = node.last_heartbeat ? escapeHtml(new Date(node.last_heartbeat).toLocaleTimeString('zh-CN')) : '—';
|
||||
const activeSlots = Number(node.active_slots || 0);
|
||||
const maxSlots = Number(node.max_slots || 4);
|
||||
const cpuUsage = Number(node.cpu_usage || 0).toFixed(1);
|
||||
const memUsage = Number(node.memory_usage || 0).toFixed(1);
|
||||
|
||||
let actionBtns;
|
||||
if (isPending) {
|
||||
actionBtns = `
|
||||
<div class="action-cell-wrap">
|
||||
<button class="btn-action-icon btn-action-approve" data-node-action="approve" data-node-id="${nodeId}" title="同意节点接入申请" aria-label="同意节点接入申请">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>
|
||||
</button>
|
||||
<button class="btn-action-icon btn-action-danger" data-node-action="reject" data-node-id="${nodeId}" title="拒绝节点接入申请" aria-label="拒绝节点接入申请">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
actionBtns = `
|
||||
<div class="action-cell-wrap">
|
||||
<button class="btn-action-icon btn-action-reissue" data-node-action="reissue" data-node-id="${nodeId}" title="重新颁发专属 Token" aria-label="重新颁发专属 Token">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M23 4v6h-6M1 20v-6h6"/><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/></svg>
|
||||
</button>
|
||||
<button class="btn-action-icon btn-action-danger" data-node-action="revoke" data-node-id="${nodeId}" title="吊销 Token 凭据" aria-label="吊销 Token 凭据">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><circle cx="12" cy="12" r="10"/><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
<tr data-node-row-id="${nodeId}">
|
||||
<td class="col-node-id node-id" title="${nodeId}">${nodeId}</td>
|
||||
<td class="col-hostname" title="${hostName}">${hostName}</td>
|
||||
<td class="col-slots tabular-num">${activeSlots} / ${maxSlots}</td>
|
||||
<td class="col-usage tabular-num">${cpuUsage}% / ${memUsage}%</td>
|
||||
<td class="col-status">${statusBadge}</td>
|
||||
<td class="col-token">${tokenBadge}</td>
|
||||
<td class="col-heartbeat tabular-num">${lastHeartbeat}</td>
|
||||
<td class="col-actions">${actionBtns}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
// 增量 Diff 判断:如果内容一致,避重刷;否则进行更新
|
||||
if (tbody.innerHTML.trim() !== html.trim()) {
|
||||
tbody.innerHTML = html;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user