mirror of https://github.com/cppla/ServerStatus
update fix some
parent
9a19d2f4bf
commit
3c8ebcf710
|
@ -2,6 +2,7 @@
|
|||
body.light{--bg:#f6f7f9;--bg-alt:#ffffff;--border:#e2e8f0;--text:#1e293b;--text-dim:#64748b;--accent:#2563eb;--accent-glow:#3b82f6;--shadow:0 4px 20px -4px rgba(0,0,0,.08);--logo-start:#2563eb;--logo-end:#1d4ed8;--logo-accent-grad-start:#1d4ed8;--logo-accent-grad-end:#60a5fa}
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%;margin:0;padding:0;font-family:var(--font);background:var(--bg);color:var(--text);-webkit-font-smoothing:antialiased}
|
||||
html.light{background:var(--bg)}
|
||||
body,button{font-size:14px;line-height:1.35}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
a:hover{color:var(--accent-glow)}
|
||||
|
@ -27,9 +28,15 @@ table.data th,table.data td{font-variant-numeric:tabular-nums}
|
|||
table.data thead th:nth-child(7),table.data tbody td:nth-child(7),
|
||||
table.data thead th:nth-child(8),table.data tbody td:nth-child(8),
|
||||
table.data thead th:nth-child(9),table.data tbody td:nth-child(9){
|
||||
width:170px;min-width:170px;max-width:170px;/* 基于最大示例 1111.12GB|1123.12GB 预留空间 */
|
||||
width:132px;min-width:132px;max-width:132px;/* 适配示例 111.1GB|222.2GB */
|
||||
font-variant-numeric:tabular-nums;letter-spacing:.3px;
|
||||
}
|
||||
/* CPU / 内存 / 硬盘 列:居中 + 固定宽度 与仪表盘一致 */
|
||||
table.data thead th:nth-child(10),table.data tbody td:nth-child(10),
|
||||
table.data thead th:nth-child(11),table.data tbody td:nth-child(11),
|
||||
table.data thead th:nth-child(12),table.data tbody td:nth-child(12){
|
||||
width:70px;min-width:70px;max-width:70px;text-align:center;padding-left:.25rem;padding-right:.25rem;
|
||||
}
|
||||
table.data tbody tr:last-child td{border-bottom:none}
|
||||
table.data tbody tr:hover{background:rgba(255,255,255,.04)}
|
||||
.badge{display:inline-block;padding:2px 6px;font-size:11px;border-radius:12px;font-weight:500;line-height:1.2;background:var(--bg);border:1px solid var(--border);color:var(--text-dim)}
|
||||
|
@ -63,6 +70,30 @@ table.data tbody tr:hover{background:rgba(255,255,255,.04)}
|
|||
/* spark lines */
|
||||
.spark{width:82px;height:28px;display:flex;align-items:center;justify-content:center}
|
||||
.spark canvas{width:100%;height:100%;display:block}
|
||||
/* 仪表盘样式 (替换 spark) */
|
||||
/* 全圆旧样式(保留以便回退) */
|
||||
.gauge{--p:0;--col:var(--accent);width:74px;height:74px;position:relative;display:grid;place-items:center;font-size:11px;font-family:ui-monospace,monospace;font-weight:600;color:var(--text);}
|
||||
.gauge:before{content:"";position:absolute;inset:0;border-radius:50%;background:conic-gradient(var(--col) calc(var(--p)*1turn),rgba(255,255,255,0.06) 0);mask:radial-gradient(circle at 50% 50%,transparent 58%,#000 59%);-webkit-mask:radial-gradient(circle at 50% 50%,transparent 58%,#000 59%);border:1px solid var(--border);box-shadow:0 2px 6px -2px rgba(0,0,0,.4),0 0 0 1px rgba(255,255,255,.05);}
|
||||
.gauge span{position:relative;z-index:1}
|
||||
|
||||
/* 半圆仪表盘 */
|
||||
.gauge-half{--p:0;width:60px;height:34px;position:relative;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;font-family:ui-monospace,monospace;font-size:10px;font-weight:600;gap:0;color:var(--text);}
|
||||
.gauge-half svg{width:100%;height:28px;overflow:visible;}
|
||||
.gauge-half path{fill:none;stroke-linecap:round;}
|
||||
/* 基础颜色 (暗色/亮色自动过渡) */
|
||||
.gauge-half path.track{stroke:color-mix(in srgb,var(--text-dim) 18%,transparent);stroke-width:6;}
|
||||
.gauge-half path.arc{stroke:var(--gauge-base,#3b82f6);stroke-width:8;stroke-dasharray:126;stroke-dashoffset:calc(126*(1 - var(--p)));transition:stroke-dashoffset .8s cubic-bezier(.4,0,.2,1),stroke .35s;filter:drop-shadow(0 1px 2px rgba(0,0,0,.45));}
|
||||
.gauge-half[data-type=mem] path.arc{--gauge-base:#10b981}
|
||||
.gauge-half[data-type=hdd] path.arc{--gauge-base:#f59e0b}
|
||||
/* 阈值颜色:>=50% 警告黄,>=90% 危险红 */
|
||||
.gauge-half[data-warn] path.arc{stroke:var(--warn)}
|
||||
.gauge-half[data-bad] path.arc{stroke:var(--danger)}
|
||||
/* 指针:以中心(50,50)为原点旋转;半圆角度范围 180deg -> 从 180deg (左) 到 0deg(右) */
|
||||
.gauge-half span{line-height:1;position:relative;top:-6px;font-size:12px;}
|
||||
/* 亮色模式细化对比度 */
|
||||
body.light .gauge-half path.track{stroke:color-mix(in srgb,var(--text-dim) 28%,transparent)}
|
||||
body.light .gauge-half path.arc{filter:none}
|
||||
body.light .gauge-half .needle{background:linear-gradient(var(--text),var(--text-dim))}
|
||||
|
||||
/* status pill */
|
||||
.pill{display:inline-block;padding:2px 12px;font-size:12px;font-weight:600;border-radius:999px;letter-spacing:.5px;min-width:54px;text-align:center;line-height:1.3;border:0;box-shadow:0 2px 4px -1px rgba(0,0,0,.4),0 0 0 1px rgba(255,255,255,.04);transition:var(--trans);color:#fff}
|
||||
|
@ -72,8 +103,8 @@ table.data tbody tr:hover{background:rgba(255,255,255,.04)}
|
|||
.pill.off:hover{filter:brightness(1.1)}
|
||||
|
||||
/* buckets CU/CT/CM (simple version) */
|
||||
.buckets{display:flex;align-items:flex-end;gap:6px;min-width:110px}
|
||||
.bucket{position:relative;width:24px;height:28px;background:linear-gradient(145deg,var(--bg),var(--bg-alt));border:1px solid var(--border);border-radius:7px;padding:3px 3px 14px;box-sizing:border-box;display:flex;justify-content:flex-end}
|
||||
.buckets{display:flex;align-items:flex-end;gap:8px;min-width:140px}
|
||||
.bucket{position:relative;width:26px;height:34px;background:linear-gradient(145deg,var(--bg),var(--bg-alt));border:1px solid var(--border);border-radius:8px;padding:4px 4px 16px;box-sizing:border-box;display:flex;justify-content:flex-end}
|
||||
.bucket span{display:block;width:100%;background:var(--accent);border-radius:4px 4px 6px 6px;height:var(--h);align-self:flex-end;transition:height .8s cubic-bezier(.4,0,.2,1),background .3s}
|
||||
.bucket[data-lv=warn] span{background:var(--warn)}
|
||||
.bucket[data-lv=bad] span{background:var(--danger)}
|
||||
|
@ -82,6 +113,10 @@ table.data tbody tr:hover{background:rgba(255,255,255,.04)}
|
|||
|
||||
/* 居中联通电信移动列 */
|
||||
table.data thead th:last-child, table.data tbody td:last-child { text-align:center; }
|
||||
/* 放大第13列宽度以容纳更宽水桶 */
|
||||
table.data thead th:nth-child(13),table.data tbody td:nth-child(13){
|
||||
width:170px;min-width:170px;max-width:170px;
|
||||
}
|
||||
.buckets{justify-content:center}
|
||||
|
||||
/* 响应式隐藏非关键列,保持可读性 */
|
||||
|
|
|
@ -91,13 +91,13 @@ function renderServers(){
|
|||
<td>${s.type||'-'}</td>
|
||||
<td>${s.location||'-'}</td>
|
||||
<td>${s.uptime||'-'}</td>
|
||||
<td>${s.load_1==-1?'–':s.load_1?.toFixed(2)}</td>
|
||||
<td>${s.load_1==-1?'–':Math.max(0,(s.load_1||0)).toFixed(2)}</td>
|
||||
<td>${monthIn} | ${monthOut}</td>
|
||||
<td>${netNow}</td>
|
||||
<td>${netTotal}</td>
|
||||
<td>${online?`<div class="spark" data-key="${key}" data-metric="cpu" title="CPU ${s.cpu||0}%"></div>`:'-'}</td>
|
||||
<td>${online?`<div class="spark" data-key="${key}" data-metric="mem" title="内存 ${memPct.toFixed(0)}%"></div>`:'-'}</td>
|
||||
<td>${online?`<div class="spark" data-key="${key}" data-metric="hdd" title="硬盘 ${hddPct.toFixed(0)}%"></div>`:'-'}</td>
|
||||
<td>${online?gaugeHTML('cpu', s.cpu||0):'-'}</td>
|
||||
<td>${online?gaugeHTML('mem', memPct):'-'}</td>
|
||||
<td>${online?gaugeHTML('hdd', hddPct):'-'}</td>
|
||||
<td>${pingBuckets}</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
@ -112,8 +112,22 @@ function renderServers(){
|
|||
});
|
||||
});
|
||||
|
||||
drawSparks();
|
||||
// 仪表盘无需 drawSparks
|
||||
}
|
||||
// 生成仪表盘 (圆形 conic-gradient)
|
||||
function gaugeHTML(type,val){
|
||||
const pct = Math.max(0,Math.min(100,val));
|
||||
const p = (pct/100).toFixed(3);
|
||||
const warnAttr = pct>=90? 'data-bad' : (pct>=50? 'data-warn' : '');
|
||||
return `<div class="gauge-half" data-type="${type}" ${warnAttr} style="--p:${p}" title="${labelOf(type)} ${pct.toFixed(0)}%">
|
||||
<svg viewBox="0 0 100 50" preserveAspectRatio="xMidYMid meet" aria-hidden="true">
|
||||
<path class="track" d="M10 50 A40 40 0 0 1 90 50" />
|
||||
<path class="arc" d="M10 50 A40 40 0 0 1 90 50" />
|
||||
</svg>
|
||||
<span>${pct.toFixed(0)}%</span>
|
||||
</div>`;
|
||||
}
|
||||
function labelOf(t){ return t==='cpu'?'CPU': t==='mem'?'内存':'硬盘'; }
|
||||
function renderServersCards(){
|
||||
const wrap = document.getElementById('serversCards');
|
||||
if(!wrap) return;
|
||||
|
@ -239,7 +253,7 @@ function bindTheme(){
|
|||
const mql = window.matchMedia('(prefers-color-scheme: light)');
|
||||
const saved = localStorage.getItem('theme'); // 'light' | 'dark' | null (auto)
|
||||
|
||||
const apply = (isLight)=>{ document.body.classList.toggle('light', isLight); };
|
||||
const apply = (isLight)=>{ document.body.classList.toggle('light', isLight); document.documentElement.classList.toggle('light', isLight); };
|
||||
|
||||
if(!saved){
|
||||
// 自动跟随系统
|
||||
|
@ -287,9 +301,9 @@ function openDetail(i){
|
|||
<div style="display:flex;flex-direction:column;gap:.4rem;">
|
||||
<canvas id="latChart" height="150" style="width:100%;border:1px solid var(--border);border-radius:10px;background:linear-gradient(145deg,var(--bg),var(--bg-alt));"></canvas>
|
||||
<div class="mono" style="font-size:11px;display:flex;gap:1rem;flex-wrap:wrap;">
|
||||
<span style="color:#3b82f6">● 联通</span>
|
||||
<span style="color:#10b981">● 电信</span>
|
||||
<span style="color:#f59e0b">● 移动</span>
|
||||
<span style="color:#3b82f6">● 联通 (<span id="lat-cu">${num(s.time_10010)}ms</span>)</span>
|
||||
<span style="color:#10b981">● 电信 (<span id="lat-ct">${num(s.time_189)}ms</span>)</span>
|
||||
<span style="color:#f59e0b">● 移动 (<span id="lat-cm">${num(s.time_10086)}ms</span>)</span>
|
||||
<span style="opacity:.6"> (~${S.hist[key]?S.hist[key].cu.length:0} 条)</span>
|
||||
</div>
|
||||
</div>`;
|
||||
|
@ -308,7 +322,6 @@ function openDetail(i){
|
|||
}
|
||||
function ioBar(label,bytesVal,role){ const peak=150*1000*1000; const pct=Math.min(100,(bytesVal/peak)*100); const lvl = bytesVal>100*1000*1000?'bad': bytesVal>50*1000*1000?'warn':''; return barHTML(label,pct, (bytes(bytesVal)+'/s'), role, true).replace('<div class="bar io"', `<div class="bar io" ${lvl?`data-${lvl}`:''}`); }
|
||||
box.innerHTML = `
|
||||
<div class="kv"><span>位置</span><span class="mono">${s.location||'-'}</span></div>
|
||||
<div class="kv"><span>TCP/UDP/进/线</span><span class="mono" id="detail-proc">${procLine}</span></div>
|
||||
<div style="display:flex;flex-direction:column;gap:.35rem;">
|
||||
<canvas id="loadChart" height="120" style="width:100%;border:1px solid var(--border);border-radius:10px;background:linear-gradient(145deg,var(--bg),var(--bg-alt));"></canvas>
|
||||
|
@ -356,6 +369,10 @@ function drawLatencyChart(key){
|
|||
const W = canvas.clientWidth; const H = canvas.height; canvas.width = W; // 适配宽度
|
||||
ctx.clearRect(0,0,W,H);
|
||||
const padL=40, padR=10, padT=10, padB=18;
|
||||
const isLight = document.body.classList.contains('light');
|
||||
const axisColor = isLight? 'rgba(0,0,0,0.22)' : 'rgba(255,255,255,0.18)';
|
||||
const gridColor = isLight? 'rgba(0,0,0,0.08)' : 'rgba(255,255,255,0.10)';
|
||||
const textColor = isLight? 'var(--text-dim)' : 'rgba(226,232,240,0.85)';
|
||||
const series = [ {arr:data.cu,color:'#3b82f6'}, {arr:data.ct,color:'#10b981'}, {arr:data.cm,color:'#f59e0b'} ];
|
||||
const allVals = series.flatMap(s=>s.arr);
|
||||
if(!allVals.length){ ctx.fillStyle='var(--text-dim)'; ctx.font='12px system-ui'; ctx.fillText('暂无数据', W/2-30, H/2); return; }
|
||||
|
@ -364,11 +381,11 @@ function drawLatencyChart(key){
|
|||
const range = Math.max(1, max-min);
|
||||
const n = Math.max(...series.map(s=>s.arr.length));
|
||||
const xStep = (W - padL - padR) / Math.max(1,n-1);
|
||||
// 网格与轴
|
||||
ctx.strokeStyle='rgba(255,255,255,0.08)'; ctx.lineWidth=1;
|
||||
// 网格与轴 (增强暗色对比)
|
||||
ctx.strokeStyle=axisColor; ctx.lineWidth=1.1;
|
||||
ctx.beginPath(); ctx.moveTo(padL,padT); ctx.lineTo(padL,H-padB); ctx.lineTo(W-padR,H-padB); ctx.stroke();
|
||||
ctx.fillStyle='var(--text-dim)'; ctx.font='10px system-ui';
|
||||
const yMarks=4; for(let i=0;i<=yMarks;i++){ const y = padT + (H-padT-padB)*i/yMarks; const val = (max - range*i/yMarks).toFixed(0)+'ms'; ctx.fillText(val,4,y+3); ctx.strokeStyle='rgba(255,255,255,0.05)'; ctx.beginPath(); ctx.moveTo(padL,y); ctx.lineTo(W-padR,y); ctx.stroke(); }
|
||||
ctx.fillStyle=textColor; ctx.font='10px system-ui';
|
||||
const yMarks=4; for(let i=0;i<=yMarks;i++){ const y = padT + (H-padT-padB)*i/yMarks; const val = (max - range*i/yMarks).toFixed(0)+'ms'; ctx.fillText(val,4,y+3); ctx.strokeStyle=gridColor; ctx.beginPath(); ctx.moveTo(padL,y); ctx.lineTo(W-padR,y); ctx.stroke(); }
|
||||
// 绘制线
|
||||
series.forEach(s=>{
|
||||
if(s.arr.length<2) return;
|
||||
|
@ -429,10 +446,14 @@ function drawLoadChart(key){
|
|||
const padL=38,padR=8,padT=8,padB=16;
|
||||
const max=Math.max(...all); const min=Math.min(...all); const range=Math.max(0.5,max-min);
|
||||
const n = Math.max(l1.length,l5.length,l15.length); const xStep=(W-padL-padR)/Math.max(1,n-1);
|
||||
// 轴 & 网格
|
||||
ctx.strokeStyle='rgba(255,255,255,0.08)'; ctx.lineWidth=1; ctx.beginPath(); ctx.moveTo(padL,padT); ctx.lineTo(padL,H-padB); ctx.lineTo(W-padR,H-padB); ctx.stroke();
|
||||
ctx.fillStyle='var(--text-dim)'; ctx.font='10px system-ui';
|
||||
const yMarks=4; for(let i=0;i<=yMarks;i++){ const y=padT+(H-padT-padB)*i/yMarks; const val=(max - range*i/yMarks).toFixed(2); ctx.fillText(val,4,y+3); ctx.strokeStyle='rgba(255,255,255,0.05)'; ctx.beginPath(); ctx.moveTo(padL,y); ctx.lineTo(W-padR,y); ctx.stroke(); }
|
||||
const isLight = document.body.classList.contains('light');
|
||||
const axisColor = isLight? 'rgba(0,0,0,0.22)' : 'rgba(255,255,255,0.18)';
|
||||
const gridColor = isLight? 'rgba(0,0,0,0.08)' : 'rgba(255,255,255,0.10)';
|
||||
const textColor = isLight? 'var(--text-dim)' : 'rgba(226,232,240,0.85)';
|
||||
// 轴 & 网格 (增强暗色对比)
|
||||
ctx.strokeStyle=axisColor; ctx.lineWidth=1.1; ctx.beginPath(); ctx.moveTo(padL,padT); ctx.lineTo(padL,H-padB); ctx.lineTo(W-padR,H-padB); ctx.stroke();
|
||||
ctx.fillStyle=textColor; ctx.font='10px system-ui';
|
||||
const yMarks=4; for(let i=0;i<=yMarks;i++){ const y=padT+(H-padT-padB)*i/yMarks; const val=(max - range*i/yMarks).toFixed(2); ctx.fillText(val,4,y+3); ctx.strokeStyle=gridColor; ctx.beginPath(); ctx.moveTo(padL,y); ctx.lineTo(W-padR,y); ctx.stroke(); }
|
||||
const series=[{arr:l1,color:'#8b5cf6',fill:true},{arr:l5,color:'#10b981'},{arr:l15,color:'#f59e0b'}];
|
||||
// 面积先画 load1
|
||||
series.forEach(s=>{
|
||||
|
@ -469,6 +490,10 @@ function updateDetailMetrics(key){
|
|||
function updIO(role,val){ const wrap=document.querySelector(`#detailContent .bar-wrap[data-role="${role}"]`); if(!wrap) return; const peak=150*1000*1000; const pct=Math.min(100,(val/peak)*100); const bar=wrap.querySelector('.bar span'); if(bar) bar.style.setProperty('--p',(pct/100).toFixed(3)); const lbl=wrap.querySelector('.bar-label span:last-child'); if(lbl) lbl.textContent= bytes(val)+'/s'; const box=wrap.querySelector('.bar'); if(box){ box.removeAttribute('data-warn'); box.removeAttribute('data-bad'); if(val>100*1000*1000) box.setAttribute('data-bad',''); else if(val>50*1000*1000) box.setAttribute('data-warn',''); } }
|
||||
updIO('io-read', ioRead);
|
||||
updIO('io-write', ioWrite);
|
||||
// 延迟动态刷新 (若存在)
|
||||
const cuEl=document.getElementById('lat-cu'); if(cuEl) cuEl.textContent = num(s.time_10010)+'ms';
|
||||
const ctEl=document.getElementById('lat-ct'); if(ctEl) ctEl.textContent = num(s.time_189)+'ms';
|
||||
const cmEl=document.getElementById('lat-cm'); if(cmEl) cmEl.textContent = num(s.time_10086)+'ms';
|
||||
}
|
||||
function startDetailAutoUpdate(){ stopDetailAutoUpdate(); S._detailTimer = setInterval(()=>{ if(S._openDetailKey) updateDetailMetrics(S._openDetailKey); }, 1000); }
|
||||
function stopDetailAutoUpdate(){ if(S._detailTimer){ clearInterval(S._detailTimer); S._detailTimer=null; } }
|
||||
|
|
Loading…
Reference in New Issue