feat: real-time system monitor in footer
This commit is contained in:
parent
34020bc562
commit
e48fef3154
4 changed files with 109 additions and 5 deletions
27
backend/package-lock.json
generated
27
backend/package-lock.json
generated
|
|
@ -34,6 +34,7 @@
|
|||
"proxy-agent": "^6.5.0",
|
||||
"signale": "1.4.0",
|
||||
"sqlite3": "^5.1.7",
|
||||
"systeminformation": "^5.31.3",
|
||||
"temp-write": "^6.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -5137,6 +5138,32 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/systeminformation": {
|
||||
"version": "5.31.3",
|
||||
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.3.tgz",
|
||||
"integrity": "sha512-vX0eeI7oGIr79NLiJRWnK8SyxDjyiNOEanaQnHRNyb5ep8QcpD8QMDvrukdrxV4pV4AKjwUDfaypXnWHMC/65A==",
|
||||
"license": "MIT",
|
||||
"os": [
|
||||
"darwin",
|
||||
"linux",
|
||||
"win32",
|
||||
"freebsd",
|
||||
"openbsd",
|
||||
"netbsd",
|
||||
"sunos",
|
||||
"android"
|
||||
],
|
||||
"bin": {
|
||||
"systeminformation": "lib/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "Buy me a coffee",
|
||||
"url": "https://www.buymeacoffee.com/systeminfo"
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
"proxy-agent": "^6.5.0",
|
||||
"signale": "1.4.0",
|
||||
"sqlite3": "^5.1.7",
|
||||
"systeminformation": "^5.31.3",
|
||||
"temp-write": "^6.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import si from "systeminformation";
|
||||
import express from "express";
|
||||
import internalReport from "../internal/report.js";
|
||||
import jwtdecode from "../lib/express/jwt-decode.js";
|
||||
|
|
@ -29,4 +30,36 @@ router
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /reports/system
|
||||
*/
|
||||
router
|
||||
.route("/system")
|
||||
.options((_, res) => {
|
||||
res.sendStatus(204);
|
||||
})
|
||||
.all(jwtdecode())
|
||||
.get(async (req, res, next) => {
|
||||
try {
|
||||
const [cpuTotal, memData, networkStats] = await Promise.all([
|
||||
si.currentLoad(),
|
||||
si.mem(),
|
||||
si.networkStats("*"),
|
||||
]);
|
||||
|
||||
// Grab eth0 or the first active interface
|
||||
const activeNet = networkStats.find(n => n.operstate === 'up' && n.iface !== 'lo') || networkStats[0] || {};
|
||||
|
||||
res.status(200).json({
|
||||
cpu: Math.round(cpuTotal.currentLoad),
|
||||
memory: Math.round((memData.active / memData.total) * 100),
|
||||
networkRx: (activeNet.rx_sec / 1024 / 1024 * 8).toFixed(2), // Mbps
|
||||
networkTx: (activeNet.tx_sec / 1024 / 1024 * 8).toFixed(2), // Mbps
|
||||
});
|
||||
} catch (err) {
|
||||
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
|
|||
|
|
@ -1,20 +1,63 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { IconCpu, IconServer, IconArrowsDownUp } from "@tabler/icons-react";
|
||||
import * as api from "../api/backend/base";
|
||||
|
||||
export function SiteFooter() {
|
||||
const [sysStats, setSysStats] = useState({
|
||||
cpu: 0,
|
||||
memory: 0,
|
||||
networkRx: "0.00",
|
||||
networkTx: "0.00"
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
|
||||
const fetchStats = async () => {
|
||||
try {
|
||||
const data = await api.get({ url: "/reports/system" });
|
||||
if (isMounted && data) {
|
||||
setSysStats(data);
|
||||
}
|
||||
} catch (err) {
|
||||
// Silently fail polling to prevent console flood
|
||||
}
|
||||
};
|
||||
|
||||
// Initial fetch
|
||||
fetchStats();
|
||||
|
||||
// Poll every 1 second
|
||||
const interval = setInterval(fetchStats, 1000);
|
||||
return () => {
|
||||
isMounted = false;
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<footer className="footer d-print-none py-3">
|
||||
<div className="container-xl">
|
||||
<div className="row text-center align-items-center flex-row-reverse">
|
||||
<div className="col-lg-auto ms-lg-auto">
|
||||
|
||||
<div className="col-lg-auto ms-lg-auto d-flex gap-3 align-items-center text-muted small">
|
||||
<div title="CPU Usage" className="d-flex align-items-center gap-1">
|
||||
<IconCpu size={16} />
|
||||
<span>{sysStats.cpu}%</span>
|
||||
</div>
|
||||
<div title="Memory Usage" className="d-flex align-items-center gap-1">
|
||||
<IconServer size={16} />
|
||||
<span>{sysStats.memory}%</span>
|
||||
</div>
|
||||
<div title="Network Bandwidth" className="d-flex align-items-center gap-1">
|
||||
<IconArrowsDownUp size={16} />
|
||||
<span>↓{sysStats.networkRx} ↑{sysStats.networkTx} Mbps</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12 col-lg-auto mt-3 mt-lg-0">
|
||||
<ul className="list-inline list-inline-dots mb-0">
|
||||
<li className="list-inline-item">
|
||||
© D3V.AC 2026{" "}
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue