console v3.3 HTML: 动态计数+企业服务器+硬件直读+滚动日志

This commit is contained in:
bingshuo 2026-05-25 23:09:42 +08:00
parent 020afd6f49
commit 0600063e21

View File

@ -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>