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,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, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
// 统一 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' });
|
||||
}
|
||||
Reference in New Issue
Block a user