feat(all): 重炼 crates/common 核心组件、上线 Web 运维看板与 Docker 容器化部署

This commit is contained in:
fmq
2026-07-28 10:31:57 +08:00
commit 4b4238d702
71 changed files with 163402 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
# Dependencies
node_modules/
# Production build outputs
dist/
dist-ssr/
# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Local env files
.env*.local
# Editor directories and OS files
.DS_Store
.vscode/*
!.vscode/extensions.json
.idea/
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+241
View File
@@ -0,0 +1,241 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>DCTS — 分布式恒星大气网格计算控制台</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/src/style.css" />
</head>
<body>
<div id="app">
<!-- 顶部导航栏 (Header) -->
<header class="header">
<div class="logo-container">
<div class="logo-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg>
</div>
<div>
<h1 class="logo-title">DCTS 集群控制台</h1>
<p class="logo-subtitle">Distributed Computing TLUSTY / SYNSPEC Grid System</p>
</div>
</div>
<div class="header-status">
<div class="status-indicator online" id="status-indicator-box">
<span class="status-dot"></span>
<span id="server-status-text">服务端正常运行</span>
</div>
<button id="btn-create-wf" class="btn btn-primary">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
创建工作流
</button>
<button id="refresh-btn" class="btn btn-secondary">
<svg class="icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<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>
</div>
</header>
<!-- 主内容区 -->
<main class="main-container">
<!-- 核心指标卡片 -->
<section class="metrics-grid">
<div class="metric-card glass">
<div class="metric-header">
<span class="metric-title">在线计算节点</span>
<div class="metric-icon nodes-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="2" width="20" height="8" rx="2" ry="2"/>
<rect x="2" y="14" width="20" height="8" rx="2" ry="2"/>
<line x1="6" y1="6" x2="6.01" y2="6"/>
<line x1="6" y1="18" x2="6.01" y2="18"/>
</svg>
</div>
</div>
<div class="metric-body">
<span class="metric-value" id="val-active-nodes">0</span>
<span class="metric-sub" id="val-total-slots">0 / 0 CPU 槽位占用</span>
</div>
</div>
<div class="metric-card glass">
<div class="metric-header">
<span class="metric-title">待计算网格点</span>
<div class="metric-icon pending-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<polyline points="12 6 12 12 16 14"/>
</svg>
</div>
</div>
<div class="metric-body">
<span class="metric-value" id="val-pending-tasks">0</span>
<span class="metric-sub" id="val-running-tasks">0 个任务计算中</span>
</div>
</div>
<div class="metric-card glass">
<div class="metric-header">
<span class="metric-title">已收敛网格模型</span>
<div class="metric-icon success-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
<polyline points="22 4 12 14.01 9 11.01"/>
</svg>
</div>
</div>
<div class="metric-body">
<span class="metric-value" id="val-completed-tasks">0</span>
<span class="metric-sub" id="val-completion-rate">全网网格总数: 0</span>
</div>
</div>
<div class="metric-card glass">
<div class="metric-header">
<span class="metric-title">计算工作流</span>
<div class="metric-icon workflow-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polygon points="12 2 2 7 12 12 22 7 12 2"/>
<polyline points="2 17 12 22 22 17"/>
<polyline points="2 12 12 17 22 12"/>
</svg>
</div>
</div>
<div class="metric-body">
<span class="metric-value" id="val-total-workflows">0</span>
<span class="metric-sub" id="val-active-wf">系统已就绪</span>
</div>
</div>
</section>
<!-- 主要内容双栏 -->
<div class="content-grid">
<!-- 节点集群状态 -->
<div class="panel glass">
<div class="panel-header">
<h2>计算节点集群 (Worker Nodes)</h2>
<span class="badge" id="node-count-badge">0 个节点</span>
</div>
<div class="panel-body table-responsive">
<table class="data-table">
<thead>
<tr>
<th>节点 ID</th>
<th>主机名</th>
<th>CPU 槽位</th>
<th>CPU 使用率</th>
<th>内存使用率</th>
<th>心跳时间</th>
<th>状态</th>
</tr>
</thead>
<tbody id="nodes-table-body">
<tr>
<td colspan="7" class="empty-cell">正在连接服务端获取计算节点...</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 右侧:工作流管理与数据资源下载 -->
<div style="display: flex; flex-direction: column; gap: 1.5rem;">
<!-- 工作流面板 -->
<div class="panel glass">
<div class="panel-header">
<h2>恒星大气网格工作流 (Workflows)</h2>
</div>
<div class="panel-body">
<div id="workflows-list" class="workflows-container">
<div class="empty-cell">正在获取工作流配置...</div>
</div>
</div>
</div>
<!-- 系统资源与数据下载 -->
<div class="panel glass">
<div class="panel-header">
<h2>大气与谱线数据资源 (Data Resources)</h2>
</div>
<div class="panel-body" style="display: flex; flex-direction: column; gap: 0.75rem;">
<p style="font-size: 0.85rem; color: var(--text-secondary);">
如需本地运行计算节点或检验谱线列表,可直接通过服务端 API 获取静态配分函数及原子文件:
</p>
<div style="display: flex; gap: 0.75rem; flex-wrap: wrap;">
<a href="/api/data/linelist" target="_blank" class="btn btn-secondary btn-sm">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
下载 原子谱线列表 (Line List)
</a>
</div>
</div>
</div>
</div>
</div>
</main>
<!-- 创建 / 编辑工作流 模态弹窗 -->
<div id="modal-workflow" class="modal-backdrop hidden">
<div class="modal-card">
<div class="modal-header">
<h3 id="modal-title">注册新的计算工作流</h3>
<button id="modal-close-btn" class="modal-close">&times;</button>
</div>
<form id="form-workflow" style="display: flex; flex-direction: column; gap: 1rem;">
<div class="form-group">
<label for="input-wf-name">工作流名称 (Identifier)</label>
<input type="text" id="input-wf-name" class="form-input" placeholder="例如: sdB_cno" required />
</div>
<div class="form-group">
<label for="input-wf-desc">描述信息</label>
<input type="text" id="input-wf-desc" class="form-input" placeholder="例如: sdB 6维恒星大气模型计算网格" />
</div>
<div class="form-group">
<label for="input-wf-yaml">YAML 配置内容 (GridConfig)</label>
<textarea id="input-wf-yaml" class="form-textarea" required placeholder="请粘贴工作流 YAML 参数网格定义..."></textarea>
</div>
<div class="modal-footer">
<button type="button" id="btn-cancel-modal" class="btn btn-secondary">取消</button>
<button type="submit" class="btn btn-primary">提交保存</button>
</div>
</form>
</div>
</div>
<!-- 查看 YAML 模态弹窗 -->
<div id="modal-view-yaml" class="modal-backdrop hidden">
<div class="modal-card">
<div class="modal-header">
<h3 id="view-yaml-title">工作流 YAML 配置详情</h3>
<button id="view-yaml-close-btn" class="modal-close">&times;</button>
</div>
<pre id="view-yaml-content" style="background: var(--bg-sunken); padding: 1rem; border-radius: 6px; font-family: var(--font-mono); font-size: 0.85rem; overflow-x: auto; max-height: 350px;"></pre>
<div class="modal-footer">
<button type="button" id="btn-close-yaml-modal" class="btn btn-secondary">关闭</button>
</div>
</div>
</div>
<!-- 页脚 -->
<footer class="footer">
<p>DCTS 分布式恒星大气计算系统 &copy; 2026 TLUSTY/SYNSPEC Project</p>
</footer>
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
+930
View File
@@ -0,0 +1,930 @@
{
"name": "dcts-dashboard",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "dcts-dashboard",
"version": "0.1.0",
"devDependencies": {
"vite": "^5.4.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.3.tgz",
"integrity": "sha512-c0wdcekXtQvvn5Tsrk/+op/gUArrbWaFduBnTLP2l1cKLSQs4diMWjJw3m6A0DdzT8dAAX95KpkJ3qynCePbmw==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.3.tgz",
"integrity": "sha512-3YjElDdWN+qXAFbJ/CzPV+0wspLqh54k/I6GfdYtEJRqg7buSgc1yPM3B+93j1M4neobtkATHZTmxK2AMVGfnA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.3.tgz",
"integrity": "sha512-Pch2pFNOxxz1hTjypIdPyRTR6riiwRl84+VcN9djS680fw+Co1nAJINrdpqp7KV0NvyuU8ilZXZCjd7ykJl1GQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.3.tgz",
"integrity": "sha512-LEuncFUHFiF8t4yZVZvvZA1wk0pjAscRnsrn1EfTEmN4HXotBi2YtcnLRyaK6UbuczW7xZS5ES+81Rdz8Z0T6g==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.3.tgz",
"integrity": "sha512-zvBUvsQUpOWALdDsk6qbS8bXf2VxmPisuudNDrY7x0p0jBdsoZl8HsHczIOgkQiZldmcacMKtBzpoGVNeIe2bQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.3.tgz",
"integrity": "sha512-C2KmNrcSem/AMg984H/dev+si0lieQGdXdR/lYGJnuumXnFb9Y7QdiI62obFdLlxRYLBv4P0eUVIDbD4c1vVvw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.3.tgz",
"integrity": "sha512-ggXnsTAEzNQx74XpunRsiZ9aBZDsI7XIa0hm2nzR9f4WzH5/f/d73ZSDaC5ejJ8YLY4NW+V3wr0tjOaeCq8hqA==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.3.tgz",
"integrity": "sha512-2vng+FlzNUhKZxtej3IUqJgbZoQk2M/dwQM20+ULV0R/E/8tr9/P6uEf2iiGIk4HL0zMKh5Jry7mUHdUOvyGgA==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.3.tgz",
"integrity": "sha512-LLLFZKt4/Nraf9rxDkhiU8QVgLF4WmCkfr0L4fj0fPfIZFBib0DeiFk1hhaYKd03LFAFJcxHslhDFlNJLylf5Q==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.3.tgz",
"integrity": "sha512-WJkdQCvS9sWNOUBJZfQRKpZGFBztRzcowI+nndmflKgU4XY+3a420FgTOSKTsVqJbnzSxeT4vaJalpOaPo2YCQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.3.tgz",
"integrity": "sha512-PwHXCCS2n64/1Ot6rP1YEYA02MGYBcQlr8CSZZyrUG2O7NH6NklYmvr9v3Jy+5e/eDeNchc/ukmKJi9LuflMIQ==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.3.tgz",
"integrity": "sha512-vUjxINQu3RC8NZS3ykk1gN65gIz8pAopOq2HXuZhiIxHdx7TFvDG+jgrdSgInu1Eza4/Rfi2VzZgyIgEH4WOaw==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.3.tgz",
"integrity": "sha512-wzko4aJ13+0G3kGnviCg5gnXFKd40izKsrf2uOw12US4XqprkDrmwOpeW14aSNa37V8bfPcz5Fkob6LZ3BAPmA==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.3.tgz",
"integrity": "sha512-8120ue0JUMSwy11stlwnfdX3pPd+WZYGCDBwEHWtIHi6pOpZmsEF5QKB7a/UN+XFdqvobxz98kv8RTqikyCEBw==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.3.tgz",
"integrity": "sha512-XLFHnR3tXMjbOCh2vtVJHmxt+995uJsTERQyseFDRA0xxMxyTZPLa3OIUlyFaO4mF/Lu0FjmWHCuPXJT1n/IOg==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.3.tgz",
"integrity": "sha512-se6yXvNGMIl0f+RQzyh7XAmia8/9kplQx424wnG2w0C1oi6XgO6Y8otKhdXFHbHs88Ihavzmvh1NWjuovE76BQ==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.3.tgz",
"integrity": "sha512-gNoxRefktVIiGflpONuxWWXZAzIQG++z9qHO3xKwk4WdDMuQja3JHGfE1u0i3PfPDyvhypdk+WrgIJqLhGG7sg==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.3.tgz",
"integrity": "sha512-V4KtWtQfAFMU7+9/A/VDps/VI8CHd3cYz0L8sgJzz8qK7eY7wI4ruFD82UYIYvW9Z4DtlTfhQcsl4XyPHW5uSg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.3.tgz",
"integrity": "sha512-LBx9LYXvj2CBkMkjLdNAWLwH0MLMin7do2VcVo9kVPibGLkY0BQQut2fv7NVqkXqZ/CrAu9LqDHVV1xHCMpCPw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-openbsd-x64": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.3.tgz",
"integrity": "sha512-ABVf3Q0RCu7NcyCCOZQI0pJ3GuSdfSl8EXcy88QtdceIMIoCUdfhsJChZ64L9zVM2aJHjde1Bhn5uqSRcX9ySA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.3.tgz",
"integrity": "sha512-+2Cy/ldweGBLlPIKsQLF8U5N44a0KDdbrk1rAjHOM9M2K+kGdIVjHLmmrZIcx+9Ny3ke/1JomCsDI1ocb11+sg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"openharmony"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.3.tgz",
"integrity": "sha512-dtZvzc8BedpSaFNy75x6uiWwAGTH+aZHDtdrqP6qk+WcLJrfti6sGje1ZJ9UxyzDLF23d/mV+PaMwuC0hL7UVA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.3.tgz",
"integrity": "sha512-Rj8Ra4noo+aYy7sKBggCx0407mws34kAb1ySyWuq5DAtFBQdkSwnsjCgPrhPe9cvgBKZIukpE+CVHvORCS93kQ==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.3.tgz",
"integrity": "sha512-vp7N084ew/odXn2gi/mzm9mUkQu9l6AiN6dt4IeUM2Uvm9o+cVmP+YkqbMOteLbiGgqBBlJZjIMYVCfOOIVbVQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.3.tgz",
"integrity": "sha512-MOG/3gTOn4Fwf574RVOaY61I5o6P90legkFADiTyn1hyjNydT+cerU2rLUwPdZkKKyJ+iT+K9p7WXK4LM1Ka6g==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@types/estree": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
"dev": true
},
"node_modules/esbuild": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.21.5",
"@esbuild/android-arm": "0.21.5",
"@esbuild/android-arm64": "0.21.5",
"@esbuild/android-x64": "0.21.5",
"@esbuild/darwin-arm64": "0.21.5",
"@esbuild/darwin-x64": "0.21.5",
"@esbuild/freebsd-arm64": "0.21.5",
"@esbuild/freebsd-x64": "0.21.5",
"@esbuild/linux-arm": "0.21.5",
"@esbuild/linux-arm64": "0.21.5",
"@esbuild/linux-ia32": "0.21.5",
"@esbuild/linux-loong64": "0.21.5",
"@esbuild/linux-mips64el": "0.21.5",
"@esbuild/linux-ppc64": "0.21.5",
"@esbuild/linux-riscv64": "0.21.5",
"@esbuild/linux-s390x": "0.21.5",
"@esbuild/linux-x64": "0.21.5",
"@esbuild/netbsd-x64": "0.21.5",
"@esbuild/openbsd-x64": "0.21.5",
"@esbuild/sunos-x64": "0.21.5",
"@esbuild/win32-arm64": "0.21.5",
"@esbuild/win32-ia32": "0.21.5",
"@esbuild/win32-x64": "0.21.5"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/nanoid": {
"version": "3.3.16",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true
},
"node_modules/postcss": {
"version": "8.5.23",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz",
"integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"nanoid": "^3.3.16",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/rollup": {
"version": "4.62.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.3.tgz",
"integrity": "sha512-Gu0c0iH9FzgX1L1t7ByIbbS3Vmdz+6KHm/EsqmmC71gUQ82yvZRkTK6XzrFObSka91WUVdynqp6nsfilzr5k6Q==",
"dev": true,
"dependencies": {
"@types/estree": "1.0.9"
},
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.62.3",
"@rollup/rollup-android-arm64": "4.62.3",
"@rollup/rollup-darwin-arm64": "4.62.3",
"@rollup/rollup-darwin-x64": "4.62.3",
"@rollup/rollup-freebsd-arm64": "4.62.3",
"@rollup/rollup-freebsd-x64": "4.62.3",
"@rollup/rollup-linux-arm-gnueabihf": "4.62.3",
"@rollup/rollup-linux-arm-musleabihf": "4.62.3",
"@rollup/rollup-linux-arm64-gnu": "4.62.3",
"@rollup/rollup-linux-arm64-musl": "4.62.3",
"@rollup/rollup-linux-loong64-gnu": "4.62.3",
"@rollup/rollup-linux-loong64-musl": "4.62.3",
"@rollup/rollup-linux-ppc64-gnu": "4.62.3",
"@rollup/rollup-linux-ppc64-musl": "4.62.3",
"@rollup/rollup-linux-riscv64-gnu": "4.62.3",
"@rollup/rollup-linux-riscv64-musl": "4.62.3",
"@rollup/rollup-linux-s390x-gnu": "4.62.3",
"@rollup/rollup-linux-x64-gnu": "4.62.3",
"@rollup/rollup-linux-x64-musl": "4.62.3",
"@rollup/rollup-openbsd-x64": "4.62.3",
"@rollup/rollup-openharmony-arm64": "4.62.3",
"@rollup/rollup-win32-arm64-msvc": "4.62.3",
"@rollup/rollup-win32-ia32-msvc": "4.62.3",
"@rollup/rollup-win32-x64-gnu": "4.62.3",
"@rollup/rollup-win32-x64-msvc": "4.62.3",
"fsevents": "~2.3.2"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/vite": {
"version": "5.4.21",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
"dev": true,
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
"rollup": "^4.20.0"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^18.0.0 || >=20.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^18.0.0 || >=20.0.0",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"less": {
"optional": true
},
"lightningcss": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
}
}
}
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"name": "dcts-dashboard",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"vite": "^5.4.0"
}
}
+308
View File
@@ -0,0 +1,308 @@
// DCTS Cluster Dashboard Client Logic (全中文,无占位符,对接所有后端 API)
const defaultYamlConfig = `name: sdB_cno
description: sdB CNO 6D Stellar Atmosphere Grid
grid:
teff: [20000.0, 40000.0, 5000.0]
logg: [5.0, 6.2, 0.3]
loghe: [-1.0, -1.0, 0.0]
logc: [-5.0, -2.0, 0.5]
logn: [-5.0, -2.0, 0.5]
logo: [-5.0, -2.0, 0.5]
convergence:
max_relc: 0.01
max_iterations: 30
`;
// 获取状态数据
async function fetchStatus() {
try {
const res = await fetch('/api/status');
if (!res.ok) throw new Error(`HTTP 错误代码: ${res.status}`);
const data = await res.json();
updateUI(data);
updateServerStatus(true);
} catch (err) {
console.warn('获取 DCTS 状态失败:', err);
updateServerStatus(false);
}
}
// 获取工作流列表
async function fetchWorkflows() {
try {
const res = await fetch('/api/workflows');
if (!res.ok) return;
const json = await res.json();
if (json.success && json.data) {
renderWorkflows(json.data);
}
} catch (err) {
console.warn('获取工作流列表失败:', err);
}
}
function updateServerStatus(online) {
const statusText = document.getElementById('server-status-text');
const indicator = document.getElementById('status-indicator-box');
if (!statusText || !indicator) return;
if (online) {
statusText.textContent = '服务端正常运行';
indicator.className = 'status-indicator online';
} else {
statusText.textContent = '服务端已断开连接';
indicator.className = 'status-indicator offline';
}
}
function updateUI(data) {
const nodes = data.nodes || [];
const activeNodesCount = data.nodes_online !== undefined ? data.nodes_online : nodes.length;
const activeSlots = data.total_active_slots || 0;
const maxSlots = data.total_max_slots || 0;
const gridStats = data.grid_stats || {};
const totalPoints = gridStats.total || 0;
const pendingPoints = gridStats.pending || 0;
const runningPoints = gridStats.running || 0;
const convergedPoints = gridStats.converged || 0;
// 更新指标卡片
document.getElementById('val-active-nodes').textContent = activeNodesCount;
document.getElementById('val-total-slots').textContent = `${activeSlots} / ${maxSlots} CPU 槽位占用`;
document.getElementById('val-pending-tasks').textContent = pendingPoints;
document.getElementById('val-running-tasks').textContent = `${runningPoints} 个网格点计算中`;
document.getElementById('val-completed-tasks').textContent = convergedPoints;
const rate = totalPoints > 0 ? ((convergedPoints / totalPoints) * 100).toFixed(1) : '0.0';
document.getElementById('val-completion-rate').textContent = `全网网格总数: ${totalPoints} (完成率 ${rate}%)`;
document.getElementById('node-count-badge').textContent = `${activeNodesCount} 个节点在线`;
renderNodesTable(nodes);
}
function renderNodesTable(nodes) {
const tbody = document.getElementById('nodes-table-body');
if (!tbody) return;
if (!nodes || nodes.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="7" class="empty-cell">
暂无在线计算节点。请启动 DCTS Node Worker 节点程序。
</td>
</tr>
`;
return;
}
tbody.innerHTML = nodes.map(node => {
const isOnline = node.status === 'online' || node.status === 'active';
const statusText = isOnline ? '在线 (Online)' : '离线 (Offline)';
const badgeClass = isOnline ? 'online' : 'offline';
const lastHeartbeat = node.last_heartbeat ? new Date(node.last_heartbeat).toLocaleTimeString('zh-CN') : '未知';
return `
<tr>
<td class="node-id">${node.node_id || node.id || 'N/A'}</td>
<td>${node.host_name || node.host || '127.0.0.1'}</td>
<td>${node.active_slots || 0} / ${node.max_slots || 4}</td>
<td>${(node.cpu_usage || 0).toFixed(1)}%</td>
<td>${(node.memory_usage || 0).toFixed(1)}%</td>
<td>${lastHeartbeat}</td>
<td><span class="status-badge ${badgeClass}">${statusText}</span></td>
</tr>
`;
}).join('');
}
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.length;
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} 个工作流运行中` : '集群待命';
}
container.innerHTML = workflows.map(wf => {
let statusCn = '闲置 (Idle)';
let statusClass = 'secondary';
if (wf.status === 'running') {
statusCn = '运行中 (Running)';
statusClass = 'online';
} else if (wf.status === 'paused') {
statusCn = '已暂停 (Paused)';
statusClass = 'offline';
}
return `
<div class="workflow-card">
<div class="wf-header">
<span class="wf-name">${wf.name}</span>
<span class="status-badge ${statusClass}">${statusCn}</span>
</div>
<div class="wf-desc">${wf.description || '无详细描述'}</div>
<div class="wf-meta">
<span>创建时间: ${wf.created_at || '最近'}</span>
</div>
<div class="wf-actions">
${wf.status !== 'running' ? `
<button class="btn btn-primary btn-sm" onclick="startWorkflow('${wf.name}')">启动计算</button>
` : `
<button class="btn btn-secondary btn-sm" onclick="stopWorkflow('${wf.name}')">暂停计算</button>
`}
<button class="btn btn-secondary btn-sm" onclick="viewWorkflowYaml('${wf.name}')">查看 YAML</button>
<button class="btn btn-danger btn-sm" onclick="deleteWorkflow('${wf.name}')">删除</button>
</div>
</div>
`;
}).join('');
}
// 模态弹窗交互
function initModals() {
const modalWf = document.getElementById('modal-workflow');
const modalYaml = document.getElementById('modal-view-yaml');
const btnCreateWf = document.getElementById('btn-create-wf');
const btnCloseModal = document.getElementById('modal-close-btn');
const btnCancelModal = document.getElementById('btn-cancel-modal');
const formWf = document.getElementById('form-workflow');
const btnCloseYaml = document.getElementById('view-yaml-close-btn');
const btnCloseYamlFoot = document.getElementById('btn-close-yaml-modal');
btnCreateWf?.addEventListener('click', () => {
document.getElementById('input-wf-name').value = '';
document.getElementById('input-wf-desc').value = '';
document.getElementById('input-wf-yaml').value = defaultYamlConfig;
modalWf?.classList.remove('hidden');
});
const hideWfModal = () => modalWf?.classList.add('hidden');
btnCloseModal?.addEventListener('click', hideWfModal);
btnCancelModal?.addEventListener('click', hideWfModal);
const hideYamlModal = () => modalYaml?.classList.add('hidden');
btnCloseYaml?.addEventListener('click', hideYamlModal);
btnCloseYamlFoot?.addEventListener('click', hideYamlModal);
// 提交创建工作流
formWf?.addEventListener('submit', async (e) => {
e.preventDefault();
const name = document.getElementById('input-wf-name').value.trim();
const description = document.getElementById('input-wf-desc').value.trim();
const config_yaml = document.getElementById('input-wf-yaml').value;
if (!name || !config_yaml) {
alert('请填写工作流名称及 YAML 配置内容!');
return;
}
try {
const res = await fetch('/api/workflows', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, description, config_yaml }),
});
const json = await res.json();
if (json.success) {
alert(`工作流 '${name}' 保存成功!`);
hideWfModal();
fetchWorkflows();
} else {
alert(`保存失败: ${json.message}`);
}
} catch (err) {
alert(`请求异常: ${err.message}`);
}
});
}
// 暴露操作给全局 window 对象
window.startWorkflow = async function(name) {
if (!confirm(`确定要启动工作流 '${name}' 展开计算网格点吗?`)) return;
try {
const res = await fetch(`/api/workflows/${name}/start`, { method: 'POST' });
const json = await res.json();
alert(json.message);
fetchWorkflows();
fetchStatus();
} catch (err) {
alert(`启动失败: ${err.message}`);
}
};
window.stopWorkflow = async function(name) {
try {
const res = await fetch(`/api/workflows/${name}/stop`, { method: 'POST' });
const json = await res.json();
alert(json.message);
fetchWorkflows();
} catch (err) {
alert(`暂停失败: ${err.message}`);
}
};
window.deleteWorkflow = async function(name) {
if (!confirm(`确定要删除工作流 '${name}' 吗?`)) return;
try {
const res = await fetch(`/api/workflows/${name}`, { method: 'DELETE' });
const json = await res.json();
alert(json.message);
fetchWorkflows();
} catch (err) {
alert(`删除失败: ${err.message}`);
}
};
window.viewWorkflowYaml = async function(name) {
try {
const res = await fetch(`/api/workflows/${name}`);
const json = await res.json();
if (json.success && json.data) {
document.getElementById('view-yaml-title').textContent = `工作流 '${name}' 配置 YAML`;
document.getElementById('view-yaml-content').textContent = json.data.config_yaml;
document.getElementById('modal-view-yaml')?.classList.remove('hidden');
} else {
alert(`获取详情失败: ${json.message}`);
}
} catch (err) {
alert(`请求异常: ${err.message}`);
}
};
// 初始化与定时轮询
document.addEventListener('DOMContentLoaded', () => {
fetchStatus();
fetchWorkflows();
initModals();
document.getElementById('refresh-btn')?.addEventListener('click', () => {
fetchStatus();
fetchWorkflows();
});
// 每 5 秒轮询刷新
setInterval(() => {
fetchStatus();
fetchWorkflows();
}, 5000);
});
+565
View File
@@ -0,0 +1,565 @@
/* DCTS Cluster Dashboard — AstroResearch 暖纸张学术风 (Light Academic Paper Style) */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap');
:root {
color-scheme: light;
/* ── AstroResearch 暖纸张学术风 (Light Academic Paper) ── */
--bg-app: #faf9f6; /* 暖白纸底 */
--bg-surface: #ffffff; /* 卡片/面板纯白 */
--bg-elevated: #ffffff; /* 弹层面板 */
--bg-sunken: #f4f2ed; /* 表头/凹陷底面 */
--text-primary: #292524; /* 暖墨黑 (stone-800) */
--text-secondary: #57534e; /* 暖深灰 (stone-600) */
--text-muted: #78716c; /* 暖中灰 (stone-500) */
--border-default: #e7e5e4; /* 基础描述线 (stone-200) */
--border-subtle: #f5f2ec; /* 更轻分割线 */
--border-strong: #ddd7cc; /* 强调分割线 */
/* 品牌与功能强调色 */
--accent-blueprint: #0369a1;/* 学术蓝 (Blueprint Blue) */
--accent-cyan: #0284c7; /* 天空蓝 (Sky Cyan) */
--accent-purple: #6d28d9; /* 皇家紫 */
--accent-emerald: #047857; /* 翡翠绿 (Success) */
--accent-amber: #b45309; /* 琥珀金 (Warning/Star) */
--accent-rose: #dc2626; /* 珊瑚红 (Danger) */
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-app);
background-image:
radial-gradient(at 10% 10%, rgba(3, 105, 161, 0.04) 0px, transparent 50%),
radial-gradient(at 90% 90%, rgba(180, 83, 9, 0.04) 0px, transparent 50%);
background-attachment: fixed;
color: var(--text-primary);
font-family: var(--font-sans);
min-height: 100vh;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
#app {
display: flex;
flex-direction: column;
min-height: 100vh;
}
/* 暖纸张卡片面板 */
.glass {
background: var(--bg-surface);
border: 1px solid var(--border-default);
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.03);
}
/* 导航顶栏 Header */
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.1rem 2.2rem;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border-default);
position: sticky;
top: 0;
z-index: 100;
}
.logo-container {
display: flex;
align-items: center;
gap: 1rem;
}
.logo-icon {
width: 42px;
height: 42px;
border-radius: 10px;
background: linear-gradient(135deg, var(--accent-blueprint), var(--accent-cyan));
display: flex;
align-items: center;
justify-content: center;
color: white;
box-shadow: 0 4px 12px rgba(3, 105, 161, 0.25);
}
.logo-title {
font-size: 1.25rem;
font-weight: 700;
letter-spacing: -0.02em;
color: var(--text-primary);
}
.logo-subtitle {
font-size: 0.8rem;
color: var(--text-secondary);
}
.header-status {
display: flex;
align-items: center;
gap: 1rem;
}
.status-indicator {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.85rem;
padding: 0.4rem 0.9rem;
border-radius: 20px;
font-family: var(--font-mono);
font-weight: 500;
}
.status-indicator.online {
background: rgba(4, 120, 87, 0.08);
border: 1px solid rgba(4, 120, 87, 0.25);
color: var(--accent-emerald);
}
.status-indicator.offline {
background: rgba(220, 38, 38, 0.08);
border: 1px solid rgba(220, 38, 38, 0.25);
color: var(--accent-rose);
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: var(--accent-emerald);
box-shadow: 0 0 8px var(--accent-emerald);
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { transform: scale(0.95); opacity: 0.8; }
50% { transform: scale(1.15); opacity: 1; }
100% { transform: scale(0.95); opacity: 0.8; }
}
.btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.55rem 1.1rem;
border-radius: 8px;
font-size: 0.85rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
border: none;
}
.btn-secondary {
background: var(--bg-sunken);
color: var(--text-primary);
border: 1px solid var(--border-default);
}
.btn-secondary:hover {
background: #eae6df;
border-color: var(--border-strong);
}
.btn-primary {
background: linear-gradient(135deg, var(--accent-blueprint), var(--accent-cyan));
color: white;
box-shadow: 0 3px 10px rgba(3, 105, 161, 0.25);
}
.btn-primary:hover {
opacity: 0.92;
transform: translateY(-1px);
}
.btn-danger {
background: rgba(220, 38, 38, 0.1);
color: var(--accent-rose);
border: 1px solid rgba(220, 38, 38, 0.2);
}
.btn-danger:hover {
background: rgba(220, 38, 38, 0.2);
}
.btn-sm {
padding: 0.35rem 0.7rem;
font-size: 0.78rem;
}
/* 主内容容器 */
.main-container {
max-width: 1400px;
width: 100%;
margin: 0 auto;
padding: 2rem;
flex: 1;
display: flex;
flex-direction: column;
gap: 2rem;
}
/* 指标卡片矩阵 */
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 1.25rem;
}
.metric-card {
padding: 1.35rem 1.5rem;
transition: transform 0.2s ease, border-color 0.2s ease;
}
.metric-card:hover {
transform: translateY(-2px);
border-color: var(--border-strong);
}
.metric-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.85rem;
}
.metric-title {
font-size: 0.875rem;
color: var(--text-secondary);
font-weight: 500;
}
.metric-icon {
width: 38px;
height: 38px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.nodes-icon { background: rgba(3, 105, 161, 0.08); color: var(--accent-blueprint); }
.pending-icon { background: rgba(180, 83, 9, 0.08); color: var(--accent-amber); }
.success-icon { background: rgba(4, 120, 87, 0.08); color: var(--accent-emerald); }
.workflow-icon { background: rgba(109, 40, 217, 0.08); color: var(--accent-purple); }
.metric-body {
display: flex;
flex-direction: column;
}
.metric-value {
font-size: 2.1rem;
font-weight: 700;
letter-spacing: -0.03em;
font-family: var(--font-mono);
color: var(--text-primary);
}
.metric-sub {
font-size: 0.8rem;
color: var(--text-secondary);
margin-top: 0.25rem;
}
/* 内容双栏布局 */
.content-grid {
display: grid;
grid-template-columns: 3fr 2fr;
gap: 1.5rem;
}
@media (max-width: 1024px) {
.content-grid {
grid-template-columns: 1fr;
}
}
/* 面板 */
.panel {
display: flex;
flex-direction: column;
overflow: hidden;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.1rem 1.5rem;
border-bottom: 1px solid var(--border-default);
background: var(--bg-sunken);
}
.panel-header h2 {
font-size: 1.05rem;
font-weight: 600;
color: var(--text-primary);
}
.badge {
background: rgba(3, 105, 161, 0.08);
color: var(--accent-blueprint);
font-size: 0.75rem;
font-weight: 600;
padding: 0.25rem 0.65rem;
border-radius: 12px;
font-family: var(--font-mono);
border: 1px solid rgba(3, 105, 161, 0.2);
}
.panel-body {
padding: 1.35rem 1.5rem;
flex: 1;
}
/* 表格组件 */
.table-responsive {
overflow-x: auto;
}
.data-table {
width: 100%;
border-collapse: collapse;
text-align: left;
font-size: 0.875rem;
}
.data-table th {
padding: 0.8rem 1rem;
color: var(--text-secondary);
font-weight: 600;
border-bottom: 1px solid var(--border-default);
background: var(--bg-sunken);
}
.data-table td {
padding: 0.85rem 1rem;
border-bottom: 1px solid var(--border-default);
color: var(--text-primary);
}
.data-table tr:hover {
background: rgba(0, 0, 0, 0.015);
}
.node-id {
font-family: var(--font-mono);
font-weight: 600;
color: var(--accent-blueprint);
}
.empty-cell {
text-align: center;
color: var(--text-muted);
padding: 2.5rem !important;
}
.status-badge {
display: inline-block;
padding: 0.2rem 0.55rem;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 600;
font-family: var(--font-mono);
}
.status-badge.online {
background: rgba(4, 120, 87, 0.1);
color: var(--accent-emerald);
}
.status-badge.offline {
background: rgba(220, 38, 38, 0.1);
color: var(--accent-rose);
}
/* 工作流卡片 */
.workflows-container {
display: flex;
flex-direction: column;
gap: 1rem;
}
.workflow-card {
background: var(--bg-surface);
border: 1px solid var(--border-default);
border-radius: 10px;
padding: 1.2rem;
transition: all 0.2s ease;
}
.workflow-card:hover {
border-color: var(--border-strong);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04);
}
.wf-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5rem;
}
.wf-name {
font-size: 1rem;
font-weight: 600;
color: var(--text-primary);
}
.wf-desc {
font-size: 0.85rem;
color: var(--text-secondary);
margin-bottom: 0.85rem;
line-height: 1.4;
}
.wf-actions {
display: flex;
gap: 0.5rem;
margin-top: 1rem;
}
.wf-progress-bar {
width: 100%;
height: 7px;
background: #e7e5e4;
border-radius: 4px;
overflow: hidden;
margin-bottom: 0.5rem;
}
.wf-progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--accent-blueprint), var(--accent-cyan));
border-radius: 4px;
transition: width 0.4s ease;
}
.wf-meta {
display: flex;
justify-content: space-between;
font-size: 0.78rem;
color: var(--text-muted);
font-family: var(--font-mono);
}
/* 模态弹窗 (Modal Dialog) */
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(41, 37, 36, 0.4);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-backdrop.hidden {
display: none;
}
.modal-card {
background: var(--bg-surface);
border: 1px solid var(--border-default);
border-radius: 12px;
width: 90%;
max-width: 650px;
padding: 1.75rem;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
display: flex;
flex-direction: column;
gap: 1.25rem;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--border-default);
padding-bottom: 0.85rem;
}
.modal-header h3 {
font-size: 1.15rem;
font-weight: 600;
color: var(--text-primary);
}
.modal-close {
background: none;
border: none;
font-size: 1.5rem;
color: var(--text-muted);
cursor: pointer;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.form-group label {
font-size: 0.85rem;
font-weight: 500;
color: var(--text-secondary);
}
.form-input, .form-textarea {
background: var(--bg-sunken);
border: 1px solid var(--border-default);
border-radius: 6px;
padding: 0.6rem 0.8rem;
font-size: 0.9rem;
color: var(--text-primary);
font-family: var(--font-sans);
}
.form-textarea {
font-family: var(--font-mono);
min-height: 180px;
resize: vertical;
}
.form-input:focus, .form-textarea:focus {
outline: none;
border-color: var(--accent-blueprint);
box-shadow: 0 0 0 3px rgba(3, 105, 161, 0.1);
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 0.75rem;
border-top: 1px solid var(--border-default);
padding-top: 1rem;
}
/* 页脚 Footer */
.footer {
text-align: center;
padding: 1.5rem;
border-top: 1px solid var(--border-default);
color: var(--text-muted);
font-size: 0.82rem;
background: var(--bg-surface);
}
+17
View File
@@ -0,0 +1,17 @@
import { defineConfig } from 'vite';
export default defineConfig({
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8090',
changeOrigin: true,
},
},
},
build: {
outDir: 'dist',
emptyOutDir: true,
},
});