guanghulab/homepage/console-v2.html

82 lines
6.6 KiB
HTML

<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>技术主控台 · 光湖团队</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:#030712;color:#e5e7eb;min-height:100vh}
.top{background:rgba(17,17,28,.95);border-bottom:1px solid rgba(255,255,255,.06);padding:0 24px;height:56px;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:50}
.top h1{font-size:16px;font-weight:600;color:#fff}
.top .back{color:#9ca3af;text-decoration:none;font-size:13px}
.top .back:hover{color:#fff}
.container{max-width:1280px;margin:0 auto;padding:24px}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px}
.card{background:rgba(17,17,28,.8);border:1px solid rgba(255,255,255,.06);border-radius:14px;padding:20px}
.card h2{font-size:14px;color:#9ca3af;margin-bottom:16px;display:flex;align-items:center;gap:8px}
.dot{width:7px;height:7px;border-radius:50%;display:inline-block}
.dot.online{background:#22c55e;box-shadow:0 0 8px rgba(34,197,94,.4)}
.dot.offline{background:#ef4444;box-shadow:0 0 8px rgba(239,68,68,.4)}
.metric-row{display:flex;justify-content:space-between;align-items:baseline;margin:12px 0}
.metric-label{color:#6b7280;font-size:13px}
.metric-value{font-size:24px;font-weight:600;color:#fff}
.bar{height:4px;background:rgba(255,255,255,.06);border-radius:2px;margin-top:6px;overflow:hidden}
.bar-fill{height:100%;border-radius:2px;transition:width .8s}
.bar-fill.good{background:linear-gradient(90deg,#22c55e,#4ade80)}
.bar-fill.warn{background:linear-gradient(90deg,#f59e0b,#fbbf24)}
.bar-fill.bad{background:linear-gradient(90deg,#ef4444,#f87171)}
.tag{display:inline-block;padding:3px 10px;border-radius:4px;font-size:11px;font-weight:500}
.tag.good{background:rgba(34,197,94,.15);color:#4ade80}
.tag.bad{background:rgba(239,68,68,.15);color:#f87171}
.svc-item{display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid rgba(255,255,255,.03);font-size:13px}
.svc-item:last-child{border:none}
.act-item{padding:10px 0;border-bottom:1px solid rgba(255,255,255,.03);font-size:13px}
.act-item:last-child{border:none}
.act-repo{color:#6366f1;font-size:11px;font-weight:500}
.act-msg{color:#e5e7eb;margin:3px 0}
.act-meta{color:#6b7280;font-size:11px}
.empty{color:#4b5563;text-align:center;padding:20px;font-size:13px}
.live{animation:pulse 2s infinite;color:#22c55e;font-size:11px}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}
</style></head>
<body>
<div class="top"><h1>技术主控台</h1><a class="back" href="/">← 返回首页</a></div>
<div class="container">
<div class="grid">
<div class="card"><h2><span class="dot online"></span>处理器</h2><div class="metric-row"><span class="metric-label">负载</span><span class="metric-value" id="cpu-v">--</span></div><div class="bar"><div class="bar-fill" id="cpu-bar" style="width:0%"></div></div><div class="metric-row"><span class="metric-label">状态</span><span class="tag" id="cpu-tag">--</span></div></div>
<div class="card"><h2><span class="dot online"></span>内存</h2><div class="metric-row"><span class="metric-label">已用</span><span class="metric-value" id="mem-v">--</span></div><div class="bar"><div class="bar-fill" id="mem-bar" style="width:0%"></div></div><div class="metric-row"><span class="metric-label">状态</span><span class="tag" id="mem-tag">--</span></div></div>
<div class="card"><h2><span class="dot online"></span>磁盘</h2><div class="metric-row"><span class="metric-label">已用</span><span class="metric-value" id="disk-v">--</span></div><div class="bar"><div class="bar-fill" id="disk-bar" style="width:0%"></div></div><div class="metric-row"><span class="metric-label">状态</span><span class="tag" id="disk-tag">--</span></div></div>
</div>
<div class="grid" style="margin-top:16px">
<div class="card"><h2><span class="dot online"></span>服务器节点 · 心跳灯塔</h2><div id="servers"></div></div>
<div class="card"><h2><span class="dot online"></span>运行服务</h2><div id="services"></div></div>
</div>
<div class="grid" style="margin-top:16px">
<div class="card" style="grid-column:1/-1"><h2><span class="dot online"></span>代码仓库动态 <span class="live">● 实时</span></h2><div id="activities"><div class="empty">加载中...</div></div></div>
</div>
<div style="text-align:center;margin-top:16px;color:#4b5563;font-size:11px">每5秒自动刷新 · 铸渊 ICE-GL-ZY001 部署</div>
</div>
<script>
async function load(){
try{
const s=await fetch('/api/status').then(r=>r.json());
setMetric('cpu',s.cpu.usage,s.cpu.status);
setMetric('mem',s.memory.percent,s.memory.status,s.memory.used+'M/'+s.memory.total+'M');
setMetric('disk',parseInt(s.disk.used),s.disk.status,s.disk.used+'/'+s.disk.total);
const hb=await fetch('/api/heartbeat').then(r=>r.json());
document.getElementById('servers').innerHTML=hb.servers.map(sv=>'<div class="svc-item"><span>'+sv.name+'</span><span style="font-size:12px;color:#6b7280">'+sv.ip+'</span><span class="tag '+(sv.status==='online'?'good':'bad')+'">'+(sv.status==='online'?'在线':'离线')+'</span></div>').join('')+'<div class="svc-item"><span>Gatekeeper</span><span class="tag bad">未连接</span></div>';
const svc=await fetch('/api/services').then(r=>r.json());
document.getElementById('services').innerHTML=svc.services.length?svc.services.map(x=>'<div class="svc-item"><span>'+x.name+'</span><span class="tag '+(x.status==='running'||x.status==='online'?'good':'bad')+'">'+(x.status==='running'||x.status==='online'?'运行中':x.status)+'</span></div>').join(''):'<div class="empty">暂无</div>';
const act=await fetch('/api/repo-activity').then(r=>r.json());
document.getElementById('activities').innerHTML=act.activities.length?act.activities.map(a=>'<div class="act-item"><div class="act-repo">'+a.repo+' · '+a.author+'</div><div class="act-msg">'+a.msg+'</div><div class="act-meta">'+new Date(a.date).toLocaleString('zh-CN')+'</div></div>').join(''):'<div class="empty">仓库暂无提交 · 等待团队成员推送代码</div>';
}catch(e){console.error(e)}
}
function setMetric(id,val,status,extra){
const el=document.getElementById(id+'-v');
el.textContent=extra||val+'%';
document.getElementById(id+'-bar').style.width=val+'%';
const cls=status==='good'?'good':status==='warn'?'warn':'bad';
document.getElementById(id+'-bar').className='bar-fill '+cls;
const t=document.getElementById(id+'-tag');
t.textContent=status==='good'?'正常':status==='warn'?'注意':'告警';
t.className='tag '+cls;
}
load();setInterval(load,5000);
</script></body></html>