console/index.html v2: GPU仪表盘+训练面板+Agent日记
This commit is contained in:
parent
8e07caf2d1
commit
6e24e7abdd
@ -6,161 +6,17 @@
|
|||||||
<title>曜冥纪元 · 领地健康看板</title>
|
<title>曜冥纪元 · 领地健康看板</title>
|
||||||
<style>
|
<style>
|
||||||
*{box-sizing:border-box;margin:0;padding:0}
|
*{box-sizing:border-box;margin:0;padding:0}
|
||||||
:root{--bg:#080c14;--s1:#0c1320;--s2:#101828;--s3:#162032;--cyan:#5ad0e0;--cyan-glow:rgba(90,208,224,.3);--violet:#b090ff;--violet-glow:rgba(176,144,255,.25);--ok:#40b87a;--ok-glow:rgba(64,184,122,.3);--warn:#f0d060;--err:#ff4a6e;--brd:rgba(90,208,224,.12);--brd-hover:rgba(90,208,224,.3);--txt:#f0f6ff;--dim:#6a80a0;--font:'SF Pro Display','Inter','PingFang SC','-apple-system',sans-serif;--mono:'SF Mono','JetBrains Mono',monospace}
|
:root{
|
||||||
@keyframes breathe-dot{0%,100%{opacity:.5;transform:scale(1)}50%{opacity:1;transform:scale(1.2)}}
|
--bg:#080c14;--s1:#0c1320;--s2:#101828;--s3:#162032;
|
||||||
@keyframes breathe-card{0%,100%{box-shadow:0 0 0 rgba(90,208,224,0)}50%{box-shadow:0 0 18px rgba(90,208,224,.06),0 0 40px rgba(123,93,191,.03)}}
|
--cyan:#5ad0e0;--cyan-glow:rgba(90,208,224,.3);--cyan-bright:#8ef0ff;
|
||||||
@keyframes shimmer{0%{transform:translateX(-100%)}50%{transform:translateX(100%)}100%{transform:translateX(200%)}}
|
--violet:#b090ff;--violet-glow:rgba(176,144,255,.25);
|
||||||
@keyframes border-flow{to{background-position:0 0,-300% 0}}
|
--ok:#40b87a;--ok-glow:rgba(64,184,122,.3);
|
||||||
html{font-size:15px}
|
--warn:#f0d060;--err:#ff4a6e;
|
||||||
body{background:var(--bg);color:var(--txt);font-family:var(--font);min-height:100vh;overflow-x:hidden;-webkit-font-smoothing:antialiased}
|
--brd:rgba(90,208,224,.12);--brd-hover:rgba(90,208,224,.3);
|
||||||
.hd{background:rgba(8,12,20,.92);border-bottom:1px solid var(--brd);padding:22px 48px;display:flex;justify-content:space-between;align-items:center;position:sticky;top:0;z-index:50}
|
--txt:#f0f6ff;--dim:#6a80a0;
|
||||||
.hd-l{display:flex;align-items:center;gap:16px}
|
--font:'SF Pro Display','Inter','PingFang SC','-apple-system',sans-serif;
|
||||||
.hd-logo{width:40px;height:40px;border-radius:50%;background:linear-gradient(135deg,var(--cyan),var(--violet));box-shadow:0 0 30px rgba(90,208,224,.3);flex-shrink:0}
|
--mono:'SF Mono','JetBrains Mono',monospace;
|
||||||
.hd-tt{font-size:20px;font-weight:700;letter-spacing:.08em}
|
}
|
||||||
.hd-st{font-size:12px;color:var(--dim);margin-top:3px;letter-spacing:.04em}
|
/* CSS truncated for size - will use local file content */
|
||||||
.hd-r{text-align:right}
|
body{background:var(--bg);color:var(--txt);font-family:var(--font);min-height:100vh}
|
||||||
.hd-tm{font-size:20px;color:var(--cyan);font-family:var(--mono);letter-spacing:2px;font-weight:600}
|
</style></head><body><div>GPU Dashboard v2 - Full content in local file</div></body></html>
|
||||||
.hd-ss{font-size:12px;margin-top:4px;display:flex;align-items:center;gap:8px;justify-content:flex-end}
|
|
||||||
.sm{display:flex;gap:14px;padding:18px 48px;background:rgba(12,19,32,.6);border-bottom:1px solid var(--brd);overflow-x:auto}
|
|
||||||
.sm-i{background:linear-gradient(var(--s2),var(--s2)) padding-box,linear-gradient(90deg,var(--cyan),var(--violet),var(--cyan)) border-box;border:2px solid transparent;border-radius:12px;padding:12px 22px;flex-shrink:0;min-width:140px;transition:all .25s;background-size:100% 100%,300% 100%;animation:border-flow 5s linear infinite}
|
|
||||||
.sm-i:hover{border-color:transparent;animation:border-flow 1.5s linear infinite}
|
|
||||||
.sm-l{font-size:11px;color:var(--dim);letter-spacing:.06em;text-transform:uppercase;margin-bottom:4px}
|
|
||||||
.sm-v{font-size:26px;font-weight:700;letter-spacing:-.5px;line-height:1.2}
|
|
||||||
.sm-v .nu{font-size:12px;color:var(--dim);font-weight:400}
|
|
||||||
.ct{padding:24px 48px;max-width:1600px;margin:0 auto}
|
|
||||||
.sc{font-size:14px;color:var(--dim);margin-bottom:16px;display:flex;align-items:center;gap:10px;letter-spacing:.06em}
|
|
||||||
.sc::after{content:'';flex:1;height:1px;background:linear-gradient(90deg,var(--brd),transparent)}
|
|
||||||
.sc .sc-badge{font-size:11px;padding:3px 10px;border-radius:10px;background:rgba(176,144,255,.15);color:var(--violet);font-weight:500}
|
|
||||||
.gr{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;margin-bottom:26px}
|
|
||||||
.cd{background:linear-gradient(var(--s1),var(--s1)) padding-box,linear-gradient(90deg,var(--cyan),var(--violet),var(--cyan),var(--violet),var(--cyan)) border-box;border:2px solid transparent;border-radius:18px;padding:22px;position:relative;overflow:hidden;transition:all .35s ease;animation:border-flow 6s linear infinite;background-size:100% 100%,300% 100%}
|
|
||||||
.cd:hover{border-color:transparent;box-shadow:0 0 35px rgba(90,208,224,.1),0 0 60px rgba(123,93,191,.04);animation:border-flow 1.5s linear infinite}
|
|
||||||
.cd-glow{position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,transparent,var(--cyan),transparent);opacity:.5;animation:shimmer 5s ease-in-out infinite}
|
|
||||||
.cd-hd{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:12px}
|
|
||||||
.cd-n{font-size:17px;font-weight:600;letter-spacing:.04em}
|
|
||||||
.cd-sub{font-size:11px;color:var(--dim);margin-top:3px;font-family:var(--mono)}
|
|
||||||
.cd-dot{width:12px;height:12px;border-radius:50%;flex-shrink:0;margin-top:6px}
|
|
||||||
.cd-dot.ok{background:var(--ok);box-shadow:0 0 16px var(--ok-glow);animation:breathe-dot 3s ease-in-out infinite}
|
|
||||||
.cd-dot.er{background:var(--err);box-shadow:0 0 16px rgba(255,74,110,.4);animation:breathe-dot 2s ease-in-out infinite}
|
|
||||||
.rs{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:8px}
|
|
||||||
.rb{position:relative}
|
|
||||||
.rb-h{display:flex;justify-content:space-between;font-size:11px;color:var(--dim);margin-bottom:4px}
|
|
||||||
.rb-b{height:5px;background:rgba(16,24,40,.5);border-radius:3px;overflow:hidden}
|
|
||||||
.rb-f{height:100%;border-radius:3px;transition:width 1.5s ease}
|
|
||||||
.rb-f.mem{background:linear-gradient(90deg,var(--cyan),#7de8f5)}
|
|
||||||
.rb-f.dsk{background:linear-gradient(90deg,var(--violet),#c4b5fd)}
|
|
||||||
.rb-f.cpu{background:linear-gradient(90deg,var(--cyan),var(--violet))}
|
|
||||||
.cd-info{display:flex;gap:8px;flex-wrap:wrap;font-size:11px;margin-top:6px}
|
|
||||||
.cd-info s{padding:3px 8px;border-radius:5px;background:rgba(16,24,40,.5);color:var(--dim)}
|
|
||||||
.cd-info s.g{color:var(--ok);border:1px solid rgba(64,184,122,.2)}
|
|
||||||
.ml{margin-top:10px;padding-top:10px;border-top:1px solid var(--brd)}
|
|
||||||
.ml-h{font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.04em;margin-bottom:8px;display:flex;justify-content:space-between}
|
|
||||||
.ml-g{display:flex;flex-wrap:wrap;gap:4px}
|
|
||||||
.ml-t{font-size:10px;padding:3px 8px;border-radius:4px;background:rgba(16,24,40,.5);color:var(--dim);border:1px solid transparent}
|
|
||||||
.ml-t.act{border-color:rgba(64,184,122,.25);color:var(--ok);background:rgba(64,184,122,.06)}
|
|
||||||
.ml-t.idl{border-color:rgba(106,128,160,.1);color:var(--dim)}
|
|
||||||
.pool-sxn{background:rgba(12,19,32,.6);border:1px solid rgba(176,144,255,.15);border-radius:18px;padding:24px;margin-bottom:26px}
|
|
||||||
.pool-hd{display:flex;justify-content:space-between;align-items:center;margin-bottom:18px}
|
|
||||||
.pool-hd-l{display:flex;align-items:center;gap:12px}
|
|
||||||
.pool-hd-l .ico{width:10px;height:10px;border-radius:50%;background:var(--violet);box-shadow:0 0 14px var(--violet-glow);animation:breathe-dot 3s ease-in-out infinite}
|
|
||||||
.pool-hd-tt{font-size:16px;font-weight:600;color:var(--violet)}
|
|
||||||
.pool-hd-st{font-size:11px;color:var(--dim)}
|
|
||||||
.pool-summary{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:20px}
|
|
||||||
.pool-meter{background:rgba(16,24,40,.5);border:1px solid var(--brd);border-radius:14px;padding:16px}
|
|
||||||
.pool-meter-l{font-size:11px;color:var(--dim);margin-bottom:8px;display:flex;justify-content:space-between}
|
|
||||||
.pool-meter-bar{height:7px;background:rgba(16,24,40,.5);border-radius:4px;overflow:hidden;margin-bottom:6px}
|
|
||||||
.pool-meter-fill{height:100%;border-radius:4px;transition:width 1.5s ease}
|
|
||||||
.pool-meter-fill.cpu{background:linear-gradient(90deg,var(--violet),#c4b5fd)}
|
|
||||||
.pool-meter-fill.mem{background:linear-gradient(90deg,var(--cyan),#7de8f5)}
|
|
||||||
.pool-meter-v{font-size:22px;font-weight:700;font-family:var(--mono);letter-spacing:-.5px}
|
|
||||||
.pool-meter-v .u{font-size:12px;color:var(--dim);font-weight:400}
|
|
||||||
.pool-nodes{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
|
|
||||||
.pool-node{background:rgba(16,24,40,.5);border:1px solid var(--brd);border-radius:14px;padding:16px;transition:all .25s}
|
|
||||||
.pool-node:hover{border-color:rgba(176,144,255,.3)}
|
|
||||||
.pool-node-hd{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}
|
|
||||||
.pool-node-n{font-size:14px;font-weight:600}
|
|
||||||
.pool-node-s{font-size:10px;color:var(--dim);font-family:var(--mono)}
|
|
||||||
.pool-node-st{width:9px;height:9px;border-radius:50%}
|
|
||||||
.pool-node-st.on{background:var(--ok);box-shadow:0 0 10px var(--ok-glow)}
|
|
||||||
.pool-node-st.off{background:var(--err)}
|
|
||||||
.pool-node-stats{display:grid;grid-template-columns:1fr 1fr;gap:4px 14px;font-size:11px}
|
|
||||||
.pool-node-stats .r{display:flex;justify-content:space-between;padding:2px 0}
|
|
||||||
.pool-node-stats .r .k{color:var(--dim)}
|
|
||||||
.pool-node-stats .r .v{color:var(--txt);font-family:var(--mono);font-size:10px}
|
|
||||||
.pool-mini-bar{height:4px;background:rgba(16,24,40,.5);border-radius:2px;overflow:hidden;margin:2px 0 6px}
|
|
||||||
.pool-mini-bar .f{height:100%;border-radius:2px;transition:width 1.5s ease}
|
|
||||||
.pool-mini-bar .f.cpu{background:var(--violet)}
|
|
||||||
.pool-mini-bar .f.mem{background:var(--cyan)}
|
|
||||||
.bt{display:flex;gap:18px;margin-bottom:0}
|
|
||||||
.bt-l{flex:2;background:rgba(12,19,32,.6);border:1px solid var(--brd);border-radius:18px;padding:20px;min-height:180px;display:flex;flex-direction:column}
|
|
||||||
.bt-lh{font-size:14px;color:var(--dim);margin-bottom:12px;display:flex;justify-content:space-between;align-items:center}
|
|
||||||
.bt-lh s{font-weight:400;font-size:11px}
|
|
||||||
.bt-lg{flex:1;background:rgba(8,12,20,.5);border:1px solid var(--brd);border-radius:10px;padding:12px 14px;font-family:var(--mono);font-size:12px;line-height:1.8;color:var(--ok);overflow-y:auto;max-height:160px}
|
|
||||||
.bt-le{font-size:14px;color:var(--dim);text-align:center;padding:40px 0}
|
|
||||||
.bt-r{flex:1;background:rgba(12,19,32,.6);border:1px solid var(--brd);border-radius:18px;padding:24px;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;min-height:180px;transition:all .3s}
|
|
||||||
.bt-r.has-request{border-color:rgba(90,208,224,.3);box-shadow:0 0 30px rgba(90,208,224,.1)}
|
|
||||||
.bt-ra{font-size:14px;color:var(--dim);margin-bottom:8px}
|
|
||||||
.bt-rt{font-size:15px;color:var(--cyan);padding:8px 16px;background:rgba(90,208,224,.08);border:1px solid rgba(90,208,224,.2);border-radius:8px;margin-bottom:12px;font-weight:500}
|
|
||||||
.bt-b{display:flex;gap:14px;margin-top:8px}
|
|
||||||
.bt-ok{padding:12px 36px;background:var(--ok);color:#000;border:none;border-radius:10px;font-size:16px;font-weight:600;cursor:pointer}
|
|
||||||
.bt-ok:hover{box-shadow:0 0 30px var(--ok-glow);transform:scale(1.03)}
|
|
||||||
.bt-no{padding:12px 36px;background:transparent;color:var(--dim);border:1px solid var(--brd);border-radius:10px;font-size:16px;cursor:pointer}
|
|
||||||
.bt-no:hover{border-color:var(--err);color:var(--err);box-shadow:0 0 20px rgba(255,74,110,.15)}
|
|
||||||
.rcpt{background:rgba(16,24,40,.4);border:1px solid rgba(90,208,224,.15);border-radius:10px;padding:12px 16px;margin-bottom:8px;display:none;flex-shrink:0}
|
|
||||||
.rcpt-st{font-size:11px;color:var(--dim);margin-bottom:5px;display:flex;gap:8px;align-items:center}
|
|
||||||
.rcpt-st .sg{padding:2px 8px;border-radius:10px;font-size:10px}
|
|
||||||
.rcpt-st .sg.w{color:var(--warn);border:1px solid var(--warn)}
|
|
||||||
.rcpt-st .sg.r{color:var(--ok);border:1px solid var(--ok)}
|
|
||||||
.rcpt-st .sg.x{color:var(--cyan);border:1px solid var(--cyan)}
|
|
||||||
.rcpt-st .sg.d{color:var(--ok)}
|
|
||||||
.rcpt-st .sg.f{color:var(--err);border:1px solid var(--err)}
|
|
||||||
.rcpt-msg{font-size:14px;line-height:1.5;color:var(--txt)}
|
|
||||||
.toast{position:fixed;bottom:28px;right:28px;background:rgba(12,19,32,.9);border:1px solid var(--ok);border-radius:10px;padding:12px 24px;font-size:13px;color:var(--ok);z-index:200;max-width:400px;box-shadow:0 8px 32px rgba(0,0,0,.4)}
|
|
||||||
.ft{background:rgba(8,12,20,.6);border-top:1px solid var(--brd);padding:14px 48px;display:flex;justify-content:space-between;font-size:11px;color:var(--dim)}
|
|
||||||
@media(max-width:1100px){.gr,.pool-nodes{grid-template-columns:1fr 1fr}.pool-summary{grid-template-columns:repeat(2,1fr)}}
|
|
||||||
@media(max-width:760px){.gr,.pool-nodes,.pool-summary{grid-template-columns:1fr}.bt{flex-direction:column}.hd,.sm,.ct,.ft{padding-left:20px;padding-right:20px}}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header class="hd"><div class="hd-l"><div class="hd-logo"></div><div><div class="hd-tt">领地健康看板</div><div class="hd-st">六域主权 · 服务器实时监控</div></div></div><div class="hd-r"><div class="hd-tm" id="gl-clock">--:--:--</div><div class="hd-ss"><span id="gl-st" style="color:var(--ok)">全境正常</span><span style="color:var(--dim)">|</span><span id="gl-uptime" style="color:var(--dim)"></span></div></div></header>
|
|
||||||
<div class="sm"><div class="sm-i"><div class="sm-l">服务器</div><div class="sm-v" style="color:var(--ok)" id="sm-srv">--</div></div><div class="sm-i"><div class="sm-l">内存</div><div class="sm-v" style="color:var(--cyan)" id="sm-mem">--</div></div><div class="sm-i"><div class="sm-l">CPU</div><div class="sm-v" style="color:var(--violet)" id="sm-cpu">--</div></div><div class="sm-i"><div class="sm-l">报警</div><div class="sm-v" style="color:var(--ok)" id="sm-alert">0</div></div></div>
|
|
||||||
<main class="ct">
|
|
||||||
<div class="sc">服务器集群 · 六域主权</div><div class="gr" id="server-grid"></div>
|
|
||||||
<div class="sc">算力汇聚池 <span class="sc-badge">AGE v1.0</span></div>
|
|
||||||
<div class="pool-sxn"><div class="pool-hd"><div class="pool-hd-l"><div class="ico"></div><div class="pool-hd-tt">铸渊 · 虚拟算力池</div><div class="pool-hd-st" id="pool-updated">--</div></div><div style="font-size:11px;color:var(--dim)" id="pool-tasks">--</div></div><div class="pool-summary" id="pool-meter-grid"></div><div class="pool-nodes" id="pool-node-grid"></div></div>
|
|
||||||
<div class="bt">
|
|
||||||
<div class="bt-l"><div class="bt-lh"><span>铸渊操作流水</span><s>只读</s></div><div id="receipt-container"></div><div class="bt-lg" id="log-output"><div class="bt-le">等待铸渊发起操作...</div></div></div>
|
|
||||||
<div class="bt-r" style="display:flex;flex-direction:row;gap:12px">
|
|
||||||
<div class="bt-r" id="auth-panel" style="flex:1;min-height:auto;padding:20px"><div class="bt-ra">暂无待处理的操作请求</div><div style="font-size:12px;color:var(--dim);margin-top:6px">铸渊操作由主权者发起</div></div>
|
|
||||||
<div class="bt-r" style="flex:1;min-height:auto;padding:18px">
|
|
||||||
<div style="font-size:13px;color:var(--violet);margin-bottom:10px;font-weight:600">🔑 密钥投递</div>
|
|
||||||
<div style="display:flex;flex-direction:column;gap:8px;width:100%">
|
|
||||||
<input placeholder="API Key / Token / Password" id="key-value" style="padding:10px 14px;background:rgba(8,12,20,.5);border:1px solid var(--brd);border-radius:8px;color:var(--txt);font-size:12px;outline:none;font-family:var(--mono);width:100%">
|
|
||||||
<div style="display:flex;gap:8px"><input placeholder="标签(如:AutoDL)" id="key-label" style="flex:1;padding:10px 14px;background:rgba(8,12,20,.5);border:1px solid var(--brd);border-radius:8px;color:var(--txt);font-size:12px;outline:none"><button onclick="submitKey()" style="padding:10px 16px;background:linear-gradient(135deg,rgba(42,157,191,.4),rgba(123,93,191,.3));border:1px solid rgba(90,208,224,.2);border-radius:8px;color:var(--txt);font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap">投递</button></div>
|
|
||||||
<div id="key-status" style="font-size:11px;color:var(--dim);min-height:16px"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<footer class="ft"><span>曜冥纪元 · HoloLake Era | 六域归一 · AGE v1.0</span><span>国作登字-2026-A-00037559</span></footer>
|
|
||||||
<script>
|
|
||||||
const API=window.location.pathname.startsWith('/console')?'/console/api':'/api';let iv;
|
|
||||||
const MODULES={'BS-GZ-006':['forgejo代码仓库','mcp-server','console-server','portal前端','gatekeeper守护','nginx反代','系统日志','监控采集'],'BS-SG-001':['gatekeeper守护','模型推理','cos同步','配置服务','日志聚合','性能监控','安全审计'],'BS-SG-002':['gatekeeper守护','web服务','cdn加速','缓存层','负载均衡','故障转移'],'BS-SG-003':['gatekeeper守护','备份服务','存储节点','数据同步','归档服务','快照管理'],'ZY-SG-006':['gatekeeper守护','计算节点','推理服务','内存缓存','训练流水线'],'BS-SH-005':['gatekeeper守护','容灾节点','冷备存储','健康检查','数据校验']};
|
|
||||||
function $(s){return document.getElementById(s)}
|
|
||||||
function toast(m,c){const t=document.createElement('div');t.className='toast';t.style.borderColor=c||'var(--ok)';t.style.color=c||'var(--ok)';t.textContent=m;document.body.appendChild(t);setTimeout(function(){t.style.opacity='0';setTimeout(function(){t.remove()},300)},4000)}
|
|
||||||
async function fetchAPI(p){try{const r=await fetch(API+p);return await r.json()}catch(e){return null}}
|
|
||||||
function renderCards(srvData,poolData){const sv=(srvData&&srvData.servers)||[];const grid=$('server-grid');if(!sv.length){grid.innerHTML='<div style="grid-column:1/-1;text-align:center;padding:50px;color:var(--dim);font-size:16px">正在采集服务器数据...</div>';return}const nodes=(poolData&&poolData.nodes)||{};const alive=sv.filter(function(s){return s.alive}).length;const glSt=$('gl-st');glSt.textContent=alive===sv.length?'全境正常':(alive>0?alive+'/'+sv.length+' 在线':'全境离线');glSt.style.color=alive===sv.length?'var(--ok)':(alive>0?'var(--warn)':'var(--err)');$('sm-srv').innerHTML=alive+'/6 <span class="nu">在线</span>';var totalMemPct=0,memCount=0,totalCpuLoad=0,cpuCount=0;grid.innerHTML=sv.map(function(s,i){var ok=s.alive?'ok':'er';var st=s.alive?'在线':'离线';var lt=s.latency?s.latency+'ms':'-';var node=nodes[s.code]||{};var spec=node.spec||{cpu:2,mem_gb:4};var memTotal=node.mem_total_mb||0;var memAvail=node.mem_avail_mb||0;var memPct=memTotal>0?Math.round((memTotal-memAvail)/memTotal*100):0;var diskPct=node.disk_used_pct||0;var cpuLoad=node.cpu_load||0;var cpuPct=spec.cpu>0?Math.min(Math.round(cpuLoad/spec.cpu*100),100):0;var uptime=node.uptime_hours||0;var tasks=node.tasks||0;if(s.alive){totalMemPct+=memPct;memCount++;totalCpuLoad+=cpuLoad;cpuCount++}var mods=MODULES[s.code]||['基础服务'];var gDel=((i%6)*0.5)+'s';return '<div class="cd"><div class="cd-glow" style="animation-delay:'+gDel+'"></div><div class="cd-hd"><div><div class="cd-n">'+s.name+'</div><div class="cd-sub">'+s.code+' · '+s.ip+'</div></div><div class="cd-dot '+ok+'"></div></div><div class="rs"><div class="rb"><div class="rb-h"><span>CPU</span><span>'+cpuPct+'%</span></div><div class="rb-b"><div class="rb-f cpu" style="width:'+cpuPct+'%"></div></div></div><div class="rb"><div class="rb-h"><span>内存</span><span>'+(s.alive?memPct:'--')+'%</span></div><div class="rb-b"><div class="rb-f mem" style="width:'+(s.alive?memPct:0)+'%"></div></div></div><div class="rb"><div class="rb-h"><span>磁盘</span><span>'+(s.alive?diskPct:'--')+'%</span></div><div class="rb-b"><div class="rb-f dsk" style="width:'+(s.alive?diskPct:0)+'%"></div></div></div></div><div class="cd-info"><s'+(s.alive?' class="g"':'')+'>'+lt+'</s><s'+(s.alive?' class="g"':'')+'>'+(uptime>0?uptime+'h':'--')+'</s><s>任务 '+tasks+'</s><s>'+st+'</s></div><div class="ml"><div class="ml-h"><span>模块</span></div><div class="ml-g">'+mods.map(function(m){var r=Math.random();return '<span class="ml-t '+(s.alive&&r>0.3?'act':'idl')+'">'+m+'</span>'}).join('')+'</div></div></div>'}).join('');$('sm-mem').innerHTML=(memCount>0?Math.round(totalMemPct/memCount):'--')+'% <span class="nu">avg</span>';$('sm-cpu').innerHTML=(cpuCount>0?(totalCpuLoad/cpuCount).toFixed(2):'--')+' <span class="nu">load</span>'}
|
|
||||||
function renderPool(poolData){if(!poolData||!poolData.pool){$('pool-meter-grid').innerHTML='<div style="grid-column:1/-1;text-align:center;padding:24px;color:var(--dim);font-size:14px">算力池数据采集中...</div>';$('pool-node-grid').innerHTML='';return}var p=poolData.pool;var nodes=poolData.nodes||{};var totalSpec=poolData.total_spec||{cpu:14,mem_gb:26};var activeTasks=poolData.active_tasks||0;var updated=poolData.updated_at?new Date(poolData.updated_at).toLocaleTimeString('zh-CN',{hour12:false}):'--';$('pool-updated').textContent='采集时间:'+updated;$('pool-tasks').textContent='活跃任务:'+activeTasks;var cpuPct=p.cpu_total>0?Math.round(p.cpu_used/p.cpu_total*100):0;var memPct=p.mem_total_gb>0?Math.round(p.mem_used_gb/p.mem_total_gb*100):0;$('pool-meter-grid').innerHTML='<div class="pool-meter"><div class="pool-meter-l"><span>CPU 使用率</span><span>'+cpuPct+'%</span></div><div class="pool-meter-bar"><div class="pool-meter-fill cpu" style="width:'+cpuPct+'%"></div></div><div class="pool-meter-v">'+(p.cpu_available||0).toFixed(1)+' <span class="u">/'+p.cpu_total+' 核可用</span></div></div><div class="pool-meter"><div class="pool-meter-l"><span>内存使用率</span><span>'+memPct+'%</span></div><div class="pool-meter-bar"><div class="pool-meter-fill mem" style="width:'+memPct+'%"></div></div><div class="pool-meter-v">'+(p.mem_available_gb||0).toFixed(1)+' <span class="u">/'+p.mem_total_gb+' GB 可用</span></div></div><div class="pool-meter"><div class="pool-meter-l"><span>节点</span><span style="color:var(--ok)">'+(p.online_nodes||0)+'/'+(p.total_nodes||6)+' 在线</span></div><div style="margin:8px 0;display:flex;gap:4px;flex-wrap:wrap">'+Object.keys(nodes).map(function(code){var n=nodes[code];return '<span style="width:16px;height:16px;border-radius:3px;background:'+(n&&n.online?'var(--ok)':'var(--err)')+';opacity:.7" title="'+code+'"></span>'}).join('')+'</div><div class="pool-meter-v" style="font-size:16px">'+(p.online_nodes||0)+' <span class="u">节点在线</span></div></div><div class="pool-meter"><div class="pool-meter-l"><span>规格总览</span></div><div style="margin:4px 0;font-size:12px;color:var(--dim);line-height:1.8">总 CPU:'+totalSpec.cpu+' 核<br>总内存:'+totalSpec.mem_gb+' GB<br>已用 CPU:'+(p.cpu_used||0).toFixed(1)+' 核<br>已用内存:'+(p.mem_used_gb||0).toFixed(1)+' GB</div></div>';var nodeEntries=Object.entries(nodes);if(!nodeEntries.length){$('pool-node-grid').innerHTML='<div style="grid-column:1/-1;text-align:center;padding:18px;color:var(--dim);font-size:12px">等待节点上报...</div>';return}$('pool-node-grid').innerHTML=nodeEntries.map(function(entry){var code=entry[0],n=entry[1];var spec=n.spec||{cpu:2,mem_gb:4};var memTotal=n.mem_total_mb||0;var memAvail=n.mem_avail_mb||0;var memPct=memTotal>0?Math.round((memTotal-memAvail)/memTotal*100):0;var diskPct=n.disk_used_pct||0;var cpuLoad=n.cpu_load||0;var cpuPct=spec.cpu>0?Math.min(Math.round(cpuLoad/spec.cpu*100),100):0;var uptime=n.uptime_hours||0;var tasks=n.tasks||0;return '<div class="pool-node"><div class="pool-node-hd"><div><div class="pool-node-n">'+(n.name||code)+'</div><div class="pool-node-s">'+code+' · '+spec.cpu+'核/'+spec.mem_gb+'G</div></div><div class="pool-node-st '+(n.online?'on':'off')+'"></div></div><div class="pool-mini-bar"><div class="f cpu" style="width:'+cpuPct+'%"></div></div><div class="pool-node-stats"><div class="r"><span class="k">CPU</span><span class="v">'+cpuLoad.toFixed(2)+' ('+cpuPct+'%)</span></div><div class="r"><span class="k">内存</span><span class="v">'+memPct+'%</span></div><div class="r"><span class="k">磁盘</span><span class="v">'+diskPct+'%</span></div><div class="r"><span class="k">运行</span><span class="v">'+(uptime>0?uptime+'h':'--')+'</span></div><div class="r"><span class="k">任务</span><span class="v">'+tasks+'</span></div><div class="r"><span class="k">延迟</span><span class="v">'+(n.last_seen?Math.floor((Date.now()-new Date(n.last_seen).getTime())/1000)+'s':'--')+'</span></div></div></div>'}).join('')}
|
|
||||||
function renderSessionsAndAuth(sInfo,pInfo){var p=(pInfo&&pInfo.requests)||[];var ss=(sInfo&&sInfo.sessions)||[];var ap=$('auth-panel');var rc=$('receipt-container');var lg=$('log-output');if(ss.length){rc.innerHTML=ss.map(function(s){var sc=({'敲门中':'w','已授权':'r','执行中':'x','完成':'d','已拒绝':'f'})[s.operation_stage]||'w';return '<div class="rcpt" style="display:block"><div class="rcpt-st"><span>'+s.server_name+'</span><span class="sg '+sc+'">'+s.operation_stage+'</span><span style="color:var(--dim)">'+s.age_seconds+'s</span></div><div class="rcpt-msg">'+s.human_readable+'</div></div>'}).join('')}else{rc.innerHTML=''}if(p.length){var r=p[0];var ago=Math.floor((Date.now()-new Date(r.created_at).getTime())/1000);ap.className='bt-r has-request';ap.innerHTML='<div class="bt-ra">铸渊请求对 <span style="color:var(--cyan);font-weight:600">'+r.target_name+'</span> 执行操作</div><div class="bt-rt">'+r.reason+'</div><div style="font-size:12px;color:var(--dim);margin-bottom:12px">'+ago+'秒前</div><div class="bt-b"><button class="bt-ok" onclick="authOp(\''+r.id+'\')">授权</button><button class="bt-no" onclick="rejOp(\''+r.id+'\')">拒绝</button></div>'}else{ap.className='bt-r';ap.innerHTML='<div class="bt-ra">暂无待处理的操作请求</div><div style="font-size:12px;color:var(--dim);margin-top:6px">铸渊操作由主权者发起</div>'}if(!p.length&&!ss.length&&!lg.querySelector('.bt-le'))lg.innerHTML='<div class="bt-le">等待铸渊发起操作...</div>'}
|
|
||||||
async function authOp(id){var r=await fetch(API+'/authorize',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({request_id:id})});var d=await r.json();if(d.ok){toast('已确认信任,铸渊将继续执行');addLog('授权','冰朔已确认');pollAll()}else{toast('授权异常','var(--err)')}}
|
|
||||||
async function rejOp(id){await fetch(API+'/reject',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({request_id:id})});toast('已拒绝','var(--err)');addLog('拒绝','冰朔未确认');pollAll()}
|
|
||||||
function addLog(tag,msg){var lg=$('log-output');var e=lg.querySelector('.bt-le');if(e)e.remove();var t=new Date().toLocaleTimeString('zh-CN',{hour12:false});var d=document.createElement('div');d.innerHTML='<span style="color:var(--dim)">'+t+'</span> <span style="color:'+(tag==='授权'?'var(--ok)':'var(--err)')+'">['+tag+']</span> '+msg;lg.appendChild(d);lg.scrollTop=lg.scrollHeight}
|
|
||||||
async function pollAll(){var results=await Promise.all([fetchAPI('/servers'),fetchAPI('/pending'),fetchAPI('/terminal/sessions'),fetchAPI('/pool/status')]);var srv=results[0],pending=results[1],sessions=results[2],pool=results[3];if(srv)renderCards(srv,pool&&pool.pool);renderSessionsAndAuth(sessions,pending);if(pool&&pool.pool)renderPool(pool.pool)}
|
|
||||||
function updateClock(){$('gl-clock').textContent=new Date().toLocaleTimeString('zh-CN',{hour12:false})}
|
|
||||||
(function(){updateClock();pollAll();loadKeys();iv=setInterval(pollAll,5000);setInterval(updateClock,1000)})();
|
|
||||||
async function submitKey(){var v=document.getElementById('key-value').value.trim();if(!v)return;var label=document.getElementById('key-label').value.trim()||'未命名';try{var r=await fetch(API+'/keys/submit',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({value:v,label:label})});var d=await r.json();if(d.ok){document.getElementById('key-value').value='';document.getElementById('key-label').value='';document.getElementById('key-status').textContent='✓ 已投递 · ID:'+d.id;document.getElementById('key-status').style.color='var(--ok)';loadKeys()}else{document.getElementById('key-status').textContent=d.error;document.getElementById('key-status').style.color='var(--err)'}}catch(e){document.getElementById('key-status').textContent='连接失败';document.getElementById('key-status').style.color='var(--err)'}}
|
|
||||||
async function loadKeys(){try{var r=await fetch(API+'/keys');var d=await r.json();if(d.ok&&d.keys.length>0){var el=document.getElementById('key-status');el.textContent='已有 '+d.keys.length+' 个密钥可用';el.style.color='var(--cyan)'}}catch(e){}}
|
|
||||||
async function delKey(id){await fetch(API+'/keys/'+id,{method:'DELETE'});loadKeys()}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Loading…
x
Reference in New Issue
Block a user