(() => {
const root = document.querySelector('#content');
const summaryEl = document.querySelector('#summaryText');
const lastUpdatedEl = document.querySelector('#lastUpdated');
const fmtBytes = n => {
if (!Number.isFinite(n) || n <= 0) return '0 B';
const u = ['B', 'KB', 'MB', 'GB', 'TB'];
let i = 0, v = n;
while (v >= 1024 && i < u.length - 1) {
v /= 1024;
i++;
}
return `${v.toFixed(i === 0 ? 0 : 1)} ${u[i]}`;
};
const fmtRate = n => `${fmtBytes(n)}/s`;
const fmtUp = n => {
let s = Math.max(0, Math.floor(n || 0));
if (s <= 0) return '--';
const d = Math.floor(s / 86400);
s %= 86400;
const h = Math.floor(s / 3600);
s %= 3600;
const m = Math.floor(s / 60);
if (d > 0) return `${d}天 ${h}时`;
if (h > 0) return `${h}小时 ${m}分`;
return `${m} 分钟`;
};
const esc = s => String(s ?? '-').replace(/[&<>"']/g, c => ({
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
}[c]));
const getFlag = (cc, country) => {
if (!cc || typeof cc !== 'string' || cc.length !== 2) return '';
const code = cc.toUpperCase();
const flag = String.fromCodePoint(...[...code].map(c => 0x1F1E6 - 65 + c.charCodeAt(0)));
const title = country ? `${country} (${code})` : code;
return `${flag}`;
};
const getStatusLevel = pct => {
const val = Number(pct || 0);
if (val >= 85) return 'danger';
if (val >= 65) return 'warn';
return 'normal';
};
let mode = localStorage.getItem('vpc_probe_view') || 'card';
const setMode = m => {
mode = m;
localStorage.setItem('vpc_probe_view', m);
render(window.__data || []);
};
document.querySelector('#cardBtn').onclick = () => setMode('card');
document.querySelector('#compactBtn').onclick = () => setMode('compact');
const renderCard = d => {
const p = d.profile || {};
const isOnline = d.status === 'online';
const cpuPct = p.cpu_usage_percent != null ? Number(p.cpu_usage_percent) : 0;
const memPct = p.mem_percent != null ? Number(p.mem_percent) : 0;
const diskPct = p.disk_percent != null ? Number(p.disk_percent) : 0;
const loadStr = [p.load_1m, p.load_5m, p.load_15m].map(x => Number(x || 0).toFixed(2)).join(', ');
return `
${getFlag(d.country_code, d.country)}
${esc(d.name)}
${d.is_local ? '本机' : ''}
${p.cpu_cores || 1} 核
${esc(d.os || 'Linux')}
${fmtUp(p.uptime_seconds)}
${isOnline ? '在线' : '离线'}
实时网络 (↓ / ↑)
↓ ${fmtRate(p.net_rx_rate)} /
↑ ${fmtRate(p.net_tx_rate)}
总流量 (↓ / ↑)
↓ ${fmtBytes(p.net_rx_total)} / ↑ ${fmtBytes(p.net_tx_total)}
系统平均负载 (1/5/15m)
${loadStr}
IO PSI / Docker
${p.io_psi != null ? Number(p.io_psi).toFixed(2) : '0.00'} / ${p.docker ? '有' : '无'}
`;
};
const renderTable = list => {
const rows = list.map(d => {
const p = d.profile || {};
const isOnline = d.status === 'online';
const cpuPct = p.cpu_usage_percent != null ? Number(p.cpu_usage_percent) : 0;
const memPct = p.mem_percent != null ? Number(p.mem_percent) : 0;
const diskPct = p.disk_percent != null ? Number(p.disk_percent) : 0;
const loadStr = [p.load_1m, p.load_5m, p.load_15m].map(x => Number(x || 0).toFixed(2)).join(' / ');
return `
|
${getFlag(d.country_code, d.country)}
${esc(d.name)}
${d.is_local ? '本机' : ''}
${p.cpu_cores || 1}核 | ${esc(d.os || 'Linux')}
|
${isOnline ? '在线' : '离线'}
|
${fmtUp(p.uptime_seconds)} |
${loadStr} |
${p.cpu_usage_percent != null ? `${cpuPct}%` : '--'}
|
${p.mem_percent != null ? `${memPct}%` : '--'}
|
↓ ${fmtRate(p.net_rx_rate)}
↑ ${fmtRate(p.net_tx_rate)}
|
↓ ${fmtBytes(p.net_rx_total)}
↑ ${fmtBytes(p.net_tx_total)}
|
${p.disk_percent != null ? `${diskPct}%` : '--'}
|
`;
}).join('');
return `
| 节点名称 |
状态 |
运行时间 |
负载 (1/5/15m) |
CPU |
内存 |
实时网速 |
累计流量 |
磁盘 |
${rows}
`;
};
function render(data) {
document.querySelector('#cardBtn').classList.toggle('active', mode === 'card');
document.querySelector('#compactBtn').classList.toggle('active', mode === 'compact');
const total = data.length;
const onlineCount = data.filter(x => x.status === 'online').length;
if (summaryEl) {
summaryEl.textContent = `${onlineCount} / ${total} 节点在线`;
}
if (!data || data.length === 0) {
root.innerHTML = '暂无节点数据
';
return;
}
if (mode === 'card') {
root.className = 'card-view';
root.innerHTML = `${data.map(renderCard).join('')}
`;
} else {
root.className = 'table-view';
root.innerHTML = renderTable(data);
}
}
async function load() {
try {
const r = await fetch('/probe/summary', { cache: 'no-store' });
if (!r.ok) throw Error();
let rawData = await r.json();
if (Array.isArray(rawData)) {
rawData.sort((a, b) => {
if (a.is_local && !b.is_local) return -1;
if (!a.is_local && b.is_local) return 1;
return String(a.name || "").localeCompare(String(b.name || ""));
});
}
window.__data = rawData;
render(window.__data);
if (lastUpdatedEl) {
const now = new Date();
const timeStr = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
lastUpdatedEl.textContent = `更新于 ${timeStr}`;
}
} catch (e) {
root.innerHTML = '探针数据暂时不可用,正在重试...
';
}
}
load();
setInterval(load, 5000);
})();