console v3.3 HTML: 动态计数+企业服务器+硬件直读+滚动日志
This commit is contained in:
parent
020afd6f49
commit
0600063e21
@ -20,12 +20,10 @@
|
||||
@keyframes breathe-border{0%,100%{border-color:rgba(90,208,224,.08)}50%{border-color:rgba(90,208,224,.25)}}
|
||||
@keyframes breathe-dot{0%,100%{opacity:.5;transform:scale(1)}50%{opacity:1;transform:scale(1.2)}}
|
||||
@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)}}
|
||||
@keyframes breathe-live{0%,100%{opacity:.4}50%{opacity:1}}
|
||||
@keyframes shimmer{0%{transform:translateX(-100%)}50%{transform:translateX(100%)}100%{transform:translateX(200%)}}
|
||||
html{font-size:15px}
|
||||
body{background:var(--bg);color:var(--txt);font-family:var(--font);min-height:100vh;overflow-x:hidden;-webkit-font-smoothing:antialiased}
|
||||
|
||||
/* ── 顶栏 ── */
|
||||
.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}
|
||||
.hd-l{display:flex;align-items:center;gap:16px}
|
||||
.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}
|
||||
@ -35,7 +33,6 @@ body{background:var(--bg);color:var(--txt);font-family:var(--font);min-height:10
|
||||
.hd-tm{font-size:20px;color:var(--cyan);font-family:var(--mono);letter-spacing:2px;font-weight:600}
|
||||
.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:rgba(16,24,40,.5);border:1px solid var(--brd);border-radius:12px;padding:12px 22px;flex-shrink:0;min-width:140px;transition:all .25s;animation:breathe-border 5s ease-in-out infinite}
|
||||
.sm-i:hover{border-color:var(--brd-hover)}
|
||||
@ -43,13 +40,11 @@ body{background:var(--bg);color:var(--txt);font-family:var(--font);min-height:10
|
||||
.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:rgba(12,19,32,.6);border:1px solid var(--brd);border-radius:18px;padding:22px;position:relative;overflow:hidden;transition:all .35s ease;animation:breathe-card 6s ease-in-out infinite}
|
||||
.cd:hover{border-color:var(--brd-hover);box-shadow:0 0 35px rgba(90,208,224,.1),0 0 60px rgba(123,93,191,.04)}
|
||||
@ -78,7 +73,6 @@ body{background:var(--bg);color:var(--txt);font-family:var(--font);min-height:10
|
||||
.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}
|
||||
@ -112,12 +106,20 @@ body{background:var(--bg);color:var(--txt);font-family:var(--font);min-height:10
|
||||
.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{display:flex;gap:18px;margin-bottom:26px}
|
||||
.bt-l{flex:2;background:rgba(12,19,32,.6);border:1px solid var(--brd);border-radius:18px;padding:20px;display:flex;flex-direction:column;min-height:200px}
|
||||
.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-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:11px;line-height:1.7;overflow-y:auto;max-height:180px;min-height:80px}
|
||||
.bt-lg .log-line{padding:2px 0;border-bottom:1px solid rgba(255,255,255,.02);display:flex;gap:8px;align-items:baseline}
|
||||
.bt-lg .log-time{color:var(--text-faint,var(--dim));font-size:10px;white-space:nowrap;min-width:48px}
|
||||
.bt-lg .log-act{font-weight:600;min-width:80px;font-size:10px}
|
||||
.bt-lg .log-act.auth{color:var(--ok)}
|
||||
.bt-lg .log-act.reject{color:var(--err)}
|
||||
.bt-lg .log-act.info{color:var(--cyan)}
|
||||
.bt-lg .log-act.warn{color:var(--warn)}
|
||||
.bt-lg .log-act.err{color:var(--err)}
|
||||
.bt-lg .log-detail{color:var(--dim);font-size:10px;word-break:break-all}
|
||||
.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)}
|
||||
@ -139,7 +141,6 @@ body{background:var(--bg);color:var(--txt);font-family:var(--font);min-height:10
|
||||
.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;transition:opacity .3s;box-shadow:0 8px 32px rgba(0,0,0,.4)}
|
||||
|
||||
/* ── 3090 GPU Agent训练台 ── */
|
||||
.gpu-sxn{background:rgba(12,19,32,.6);border:1px solid rgba(90,208,224,.15);border-radius:18px;padding:24px;margin-bottom:26px}
|
||||
.gpu-sxn-hd{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}
|
||||
.gpu-sxn-hd-l{display:flex;align-items:center;gap:12px}
|
||||
@ -209,7 +210,7 @@ body{background:var(--bg);color:var(--txt);font-family:var(--font);min-height:10
|
||||
<div class="hd-logo"></div>
|
||||
<div>
|
||||
<div class="hd-tt">领地健康看板</div>
|
||||
<div class="hd-st">六域主权 · 服务器实时监控</div>
|
||||
<div class="hd-st">六域主权 · 11台服务器实时监控</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hd-r">
|
||||
@ -230,7 +231,7 @@ body{background:var(--bg);color:var(--txt);font-family:var(--font);min-height:10
|
||||
</div>
|
||||
|
||||
<main class="ct">
|
||||
<div class="sc">服务器集群 · 六域主权</div>
|
||||
<div class="sc" id="sc-servers">服务器集群 · 全境监控</div>
|
||||
<div class="gr" id="server-grid"></div>
|
||||
|
||||
<div class="sc">算力汇聚池 <span class="sc-badge">AGE v1.0</span></div>
|
||||
@ -282,8 +283,7 @@ body{background:var(--bg);color:var(--txt);font-family:var(--font);min-height:10
|
||||
|
||||
<div class="bt">
|
||||
<div class="bt-l">
|
||||
<div class="bt-lh"><span>铸渊操作流水</span><s>只读</s></div>
|
||||
<div id="receipt-container"></div>
|
||||
<div class="bt-lh"><span>铸渊操作流水 · 滚动日志</span><s id="log-count">0条</s></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:18px">
|
||||
@ -313,7 +313,7 @@ body{background:var(--bg);color:var(--txt);font-family:var(--font);min-height:10
|
||||
|
||||
<script>
|
||||
const API = window.location.pathname.startsWith('/console') ? '/console/api' : '/api';
|
||||
let iv;
|
||||
var iv, serverData = [];
|
||||
|
||||
const MODULES = {
|
||||
'BS-GZ-006':['forgejo代码仓库','mcp-server','console-server','portal前端','gatekeeper守护','nginx反代','系统日志','监控采集'],
|
||||
@ -321,13 +321,16 @@ const MODULES = {
|
||||
'BS-SG-002':['gatekeeper守护','web服务','cdn加速','缓存层','负载均衡','故障转移'],
|
||||
'BS-SG-003':['gatekeeper守护','备份服务','存储节点','数据同步','归档服务','快照管理'],
|
||||
'ZY-SG-006':['gatekeeper守护','计算节点','推理服务','内存缓存','训练流水线'],
|
||||
'BS-SH-005':['gatekeeper守护','容灾节点','冷备存储','健康检查','数据校验']
|
||||
'BS-SH-005':['gatekeeper守护','容灾节点','冷备存储','健康检查','数据校验'],
|
||||
'AW-GZ-001':['企业代码仓库','CI/CD流水线','团队开发','监控采集','备份服务','gatekeeper守护'],
|
||||
'AW-SH-002':['灾备节点','数据同步','快照管理','故障转移','gatekeeper守护'],
|
||||
'AW-GZ-003':['灾备节点','冷备存储','健康检查','数据校验','gatekeeper守护']
|
||||
};
|
||||
|
||||
function $(s) { return document.getElementById(s) }
|
||||
|
||||
function toast(m, c) {
|
||||
const t = document.createElement('div');
|
||||
var t = document.createElement('div');
|
||||
t.className = 'toast';
|
||||
t.style.borderColor = c || 'var(--ok)';
|
||||
t.style.color = c || 'var(--ok)';
|
||||
@ -337,62 +340,66 @@ function toast(m, c) {
|
||||
}
|
||||
|
||||
async function fetchAPI(p) {
|
||||
try { const r = await fetch(API + p); return await r.json() }
|
||||
try { var 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');
|
||||
function renderCards(srvData) {
|
||||
var sv = (srvData && srvData.servers) || [];
|
||||
if (!sv.length) {
|
||||
grid.innerHTML = '<div style="grid-column:1/-1;text-align:center;padding:50px;color:var(--dim);font-size:16px">正在采集服务器数据...</div>';
|
||||
$('server-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 alive = sv.filter(function(s) { return s.alive }).length;
|
||||
var total = sv.length;
|
||||
|
||||
$('gl-st').textContent = alive === total ? '全境正常' : (alive > 0 ? alive + '/' + total + ' 在线' : '全境离线');
|
||||
$('gl-st').style.color = alive === total ? 'var(--ok)' : (alive > 0 ? 'var(--warn)' : 'var(--err)');
|
||||
$('sm-srv').innerHTML = alive + '/' + total + ' <span class="nu">在线</span>';
|
||||
$('sc-servers').innerHTML = '服务器集群 · ' + total + '台主机 <span class="sc-badge">冰朔6 + 企业3</span>';
|
||||
|
||||
var totalMemPct = 0, memCount = 0, totalCpuLoad = 0, cpuCount = 0;
|
||||
|
||||
grid.innerHTML = sv.map(function(s, i) {
|
||||
$('server-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 spec = s.spec || { cpu: 2, mem_gb: 4, disk_gb: 50 };
|
||||
|
||||
var hw = s.hardware || {};
|
||||
var cpuPct = hw.cpu_pct !== undefined ? hw.cpu_pct : 0;
|
||||
var memPct = hw.mem_pct !== undefined ? hw.mem_pct : 0;
|
||||
var diskPct = hw.disk_used_pct !== undefined ? hw.disk_used_pct : 0;
|
||||
var uptime = hw.uptime_h || 0;
|
||||
var load1 = hw.load_1min || 0;
|
||||
var memUsed = hw.mem_used_mb ? (hw.mem_used_mb / 1024).toFixed(1) : '--';
|
||||
var memTotal = hw.mem_total_mb ? (hw.mem_total_mb / 1024).toFixed(0) : spec.mem_gb;
|
||||
var diskUsed = hw.disk_used || '--';
|
||||
var diskTotal = hw.disk_total || spec.disk_gb + 'G';
|
||||
var source = hw.source || '--';
|
||||
|
||||
if (s.alive && hw.source === 'live') { totalMemPct += memPct; memCount++; totalCpuLoad += load1; 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-hd"><div><div class="cd-n">' + s.name + '</div><div class="cd-sub">' + s.code + ' · ' + s.ip + ' · ' + spec.cpu + '核/' + spec.mem_gb + 'G/' + spec.disk_gb + 'G</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 class="rb"><div class="rb-h"><span>内存 ' + memUsed + '/' + memTotal + 'G</span><span>' + memPct + '%</span></div><div class="rb-b"><div class="rb-f mem" style="width:' + memPct + '%"></div></div></div>' +
|
||||
'<div class="rb"><div class="rb-h"><span>磁盘 ' + diskUsed + '/' + diskTotal + '</span><span>' + diskPct + '%</span></div><div class="rb-b"><div class="rb-f dsk" style="width:' + diskPct + '%"></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>' +
|
||||
'<s>load ' + load1.toFixed(1) + '</s><s>' + st + '</s>' +
|
||||
'</div>' +
|
||||
'<div class="ml"><div class="ml-h"><span>模块</span></div>' +
|
||||
'<div class="ml"><div class="ml-h"><span>模块</span><span style="font-size:9px;color:var(--text-faint)">数据源:' + source + '</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>';
|
||||
return '<span class="ml-t ' + (s.alive ? 'act' : 'idl') + '">' + m + '</span>';
|
||||
}).join('') + '</div></div></div>';
|
||||
}).join('');
|
||||
|
||||
@ -408,7 +415,7 @@ function renderPool(poolData) {
|
||||
}
|
||||
var p = poolData.pool;
|
||||
var nodes = poolData.nodes || {};
|
||||
var totalSpec = poolData.total_spec || { cpu: 14, mem_gb: 26 };
|
||||
var totalSpec = poolData.total_spec || { cpu: 32, mem_gb: 77 };
|
||||
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;
|
||||
@ -429,7 +436,7 @@ function renderPool(poolData) {
|
||||
'<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 class="pool-meter-l"><span>节点</span><span style="color:var(--ok)">' + (p.online_nodes || 0) + '/' + (p.total_nodes || 9) + ' 在线</span></div>' +
|
||||
'<div style="margin:8px 0;display:flex;gap:4px;flex-wrap:wrap">' +
|
||||
Object.keys(nodes).map(function(code) {
|
||||
var n = nodes[code];
|
||||
@ -448,7 +455,7 @@ function renderPool(poolData) {
|
||||
|
||||
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>';
|
||||
$('pool-node-grid').innerHTML = '<div style="grid-column:1/-1;text-align:center;padding:18px;color:var(--dim);font-size:12px">等待节点上报(需要部署心跳Agent)...</div>';
|
||||
return;
|
||||
}
|
||||
$('pool-node-grid').innerHTML = nodeEntries.map(function(entry) {
|
||||
@ -481,19 +488,31 @@ function renderPool(poolData) {
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function renderSessionsAndAuth(sInfo, pInfo) {
|
||||
var p = (pInfo && pInfo.requests) || [];
|
||||
var ss = (sInfo && sInfo.sessions) || [];
|
||||
var ap = $('auth-panel');
|
||||
var rc = $('receipt-container');
|
||||
function renderLogs(logData) {
|
||||
var logs = (logData && logData.logs) || [];
|
||||
var lg = $('log-output');
|
||||
$('log-count').textContent = (logData && logData.total) ? logData.total + '条' : '0条';
|
||||
|
||||
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 (!logs.length) {
|
||||
lg.innerHTML = '<div class="bt-le">等待铸渊发起操作...</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
lg.innerHTML = logs.map(function(l) {
|
||||
var t = l.time ? new Date(l.time).toLocaleTimeString('zh-CN', { hour12: false }) : '';
|
||||
var cls = l.level === 'error' ? 'err' : (l.level === 'warn' ? 'warn' : 'info');
|
||||
return '<div class="log-line">' +
|
||||
'<span class="log-time">' + t + '</span>' +
|
||||
'<span class="log-act ' + cls + '">' + (l.action || '') + '</span>' +
|
||||
'<span class="log-detail">' + (l.name || l.code || '') + ' ' + (l.detail || '') + '</span>' +
|
||||
'</div>';
|
||||
}).join('');
|
||||
lg.scrollTop = 0;
|
||||
}
|
||||
|
||||
function renderSessionsAndAuth(pInfo) {
|
||||
var p = (pInfo && pInfo.requests) || [];
|
||||
var ap = $('auth-panel');
|
||||
|
||||
if (p.length) {
|
||||
var r = p[0];
|
||||
@ -504,43 +523,43 @@ function renderSessionsAndAuth(sInfo, pInfo) {
|
||||
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() }
|
||||
if (d.ok) { toast('已确认信任,铸渊将继续执行'); 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'), fetchAPI('/agent/combined')]);
|
||||
var srv = results[0], pending = results[1], sessions = results[2], pool = results[3], agent = results[4];
|
||||
if (srv) renderCards(srv, pool && pool.pool);
|
||||
renderSessionsAndAuth(sessions, pending);
|
||||
var results = await Promise.all([
|
||||
fetchAPI('/servers'),
|
||||
fetchAPI('/pending'),
|
||||
fetchAPI('/pool/status'),
|
||||
fetchAPI('/logs?limit=30'),
|
||||
fetchAPI('/summary'),
|
||||
fetchAPI('/agent/combined')
|
||||
]);
|
||||
var srv = results[0], pending = results[1], pool = results[2], logs = results[3], summary = results[4], agent = results[5];
|
||||
|
||||
if (srv) renderCards(srv);
|
||||
renderSessionsAndAuth(pending);
|
||||
if (pool && pool.pool) renderPool(pool.pool);
|
||||
if (logs) renderLogs(logs);
|
||||
if (agent) renderAgentData(agent);
|
||||
|
||||
if (summary && summary.alerts !== undefined) {
|
||||
var c = summary.alerts;
|
||||
$('sm-alert').innerHTML = c;
|
||||
$('sm-alert').style.color = c > 0 ? 'var(--err)' : 'var(--ok)';
|
||||
}
|
||||
}
|
||||
|
||||
function updateClock() {
|
||||
@ -553,7 +572,7 @@ function renderAgentData(data) {
|
||||
var updated = $('agent-updated');
|
||||
var host = $('agent-hostname');
|
||||
|
||||
if (data.online && data.last_seen) {
|
||||
if (data && data.online && data.last_seen) {
|
||||
dot.className = 'gpu-sxn-dot on';
|
||||
badge.textContent = '在线';
|
||||
badge.style.background = 'rgba(64,184,122,.15)';
|
||||
@ -570,17 +589,14 @@ function renderAgentData(data) {
|
||||
host.textContent = '--';
|
||||
}
|
||||
|
||||
if (data.gpu && data.gpu.gpus && data.gpu.gpus.length > 0) {
|
||||
if (data && data.gpu && data.gpu.gpus && data.gpu.gpus.length > 0) {
|
||||
renderGPU(data.gpu.gpus);
|
||||
} else if (!data.online) {
|
||||
} else if (!data || !data.online) {
|
||||
$('gpu-info-card').innerHTML = '<div class="agent-offline">等待铸渊Agent上报GPU数据...</div>';
|
||||
}
|
||||
|
||||
if (data.training) {
|
||||
renderTraining(data.training);
|
||||
}
|
||||
|
||||
if (data.diary && data.diary.length > 0) {
|
||||
if (data && data.training) renderTraining(data.training);
|
||||
if (data && data.diary && data.diary.length > 0) {
|
||||
renderDiary(data.diary);
|
||||
} else {
|
||||
$('diary-grid').innerHTML = '<div class="agent-offline" style="grid-column:1/-1">Agent尚未产生日记条目</div>';
|
||||
@ -676,9 +692,8 @@ function formatDuration(sec) {
|
||||
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 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