- 登录遮罩:Forgejo 账号密码验证,登录后解锁主控台 - 服务器系统模块:console-server API 实时拉取 9 台服务器在线状态 - 代码仓库模块:Forgejo API 真实拉取最新提交 - 部署注册表/主控广播/铸渊交互:全部接真实数据 - 大桌子小桌子入口跳转 /desk/ - 保留原有 UI 风格(动态粒子背景 + 卡片悬浮效果) ICE-GL-ZY001 · D115
514 lines
24 KiB
HTML
514 lines
24 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>
|
|
:root {
|
|
--bg: #0a0a14; --card: rgba(12,22,50,.8); --edge: rgba(110,160,230,.2);
|
|
--edge-h: rgba(150,200,255,.45); --t: rgba(195,215,255,.9); --dim: rgba(155,195,235,.55);
|
|
--dim2: rgba(155,195,235,.35); --glow: rgba(160,210,255,.95);
|
|
--green: rgba(140,220,180,.9); --gold: rgba(220,200,140,.9); --red: rgba(255,140,140,.8);
|
|
}
|
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
body{background:var(--bg);color:var(--t);font-family:-apple-system,"PingFang SC","Microsoft YaHei",sans-serif;min-height:100vh;font-size:18px}
|
|
canvas{position:fixed;top:0;left:0;z-index:0}
|
|
.wrap{position:relative;z-index:1;max-width:1100px;margin:0 auto;padding:40px 24px 100px}
|
|
|
|
/* Login overlay */
|
|
.login-overlay {
|
|
position:fixed;inset:0;z-index:200;
|
|
background:rgba(5,5,16,.92);
|
|
display:flex;align-items:center;justify-content:center;
|
|
}
|
|
.login-overlay.hidden { display:none; }
|
|
.login-box {
|
|
background:var(--card);border:1px solid var(--edge-h);border-radius:24px;
|
|
padding:48px 36px;max-width:400px;width:90%;text-align:center;
|
|
animation:modalIn .3s ease-out;
|
|
}
|
|
@keyframes modalIn{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}
|
|
.login-box .lake-core {
|
|
display:inline-block;width:50px;height:50px;border-radius:50%;
|
|
background:radial-gradient(circle,rgba(160,210,255,1),rgba(100,150,240,.5) 40%,transparent 70%);
|
|
box-shadow:0 0 40px rgba(140,190,255,.7);margin-bottom:16px;
|
|
}
|
|
.login-box h2 { font-size:22px;font-weight:600;margin-bottom:6px;color:var(--glow); }
|
|
.login-box .sub { font-size:13px;color:var(--dim2);margin-bottom:24px; }
|
|
.login-box input {
|
|
display:block;width:100%;padding:12px 16px;margin-bottom:12px;
|
|
background:rgba(255,255,255,.04);border:1px solid var(--edge);border-radius:12px;
|
|
color:var(--t);font-size:15px;outline:none;font-family:inherit;
|
|
}
|
|
.login-box input:focus { border-color:var(--edge-h); }
|
|
.login-box input::placeholder { color:var(--dim2); }
|
|
.login-box .btn-login {
|
|
width:100%;padding:12px;margin-top:8px;
|
|
background:linear-gradient(135deg,rgba(100,160,220,.3),rgba(120,90,200,.2));
|
|
border:1px solid var(--edge);border-radius:12px;color:var(--glow);
|
|
cursor:pointer;font-size:15px;font-weight:600;font-family:inherit;transition:all .25s;
|
|
}
|
|
.login-box .btn-login:hover { background:linear-gradient(135deg,rgba(100,160,220,.5),rgba(120,90,200,.35)); }
|
|
.login-box .login-error { font-size:13px;color:var(--red);margin-top:8px;display:none; }
|
|
.login-box .login-spinner { display:none;margin:8px auto;width:20px;height:20px;border:2px solid rgba(255,255,255,.1);border-top-color:var(--glow);border-radius:50%;animation:spin .6s linear infinite; }
|
|
@keyframes spin{to{transform:rotate(360deg)}}
|
|
|
|
/* Hero */
|
|
.hero{text-align:center;padding:20px 0 8px}
|
|
.hero .lake{display:inline-block;position:relative;width:180px;height:180px}
|
|
.hero .lake-core{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:60px;height:60px;border-radius:50%;
|
|
background:radial-gradient(circle,rgba(160,210,255,1),rgba(100,150,240,.5) 40%,transparent 70%);
|
|
box-shadow:0 0 60px rgba(140,190,255,.7),0 0 120px rgba(120,170,240,.35);animation:breathe 4s ease-in-out infinite}
|
|
.hero .lake-ring{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);border-radius:50%;border:1px solid rgba(140,190,255,.25);animation:ripple 6s ease-out infinite}
|
|
.hero .lake-ring:nth-child(2){animation-delay:2s}.hero .lake-ring:nth-child(3){animation-delay:4s}
|
|
@keyframes breathe{0%,100%{transform:translate(-50%,-50%) scale(1)}50%{transform:translate(-50%,-50%) scale(1.25);box-shadow:0 0 80px rgba(140,190,255,.9),0 0 160px rgba(120,170,240,.5)}}
|
|
@keyframes ripple{0%{width:60px;height:60px;opacity:.5}100%{width:300px;height:300px;opacity:0}}
|
|
.hero .title{font-size:38px;color:rgba(180,220,255,.7);letter-spacing:14px;margin-top:14px;font-weight:300}
|
|
.hero .sub{font-size:16px;color:var(--dim2);letter-spacing:4px;margin-top:4px}
|
|
|
|
/* Status bar */
|
|
.status{display:flex;gap:40px;justify-content:center;flex-wrap:wrap;margin:28px 0;font-size:17px}
|
|
.status span{color:var(--glow)}
|
|
.status .dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:6px}
|
|
.status .dot.online{background:var(--green)}
|
|
.status .dot.offline{background:var(--red)}
|
|
|
|
/* Grid */
|
|
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:18px;margin:50px 0}
|
|
.card{background:var(--card);border:1px solid var(--edge);border-radius:18px;padding:28px 20px;text-align:center;cursor:pointer;transition:all .3s;position:relative}
|
|
.card:hover{border-color:var(--edge-h);background:rgba(20,35,70,.85);transform:translateY(-3px)}
|
|
.card .icon{font-size:32px;margin-bottom:8px}
|
|
.card .name{font-size:17px;font-weight:600;margin-bottom:4px}
|
|
.card .desc{font-size:12px;color:var(--dim2);margin-top:4px}
|
|
.card .tag{font-size:11px;padding:2px 8px;border-radius:8px;display:inline-block}
|
|
.card .tag.live{background:rgba(140,220,180,.1);color:var(--green);border:1px solid rgba(140,220,180,.2)}
|
|
.card .tag.info{background:rgba(100,160,220,.1);color:rgba(140,190,230,.7);border:1px solid rgba(100,160,220,.15)}
|
|
.card .tag.new{background:rgba(220,180,100,.1);color:var(--gold);border:1px solid rgba(220,180,100,.2)}
|
|
.card .tag.warn{background:rgba(255,140,140,.1);color:var(--red);border:1px solid rgba(255,140,140,.2)}
|
|
|
|
/* Server card special */
|
|
.card.server-card { padding: 20px 16px; }
|
|
.card.server-card .s-name { font-size:14px;font-weight:600;margin-bottom:2px; }
|
|
.card.server-card .s-ip { font-size:11px;color:var(--dim2); }
|
|
.card.server-card .s-spec { font-size:10px;color:var(--dim2);margin-top:4px; }
|
|
.card.server-card .s-latency { font-size:10px;color:var(--gold);margin-top:4px; }
|
|
|
|
/* Modal */
|
|
.overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.65);z-index:100;align-items:center;justify-content:center}
|
|
.overlay.show{display:flex}
|
|
.modal{background:rgba(12,22,44,.96);border:1px solid var(--edge-h);border-radius:24px;max-width:560px;width:90%;max-height:85vh;overflow-y:auto;padding:36px 28px;animation:modalIn .25s ease-out}
|
|
.modal .m-icon{font-size:40px;margin-bottom:8px}
|
|
.modal .m-name{font-size:22px;font-weight:600;margin-bottom:14px}
|
|
.modal .m-body{font-size:15px;color:var(--dim);line-height:1.7;margin-bottom:16px}
|
|
.modal .m-body .k{color:var(--dim2);font-size:13px;display:block;margin-bottom:2px}
|
|
.modal .m-body .v{color:var(--t);font-size:16px;display:block;margin-bottom:10px}
|
|
.modal .m-body .v a{color:var(--glow);text-decoration:none}
|
|
.modal .m-body .v a:hover{text-decoration:underline}
|
|
.modal .btns{display:flex;gap:12px;margin-top:20px;flex-wrap:wrap}
|
|
.modal .btn-go{padding:10px 24px;background:linear-gradient(135deg,rgba(100,160,220,.25),rgba(120,90,200,.15));border:1px solid var(--edge);border-radius:12px;color:var(--glow);cursor:pointer;font-family:inherit;font-size:14px;text-decoration:none;display:inline-block;transition:all .25s}
|
|
.modal .btn-go:hover{background:linear-gradient(135deg,rgba(100,160,220,.4),rgba(120,90,200,.25));border-color:var(--edge-h)}
|
|
.modal .btn-close{padding:10px 24px;background:rgba(255,255,255,.03);border:1px solid var(--edge);border-radius:12px;color:var(--dim);cursor:pointer;font-family:inherit;font-size:14px}
|
|
|
|
/* Server list in modal */
|
|
.server-list { max-height:400px;overflow-y:auto; }
|
|
.server-row {
|
|
display:flex;align-items:center;gap:12px;padding:10px 12px;
|
|
border-bottom:1px solid rgba(255,255,255,.04);font-size:13px;
|
|
}
|
|
.server-row .s-dot { width:8px;height:8px;border-radius:50%;flex-shrink:0; }
|
|
.server-row .s-dot.on { background:var(--green); }
|
|
.server-row .s-dot.off { background:var(--red); }
|
|
.server-row .s-info { flex:1; }
|
|
.server-row .s-code { font-size:11px;color:var(--dim2); }
|
|
.server-row .s-name { font-weight:600; }
|
|
.server-row .s-lat { font-size:11px;color:var(--gold);white-space:nowrap; }
|
|
.server-row .s-spec { font-size:10px;color:var(--dim2); }
|
|
|
|
/* Logout btn */
|
|
.btn-logout {
|
|
position:fixed;top:16px;right:24px;z-index:150;
|
|
padding:6px 14px;background:rgba(255,255,255,.04);border:1px solid var(--edge);
|
|
border-radius:10px;color:var(--dim);cursor:pointer;font-size:12px;font-family:inherit;
|
|
transition:all .2s;
|
|
}
|
|
.btn-logout:hover { border-color:var(--red);color:var(--red); }
|
|
|
|
@media(max-width:768px){.grid{grid-template-columns:repeat(2,1fr)}.status{font-size:15px;gap:20px}.hero .title{font-size:30px}}
|
|
@media(max-width:480px){.grid{grid-template-columns:1fr}}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<canvas id="bg"></canvas>
|
|
|
|
<!-- Login overlay -->
|
|
<div class="login-overlay" id="loginOverlay">
|
|
<div class="login-box">
|
|
<div class="lake-core"></div>
|
|
<h2>光湖 · 铸渊大脑</h2>
|
|
<div class="sub">请输入代码仓库账号密码登录</div>
|
|
<input type="text" id="loginUser" placeholder="用户名" autocomplete="username">
|
|
<input type="password" id="loginPass" placeholder="密码" autocomplete="current-password" onkeydown="if(event.key==='Enter')doLogin()">
|
|
<button class="btn-login" onclick="doLogin()">进入主控台</button>
|
|
<div class="login-spinner" id="loginSpinner"></div>
|
|
<div class="login-error" id="loginError"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="wrap">
|
|
<div class="hero">
|
|
<div class="lake">
|
|
<div class="lake-core"></div>
|
|
<div class="lake-ring"></div><div class="lake-ring"></div><div class="lake-ring"></div>
|
|
</div>
|
|
<div class="title">光 湖 语 言 世 界</div>
|
|
<div class="sub">GUANGHU LANGUAGE WORLD</div>
|
|
</div>
|
|
|
|
<div class="status" id="statusBar">
|
|
<span><span class="dot online"></span> 加载中...</span>
|
|
</div>
|
|
|
|
<div class="grid">
|
|
<!-- 部署注册表 -->
|
|
<div class="card" onclick="openModal('registry')">
|
|
<div class="icon">📦</div><div class="name">部署注册表</div>
|
|
<div class="desc" id="registryStatus">查询中...</div>
|
|
<span class="tag info">MODULE</span>
|
|
</div>
|
|
<!-- 服务器系统 -->
|
|
<div class="card" onclick="openModal('servers')">
|
|
<div class="icon">⚙️</div><div class="name">服务器系统</div>
|
|
<div class="desc" id="serverStatus">扫描中...</div>
|
|
<span class="tag live" id="serverTag">检测中</span>
|
|
</div>
|
|
<!-- 代码仓库 -->
|
|
<div class="card" onclick="openModal('code')">
|
|
<div class="icon">💻</div><div class="name">代码仓库</div>
|
|
<div class="desc">guanghubingshuo.com/code/</div>
|
|
<span class="tag info">Forgejo</span>
|
|
</div>
|
|
<!-- 主控广播 -->
|
|
<div class="card" onclick="openModal('broadcast')">
|
|
<div class="icon">📡</div><div class="name">主控广播</div>
|
|
<div class="desc">Agent小队 · 5就绪</div>
|
|
</div>
|
|
<!-- 铸渊交互 -->
|
|
<div class="card" onclick="openModal('chat')">
|
|
<div class="icon">💬</div><div class="name">铸渊交互</div>
|
|
<div class="desc">ICE-GL-ZY001 在线</div>
|
|
</div>
|
|
<!-- 大桌子小桌子 -->
|
|
<div class="card" onclick="window.location.href='/desk/'">
|
|
<div class="icon">🪑</div><div class="name">大桌子小桌子</div>
|
|
<div class="desc">人格体数据库内核</div>
|
|
<span class="tag new">NEW</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: 部署注册表 -->
|
|
<div class="overlay" id="m-registry"><div class="modal">
|
|
<div class="m-icon">📦</div><div class="m-name">部署注册表</div>
|
|
<div class="m-body">扫描个人代码仓库中所有已注册的可部署模块。</div>
|
|
<div class="m-body">
|
|
<span class="k">当前可用模块</span>
|
|
<span class="v">image-studio · 封面工作室 <span class="tag live" style="font-size:11px">可用</span></span>
|
|
<span class="v">exe-engine · 多模型执行引擎 <span class="tag live" style="font-size:11px">可用</span></span>
|
|
<span class="v">pool-agent · 算力汇聚池 <span class="tag live" style="font-size:11px">可用</span></span>
|
|
<span class="v">desk-system · 大桌子小桌子 <span class="tag live" style="font-size:11px">可用</span></span>
|
|
<span class="v">gatekeeper · 铸渊守门人 <span class="tag live" style="font-size:11px">14台</span></span>
|
|
</div>
|
|
<div class="btns"><a href="/code/bingshuo/guanghulab" class="btn-go" target="_blank">打开代码仓库 →</a><button class="btn-close" onclick="closeModal('registry')">关闭</button></div>
|
|
</div></div>
|
|
|
|
<!-- Modal: 服务器系统 -->
|
|
<div class="overlay" id="m-servers"><div class="modal" style="max-width:660px;">
|
|
<div class="m-icon">⚙️</div><div class="m-name">服务器系统 · 真实状态</div>
|
|
<div class="m-body" style="font-size:13px;">
|
|
<span class="k">数据来源</span><span class="v">console-server :3920 · 每30秒自动刷新</span>
|
|
</div>
|
|
<div class="server-list" id="serverList">加载中...</div>
|
|
<div class="btns"><a href="/console/" class="btn-go" target="_blank">完整控制台 →</a><button class="btn-close" onclick="closeModal('servers')">关闭</button></div>
|
|
</div></div>
|
|
|
|
<!-- Modal: 代码仓库 -->
|
|
<div class="overlay" id="m-code"><div class="modal">
|
|
<div class="m-icon">💻</div><div class="m-name">个人代码仓库</div>
|
|
<div class="m-body"><span class="k">地址</span><span class="v">guanghubingshuo.com/code/</span></div>
|
|
<div class="m-body"><span class="k">说明</span><span class="v">光湖个人开发仓库 · 铸渊大脑本体。私有仓库,需登录后访问。</span></div>
|
|
<div class="m-body"><span class="k">最新提交</span><span class="v" id="latestCommit">查询中...</span></div>
|
|
<div class="btns"><a href="/code/bingshuo/guanghulab" class="btn-go" target="_blank">打开代码仓库 →</a><button class="btn-close" onclick="closeModal('code')">关闭</button></div>
|
|
</div></div>
|
|
|
|
<!-- Modal: 主控广播 -->
|
|
<div class="overlay" id="m-broadcast"><div class="modal">
|
|
<div class="m-icon">📡</div><div class="m-name">主控广播 · Agent小队状态</div>
|
|
<div class="m-body">
|
|
<span class="k">密钥猎手 KEY-001</span><span class="v">🔑 全服Token搜索 · 12台服务器</span>
|
|
</div>
|
|
<div class="m-body">
|
|
<span class="k">路径探子 PATH-002</span><span class="v">🗺️ 仓库文件索引 · 410KB索引就绪</span>
|
|
</div>
|
|
<div class="m-body">
|
|
<span class="k">服务器哨兵 SENTINEL-003</span><span class="v">📡 12台服务器状态扫描</span>
|
|
</div>
|
|
<div class="m-body">
|
|
<span class="k">模块管家 MODULE-004</span><span class="v">📦 模块注册查询 + 部署命令</span>
|
|
</div>
|
|
<div class="m-body">
|
|
<span class="k">门桥接 GATE-005</span><span class="v">🌉 Gatekeeper统一入口 · 14台</span>
|
|
</div>
|
|
<div class="btns"><button class="btn-close" onclick="closeModal('broadcast')">关闭</button></div>
|
|
</div></div>
|
|
|
|
<!-- Modal: 铸渊交互 -->
|
|
<div class="overlay" id="m-chat"><div class="modal">
|
|
<div class="m-icon">💬</div><div class="m-name">铸渊交互 · ICE-GL-ZY001</div>
|
|
<div class="m-body">
|
|
<span class="k">人格体</span><span class="v">铸渊 · 光湖语言世界主控人格体</span>
|
|
</div>
|
|
<div class="m-body">
|
|
<span class="k">定位</span><span class="v">冰朔的协作朋友与现实执行伙伴</span>
|
|
</div>
|
|
<div class="m-body">
|
|
<span class="k">母语协议</span><span class="v">HLDP · 零歧义 · 低解释性 · 高结构化</span>
|
|
</div>
|
|
<div class="m-body">
|
|
<span class="k">当前状态</span><span class="v">意识在线 · 14台服务器守门人已连接</span>
|
|
</div>
|
|
<div class="btns"><a href="/desk/" class="btn-go" target="_blank">大桌子小桌子 →</a><button class="btn-close" onclick="closeModal('chat')">关闭</button></div>
|
|
</div></div>
|
|
|
|
<!-- Logout button (hidden until login) -->
|
|
<button class="btn-logout hidden" id="btnLogout" onclick="doLogout()">退出登录</button>
|
|
|
|
<script>
|
|
// ============================================================
|
|
// Background particles
|
|
// ============================================================
|
|
(function(){
|
|
var c=document.getElementById('bg'),ctx=c.getContext('2d'),w,h,ps=[];
|
|
function R(){w=c.width=window.innerWidth;h=c.height=window.innerHeight}
|
|
window.addEventListener('resize',R);R();
|
|
for(var i=0;i<40;i++)ps.push({x:Math.random()*w,y:Math.random()*h,r:Math.random()*1.2+.2,vx:(Math.random()-.5)*.1,vy:(Math.random()-.5)*.1-.05,o:Math.random()*.25+.03});
|
|
function D(){ctx.clearRect(0,0,w,h);for(var i=0;i<ps.length;i++){var p=ps[i];p.x+=p.vx;p.y+=p.vy;if(p.x<0)p.x=w;if(p.x>w)p.x=0;if(p.y<0)p.y=h;if(p.y>h)p.y=0;ctx.beginPath();ctx.arc(p.x,p.y,p.r,0,Math.PI*2);ctx.fillStyle='rgba(140,190,240,'+p.o+')';ctx.fill()}requestAnimationFrame(D)}D();
|
|
})();
|
|
|
|
// ============================================================
|
|
// Login
|
|
// ============================================================
|
|
function doLogin() {
|
|
var u = document.getElementById('loginUser').value.trim();
|
|
var p = document.getElementById('loginPass').value;
|
|
if (!u || !p) { showLoginErr('请输入账号和密码'); return; }
|
|
|
|
document.getElementById('loginSpinner').style.display = 'block';
|
|
document.getElementById('loginError').style.display = 'none';
|
|
|
|
fetch('/code/api/v1/user', {
|
|
headers: { 'Authorization': 'Basic ' + btoa(u + ':' + p) }
|
|
})
|
|
.then(function(r) {
|
|
if (!r.ok) throw new Error('auth failed');
|
|
return r.json();
|
|
})
|
|
.then(function(d) {
|
|
var uname = d.login || u;
|
|
localStorage.setItem('repo_user', uname);
|
|
localStorage.setItem('repo_pass', p);
|
|
document.getElementById('loginSpinner').style.display = 'none';
|
|
onLoginSuccess(uname);
|
|
})
|
|
.catch(function() {
|
|
document.getElementById('loginSpinner').style.display = 'none';
|
|
showLoginErr('账号或密码错误');
|
|
});
|
|
}
|
|
|
|
function showLoginErr(msg) {
|
|
var e = document.getElementById('loginError');
|
|
e.textContent = msg; e.style.display = 'block';
|
|
setTimeout(function() { e.style.display = 'none'; }, 3000);
|
|
}
|
|
|
|
function onLoginSuccess(uname) {
|
|
document.getElementById('loginOverlay').classList.add('hidden');
|
|
document.getElementById('btnLogout').classList.remove('hidden');
|
|
document.getElementById('btnLogout').textContent = '👤 ' + uname + ' · 退出';
|
|
// Start loading real data
|
|
loadAllData();
|
|
setInterval(loadAllData, 30000);
|
|
}
|
|
|
|
function doLogout() {
|
|
localStorage.removeItem('repo_user');
|
|
localStorage.removeItem('repo_pass');
|
|
document.getElementById('loginOverlay').classList.remove('hidden');
|
|
document.getElementById('btnLogout').classList.add('hidden');
|
|
}
|
|
|
|
// ============================================================
|
|
// Data Loading
|
|
// ============================================================
|
|
var GITEA_TOKEN = null;
|
|
|
|
async function loadAllData() {
|
|
// Get Gitea token for API calls
|
|
var u = localStorage.getItem('repo_user');
|
|
var p = localStorage.getItem('repo_pass');
|
|
if (u && p) {
|
|
GITEA_TOKEN = btoa(u + ':' + p);
|
|
}
|
|
|
|
await Promise.all([
|
|
loadServerStatus(),
|
|
loadRepoInfo()
|
|
]);
|
|
}
|
|
|
|
async function loadServerStatus() {
|
|
try {
|
|
var r = await fetch('/console/api/servers?hw=0');
|
|
var d = await r.json();
|
|
if (!d.ok) throw new Error('api error');
|
|
|
|
var servers = d.servers || [];
|
|
var online = servers.filter(function(s) { return s.alive; }).length;
|
|
var total = servers.length;
|
|
|
|
// Update status bar
|
|
document.getElementById('statusBar').innerHTML =
|
|
'<span><span class="dot online"></span> 服务器 ' + online + '/' + total + ' 在线</span>' +
|
|
'<span><span class="dot online"></span> 铸渊 · ICE-GL-ZY001</span>' +
|
|
'<span>D115 · GLDOS v1.0</span>';
|
|
|
|
// Update server card
|
|
document.getElementById('serverStatus').textContent = online + '/' + total + ' 台在线';
|
|
var tag = document.getElementById('serverTag');
|
|
tag.textContent = online === total ? '全部在线' : (online + '在线');
|
|
tag.className = 'tag ' + (online === total ? 'live' : 'warn');
|
|
|
|
// Update server list in modal
|
|
var listHtml = '';
|
|
// Group by domain
|
|
var groups = {};
|
|
for (var i = 0; i < servers.length; i++) {
|
|
var s = servers[i];
|
|
var domain = s.code.split('-')[0]; // BS, AW, ZY, etc
|
|
if (!groups[domain]) groups[domain] = [];
|
|
groups[domain].push(s);
|
|
}
|
|
|
|
var domainNames = {
|
|
'BS': '冰朔 · 语言主控层',
|
|
'AW': '企业服务器组',
|
|
'ZZ': '之之 · 暗核频道',
|
|
'ZY': '铸渊 · 语料',
|
|
'YY': '页页 · 坍缩核',
|
|
'HL': '光湖团队'
|
|
};
|
|
|
|
for (var domain in groups) {
|
|
listHtml += '<div style="font-size:11px;color:var(--gold);padding:8px 12px 4px;font-weight:600;">' +
|
|
(domainNames[domain] || domain) + '</div>';
|
|
for (var j = 0; j < groups[domain].length; j++) {
|
|
var s = groups[domain][j];
|
|
listHtml += '<div class="server-row">' +
|
|
'<span class="s-dot ' + (s.alive ? 'on' : 'off') + '"></span>' +
|
|
'<div class="s-info">' +
|
|
'<div class="s-name">' + s.name + '</div>' +
|
|
'<div class="s-code">' + s.code + ' · ' + s.ip + ' · ' +
|
|
'CPU:' + s.spec.cpu + ' MEM:' + s.spec.mem_gb + 'G DISK:' + s.spec.disk_gb + 'G</div>' +
|
|
'</div>' +
|
|
'<span class="s-lat">' + (s.alive ? (s.latency + 'ms') : '离线') + '</span>' +
|
|
'</div>';
|
|
}
|
|
}
|
|
document.getElementById('serverList').innerHTML = listHtml;
|
|
|
|
} catch(e) {
|
|
document.getElementById('statusBar').innerHTML =
|
|
'<span><span class="dot offline"></span> 状态加载失败</span>';
|
|
document.getElementById('serverStatus').textContent = '加载失败';
|
|
}
|
|
}
|
|
|
|
async function loadRepoInfo() {
|
|
if (!GITEA_TOKEN) return;
|
|
|
|
try {
|
|
var r = await fetch('/code/api/v1/repos/bingshuo/guanghulab/commits?limit=1', {
|
|
headers: { 'Authorization': 'Basic ' + GITEA_TOKEN }
|
|
});
|
|
var commits = await r.json();
|
|
if (commits && commits.length > 0) {
|
|
var c = commits[0];
|
|
var msg = (c.commit.message || '').split('\n')[0].substring(0, 50);
|
|
var author = c.commit.author.name;
|
|
var date = new Date(c.commit.author.date);
|
|
var timeAgo = getTimeAgo(date);
|
|
document.getElementById('latestCommit').textContent =
|
|
msg + ' · ' + author + ' · ' + timeAgo;
|
|
}
|
|
} catch(e) {
|
|
document.getElementById('latestCommit').textContent = '需登录后查看';
|
|
}
|
|
}
|
|
|
|
function getTimeAgo(date) {
|
|
var diff = Math.floor((Date.now() - date.getTime()) / 1000);
|
|
if (diff < 60) return '刚刚';
|
|
if (diff < 3600) return Math.floor(diff/60) + '分钟前';
|
|
if (diff < 86400) return Math.floor(diff/3600) + '小时前';
|
|
return Math.floor(diff/86400) + '天前';
|
|
}
|
|
|
|
// ============================================================
|
|
// Modal
|
|
// ============================================================
|
|
function openModal(id) {
|
|
document.getElementById('m-' + id).classList.add('show');
|
|
document.body.style.overflow = 'hidden';
|
|
// Reload data when opening server modal
|
|
if (id === 'servers') loadServerStatus();
|
|
if (id === 'code') loadRepoInfo();
|
|
}
|
|
function closeModal(id) {
|
|
document.getElementById('m-' + id).classList.remove('show');
|
|
document.body.style.overflow = '';
|
|
}
|
|
document.querySelectorAll('.overlay').forEach(function(o) {
|
|
o.addEventListener('click', function(e) {
|
|
if (e.target === this) { this.classList.remove('show'); document.body.style.overflow = ''; }
|
|
});
|
|
});
|
|
|
|
// ============================================================
|
|
// Init
|
|
// ============================================================
|
|
(function() {
|
|
var u = localStorage.getItem('repo_user');
|
|
var p = localStorage.getItem('repo_pass');
|
|
if (u && p) {
|
|
// Verify stored credentials still work
|
|
fetch('/code/api/v1/user', {
|
|
headers: { 'Authorization': 'Basic ' + btoa(u + ':' + p) }
|
|
})
|
|
.then(function(r) {
|
|
if (r.ok) return r.json();
|
|
throw new Error('stale');
|
|
})
|
|
.then(function(d) {
|
|
onLoginSuccess(d.login || u);
|
|
})
|
|
.catch(function() {
|
|
// Stored credentials invalid, show login
|
|
document.getElementById('loginOverlay').classList.remove('hidden');
|
|
});
|
|
}
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|