test/script.js

140 lines
5.2 KiB
JavaScript
Raw Permalink 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.

const $ = (id) => document.getElementById(id);
const HISTORY = { cpu: [], mem: [] };
const MAX_HISTORY = 60;
const DEMO = () => {
const rnd = (a, b) => Math.round(a + Math.random() * (b - a));
const memPct = rnd(35, 55);
return {
hostname: "demo-server", os: "linux", release: "6.x", arch: "x64",
uptime: 3 * 86400 + 7 * 3600 + 42 * 60,
loadavg: [0.4, 0.3, 0.2],
cpu: { usage: rnd(5, 25), model: "Пример данных (Предпросмотр)", cores: 8, speed: 2.4 },
mem: { total: 16 * 1024 ** 3, used: Math.round(memPct / 100 * 16 * 1024 ** 3), free: Math.round((100 - memPct) / 100 * 16 * 1024 ** 3), percent: memPct },
disk: { total: 100 * 1024 ** 3, used: 64 * 1024 ** 3, free: 36 * 1024 ** 3, percent: 64 },
network: "1.2 Гбит/с",
processes: [
{ pid: 1234, name: "node", cpu: 12.3, mem: 4.2 },
{ pid: 5678, name: "nginx", cpu: 3.1, mem: 1.0 },
{ pid: 9012, name: "python3", cpu: 2.4, mem: 5.7 },
{ pid: 3456, name: "postgres", cpu: 1.8, mem: 12.5 },
{ pid: 7890, name: "redis", cpu: 0.9, mem: 0.8 },
{ pid: 1112, name: "docker", cpu: 0.6, mem: 1.4 },
],
};
};
function fmtBytes(n) {
if (n == null || isNaN(n)) return "—";
const u = ["Б", "КБ", "МБ", "ГБ", "ТБ"];
let i = 0;
while (n >= 1024 && i < u.length - 1) { n /= 1024; i++; }
return n.toFixed(i >= 2 ? 1 : 0) + " " + u[i];
}
function fmtUptime(sec) {
if (sec == null) return "—";
const d = Math.floor(sec / 86400);
const h = Math.floor((sec % 86400) / 3600);
const m = Math.floor((sec % 3600) / 60);
let s = m + " мин";
if (h > 0) s = h + " ч " + s;
if (d > 0) s = d + " дн " + s;
return s;
}
function level(p) {
if (p >= 90) return { c: "bad", w: "bad" };
if (p >= 70) return { c: "warn", w: "warn" };
return { c: "ok", w: "" };
}
function spark(id, arr) {
const el = $(id);
if (!arr.length) return;
const w = 100, h = 30, pad = 2;
const min = Math.min(...arr), max = Math.max(...arr);
const rng = max - min || 1;
const pts = arr.map((v, i) => {
const x = pad + (i / (arr.length - 1)) * (w - pad * 2);
const y = h - pad - ((v - min) / rng) * (h - pad * 2);
return x + "," + y;
});
const path = "M" + pts.join(" L");
const last = pts[pts.length - 1].split(",");
el.innerHTML = '<path d="' + path + '"/>' +
'<circle cx="' + last[0] + '" cy="' + last[1] + '" r="2"/>';
}
function pushHistory(m) {
HISTORY.cpu.push(m.cpu.usage);
HISTORY.mem.push(m.mem.percent);
if (HISTORY.cpu.length > MAX_HISTORY) { HISTORY.cpu.shift(); HISTORY.mem.shift(); }
}
function render(m) {
const status = m.status || "ok";
const dot = $("statusDot");
dot.className = "dot " + status;
$("hostname").textContent = m.hostname || "—";
$("osinfo").textContent = (m.os || "") + " " + (m.release || "") + " · " + (m.arch || "");
$("lastUpd").textContent = "последнее обновление: " + new Date().toLocaleTimeString("ru-RU");
$("cpuPct").textContent = Math.round(m.cpu.usage);
$("cpuBar").style.width = Math.min(100, m.cpu.usage) + "%";
$("cpuBar").className = level(m.cpu.usage).w;
$("cpuSub").textContent = m.cpu.cores + " ядер" + (m.cpu.speed > 0 ? " · " + m.cpu.speed + " ГГц" : "");
$("cpuModel").textContent = m.cpu.model || "—";
$("cpuCard").classList.toggle("dim", false);
$("cpuSpark").style.display = "block";
spark("cpuSpark", HISTORY.cpu);
$("memPct").textContent = Math.round(m.mem.percent);
$("memBar").style.width = Math.min(100, m.mem.percent) + "%";
$("memBar").className = level(m.mem.percent).w;
$("memSub").textContent = "использовано " + fmtBytes(m.mem.used) + " из " + fmtBytes(m.mem.total);
spark("memSpark", HISTORY.mem);
$("diskPct").textContent = Math.round(m.disk.percent);
$("diskBar").style.width = Math.min(100, m.disk.percent) + "%";
$("diskBar").className = level(m.disk.percent).w;
$("diskSub").textContent = "свободно " + fmtBytes(m.disk.free) + " из " + fmtBytes(m.disk.total);
$("uptime").textContent = fmtUptime(m.uptime);
$("loadavg").textContent = (m.loadavg || []).map((x) => x.toFixed ? x.toFixed(2) : x).join(" / ");
$("network").textContent = m.network || "—";
$("cores").textContent = (m.cpu.cores || "—") + " · " + (m.arch || "—");
const body = $("procBody");
if (!m.processes || !m.processes.length) {
body.innerHTML = '<tr><td colspan="4" class="muted">нет данных</td></tr>';
return;
}
body.innerHTML = m.processes.map((p) =>
'<tr>' +
'<td class="pid">' + p.pid + '</td>' +
'<td>' + p.name + '</td>' +
'<td class="num-cell"><span class="cpu-cell"><span class="minibar"><i style="width:' + Math.min(100, p.cpu) + '%"></i></span>' + Number(p.cpu).toFixed(1) + '%</span></td>' +
'<td class="num-cell">' + Number(p.mem).toFixed(1) + '%</td>' +
'</tr>'
).join("");
}
async function load() {
let m;
try {
const r = await fetch("/api/metrics", { cache: "no-store" });
if (!r.ok) throw new Error("http " + r.status);
m = await r.json();
} catch (e) {
m = DEMO();
$("cpuModel").textContent = "Сервер не запущен — показаны демо-данные";
$("statusDot").className = "dot warn";
}
pushHistory(m);
render(m);
}
load();
setInterval(load, 3000);