Files
SMANAGE/ATestCServer/apps/sub_server/templates/status.html
T

217 lines
9.6 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ATestCServer — MSYNC 连接状态</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Segoe UI',system-ui,-apple-system,sans-serif;background:#0f1923;color:#c0d0e0;min-height:100vh}
.header{background:#1a2a3a;border-bottom:2px solid #2a4a6a;padding:20px 30px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px}
.header h1{font-size:22px;color:#e0f0ff;font-weight:600}
.header .badge{padding:6px 14px;border-radius:20px;font-size:13px;font-weight:600;letter-spacing:.5px}
.badge-online{background:#0a3a0a;color:#5ae05a;border:1px solid #1a6a1a}
.badge-offline{background:#3a0a0a;color:#e05a5a;border:1px solid #6a1a1a}
.main{max-width:980px;margin:30px auto;padding:0 20px;display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:20px}
.card{background:#152232;border:1px solid #1e3a50;border-radius:12px;padding:24px;transition:border-color .2s}
.card:hover{border-color:#2a5a80}
.card h3{font-size:14px;color:#6888a8;text-transform:uppercase;letter-spacing:1.5px;margin-bottom:14px}
.card .value{font-size:28px;font-weight:700;color:#e8f8ff;margin-bottom:6px}
.card .sub{font-size:12px;color:#5a7a9a}
.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.kv{display:flex;flex-direction:column;gap:3px}
.kv .key{font-size:11px;color:#5a7a9a;text-transform:uppercase;letter-spacing:.8px}
.kv .val{font-size:14px;color:#c8e0f8;word-break:break-all}
.status-row{display:flex;align-items:center;gap:8px;margin:6px 0}
.dot{width:10px;height:10px;border-radius:50%;display:inline-block}
.dot-green{background:#4ade80;box-shadow:0 0 8px #4ade80}
.dot-red{background:#ef4444;box-shadow:0 0 8px #ef4444}
.dot-yellow{background:#f59e0b;box-shadow:0 0 8px #f59e0b}
.dot-gray{background:#555;box-shadow:none}
.divider{border:0;border-top:1px solid #1e3a50;margin:16px 0}
.refresh{font-size:12px;color:#5a7a9a;margin-top:8px;text-align:right}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}
.pulse{animation:pulse 2s infinite}
.footer{text-align:center;padding:20px;font-size:12px;color:#3a5a7a}
pre{background:#0a1620;border-radius:8px;padding:12px;overflow-x:auto;font-size:12px;color:#80a0c0;max-height:260px;overflow-y:auto}
.services-list{list-style:none}
.services-list li{padding:8px 10px;border-bottom:1px solid #1a2a3a;font-size:13px;display:flex;justify-content:space-between;color:#90b0d0}
.services-list li:last-child{border-bottom:none}
.section-title{font-size:15px;color:#a0c0e0;font-weight:600;margin-bottom:10px}
</style>
</head>
<body>
<div class="header">
<div>
<h1>ATestCServer</h1>
<span style="font-size:12px;color:#5a7a9a">SMANAGE 子服务器 · HTTP :25080 · HTTPS :25443</span>
</div>
<div style="display:flex;align-items:center;gap:12px">
<span id="clock" style="font-size:14px;color:#6888a8"></span>
<span id="topBadge" class="badge badge-offline">断开</span>
</div>
</div>
<div class="main">
<!-- 连接状态卡片 -->
<div class="card">
<h3>连接状态</h3>
<div class="status-row"><span id="dotConn" class="dot dot-gray"></span> <span id="txtConn">加载中...</span></div>
<div class="status-row"><span id="dotAuth" class="dot dot-gray"></span> <span id="txtAuth">加载中...</span></div>
<div class="status-row"><span id="dotReg" class="dot dot-gray"></span> <span id="txtReg">加载中...</span></div>
<div class="status-row"><span id="dotHear" class="dot dot-gray"></span> <span id="txtHear">加载中...</span></div>
<div class="refresh pulse" id="refresher">自动刷新中...</div>
</div>
<!-- 服务信息卡片 -->
<div class="card">
<h3>服务标识</h3>
<div class="grid-2">
<div class="kv"><span class="key">服务名</span><span class="val" id="svcName">—</span></div>
<div class="kv"><span class="key">域名</span><span class="val" id="svcDomain">—</span></div>
<div class="kv"><span class="key">主服务器</span><span class="val" id="svcMaster">—</span></div>
<div class="kv"><span class="key">Token</span><span class="val" id="svcToken">—</span></div>
</div>
</div>
<!-- 对等服务卡片 -->
<div class="card" style="grid-column:span 2">
<h3>对等服务</h3>
<ul class="services-list" id="peerList"><li style="color:#5a7a9a">加载中...</li></ul>
</div>
<!-- 最近事件卡片 -->
<div class="card" style="grid-column:span 2">
<h3>最近同步事件</h3>
<ul class="services-list" id="eventLog"><li style="color:#5a7a9a">暂无事件</li></ul>
</div>
<!-- 原始数据卡片 -->
<div class="card" style="grid-column:span 2">
<h3>原始响应</h3>
<pre id="rawData">{}</pre>
</div>
</div>
<div class="footer">
MSYNC Bridge · Heartbeat/30s · ATestCServer &copy; SMANAGE
</div>
<script>
const HEALTH_URL = '/health/';
const STATUS_URL = '/api/sub/msync/status/';
function fmtTime(){const n=new Date();return n.toLocaleTimeString('zh-CN',{hour12:false})}
function updClock(){document.getElementById('clock').textContent=fmtTime()}
async function fetchStatus(){
try{
const[h,s]=await Promise.all([
fetch(HEALTH_URL).then(r=>r.json()),
fetch(STATUS_URL).then(r=>r.json())
]);
render(h?.data,h?.code===200,s?.data,s?.code===200);
}catch(e){
showError('请求失败: '+e.message);
}
}
function dot(cls){return '<span class="dot '+cls+'"></span>'}
function showError(msg){
document.getElementById('topBadge').className='badge badge-offline';
document.getElementById('topBadge').textContent='错误';
document.getElementById('dotConn').className='dot dot-red';
document.getElementById('txtConn').innerHTML=msg;
document.getElementById('refresher').textContent='刷新失败,10s后重试';
}
function render(hdata,hok,sdata,sok){
const connected = hdata?.MSync?.Connected || false;
const authenticated = hdata?.MSync?.Authenticated || false;
const registered = hdata?.MSync?.Registered || false;
const running = sdata?.running || connected;
const tokenRaw = hdata?.MSync?.AccessToken || sdata?.AccessToken || null;
// 顶部徽章
const badge = document.getElementById('topBadge');
if(connected && authenticated){
badge.className='badge badge-online'; badge.textContent='已连接';
}else if(connected){
badge.className='badge badge-online'; badge.textContent='已注册';
}else{
badge.className='badge badge-offline'; badge.textContent='断开';
}
// 连接状态
document.getElementById('dotConn').className='dot '+(connected?'dot-green pulse':'dot-red');
document.getElementById('txtConn').textContent='MSYNC 运行: '+(running?'是':'否');
// 认证状态——只有连接成功后才看 token,否则一律视为未认证
const authEffective = connected && authenticated;
const tokenEffective = connected && tokenRaw;
document.getElementById('dotAuth').className='dot '+(authEffective?'dot-green':tokenEffective?'dot-yellow':'dot-red');
document.getElementById('txtAuth').textContent='服务认证: '+(authEffective?'已认证':tokenEffective?'令牌存在(可能过期)':connected?'认证中':'未连接');
document.getElementById('dotReg').className='dot '+(registered?'dot-green':'dot-red');
document.getElementById('txtReg').textContent='服务注册: '+(registered?'已注册':'未注册');
document.getElementById('dotHear').className='dot '+(connected&&registered?'dot-green pulse':'dot-gray');
document.getElementById('txtHear').textContent='心跳线程: '+(connected&&registered?'运行中(间隔30s)':'未启动');
// 服务信息
document.getElementById('svcName').textContent=hdata?.MSync?.ServiceName||sdata?.ServiceName||'—';
document.getElementById('svcDomain').textContent=hdata?.MSync?.ServiceDomain||sdata?.ServiceDomain||'—';
document.getElementById('svcMaster').textContent=hdata?.MSync?.MasterURL||sdata?.MasterURL||'—';
// Token 截断展示(防完整泄露)
const tokenEl = document.getElementById('svcToken');
if(tokenRaw && tokenRaw.length>40){
tokenEl.textContent = tokenRaw.slice(0,30)+'…'+tokenRaw.slice(-15);
tokenEl.title = tokenRaw;
tokenEl.style.cursor = 'help';
}else{
tokenEl.textContent = tokenRaw||'—';
}
// 对等服务(PascalCase 字段:Name/Domain)
const peers=sdata?.PeerServices||[];
const pl=document.getElementById('peerList');
if(peers.length===0){
pl.innerHTML='<li style="color:#5a7a9a">无对等服务(主服务器可能未上线,或尚无其他子服务器)</li>';
}else{
pl.innerHTML=peers.map(p=>
'<li><span>'+(p.Name||p.name||'?')+'</span><span style="color:#6888a8;font-size:11px">'+(p.Domain||p.domain||'')+'</span></li>'
).join('');
}
// 事件日志
fetch('/api/sub/sync/logs/').then(r=>r.json()).then(d=>{
const logs=d?.data||[];
const el=document.getElementById('eventLog');
if(logs.length===0){
el.innerHTML='<li style="color:#5a7a9a">暂无同步事件(需要与主服务器通信后产生)</li>';
}else{
el.innerHTML=logs.slice(0,10).map(l=>
'<li><span>'+l.EventType+'</span><span style="color:#5a7a9a;font-size:11px">'+l.CreateTime+'</span></li>'
).join('');
}
}).catch(()=>{});
// 原始数据
document.getElementById('rawData').textContent=JSON.stringify({
health: hdata?.msync||{},
status: {running:running,authenticated:authenticated,registered:registered,peers:peers.length}
},null,2);
document.getElementById('refresher').textContent='最后刷新: '+fmtTime();
}
updClock();
setInterval(updClock,1000);
fetchStatus();
setInterval(fetchStatus,10000);
</script>
</body>
</html>