feat: redesign console UI [D109] 光湖 visual language, clean cards, polished auth panel
This commit is contained in:
parent
eb01b7cf1f
commit
bf6a120df6
@ -3,156 +3,159 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>光湖 · 领地健康看板</title>
|
||||
<title>光湖 · 铸渊领地主控台</title>
|
||||
<style>
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{--bg:#080c18;--s1:#0f1623;--s2:#141e2e;--s3:#1a2640;--brd:#1e3251;--blue:#4f8ef7;--txt:#e2e8f0;--dim:#64748b;--ok:#34d399;--warn:#fbbf24;--err:#f87171}
|
||||
body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:16px;min-height:100vh;margin:0}
|
||||
|
||||
/* Header */
|
||||
.hd{background:var(--s1);border-bottom:1px solid var(--brd);padding:24px 40px;display:flex;justify-content:space-between;align-items:center}
|
||||
.hd-l{font-size:28px;font-weight:500;letter-spacing:1px}
|
||||
.hd-s{font-size:15px;color:var(--dim);margin-top:6px}
|
||||
:root{
|
||||
--bg:#070b14;
|
||||
--s1:#0d1325;
|
||||
--s2:#141e33;
|
||||
--s3:#1c2845;
|
||||
--brd:rgba(79,142,247,.18);
|
||||
--blue:#4f8ef7;
|
||||
--blue-glow:rgba(79,142,247,.35);
|
||||
--gold:#f0b429;
|
||||
--gold-glow:rgba(240,180,41,.25);
|
||||
--ok:#34d399;
|
||||
--ok-glow:rgba(52,211,153,.3);
|
||||
--warn:#fbbf24;
|
||||
--err:#f87171;
|
||||
--txt:#e2e8f0;
|
||||
--dim:#6b7a9e;
|
||||
--font:'Inter','-apple-system','BlinkMacSystemFont','PingFang SC','Microsoft YaHei',sans-serif;
|
||||
--mono:'SF Mono','JetBrains Mono','Fira Code',monospace;
|
||||
}
|
||||
html{font-size:15px}
|
||||
body{background:var(--bg);color:var(--txt);font-family:var(--font);min-height:100vh;overflow-x:hidden}
|
||||
.hd{background:linear-gradient(180deg,var(--s1),transparent);border-bottom:1px solid var(--brd);padding:22px 44px;display:flex;justify-content:space-between;align-items:center;position:sticky;top:0;z-index:50;backdrop-filter:blur(12px)}
|
||||
.hd-l{display:flex;align-items:center;gap:14px}
|
||||
.hd-logo{width:36px;height:36px;border-radius:50%;background:radial-gradient(circle at 40% 35%,var(--gold),#8a5e10);opacity:.85;flex-shrink:0;position:relative}
|
||||
.hd-logo::after{content:'';position:absolute;inset:-3px;border-radius:50%;border:1px solid var(--gold);opacity:.3}
|
||||
.hd-tt{font-size:19px;font-weight:500;letter-spacing:.5px}
|
||||
.hd-st{font-size:12px;color:var(--dim);margin-top:2px}
|
||||
.hd-r{text-align:right}
|
||||
.hd-c{font-size:15px;color:var(--blue);font-family:'SF Mono',monospace}
|
||||
.hd-st{font-size:14px;margin-top:2px}
|
||||
|
||||
/* Summary */
|
||||
.sm{display:flex;gap:14px;padding:18px 40px;background:var(--s1);border-bottom:1px solid var(--brd)}
|
||||
.sm-i{background:var(--s2);border:1px solid var(--brd);border-radius:10px;padding:10px 20px;flex:1}
|
||||
.sm-l{font-size:13px;color:var(--dim)}
|
||||
.sm-v{font-size:22px;font-weight:500;margin-top:3px}
|
||||
|
||||
/* Container */
|
||||
.ct{padding:24px 40px;max-width:1600px;margin:0 auto}
|
||||
|
||||
/* Grid */
|
||||
.gr{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;margin-bottom:24px}
|
||||
|
||||
/* Card */
|
||||
.cd{background:var(--s1);border:1px solid rgba(52,211,153,.3);border-radius:14px;padding:20px;position:relative;overflow:hidden;cursor:default;transition:all .35s ease}
|
||||
.cd:hover{border-color:rgba(79,142,247,.7)!important;box-shadow:0 0 36px rgba(79,142,247,.15),inset 0 0 12px rgba(79,142,247,.04)}
|
||||
.cd-g{position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,transparent,var(--ok),transparent);opacity:.6}
|
||||
.cd-hd{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:12px}
|
||||
.cd-n{font-size:17px;font-weight:500}
|
||||
.cd-ip{font-size:12px;color:var(--dim);margin-top:3px}
|
||||
.cd-dot{width:13px;height:13px;border-radius:50%;flex-shrink:0;margin-top:4px;transition:box-shadow .3s}
|
||||
.cd:hover .cd-dot.ok{box-shadow:0 0 24px rgba(52,211,153,.9)!important}
|
||||
.cd-dot.ok{background:var(--ok);box-shadow:0 0 14px rgba(52,211,153,.5);animation:pulse 2s ease-in-out infinite}
|
||||
.cd-dot.er{background:var(--err);box-shadow:0 0 14px rgba(248,113,113,.5)}
|
||||
|
||||
/* Resource bars */
|
||||
.rb{margin-bottom:8px}
|
||||
.rb-h{display:flex;justify-content:space-between;font-size:12px;color:var(--dim);margin-bottom:3px}
|
||||
.rb-b{height:5px;background:var(--s3);border-radius:3px;overflow:hidden}
|
||||
.rb-f{height:100%;border-radius:3px;transition:width 1.2s ease}
|
||||
.rb-f.mem{background:var(--blue)}
|
||||
.rb-f.dsk{background:#639922}
|
||||
|
||||
/* Tags */
|
||||
.tags{display:flex;gap:8px;font-size:12px;color:var(--dim);flex-wrap:wrap;margin-top:10px}
|
||||
.tags s{background:var(--s2);padding:3px 10px;border-radius:5px}
|
||||
|
||||
/* Process list inside card */
|
||||
.pc{margin-top:12px;padding-top:10px;border-top:1px solid rgba(30,50,81,.6)}
|
||||
.pc-h{font-size:11px;color:var(--dim);margin-bottom:6px;display:flex;justify-content:space-between}
|
||||
.pc-l{max-height:72px;overflow:hidden;position:relative}
|
||||
.pc-it{font-size:11px;color:var(--txt);padding:2px 0;display:flex;align-items:center;gap:6px;white-space:nowrap;animation:scrollText 8s linear infinite;transform:translateX(100%)}
|
||||
.pc-it.o{opacity:.6}
|
||||
.pc-d{width:5px;height:5px;border-radius:50%;flex-shrink:0}
|
||||
.pc-d.g{background:var(--ok)}
|
||||
.pc-d.b{background:var(--blue)}
|
||||
.pc-d.y{background:var(--warn)}
|
||||
@keyframes scrollText{0%{transform:translateX(100%)}100%{transform:translateX(-100%)}}
|
||||
|
||||
/* Bottom row */
|
||||
.bt{display:flex;gap:18px;margin-bottom:24px}
|
||||
.bt-l{flex:2;background:var(--s1);border:1px solid var(--brd);border-radius:14px;padding:18px}
|
||||
.bt-lh{font-size:15px;font-weight:500;color:var(--dim);margin-bottom:10px;display:flex;justify-content:space-between}
|
||||
.bt-lh s{font-weight:400;color:var(--dim);font-size:13px}
|
||||
.bt-lg{background:#0a0e1a;border:1px solid var(--brd);border-radius:8px;padding:12px 14px;font-family:'SF Mono',monospace;font-size:13px;line-height:1.8;color:#a8d8a8;max-height:160px;overflow-y:auto}
|
||||
.bt-le{font-size:13px;color:var(--dim);text-align:center;padding:36px}
|
||||
|
||||
/* Auth card */
|
||||
.bt-r{flex:1;background:var(--s1);border:1px solid var(--brd);border-radius:14px;padding:20px;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;min-height:200px}
|
||||
.bt-ra{font-size:15px;color:var(--dim);margin-bottom:8px}
|
||||
.bt-rt{font-size:14px;color:var(--warn);padding:6px 14px;background:var(--s3);border-radius:6px;margin-bottom:12px}
|
||||
.bt-b{display:flex;gap:14px}
|
||||
.bt-ok{padding:12px 34px;background:var(--ok);color:#000;border:none;border-radius:9px;font-size:17px;font-weight:600;cursor:pointer;transition:all .25s}
|
||||
.bt-ok:hover{box-shadow:0 0 32px rgba(52,211,153,.6);transform:scale(1.03)}
|
||||
.bt-no{padding:12px 34px;background:var(--s3);color:var(--dim);border:1px solid var(--brd);border-radius:9px;font-size:17px;cursor:pointer;transition:all .25s}
|
||||
.bt-no:hover{border-color:var(--err);color:var(--err);box-shadow:0 0 20px rgba(248,113,113,.2)}
|
||||
|
||||
/* Operation receipt */
|
||||
.rcpt{background:var(--s2);border:1px solid rgba(79,142,247,.25);border-radius:10px;padding:12px 16px;margin-bottom:12px;display:none}
|
||||
.rcpt-st{font-size:12px;color:var(--dim);margin-bottom:5px;display:flex;gap:10px;align-items:center}
|
||||
.rcpt-st .sg{padding:2px 10px;border-radius:12px;font-size:11px}
|
||||
.hd-tm{font-size:18px;color:var(--blue);font-family:var(--mono);letter-spacing:1px}
|
||||
.hd-ss{font-size:12px;margin-top:3px;display:flex;align-items:center;gap:6px;justify-content:flex-end}
|
||||
.sm{display:flex;gap:12px;padding:16px 44px;background:var(--s1);border-bottom:1px solid var(--brd);overflow-x:auto}
|
||||
.sm-i{background:var(--s2);border:1px solid var(--brd);border-radius:10px;padding:10px 18px;flex-shrink:0;min-width:130px;transition:border-color .3s}
|
||||
.sm-i:hover{border-color:rgba(79,142,247,.35)}
|
||||
.sm-l{font-size:11px;color:var(--dim);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px}
|
||||
.sm-v{font-size:24px;font-weight:600;letter-spacing:-.5px;line-height:1.2}
|
||||
.ct{padding:22px 44px;max-width:1600px;margin:0 auto}
|
||||
.sc{font-size:13px;color:var(--dim);margin-bottom:14px;display:flex;align-items:center;gap:8px;letter-spacing:.3px}
|
||||
.sc::after{content:'';flex:1;height:1px;background:linear-gradient(90deg,var(--brd),transparent)}
|
||||
.gr{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-bottom:22px}
|
||||
.cd{background:linear-gradient(145deg,var(--s1),var(--s2));border:1px solid var(--brd);border-radius:14px;padding:20px;position:relative;overflow:hidden;transition:all .4s ease}
|
||||
.cd:hover{border-color:rgba(79,142,247,.4);box-shadow:0 0 40px rgba(79,142,247,.08),inset 0 0 20px rgba(79,142,247,.03)}
|
||||
.cd-glow{position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,transparent,var(--ok),transparent);opacity:.5;animation:glide 4s ease-in-out infinite}
|
||||
.cd-hd{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:10px}
|
||||
.cd-n{font-size:16px;font-weight:500}
|
||||
.cd-sub{font-size:11px;color:var(--dim);margin-top:2px;font-family:var(--mono)}
|
||||
.cd-dot{width:12px;height:12px;border-radius:50%;flex-shrink:0;margin-top:5px}
|
||||
.cd-dot.ok{background:var(--ok);box-shadow:0 0 16px var(--ok-glow);animation:breathe 3s ease-in-out infinite}
|
||||
.cd-dot.er{background:var(--err);box-shadow:0 0 16px rgba(248,113,113,.4);animation:breathe 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:3px}
|
||||
.rb-b{height:4px;background:var(--s3);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(--blue),#7eb8ff)}
|
||||
.rb-f.dsk{background:linear-gradient(90deg,#639922,#82c42e)}
|
||||
.tags{display:flex;gap:6px;font-size:11px;flex-wrap:wrap;margin-top:8px}
|
||||
.tags s{background:var(--s3);padding:2px 8px;border-radius:4px;color:var(--dim);border:1px solid transparent}
|
||||
.tags s.alive{border-color:rgba(52,211,153,.3);color:var(--ok)}
|
||||
.tags s.dead{border-color:rgba(248,113,113,.3);color:var(--err)}
|
||||
.ml{margin-top:10px;padding-top:8px;border-top:1px solid rgba(30,50,81,.4)}
|
||||
.ml-h{font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px;display:flex;justify-content:space-between}
|
||||
.ml-g{display:flex;flex-wrap:wrap;gap:4px}
|
||||
.ml-t{font-size:10px;padding:2px 6px;border-radius:3px;background:var(--s3);color:var(--dim);border:1px solid transparent}
|
||||
.ml-t.act{border-color:rgba(52,211,153,.25);color:var(--ok);background:rgba(52,211,153,.06)}
|
||||
.ml-t.idl{border-color:rgba(107,122,158,.15);color:var(--dim)}
|
||||
.bt{display:flex;gap:16px;margin-bottom:22px}
|
||||
.bt-l{flex:2;background:var(--s1);border:1px solid var(--brd);border-radius:14px;padding:18px;min-height:200px;display:flex;flex-direction:column}
|
||||
.bt-lh{font-size:13px;color:var(--dim);margin-bottom:10px;display:flex;justify-content:space-between;align-items:center;flex-shrink:0}
|
||||
.bt-lh s{font-weight:400;font-size:11px}
|
||||
.bt-lg{flex:1;background:rgba(7,11,20,.5);border:1px solid var(--brd);border-radius:8px;padding:10px 12px;font-family:var(--mono);font-size:12px;line-height:1.7;color:var(--ok);overflow-y:auto;max-height:180px}
|
||||
.bt-le{font-size:13px;color:var(--dim);text-align:center;padding:40px 0;font-family:var(--font)}
|
||||
.bt-r{flex:1;background:linear-gradient(145deg,var(--s1),var(--s2));border:1px solid var(--brd);border-radius:14px;padding:22px;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;min-height:200px;transition:all .3s}
|
||||
.bt-r.has-request{border-color:var(--gold);box-shadow:0 0 32px var(--gold-glow)}
|
||||
.bt-ra{font-size:13px;color:var(--dim);margin-bottom:6px}
|
||||
.bt-rt{font-size:14px;color:var(--gold);padding:6px 14px;background:rgba(240,180,41,.08);border:1px solid rgba(240,180,41,.25);border-radius:6px;margin-bottom:10px;font-weight:500}
|
||||
.bt-b{display:flex;gap:12px;margin-top:6px}
|
||||
.bt-ok{padding:11px 32px;background:var(--ok);color:#000;border:none;border-radius:8px;font-size:16px;font-weight:600;cursor:pointer;transition:all .25s;font-family:var(--font)}
|
||||
.bt-ok:hover{box-shadow:0 0 28px var(--ok-glow);transform:scale(1.03)}
|
||||
.bt-no{padding:11px 32px;background:transparent;color:var(--dim);border:1px solid var(--brd);border-radius:8px;font-size:16px;cursor:pointer;transition:all .25s;font-family:var(--font)}
|
||||
.bt-no:hover{border-color:var(--err);color:var(--err);box-shadow:0 0 20px rgba(248,113,113,.15)}
|
||||
.rcpt{background:var(--s2);border:1px solid rgba(79,142,247,.2);border-radius:8px;padding:10px 14px;margin-bottom:8px;display:none;flex-shrink:0}
|
||||
.rcpt-st{font-size:11px;color:var(--dim);margin-bottom:4px;display:flex;gap:8px;align-items:center}
|
||||
.rcpt-st .sg{padding:1px 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(--blue);border:1px solid var(--blue)}
|
||||
.rcpt-st .sg.d{color:var(--ok)}
|
||||
.rcpt-st .sg.f{color:var(--err);border:1px solid var(--err)}
|
||||
.rcpt-msg{font-size:16px;line-height:1.6;color:var(--txt)}
|
||||
|
||||
/* Footer */
|
||||
.ft{background:#0a0e1a;border-top:1px solid var(--brd);padding:14px 40px;display:flex;justify-content:space-between;font-size:12px;color:#4a5568}
|
||||
|
||||
/* Animations */
|
||||
@keyframes pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.55;transform:scale(1.25)}}
|
||||
.rcpt-msg{font-size:14px;line-height:1.5;color:var(--txt)}
|
||||
.toast{position:fixed;bottom:28px;right:28px;background:var(--s2);border:1px solid var(--ok);border-radius:10px;padding:12px 22px;font-size:13px;color:var(--ok);z-index:200;max-width:380px;transition:opacity .3s}
|
||||
.ft{background:rgba(7,11,20,.6);border-top:1px solid var(--brd);padding:12px 44px;display:flex;justify-content:space-between;font-size:11px;color:#3a4463;backdrop-filter:blur(6px)}
|
||||
@keyframes glide{0%{transform:translateX(-100%)}50%{transform:translateX(100%)}100%{transform:translateX(200%)}}
|
||||
|
||||
@media(max-width:900px){
|
||||
.gr{grid-template-columns:1fr 1fr}
|
||||
.sm{flex-wrap:wrap}
|
||||
.bt{flex-direction:column}
|
||||
}
|
||||
@media(max-width:600px){
|
||||
@keyframes breathe{0%,100%{opacity:.7;transform:scale(1)}50%{opacity:1;transform:scale(1.18)}}
|
||||
@media(max-width:1100px){.gr{grid-template-columns:1fr 1fr}}
|
||||
@media(max-width:800px){
|
||||
.gr{grid-template-columns:1fr}
|
||||
.ct{padding:16px}
|
||||
.hd-l{font-size:20px}
|
||||
.hd,.sm,.ft{padding-left:16px;padding-right:16px}
|
||||
.bt{flex-direction:column}
|
||||
.hd,.sm,.ct,.ft{padding-left:18px;padding-right:18px}
|
||||
.hd-logo{display:none}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="hd">
|
||||
<div>
|
||||
<div class="hd-l">光湖 · 领地健康看板</div>
|
||||
<div class="hd-s">主权者:冰朔 | 驻守者:铸渊 | 六台服务器实时投影</div>
|
||||
<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-c" id="gl-clock">--:--:--</div>
|
||||
<div class="hd-st" id="gl-st" style="color:var(--ok)">全境正常</div>
|
||||
<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>
|
||||
</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">--/6</div></div>
|
||||
<div class="sm-i"><div class="sm-l">总内存</div><div class="sm-v" style="color:var(--blue)" id="sm-mem">--%</div></div>
|
||||
<div class="sm-i"><div class="sm-l">内存平均</div><div class="sm-v" style="color:var(--blue)" id="sm-mem">--%</div></div>
|
||||
<div class="sm-i"><div class="sm-l">进程总量</div><div class="sm-v" style="color:var(--txt)" id="sm-proc">--</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 class="sm-i"><div class="sm-l">关键进程</div><div class="sm-v" id="sm-proc">--</div></div>
|
||||
</div>
|
||||
|
||||
<div class="ct">
|
||||
<main class="ct">
|
||||
<div class="sc">服务器集群 · 六域主权</div>
|
||||
<div class="gr" id="server-grid"></div>
|
||||
<div class="bt">
|
||||
<div class="bt-l">
|
||||
<div class="bt-lh"><span>铸渊操作流水</span><span style="font-weight:400;color:var(--dim);font-size:13px">只读 · 不可干预</span></div>
|
||||
<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 class="bt-lg" id="log-output">
|
||||
<div class="bt-le">等待铸渊发起操作...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bt-r" id="auth-panel">
|
||||
<div class="bt-ra">暂无待处理的操作请求</div>
|
||||
<div style="font-size:13px;color:var(--dim)">铸渊操作由主权者发起,全程不可取消</div>
|
||||
<div style="font-size:12px;color:var(--dim);margin-top:6px">铸渊操作由主权者发起</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ft">
|
||||
</main>
|
||||
<footer class="ft">
|
||||
<span>光湖语言世界 · 第五域 · 铸渊半边主控</span>
|
||||
<span>投影仪 · 不承载服务器运算 · 前端纯展示</span>
|
||||
</div>
|
||||
|
||||
<span>投影仪 · 不承载服务器运算</span>
|
||||
</footer>
|
||||
<script>
|
||||
const API='/console/api';
|
||||
let iv;
|
||||
@ -164,25 +167,26 @@ const MODULES={
|
||||
'ZY-SG-006':['gatekeeper守护','计算节点','推理服务','内存缓存','训练流水线'],
|
||||
'BS-SH-005':['gatekeeper守护','容灾节点','冷备存储','健康检查','数据校验']
|
||||
};
|
||||
|
||||
function byId(s){return document.getElementById(s)}
|
||||
function $(s){return document.getElementById(s)}
|
||||
function toast(m,c){
|
||||
const t=document.createElement('div');
|
||||
t.style.cssText='position:fixed;bottom:28px;right:28px;background:var(--s3);border:1px solid '+(c||'var(--ok)')+';border-radius:10px;padding:12px 22px;font-size:14px;color:'+(c||'var(--ok)')+';z-index:100;max-width:400px';
|
||||
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(()=>{t.style.opacity='0';setTimeout(()=>t.remove(),300)},4000);
|
||||
}
|
||||
async function fetchAPI(p){try{const r=await fetch(API+p);return await r.json()}catch(e){return null}}
|
||||
|
||||
async function fetchAPI(p){
|
||||
try{const r=await fetch(API+p);return await r.json()}
|
||||
catch(e){return null}
|
||||
}
|
||||
function renderCards(data){
|
||||
const sv=(data&&data.servers)||[];
|
||||
const grid=byId('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 grid=$('server-grid');
|
||||
if(!sv.length){grid.innerHTML='<div style="grid-column:1/-1;text-align:center;padding:50px;color:var(--dim);font-size:15px">正在采集服务器数据...</div>';return}
|
||||
const alive=sv.filter(s=>s.alive).length;
|
||||
const glSt=byId('gl-st');
|
||||
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)');
|
||||
byId('sm-srv').innerHTML=alive+'/'+sv.length+' <span style="font-size:13px;color:var(--dim);font-weight:400">在线</span>';
|
||||
$('sm-srv').innerHTML=alive+'/6 <span style="font-size:12px;color:var(--dim);font-weight:400">在线</span>';
|
||||
let tM=0,mC=0,tP=0;
|
||||
grid.innerHTML=sv.map(s=>{
|
||||
const ok=s.alive?'ok':'er';
|
||||
@ -196,25 +200,24 @@ function renderCards(data){
|
||||
const activeMods=mods.filter(()=>Math.random()>0.3);
|
||||
const idleMods=mods.filter(m=>!activeMods.includes(m));
|
||||
return '<div class="cd">'+
|
||||
'<div class="cd-g" style="animation:glide 3s ease-in-out infinite;animation-delay:'+gDel+'"></div>'+
|
||||
'<div class="cd-hd"><div><div class="cd-n">'+s.name+'</div><div class="cd-ip">'+s.code+' · '+s.ip+'</div></div>'+
|
||||
'<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+'" style="animation-delay:'+(sv.indexOf(s)*0.3)+'s"></div></div>'+
|
||||
'<div class="rb"><div class="rb-h"><span>内存</span><span>'+mE+'%</span></div><div class="rb-b"><div class="rb-f mem" style="width:'+mE+'%"></div></div></div>'+
|
||||
'<div class="rb"><div class="rb-h"><span>磁盘</span><span>'+dE+'%</span></div><div class="rb-b"><div class="rb-f dsk" style="width:'+dE+'%"></div></div></div>'+
|
||||
'<div class="tags"><span>延迟 '+lt+'</span><span>进程 '+pc+'个</span><span style="color:'+(s.alive?'var(--ok)':'var(--err)')+'">'+st+'</span></div>'+
|
||||
'<div class="pc"><div class="pc-h"><span>模块</span><span style="color:var(--ok)">'+(s.alive?activeMods.length+'运行中':'—')+'</span></div>'+
|
||||
'<div class="pc-l">'+
|
||||
activeMods.map(m=>'<div class="pc-it" style="animation-delay:0s"><span class="pc-d g"></span>'+m+'</div>').join('')+
|
||||
idleMods.map(m=>'<div class="pc-it o" style="animation-delay:0s"><span class="pc-d y"></span>'+m+'</div>').join('')+
|
||||
'<div class="rs"><div class="rb"><div class="rb-h"><span>内存</span><span>'+mE+'%</span></div><div class="rb-b"><div class="rb-f mem" style="width:'+mE+'%"></div></div></div>'+
|
||||
'<div class="rb"><div class="rb-h"><span>磁盘</span><span>'+dE+'%</span></div><div class="rb-b"><div class="rb-f dsk" style="width:'+dE+'%"></div></div></div></div>'+
|
||||
'<div class="tags"><s class="'+(s.alive?'alive':'dead')+'">'+lt+'</s><s class="'+(s.alive?'alive':'dead')+'">进程 '+pc+'</s><s class="'+(s.alive?'alive':'dead')+'">'+st+'</s></div>'+
|
||||
'<div class="ml"><div class="ml-h"><span>模块</span><span style="color:var(--ok);font-weight:400">'+(s.alive?activeMods.length+' 运行中':'—')+'</span></div>'+
|
||||
'<div class="ml-g">'+
|
||||
activeMods.map(m=>'<span class="ml-t act">'+m+'</span>').join('')+
|
||||
idleMods.map(m=>'<span class="ml-t idl">'+m+'</span>').join('')+
|
||||
'</div></div></div>'
|
||||
}).join('');
|
||||
byId('sm-mem').innerHTML=Math.round(tM/(mC||1))+'% <span style="font-size:13px;color:var(--dim);font-weight:400">平均</span>';
|
||||
byId('sm-proc').innerHTML=tP+' <span style="font-size:13px;color:var(--dim);font-weight:400">运行中</span>';
|
||||
$('sm-mem').innerHTML=Math.round(tM/(mC||1))+'% <span style="font-size:12px;color:var(--dim);font-weight:400">平均</span>';
|
||||
$('sm-proc').innerHTML=tP+' <span style="font-size:12px;color:var(--dim);font-weight:400">运行中</span>';
|
||||
}
|
||||
|
||||
function renderSessionsAndAuth(sInfo,pInfo){
|
||||
const p=(pInfo&&pInfo.requests)||[];const ss=(sInfo&&sInfo.sessions)||[];
|
||||
const ap=byId('auth-panel');const rc=byId('receipt-container');const lg=byId('log-output');
|
||||
const ap=$('auth-panel');const rc=$('receipt-container');const lg=$('log-output');
|
||||
if(ss.length){
|
||||
rc.innerHTML=ss.map(s=>{
|
||||
const sc={'敲门中':'w','已授权':'r','执行中':'x','完成':'d','已拒绝':'f'}[s.operation_stage]||'w';
|
||||
@ -223,14 +226,15 @@ function renderSessionsAndAuth(sInfo,pInfo){
|
||||
}else{rc.innerHTML=''}
|
||||
if(p.length){
|
||||
const r=p[0];const ago=Math.floor((Date.now()-new Date(r.created_at).getTime())/1000);
|
||||
ap.innerHTML='<div class="bt-ra">铸渊请求对 <span style="color:var(--blue);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"><div class="bt-ok" onclick="authOp(\''+r.id+'\')">授权</div><div class="bt-no" onclick="rejOp(\''+r.id+'\')">拒绝</div></div>';
|
||||
ap.className='bt-r has-request';
|
||||
ap.innerHTML='<div class="bt-ra">铸渊请求对 <span style="color:var(--blue);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:10px">'+ago+'秒前</div><div class="bt-b"><div class="bt-ok" onclick="authOp(\''+r.id+'\')">授权</div><div class="bt-no" onclick="rejOp(\''+r.id+'\')">拒绝</div></div>';
|
||||
}else{
|
||||
ap.innerHTML='<div class="bt-ra">暂无待处理的操作请求</div><div style="font-size:13px;color:var(--dim)">铸渊操作由主权者发起,全程不可取消</div>';
|
||||
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){
|
||||
const r=await fetch(API+'/authorize',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({request_id:id})});
|
||||
const d=await r.json();
|
||||
@ -239,19 +243,26 @@ async function authOp(id){
|
||||
}
|
||||
async function rejOp(id){
|
||||
await fetch(API+'/reject',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({request_id:id})});
|
||||
toast('已拒绝');addLog('拒绝','冰朔未确认此操作');pollAll();
|
||||
toast('已拒绝','var(--err)');addLog('拒绝','冰朔未确认此操作');pollAll();
|
||||
}
|
||||
function addLog(tag,msg){
|
||||
const lg=byId('log-output');const e=lg.querySelector('.bt-le');
|
||||
const lg=$('log-output');const e=lg.querySelector('.bt-le');
|
||||
if(e)e.remove();
|
||||
const t=new Date().toLocaleTimeString('zh-CN',{hour12:false});
|
||||
const 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(){const[s,p,se]=await Promise.all([fetchAPI('/servers'),fetchAPI('/pending'),fetchAPI('/terminal/sessions')]);if(s)renderCards(s);renderSessionsAndAuth(se,p)}
|
||||
function updateClock(){byId('gl-clock').textContent=new Date().toLocaleTimeString('zh-CN',{hour12:false})}
|
||||
(async function init(){updateClock();await pollAll();iv=setInterval(pollAll,5000);setInterval(updateClock,1000)})();
|
||||
async function pollAll(){
|
||||
const[s,p,se]=await Promise.all([fetchAPI('/servers'),fetchAPI('/pending'),fetchAPI('/terminal/sessions')]);
|
||||
if(s)renderCards(s);renderSessionsAndAuth(se,p);
|
||||
}
|
||||
function updateClock(){$('gl-clock').textContent=new Date().toLocaleTimeString('zh-CN',{hour12:false})}
|
||||
(async function init(){
|
||||
updateClock();await pollAll();
|
||||
iv=setInterval(pollAll,5000);
|
||||
setInterval(updateClock,1000);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user