Skip to content

Commit a31fb17

Browse files
committed
update
1 parent f19d109 commit a31fb17

2 files changed

Lines changed: 68 additions & 27 deletions

File tree

web/css/app.css

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,24 +61,24 @@ table.data th,table.data td{font-variant-numeric:tabular-nums}
6161
.caps-traffic .sep{opacity:.4;font-size:11px;display:none;}
6262
.caps-traffic.sm{padding:2px 8px 2px 7px;font-size:11px;gap:5px;}
6363
.caps-traffic.sm .io{font-size:11px;}
64-
/* 双色胶囊:左红右黄 */
64+
/* 双色胶囊 */
6565
.caps-traffic.duo{background:none;border:0;gap:0;padding:0;box-shadow:none;position:relative;border-radius:999px;overflow:hidden;font-size:12px;}
6666
/* 宽度按内容自适应(不再拉伸占满列),每半边仅为其文本 + padding,可容纳最大 111.1MB */
6767
.caps-traffic.duo .half{flex:0 0 auto;display:flex;align-items:center;justify-content:center;padding:2px 4px;font-variant-numeric:tabular-nums;font-weight:600;line-height:1.25; /* 与 .pill 保持一致高度 */ letter-spacing:.25px;color:#fff;font-size:12px;white-space:nowrap;}
6868
/* 双色胶囊配色:
69-
normal (默认): 左白(#fff) 右蓝(accent)
70-
heavy (>=500GB 任一方向): 左黄(warn) 右红(danger)
69+
normal (默认): 左绿右蓝
70+
heavy (>=500GB 总量): 黄色提醒,不再使用故障红
7171
*/
7272
/* normal 初始:淡绿色(入) + 淡蓝色(出) */
7373
.caps-traffic.duo.normal .half.in{background:#d1fae5;color:#065f46;} /* emerald-100 / text-emerald-800 */
7474
body.light .caps-traffic.duo.normal .half.in{background:#d1fae5;color:#065f46;}
7575
.caps-traffic.duo.normal .half.out{background:#bfdbfe;color:#1e3a8a;} /* blue-200 / text-blue-900 */
7676
body.light .caps-traffic.duo.normal .half.out{background:#bfdbfe;color:#1e3a8a;}
7777

78-
.caps-traffic.duo.heavy .half.in{background:var(--warn);color:#111;}
79-
body.light .caps-traffic.duo.heavy .half.in{background:var(--warn);color:#111;}
80-
.caps-traffic.duo.heavy .half.out{background:var(--danger);color:#fff;}
81-
body.light .caps-traffic.duo.heavy .half.out{color:#fff;}
78+
.caps-traffic.duo.heavy .half.in{background:#fde68a;color:#78350f;}
79+
body.light .caps-traffic.duo.heavy .half.in{background:#fde68a;color:#78350f;}
80+
.caps-traffic.duo.heavy .half.out{background:#fbbf24;color:#111827;}
81+
body.light .caps-traffic.duo.heavy .half.out{background:#fbbf24;color:#111827;}
8282

8383
/* 半之间分隔线 */
8484
.caps-traffic.duo .half + .half{border-left:1px solid rgba(0,0,0,.18);}
@@ -130,7 +130,7 @@ body:not(.light) .modal-box.high-load{background:linear-gradient(180deg, rgba(23
130130
.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));}
131131
.gauge-half[data-type=mem] path.arc{--gauge-base:#10b981}
132132
.gauge-half[data-type=hdd] path.arc{--gauge-base:#06b6d4}
133-
/* 阈值颜色:>=50% 警告黄,>=90% 危险红 */
133+
/* 阈值颜色:CPU>=75%、内存>=80%、硬盘>=85% 警告黄,>=90% 危险红 */
134134
.gauge-half[data-warn] path.arc{stroke:var(--warn)}
135135
.gauge-half[data-bad] path.arc{stroke:var(--danger)}
136136
/* 指针:以中心(50,50)为原点旋转;半圆角度范围 180deg -> 从 180deg (左) 到 0deg(右) */
@@ -231,9 +231,15 @@ body.light .gauge-half .needle{background:linear-gradient(var(--text),var(--text
231231
}
232232
.cards .card{border:1px solid var(--border);border-radius:12px;padding:.75rem .85rem;background:linear-gradient(145deg,var(--bg),var(--bg-alt));display:flex;flex-direction:column;gap:.45rem;position:relative;}
233233
.cards .card.offline{filter:saturate(.92);}
234-
.cards .card.high-load{border-color:rgba(239,68,68,.6);background:linear-gradient(180deg, rgba(239,68,68,.22), rgba(239,68,68,.12)), var(--bg-alt);box-shadow:0 0 0 1px rgba(239,68,68,.48),0 6px 18px -6px rgba(239,68,68,.28);}
235-
table.data tbody tr.high-load{background:rgba(239,68,68,.18) !important;}
236-
table.data tbody tr.high-load:hover{background:rgba(239,68,68,.26) !important;}
234+
.cards .card.high-load,
235+
.cards .card.alert-critical{border-color:rgba(239,68,68,.6);background:linear-gradient(180deg, rgba(239,68,68,.22), rgba(239,68,68,.12)), var(--bg-alt);box-shadow:0 0 0 1px rgba(239,68,68,.48),0 6px 18px -6px rgba(239,68,68,.28);}
236+
.cards .card.alert-warning{border-color:rgba(245,158,11,.55);background:linear-gradient(180deg, rgba(245,158,11,.16), rgba(245,158,11,.08)), var(--bg-alt);box-shadow:0 0 0 1px rgba(245,158,11,.32),0 6px 18px -8px rgba(245,158,11,.22);}
237+
table.data tbody tr.high-load,
238+
table.data tbody tr.alert-critical{background:rgba(239,68,68,.18) !important;}
239+
table.data tbody tr.high-load:hover,
240+
table.data tbody tr.alert-critical:hover{background:rgba(239,68,68,.26) !important;}
241+
table.data tbody tr.alert-warning{background:rgba(245,158,11,.12) !important;}
242+
table.data tbody tr.alert-warning:hover{background:rgba(245,158,11,.18) !important;}
237243

238244
/* SSL 域名告警底色:与高负载相同 */
239245
#sslTable td.alert-domain{background:rgba(239,68,68,.18) !important;}
@@ -353,7 +359,9 @@ th[data-sort].sorted-desc:after{border-top-color:var(--accent);opacity:1}
353359

354360
/* 右侧详情抽屉 */
355361
.drawer-backdrop{align-items:stretch;justify-content:flex-end;padding:0;background:rgba(0,0,0,.48)}
356-
.detail-drawer{height:100vh;max-height:100vh;width:min(620px,100vw);max-width:min(620px,100vw);border-radius:0;border-top:0;border-right:0;border-bottom:0;padding:1.1rem;overflow:auto;animation:slideIn .24s ease}
362+
.detail-drawer{height:100dvh;max-height:100dvh;width:min(620px,100vw);max-width:min(620px,100vw);border-radius:0;border-top:0;border-right:0;border-bottom:0;padding:1.1rem;overflow:auto;animation:slideIn .24s ease}
363+
.detail-drawer.alert-critical{border-color:rgba(239,68,68,.6);background:linear-gradient(180deg, rgba(239,68,68,.16), rgba(239,68,68,.08)), var(--bg-alt);box-shadow:0 0 0 1px rgba(239,68,68,.38),0 10px 28px -10px rgba(239,68,68,.28)}
364+
.detail-drawer.alert-warning{border-color:rgba(245,158,11,.55);background:linear-gradient(180deg, rgba(245,158,11,.14), rgba(245,158,11,.06)), var(--bg-alt);box-shadow:0 0 0 1px rgba(245,158,11,.28),0 10px 28px -10px rgba(245,158,11,.2)}
357365
.modal-title{padding-right:2.2rem}
358366
.detail-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.55rem}
359367
.detail-section{border:1px solid var(--border);border-radius:8px;background:linear-gradient(145deg,var(--bg),var(--bg-alt));padding:.75rem;display:flex;flex-direction:column;gap:.65rem}
@@ -428,5 +436,6 @@ th[data-sort].sorted-desc:after{border-top-color:var(--accent);opacity:1}
428436
.section-head{flex-direction:column}
429437
.section-actions{width:100%;justify-content:stretch}
430438
.detail-grid,.config-fields{grid-template-columns:1fr}
431-
.detail-drawer{width:100vw}
439+
.detail-drawer{width:100vw;height:100dvh;max-height:100dvh;padding:calc(.95rem + env(safe-area-inset-top)) .95rem calc(.95rem + env(safe-area-inset-bottom));}
440+
.detail-drawer .modal-content{max-height:none;overflow:visible;}
432441
}

web/js/app.js

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,12 @@ function osLabel(os){
9494
return String(os).charAt(0).toUpperCase() + String(os).slice(1);
9595
}
9696

97+
function lossValues(s){
98+
return [s.ping_10010, s.ping_189, s.ping_10086].map(p => clamp(num(p), 0, 100));
99+
}
100+
97101
function isBlocked(s){
98-
return [s.ping_10010, s.ping_189, s.ping_10086].every(p => clamp(num(p), 0, 100) >= 100);
102+
return lossValues(s).every(p => p >= 100);
99103
}
100104

101105
function metrics(s){
@@ -105,11 +109,20 @@ function metrics(s){
105109
const monthIn = Math.max(0, num(s.network_in) - num(s.last_network_in));
106110
const monthOut = Math.max(0, num(s.network_out) - num(s.last_network_out));
107111
const traffic = monthIn + monthOut;
108-
const loss = Math.max(num(s.ping_10010), num(s.ping_189), num(s.ping_10086));
109-
const blocked = online && isBlocked(s);
110-
const critical = online && (num(s.cpu) >= 90 || memPct >= 90 || hddPct >= 90 || loss >= 20);
111-
const warning = online && (traffic >= 500 * 1000 * 1000 * 1000 || loss >= 10 || num(s.cpu) >= 75 || memPct >= 80 || hddPct >= 85);
112-
return { online, memPct, hddPct, monthIn, monthOut, traffic, loss, blocked, critical, warning, alert: critical || warning, highlight: !online || critical || warning };
112+
const losses = lossValues(s);
113+
const loss = Math.max(...losses);
114+
const lossBadCount = losses.filter(p => p >= 40).length;
115+
const lossWarnCount = losses.filter(p => p >= 30).length;
116+
const blocked = online && losses.every(p => p >= 100);
117+
const resourceCritical = online && (num(s.cpu) >= 90 || memPct >= 90 || hddPct >= 90);
118+
const resourceWarning = online && (num(s.cpu) >= 75 || memPct >= 80 || hddPct >= 85);
119+
const trafficWarning = online && traffic >= 500 * 1000 * 1000 * 1000;
120+
const lossCritical = online && loss >= 40;
121+
const lossWarning = online && !lossCritical && loss >= 30;
122+
const critical = online && (resourceCritical || blocked || lossCritical);
123+
const warning = online && (resourceWarning || lossWarning);
124+
const rowLevel = !online || critical ? 'critical' : (resourceWarning || lossWarning ? 'warning' : '');
125+
return { online, memPct, hddPct, monthIn, monthOut, traffic, loss, lossBadCount, lossWarnCount, blocked, resourceCritical, resourceWarning, trafficWarning, lossCritical, lossWarning, critical, warning, alert: critical || warning, highlight: !!rowLevel, rowLevel };
113126
}
114127

115128
async function fetchData(){
@@ -255,7 +268,12 @@ function trafficCaps(s, small){
255268
}
256269
function gaugeHTML(type, value){
257270
const pct = clamp(num(value), 0, 100);
258-
const warnAttr = pct >= 90 ? 'data-bad' : (pct >= 50 ? 'data-warn' : '');
271+
const thresholds = {
272+
cpu: { warn: 75, bad: 90 },
273+
mem: { warn: 80, bad: 90 },
274+
hdd: { warn: 85, bad: 90 }
275+
}[type] || { warn: 75, bad: 90 };
276+
const warnAttr = pct >= thresholds.bad ? 'data-bad' : (pct >= thresholds.warn ? 'data-warn' : '');
259277
const label = type === 'cpu' ? 'CPU' : type === 'mem' ? '内存' : '硬盘';
260278
return `<div class="gauge-half" data-type="${type}" ${warnAttr} style="--p:${(pct / 100).toFixed(3)}" title="${label} ${pct.toFixed(0)}%">
261279
<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>
@@ -264,7 +282,13 @@ function gaugeHTML(type, value){
264282
}
265283
function resourceMeter(label, value, pct, kind){
266284
const safePct = clamp(num(pct), 0, 100);
267-
const level = safePct >= 90 ? 'bad' : (safePct >= 75 ? 'warn' : 'ok');
285+
const thresholds = {
286+
cpu: { warn: 75, bad: 90 },
287+
mem: { warn: 80, bad: 90 },
288+
swap: { warn: 80, bad: 90 },
289+
hdd: { warn: 85, bad: 90 }
290+
}[kind] || { warn: 75, bad: 90 };
291+
const level = safePct >= thresholds.bad ? 'bad' : (safePct >= thresholds.warn ? 'warn' : 'ok');
268292
return `<div class="resource-meter" data-kind="${esc(kind)}" data-level="${level}" style="--p:${safePct.toFixed(1)}%">
269293
<div class="resource-meter-head"><span>${esc(label)}</span><strong>${esc(value)}</strong></div>
270294
<div class="resource-track"><i></i></div>
@@ -288,7 +312,7 @@ function chartLegend(series){
288312
function buckets(s){
289313
return `<div class="buckets" title="联通 / 电信 / 移动">${[s.ping_10010, s.ping_189, s.ping_10086].map(p => {
290314
const v = clamp(num(p), 0, 100);
291-
const level = v >= 20 ? 'bad' : (v >= 10 ? 'warn' : 'ok');
315+
const level = v >= 40 ? 'bad' : (v >= 30 ? 'warn' : 'ok');
292316
return `<div class="bucket" data-lv="${level}"><span style="--h:${v}%"></span><label>${v.toFixed(0)}%</label></div>`;
293317
}).join('')}</div>`;
294318
}
@@ -303,7 +327,8 @@ function renderServers(){
303327
const m = metrics(s);
304328
const netNow = `${humanMinKBFromB(s.network_rx)} | ${humanMinKBFromB(s.network_tx)}`;
305329
const netTotal = `${humanMinMBFromB(s.network_in)} | ${humanMinMBFromB(s.network_out)}`;
306-
return `<tr data-key="${esc(s._key)}" data-online="${m.online ? 1 : 0}" class="row-server${m.highlight ? ' high-load' : ''}${osClass(s.os)}" style="cursor:${m.online ? 'pointer' : 'default'};">
330+
const alertClass = m.rowLevel ? ` alert-${m.rowLevel}` : '';
331+
return `<tr data-key="${esc(s._key)}" data-online="${m.online ? 1 : 0}" class="row-server${alertClass}${osClass(s.os)}" style="cursor:${m.online ? 'pointer' : 'default'};">
307332
<td>${protoPill(s)}</td>
308333
<td>${trafficCaps(s)}</td>
309334
<td>${esc(s.name || '-')}</td>
@@ -330,7 +355,8 @@ function renderServersCards(){
330355
if(window.innerWidth > 700){ wrap.innerHTML = ''; return; }
331356
wrap.innerHTML = visibleServers().map(s => {
332357
const m = metrics(s);
333-
return `<div class="card${m.online ? '' : ' offline'}${m.highlight ? ' high-load' : ''}${osClass(s.os)}" data-key="${esc(s._key)}" data-online="${m.online ? 1 : 0}">
358+
const alertClass = m.rowLevel ? ` alert-${m.rowLevel}` : '';
359+
return `<div class="card${m.online ? '' : ' offline'}${alertClass}${osClass(s.os)}" data-key="${esc(s._key)}" data-online="${m.online ? 1 : 0}">
334360
<div class="card-header"><div class="card-title">${esc(s.name || '-')} <span class="tag">${esc(s.location || '-')}</span></div>${protoPill(s)}</div>
335361
<div class="kvlist">
336362
<div><span class="key">负载</span><span>${s.load_1 === -1 ? '–' : num(s.load_1).toFixed(2)}</span></div>
@@ -379,7 +405,8 @@ function renderSSL(){
379405
const cls = c.mismatch || c.expire_days <= 0 ? 'err' : c.expire_days <= 7 ? 'warn' : 'ok';
380406
const status = c.mismatch ? '域名不匹配' : c.expire_days <= 0 ? '已过期' : c.expire_days <= 7 ? '将到期' : '正常';
381407
const dt = c.expire_ts ? new Date(c.expire_ts * 1000).toISOString().replace('T',' ').replace(/\.\d+Z/,'') : '-';
382-
return `<tr class="${cls !== 'ok' ? 'high-load' : ''}"><td>${esc(c.name || '-')}</td><td>${esc(String(c.domain || '').replace(/^https?:\/\//,''))}</td><td>${esc(c.port || 443)}</td><td><span class="badge ${cls}">${esc(c.expire_days ?? '-')}</span></td><td>${dt}</td><td><span class="badge ${cls}">${status}</span></td></tr>`;
408+
const alertClass = cls === 'err' ? 'alert-critical' : cls === 'warn' ? 'alert-warning' : '';
409+
return `<tr class="${alertClass}"><td>${esc(c.name || '-')}</td><td>${esc(String(c.domain || '').replace(/^https?:\/\//,''))}</td><td>${esc(c.port || 443)}</td><td><span class="badge ${cls}">${esc(c.expire_days ?? '-')}</span></td><td>${dt}</td><td><span class="badge ${cls}">${status}</span></td></tr>`;
383410
}).join('') || `<tr><td colspan="6" class="muted" style="text-align:center;padding:1rem;">无证书数据</td></tr>`;
384411
}
385412
function renderSSLCards(){
@@ -389,7 +416,8 @@ function renderSSLCards(){
389416
const cls = c.mismatch || c.expire_days <= 0 ? 'err' : c.expire_days <= 7 ? 'warn' : 'ok';
390417
const status = c.mismatch ? '域名不匹配' : c.expire_days <= 0 ? '已过期' : c.expire_days <= 7 ? '将到期' : '正常';
391418
const dt = c.expire_ts ? new Date(c.expire_ts * 1000).toISOString().slice(0,10) : '-';
392-
return `<div class="card${cls !== 'ok' ? ' high-load' : ''}"><div class="card-header"><div class="card-title">${esc(c.name || '-')}</div><span class="status-pill ${cls === 'err' ? 'off' : 'on'}">${status}</span></div><div class="kvlist"><div><span class="key">域名</span><span>${esc(String(c.domain || '').replace(/^https?:\/\//,''))}</span></div><div><span class="key">端口</span><span>${esc(c.port || 443)}</span></div><div><span class="key">剩余</span><span>${esc(c.expire_days ?? '-')} 天</span></div><div><span class="key">到期</span><span>${dt}</span></div></div></div>`;
419+
const alertClass = cls === 'err' ? ' alert-critical' : cls === 'warn' ? ' alert-warning' : '';
420+
return `<div class="card${alertClass}"><div class="card-header"><div class="card-title">${esc(c.name || '-')}</div><span class="status-pill ${cls === 'err' ? 'off' : 'on'}">${status}</span></div><div class="kvlist"><div><span class="key">域名</span><span>${esc(String(c.domain || '').replace(/^https?:\/\//,''))}</span></div><div><span class="key">端口</span><span>${esc(c.port || 443)}</span></div><div><span class="key">剩余</span><span>${esc(c.expire_days ?? '-')} 天</span></div><div><span class="key">到期</span><span>${dt}</span></div></div></div>`;
393421
}).join('') || '<div class="empty-state">无证书数据</div>';
394422
}
395423

@@ -407,7 +435,11 @@ function refreshDetail(){
407435
const title = $('detailTitle');
408436
title.innerHTML = `${esc(s.name || '-')} 详情${s.os ? `<span class="os-chip${osClass(s.os)}">${esc(osLabel(s.os))}</span>` : ''}`;
409437
const modalBox = document.querySelector('#detailModal .modal-box');
410-
if(modalBox) modalBox.classList.toggle('high-load', m.highlight);
438+
if(modalBox){
439+
modalBox.classList.remove('high-load');
440+
modalBox.classList.toggle('alert-critical', m.rowLevel === 'critical');
441+
modalBox.classList.toggle('alert-warning', m.rowLevel === 'warning');
442+
}
411443
const loadSeries = [
412444
{ data: S.loadHist[s._key]?.l1 || [], color:'#8b5cf6', label:'load1' },
413445
{ data: S.loadHist[s._key]?.l5 || [], color:'#10b981', label:'load5' },

0 commit comments

Comments
 (0)