const http = require("http"); const fs = require("fs"); const path = require("path"); const os = require("os"); const { exec } = require("child_process"); const PORT = process.env.PORT || 3000; const MIME = { ".html": "text/html; charset=utf-8", ".css": "text/css; charset=utf-8", ".js": "application/javascript; charset=utf-8", ".json": "application/json; charset=utf-8", ".svg": "image/svg+xml", }; function run(cmd) { return new Promise((resolve) => { exec(cmd, { timeout: 4000 }, (err, stdout) => { if (err) return resolve(null); resolve(stdout.trim()); }); }); } let lastCpu = os.cpus(); function cpuUsage() { const now = os.cpus(); let total = 0, idle = 0; for (let i = 0; i < now.length; i++) { const cur = now[i].times; const prev = lastCpu[i].times; const t = (cur.user + cur.nice + cur.sys + cur.idle + cur.irq) - (prev.user + prev.nice + prev.sys + prev.idle + prev.irq); const i2 = cur.idle - prev.idle; if (t > 0) { total += t; idle += i2; } } lastCpu = now; return total > 0 ? Math.round(((total - idle) / total) * 1000) / 10 : 0; } async function diskInfo() { const out = await run("df -k -P /"); if (!out) return null; const line = out.split("\n").pop(); const parts = line.split(/\s+/); if (parts.length < 5) return null; const total = +parts[1] * 1024; const used = +parts[2] * 1024; const free = +parts[3] * 1024; return { total, used, free, percent: total > 0 ? Math.round((used / total) * 1000) / 10 : 0, }; } async function topProcesses() { const out = await run("ps -eo pid=,comm=,%cpu=,%mem= --sort=-%cpu | head -8"); if (!out) return []; const rows = out.split("\n").filter(Boolean).map((l) => { const m = l.trim().split(/\s+/); if (m.length < 4) return null; const pid = +m[0]; const name = m.slice(1, m.length - 2).join(" ") || "?"; return { pid, name, cpu: +parseFloat(m[m.length - 2]).toFixed(1), mem: +parseFloat(m[m.length - 1]).toFixed(1) }; }); return rows.filter(Boolean); } function cpuInfo() { const cpus = os.cpus(); const speed = cpus[0] ? Math.round(cpus[0].speed / 100) / 10 : 0; const model = cpus[0] ? cpus[0].model.trim() : ""; return { usage: cpuUsage(), model, cores: cpus.length, speed, }; } function memInfo() { const total = os.totalmem(); const free = os.freemem(); const used = total - free; return { total, used, free, percent: Math.round((used / total) * 1000) / 10 }; } function networkInfo() { const ifs = os.networkInterfaces(); for (const name of Object.keys(ifs)) { for (const a of ifs[name] || []) { if (a.family === "IPv4" && !a.internal) return name + ": " + a.address; } } return "—"; } async function metrics() { const disk = await diskInfo(); return { status: "ok", hostname: os.hostname(), os: os.platform(), release: os.release(), arch: os.arch(), uptime: Math.floor(os.uptime()), loadavg: os.loadavg(), cpu: cpuInfo(), mem: memInfo(), disk: disk || { total: 0, used: 0, free: 0, percent: 0 }, network: networkInfo(), processes: await topProcesses(), timestamp: Date.now(), }; } http.createServer(async (req, res) => { const url = (req.url || "/").split("?")[0]; if (req.method === "GET" && url === "/api/metrics") { res.writeHead(200, { "Content-Type": MIME[".json"] }); return res.end(JSON.stringify(await metrics())); } let file = url === "/" ? "/index.html" : url; const full = path.join(__dirname, file); if (full.startsWith(__dirname) && fs.existsSync(full) && fs.statSync(full).isFile()) { const type = MIME[path.extname(full)] || "application/octet-stream"; res.writeHead(200, { "Content-Type": type }); return res.end(fs.readFileSync(full)); } res.writeHead(404); res.end("Not found"); }).listen(PORT, () => console.log("Мониторинг запущен на порту " + PORT));