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:
fmq
2026-07-28 21:54:02 +08:00
parent 4b4238d702
commit b91f1e4fa5
59 changed files with 7697 additions and 1490 deletions
+109
View File
@@ -0,0 +1,109 @@
/* DCTS Dashboard API & Authentication Module */
const TOKEN_KEY = 'dcts_admin_token';
export function getAdminToken() {
return localStorage.getItem(TOKEN_KEY);
}
export function setAdminToken(token) {
localStorage.setItem(TOKEN_KEY, token);
}
export function clearAdminToken() {
localStorage.removeItem(TOKEN_KEY);
}
// 安全 HTML 转义,防止存储型 XSS
export function escapeHtml(s) {
if (s == null) return '';
return String(s)
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
// 统一 fetch 包装:自动注入 Authorization 头,401 时清除 token 并刷新
export async function apiFetch(url, opts = {}) {
const token = getAdminToken();
const headers = { ...(opts.headers || {}) };
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const res = await fetch(url, { ...opts, headers });
if (res.status === 401) {
clearAdminToken();
location.reload();
}
return res;
}
// API 请求接口定义
export async function loginAdmin(password) {
const res = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password }),
});
return res;
}
export async function fetchClusterStatus() {
const res = await apiFetch('/api/status');
if (!res.ok) throw new Error(`HTTP 错误代码: ${res.status}`);
return res.json();
}
export async function fetchWorkflowsList() {
const res = await apiFetch('/api/workflows');
if (!res.ok) throw new Error(`HTTP 错误代码: ${res.status}`);
return res.json();
}
export async function fetchNodesList() {
const res = await apiFetch('/api/admin/nodes');
if (!res.ok) throw new Error(`HTTP 错误代码: ${res.status}`);
return res.json();
}
export async function createWorkflow(workflowData) {
return apiFetch('/api/workflows', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(workflowData),
});
}
export async function startWorkflowApi(name) {
return apiFetch(`/api/workflows/${encodeURIComponent(name)}/start`, { method: 'POST' });
}
export async function stopWorkflowApi(name) {
return apiFetch(`/api/workflows/${encodeURIComponent(name)}/stop`, { method: 'POST' });
}
export async function deleteWorkflowApi(name) {
return apiFetch(`/api/workflows/${encodeURIComponent(name)}`, { method: 'DELETE' });
}
export async function getWorkflowDetailApi(name) {
return apiFetch(`/api/workflows/${encodeURIComponent(name)}`);
}
export async function approveNodeApi(nodeId) {
return apiFetch(`/api/admin/nodes/${encodeURIComponent(nodeId)}/approve`, { method: 'POST' });
}
export async function rejectNodeApi(nodeId) {
return apiFetch(`/api/admin/nodes/${encodeURIComponent(nodeId)}/reject`, { method: 'POST' });
}
export async function revokeNodeApi(nodeId) {
return apiFetch(`/api/admin/nodes/${encodeURIComponent(nodeId)}/revoke`, { method: 'POST' });
}
export async function reissueNodeApi(nodeId) {
return apiFetch(`/api/admin/nodes/${encodeURIComponent(nodeId)}/reissue`, { method: 'POST' });
}