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,95 @@
|
||||
/* DCTS Dashboard Workflows Component */
|
||||
|
||||
import { escapeHtml } from '../api.js';
|
||||
|
||||
export function renderWorkflowsSkeleton() {
|
||||
const container = document.getElementById('workflows-list');
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = Array.from({ length: 2 }).map(() => `
|
||||
<div class="workflow-card skeleton-card">
|
||||
<div class="workflow-header">
|
||||
<div>
|
||||
<div class="skeleton-shimmer skeleton-title" style="width: 140px; height: 18px; margin-bottom: 8px;"></div>
|
||||
<div class="skeleton-shimmer skeleton-text" style="width: 220px; height: 14px;"></div>
|
||||
</div>
|
||||
<div class="skeleton-shimmer skeleton-badge"></div>
|
||||
</div>
|
||||
<div class="workflow-actions" style="margin-top: 16px;">
|
||||
<div class="skeleton-shimmer skeleton-btn"></div>
|
||||
<div class="skeleton-shimmer skeleton-btn"></div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
export function renderWorkflows(workflows) {
|
||||
const container = document.getElementById('workflows-list');
|
||||
const wfMetric = document.getElementById('val-total-workflows');
|
||||
const wfSub = document.getElementById('val-active-wf');
|
||||
if (!container) return;
|
||||
|
||||
if (wfMetric) wfMetric.textContent = workflows ? workflows.length : 0;
|
||||
|
||||
if (!workflows || workflows.length === 0) {
|
||||
if (wfSub) wfSub.textContent = '暂无已注册工作流';
|
||||
container.innerHTML = `
|
||||
<div class="empty-cell">
|
||||
暂无预置工作流。可点击右上角「创建工作流」按钮快速注册。
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
const runningCount = workflows.filter(w => w.status === 'running').length;
|
||||
if (wfSub) {
|
||||
wfSub.textContent = runningCount > 0 ? `${runningCount} 个工作流运行中` : '集群待命';
|
||||
}
|
||||
|
||||
const html = workflows.map(wf => {
|
||||
let statusCn = '闲置 (Idle)';
|
||||
let statusClass = 'secondary';
|
||||
if (wf.status === 'running') {
|
||||
statusCn = '运行中 (Running)';
|
||||
statusClass = 'online';
|
||||
} else if (wf.status === 'completed') {
|
||||
statusCn = '已完成 (Completed)';
|
||||
statusClass = 'online';
|
||||
}
|
||||
|
||||
const name = escapeHtml(wf.name);
|
||||
const desc = escapeHtml(wf.description || '无描述');
|
||||
const isRunning = wf.status === 'running';
|
||||
|
||||
return `
|
||||
<div class="workflow-card">
|
||||
<div class="workflow-header">
|
||||
<div>
|
||||
<h3 class="workflow-name">${name}</h3>
|
||||
<p class="workflow-desc">${desc}</p>
|
||||
</div>
|
||||
<span class="status-badge ${statusClass}">${statusCn}</span>
|
||||
</div>
|
||||
<div class="workflow-actions">
|
||||
<button class="btn btn-secondary btn-sm" data-wf-action="view" data-wf-name="${name}">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
|
||||
查看 YAML
|
||||
</button>
|
||||
${
|
||||
isRunning
|
||||
? `<button class="btn btn-secondary btn-sm" data-wf-action="stop" data-wf-name="${name}"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg> 暂停</button>`
|
||||
: `<button class="btn btn-primary btn-sm" data-wf-action="start" data-wf-name="${name}"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg> 启动</button>`
|
||||
}
|
||||
<button class="btn btn-danger btn-sm" data-wf-action="delete" data-wf-name="${name}">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
if (container.innerHTML.trim() !== html.trim()) {
|
||||
container.innerHTML = html;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user