feat: informe de wallet completo (salud, clusters CIOH, historial); refactor: monitor sin dependencias (fuera express), nombres de proceso limpios; mejora: presentación de checks agrupada con didáctica directa
This commit is contained in:
+81
-62
@@ -11,14 +11,12 @@
|
||||
* - Caché con TTL: mide una vez cada pocos segundos, sirva a N navegadores
|
||||
* - Todo asíncrono: nada bloquea el servidor
|
||||
*
|
||||
* Setup:
|
||||
* npm install express
|
||||
* Setup: ninguno. Sin dependencias — solo Node.js.
|
||||
* node system-metrics.js
|
||||
*
|
||||
* Acceso: http://127.0.0.1:4082
|
||||
*/
|
||||
|
||||
const express = require("express");
|
||||
const { exec } = require("child_process");
|
||||
const fs = require("fs");
|
||||
const os = require("os");
|
||||
@@ -34,14 +32,6 @@ const PORT = 4082;
|
||||
const HOST = "127.0.0.1";
|
||||
// ── FIN CONFIGURACIÓN ───────────────────────────────────────────────────────
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
res.setHeader("Access-Control-Allow-Methods", "GET");
|
||||
next();
|
||||
});
|
||||
|
||||
// ── Helpers asíncronos ──────────────────────────────────────────────────────
|
||||
const readFile = (path) => {
|
||||
try { return fs.readFileSync(path, "utf8").trim(); } catch { return null; }
|
||||
@@ -93,7 +83,7 @@ function readProcStat() {
|
||||
|
||||
async function getCpuUsage() {
|
||||
const s1 = readProcStat();
|
||||
await sleep(300);
|
||||
await sleep(500);
|
||||
const s2 = readProcStat();
|
||||
if (!s1 || !s2) return { total: 0, cores: [] };
|
||||
|
||||
@@ -186,9 +176,19 @@ async function getProcesses() {
|
||||
// Nombre limpio: basename del ejecutable; si es un intérprete (node,
|
||||
// python...), añade el basename del script que ejecuta.
|
||||
const exe = (parts[10] || "").split("/").pop();
|
||||
const arg1 = (parts[11] || "").split("/").pop();
|
||||
const interp = /^(node|python\d?|bash|sh|perl)$/.test(exe);
|
||||
const command = (interp && arg1 ? `${exe} (${arg1})` : exe).slice(0, 24);
|
||||
const interp = /^(node|python\d?|bash|sh|perl|ruby|java)$/.test(exe);
|
||||
let command = exe;
|
||||
if (interp) {
|
||||
// Saltar flags (--max-old-space-size=…, -e, etc.) y quedarse con el
|
||||
// primer argumento "real": el script (suele acabar en .js/.py o ser ruta)
|
||||
const args = parts.slice(11);
|
||||
const script = args.find(a => a && !a.startsWith("-"));
|
||||
if (script) {
|
||||
const name = script.split("/").pop();
|
||||
command = `${exe} (${name})`;
|
||||
}
|
||||
}
|
||||
command = command.slice(0, 24);
|
||||
return {
|
||||
user: parts[0],
|
||||
cpu: parseFloat(parts[2]),
|
||||
@@ -278,63 +278,82 @@ async function getLogs(service, lines = 50) {
|
||||
});
|
||||
}
|
||||
|
||||
// ── Routes (caché por ruta: el trabajo se hace 1 vez por ventana) ──────────
|
||||
app.get("/system/info", async (req, res) => {
|
||||
try {
|
||||
const data = await cached("info", 3000, async () => {
|
||||
const [cpu, disk, processes] = await Promise.all([
|
||||
getCpuUsage(), getDisk(), getProcesses(),
|
||||
]);
|
||||
return {
|
||||
cpu: {
|
||||
usage: cpu.total,
|
||||
cores: cpu.cores,
|
||||
temp: getCpuTemp(),
|
||||
count: os.cpus().length,
|
||||
model: os.cpus()[0]?.model?.trim() || "—",
|
||||
},
|
||||
ram: getRam(),
|
||||
disk,
|
||||
load: getLoad(),
|
||||
processes,
|
||||
uptime: getUptime(),
|
||||
os: `${os.type()} ${os.release()}`,
|
||||
};
|
||||
});
|
||||
res.json(data);
|
||||
} catch (e) { res.status(500).json({ error: e.message }); }
|
||||
});
|
||||
// ── Handlers (caché por ruta: el trabajo se hace 1 vez por ventana) ────────
|
||||
const routes = {
|
||||
|
||||
app.get("/system/bitcoin", async (req, res) => {
|
||||
try {
|
||||
"/system/info": async () => cached("info", 3000, async () => {
|
||||
const [cpu, disk, processes] = await Promise.all([
|
||||
getCpuUsage(), getDisk(), getProcesses(),
|
||||
]);
|
||||
return {
|
||||
cpu: {
|
||||
usage: cpu.total,
|
||||
cores: cpu.cores,
|
||||
temp: getCpuTemp(),
|
||||
count: os.cpus().length,
|
||||
model: os.cpus()[0]?.model?.trim() || "—",
|
||||
},
|
||||
ram: getRam(),
|
||||
disk,
|
||||
load: getLoad(),
|
||||
processes,
|
||||
uptime: getUptime(),
|
||||
os: `${os.type()} ${os.release()}`,
|
||||
};
|
||||
}),
|
||||
|
||||
"/system/bitcoin": async () => {
|
||||
const info = await cached("bitcoin", 5000, getBitcoinInfo);
|
||||
if (!info) return res.status(503).json({ error: "No se pudo conectar a Bitcoin Core" });
|
||||
res.json(info);
|
||||
} catch (e) { res.status(500).json({ error: e.message }); }
|
||||
});
|
||||
if (!info) return { __status: 503, error: "No se pudo conectar a Bitcoin Core" };
|
||||
return info;
|
||||
},
|
||||
|
||||
app.get("/system/logs/bitcoin", async (req, res) => {
|
||||
try {
|
||||
const lines = parseInt(req.query.lines) || 50;
|
||||
"/system/logs/bitcoin": async (query) => {
|
||||
const lines = parseInt(query.get("lines")) || 50;
|
||||
const logs = await cached(`logs-btc-${lines}`, 10000, () => getLogs("bitcoind", lines));
|
||||
res.json({ logs });
|
||||
} catch (e) { res.status(500).json({ error: e.message }); }
|
||||
});
|
||||
return { logs };
|
||||
},
|
||||
|
||||
app.get("/system/logs/fulcrum", async (req, res) => {
|
||||
try {
|
||||
const lines = parseInt(req.query.lines) || 50;
|
||||
"/system/logs/fulcrum": async (query) => {
|
||||
const lines = parseInt(query.get("lines")) || 50;
|
||||
const logs = await cached(`logs-ful-${lines}`, 10000, () => getLogs("fulcrum", lines));
|
||||
res.json({ logs });
|
||||
} catch (e) { res.status(500).json({ error: e.message }); }
|
||||
});
|
||||
return { logs };
|
||||
},
|
||||
|
||||
app.get("/system/health", (req, res) => {
|
||||
res.json({ status: "ok", timestamp: Date.now() });
|
||||
"/system/health": async () => ({ status: "ok", timestamp: Date.now() }),
|
||||
};
|
||||
|
||||
// ── Servidor HTTP nativo (sin dependencias) ─────────────────────────────────
|
||||
const server = http.createServer(async (req, res) => {
|
||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
res.setHeader("Access-Control-Allow-Methods", "GET");
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
|
||||
const url = new URL(req.url, `http://${req.headers.host || "localhost"}`);
|
||||
const handler = routes[url.pathname];
|
||||
|
||||
if (!handler) {
|
||||
res.writeHead(404);
|
||||
return res.end(JSON.stringify({ error: "Not found" }));
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await handler(url.searchParams);
|
||||
if (data && data.__status) {
|
||||
const { __status, ...body } = data;
|
||||
res.writeHead(__status);
|
||||
return res.end(JSON.stringify(body));
|
||||
}
|
||||
res.writeHead(200);
|
||||
res.end(JSON.stringify(data));
|
||||
} catch (e) {
|
||||
res.writeHead(500);
|
||||
res.end(JSON.stringify({ error: e.message || "Internal error" }));
|
||||
}
|
||||
});
|
||||
|
||||
// ── Start ───────────────────────────────────────────────────────────────────
|
||||
app.listen(PORT, HOST, () => {
|
||||
server.listen(PORT, HOST, () => {
|
||||
console.log(`
|
||||
₿ Txoko System Metrics v2
|
||||
─────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user