diff --git a/package.json b/package.json
index bbe3cbf0..ee6e7af6 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,7 @@
"minecraft-ping-js": "^1.0.2",
"next": "^12.3.1",
"next-i18next": "^12.0.1",
- "node-os-utils": "^1.3.7",
+ "osx-temperature-sensor": "^1.0.8",
"pretty-bytes": "^6.0.0",
"raw-body": "^2.5.1",
"react": "^18.2.0",
@@ -32,6 +32,7 @@
"react-icons": "^4.4.0",
"shvl": "^3.0.0",
"swr": "^1.3.0",
+ "systeminformation": "^5.17.12",
"tough-cookie": "^4.1.2",
"winston": "^3.8.2",
"xml-js": "^1.6.11"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a3ce3fcd..fabcb86a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -25,7 +25,7 @@ specifiers:
minecraft-ping-js: ^1.0.2
next: ^12.3.1
next-i18next: ^12.0.1
- node-os-utils: ^1.3.7
+ osx-temperature-sensor: ^1.0.8
postcss: ^8.4.16
prettier: ^2.7.1
pretty-bytes: ^6.0.0
@@ -36,6 +36,7 @@ specifiers:
react-icons: ^4.4.0
shvl: ^3.0.0
swr: ^1.3.0
+ systeminformation: ^5.17.12
tailwind-scrollbar: ^2.0.1
tailwindcss: ^3.1.8
tough-cookie: ^4.1.2
@@ -57,7 +58,7 @@ dependencies:
minecraft-ping-js: 1.0.2
next: 12.3.1_biqbaboplfbrettd7655fr4n2y
next-i18next: 12.0.1_azq6kxkn3od7qdylwkyksrwopy
- node-os-utils: 1.3.7
+ osx-temperature-sensor: 1.0.8
pretty-bytes: 6.0.0
raw-body: 2.5.1
react: 18.2.0
@@ -66,6 +67,7 @@ dependencies:
react-icons: 4.4.0_react@18.2.0
shvl: 3.0.0
swr: 1.3.0_react@18.2.0
+ systeminformation: 5.17.12
tough-cookie: 4.1.2
winston: 3.8.2
xml-js: 1.6.11
@@ -2301,10 +2303,6 @@ packages:
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
dev: false
- /node-os-utils/1.3.7:
- resolution: {integrity: sha512-fvnX9tZbR7WfCG5BAy3yO/nCLyjVWD6MghEq0z5FDfN+ZXpLWNITBdbifxQkQ25ebr16G0N7eRWJisOcMEHG3Q==}
- dev: false
-
/node-releases/2.0.6:
resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==}
dev: true
@@ -2446,6 +2444,13 @@ packages:
word-wrap: 1.2.3
dev: true
+ /osx-temperature-sensor/1.0.8:
+ resolution: {integrity: sha512-Gl3b+bn7+oDDnqPa+4v/cg3yg9lnE8ppS7ivL3opBZh4i7h99JNmkm6zWmo0m2a83UUJu+C9D7lGP0OS8IlehA==}
+ engines: {node: '>=4.0.0'}
+ os: [darwin]
+ requiresBuild: true
+ dev: false
+
/p-limit/3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
@@ -3063,6 +3068,13 @@ packages:
react: 18.2.0
dev: false
+ /systeminformation/5.17.12:
+ resolution: {integrity: sha512-I3pfMW2vue53u+X08BNxaJieaHkRoMMKjWetY9lbYJeWFaeWPO6P4FkNc4XOCX8F9vbQ0HqQ25RJoz3U/B7liw==}
+ engines: {node: '>=8.0.0'}
+ os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android]
+ hasBin: true
+ dev: false
+
/tailwind-scrollbar/2.0.1_tailwindcss@3.1.8:
resolution: {integrity: sha512-OcR7qHBbux4k+k6bWqnEQFYFooLK/F4dhkBz6nvswIoaA9ancZ5h20e0tyV7ifSWLDCUBtpG+1NHRA8HMRH/wg==}
engines: {node: '>=12.13.0'}
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 92f42926..3eb3c478 100755
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -36,7 +36,14 @@
"total": "Total",
"free": "Free",
"used": "Used",
- "load": "Load"
+ "load": "Load",
+ "temp": "TEMP",
+ "max": "Max",
+ "uptime": "UP",
+ "months": "mo",
+ "days": "d",
+ "hours": "h",
+ "minutes": "m"
},
"unifi": {
"users": "Users",
@@ -298,7 +305,11 @@
"glances": {
"cpu": "CPU",
"mem": "MEM",
- "wait": "Please wait"
+ "wait": "Please wait",
+ "temp": "TEMP",
+ "uptime": "UP",
+ "days": "d",
+ "hours": "h"
},
"quicklaunch": {
"bookmark": "Bookmark",
diff --git a/src/components/widgets/glances/glances.jsx b/src/components/widgets/glances/glances.jsx
index a48cef50..d91cb263 100644
--- a/src/components/widgets/glances/glances.jsx
+++ b/src/components/widgets/glances/glances.jsx
@@ -1,6 +1,6 @@
import useSWR from "swr";
import { BiError } from "react-icons/bi";
-import { FaMemory } from "react-icons/fa";
+import { FaMemory, FaRegClock, FaThermometerHalf } from "react-icons/fa";
import { FiCpu } from "react-icons/fi";
import { useTranslation } from "next-i18next";
@@ -64,6 +64,9 @@ export default function Widget({ options }) {
);
}
+ const unit = options.units === "imperial" ? "fahrenheit" : "celsius";
+ const mainTemp = (options.cputemp && data.sensors && unit === "celsius") ? data.sensors.find(s => s.label.includes("cpu_thermal")).value : data.sensors.find(s => s.label.includes("cpu_thermal")).value * 5/9 + 32;
+
return (
@@ -73,7 +76,7 @@ export default function Widget({ options }) {
{t("common.number", {
- value: data.cpu,
+ value: data.quicklook.cpu,
style: "unit",
unit: "percent",
maximumFractionDigits: 0,
@@ -81,7 +84,7 @@ export default function Widget({ options }) {
{t("glances.cpu")}
-
+
@@ -90,7 +93,7 @@ export default function Widget({ options }) {
{t("common.number", {
- value: data.mem,
+ value: data.quicklook.mem,
style: "unit",
unit: "percent",
maximumFractionDigits: 0,
@@ -98,9 +101,38 @@ export default function Widget({ options }) {
{t("glances.mem")}
-
+
+ {options.cputemp &&
+ (
+
+
+
+
+ {t("common.number", {
+ value: mainTemp,
+ maximumFractionDigits: 1,
+ style: "unit",
+ unit
+ })}
+
+ {t("glances.temp")}
+
+
+
)}
+ {options.uptime && data.uptime &&
+ (
+
+
+
+
+ {data.uptime.replace(" days,", t("glances.days")).replace(/:\d\d:\d\d$/g, t("glances.hours"))}
+
+ {t("glances.uptime")}
+
+
+
)}
{options.label && (
{options.label}
diff --git a/src/components/widgets/resources/cputemp.jsx b/src/components/widgets/resources/cputemp.jsx
new file mode 100644
index 00000000..d5cb6100
--- /dev/null
+++ b/src/components/widgets/resources/cputemp.jsx
@@ -0,0 +1,79 @@
+import useSWR from "swr";
+import { FaThermometerHalf } from "react-icons/fa";
+import { BiError } from "react-icons/bi";
+import { useTranslation } from "next-i18next";
+
+export default function CpuTemp({ expanded, units }) {
+ const { t } = useTranslation();
+
+ const { data, error } = useSWR(`/api/widgets/resources?type=cputemp`, {
+ refreshInterval: 1500,
+ });
+
+ if (error || data?.error) {
+ return (
+
+
+
+ {t("widget.api_error")}
+
+
+ );
+ }
+
+ if (!data) {
+ return (
+
+
+
+
+ -
+ {t("resources.temp")}
+
+ {expanded && (
+
+ -
+ {t("resources.max")}
+
+ )}
+
+
+ );
+ }
+
+ const unit = units === "imperial" ? "fahrenheit" : "celsius";
+ const mainTemp = (unit === "celsius") ? data.cputemp.main : data.cputemp.main * 5/9 + 32;
+ const maxTemp = (unit === "celsius") ? data.cputemp.max : data.cputemp.max * 5/9 + 32;
+
+ return (
+
+
+
+
+
+ {t("common.number", {
+ value: mainTemp,
+ maximumFractionDigits: 1,
+ style: "unit",
+ unit
+ })}
+
+ {t("resources.temp")}
+
+ {expanded && (
+
+
+ {t("common.number", {
+ value: maxTemp,
+ maximumFractionDigits: 1,
+ style: "unit",
+ unit
+ })}
+
+ {t("resources.max")}
+
+ )}
+
+
+ );
+}
diff --git a/src/components/widgets/resources/disk.jsx b/src/components/widgets/resources/disk.jsx
index fb770dbb..dbc41b1c 100644
--- a/src/components/widgets/resources/disk.jsx
+++ b/src/components/widgets/resources/disk.jsx
@@ -44,19 +44,19 @@ export default function Disk({ options, expanded }) {
);
}
- const percent = Math.round((data.drive.usedGb / data.drive.totalGb) * 100);
+ const percent = Math.round((data.drive.used / data.drive.size) * 100);
return (
- {t("common.bytes", { value: data.drive.freeGb * 1024 * 1024 * 1024 })}
+ {t("common.bytes", { value: data.drive.available })}
{t("resources.free")}
{expanded && (
- {t("common.bytes", { value: data.drive.totalGb * 1024 * 1024 * 1024 })}
+ {t("common.bytes", { value: data.drive.size })}
{t("resources.total")}
)}
diff --git a/src/components/widgets/resources/memory.jsx b/src/components/widgets/resources/memory.jsx
index 27351998..2ee0a15e 100644
--- a/src/components/widgets/resources/memory.jsx
+++ b/src/components/widgets/resources/memory.jsx
@@ -44,7 +44,7 @@ export default function Memory({ expanded }) {
);
}
- const percent = Math.round((data.memory.usedMemMb / data.memory.totalMemMb) * 100);
+ const percent = Math.round((data.memory.used / data.memory.total) * 100);
return (
@@ -52,7 +52,7 @@ export default function Memory({ expanded }) {
- {t("common.bytes", { value: data.memory.freeMemMb * 1024 * 1024, maximumFractionDigits: 1, binary: true })}
+ {t("common.bytes", { value: data.memory.free, maximumFractionDigits: 1, binary: true })}
{t("resources.free")}
@@ -60,7 +60,7 @@ export default function Memory({ expanded }) {
{t("common.bytes", {
- value: data.memory.totalMemMb * 1024 * 1024,
+ value: data.memory.total,
maximumFractionDigits: 1,
binary: true,
})}
diff --git a/src/components/widgets/resources/resources.jsx b/src/components/widgets/resources/resources.jsx
index 0524e39a..4ff0c81c 100644
--- a/src/components/widgets/resources/resources.jsx
+++ b/src/components/widgets/resources/resources.jsx
@@ -1,9 +1,11 @@
import Disk from "./disk";
import Cpu from "./cpu";
import Memory from "./memory";
+import CpuTemp from "./cputemp";
+import Uptime from "./uptime";
export default function Resources({ options }) {
- const { expanded } = options;
+ const { expanded, units } = options;
return (
@@ -12,6 +14,8 @@ export default function Resources({ options }) {
{Array.isArray(options.disk)
? options.disk.map((disk) => )
: options.disk && }
+ {options.cputemp && }
+ {options.uptime && }
{options.label && (
{options.label}
diff --git a/src/components/widgets/resources/uptime.jsx b/src/components/widgets/resources/uptime.jsx
new file mode 100644
index 00000000..2624bef1
--- /dev/null
+++ b/src/components/widgets/resources/uptime.jsx
@@ -0,0 +1,61 @@
+import useSWR from "swr";
+import { FaRegClock } from "react-icons/fa";
+import { BiError } from "react-icons/bi";
+import { useTranslation } from "next-i18next";
+
+export default function Uptime() {
+ const { t } = useTranslation();
+
+ const { data, error } = useSWR(`/api/widgets/resources?type=uptime`, {
+ refreshInterval: 1500,
+ });
+
+ if (error || data?.error) {
+ return (
+
+
+
+ {t("widget.api_error")}
+
+
+ );
+ }
+
+ if (!data) {
+ return (
+
+
+
+
+ -
+ {t("resources.temp")}
+
+
+
+ );
+ }
+
+ const mo = Math.floor(data.uptime / (3600 * 24 * 31));
+ const d = Math.floor(data.uptime % (3600 * 24 * 31) / (3600 * 24));
+ const h = Math.floor(data.uptime % (3600 * 24) / 3600);
+ const m = Math.floor(data.uptime % 3600 / 60);
+
+ let uptime;
+ if (mo > 0) uptime = `${mo}${t("resources.months")} ${d}${t("resources.days")}`;
+ else if (d > 0) uptime = `${d}${t("resources.days")} ${h}${t("resources.hours")}`;
+ else uptime = `${h}${t("resources.hours")} ${m}${t("resources.minutes")}`;
+
+ return (
+
+
+
+
+
+ {uptime}
+
+ {t("resources.uptime")}
+
+
+
+ );
+}
diff --git a/src/pages/api/widgets/glances.js b/src/pages/api/widgets/glances.js
index 5d4622b8..46be14a0 100644
--- a/src/pages/api/widgets/glances.js
+++ b/src/pages/api/widgets/glances.js
@@ -4,19 +4,16 @@ import { getPrivateWidgetOptions } from "utils/config/widget-helpers";
const logger = createLogger("glances");
-export default async function handler(req, res) {
- const { index } = req.query;
-
- const privateWidgetOptions = await getPrivateWidgetOptions("glances", index);
-
+async function retrieveFromGlancesAPI(privateWidgetOptions, endpoint) {
+ let errorMessage;
const url = privateWidgetOptions?.url;
if (!url) {
- const errorMessage = "Missing Glances URL";
+ errorMessage = "Missing Glances URL";
logger.error(errorMessage);
- return res.status(400).json({ error: errorMessage });
+ throw new Error(errorMessage);
}
- const apiUrl = `${url}/api/3/quicklook`;
+ const apiUrl = `${url}/api/3/${endpoint}`;
const headers = {
"Accept-Encoding": "application/json"
};
@@ -25,16 +22,41 @@ export default async function handler(req, res) {
}
const params = { method: "GET", headers };
- const [status, contentType, data] = await httpProxy(apiUrl, params);
+ const [status, , data] = await httpProxy(apiUrl, params);
if (status === 401) {
- logger.error("Authorization failure getting data from glances API. Data: %s", data);
+ errorMessage = `Authorization failure getting data from glances API. Data: ${data.toString()}`
+ logger.error(errorMessage);
+ throw new Error(errorMessage);
}
-
+
if (status !== 200) {
- logger.error("HTTP %d getting data from glances API. Data: %s", status, data);
+ errorMessage = `HTTP ${status} getting data from glances API. Data: ${data.toString()}`
+ logger.error(errorMessage);
+ throw new Error(errorMessage);
}
- if (contentType) res.setHeader("Content-Type", contentType);
- return res.status(status).send(data);
+ return JSON.parse(Buffer.from(data).toString());
+}
+
+export default async function handler(req, res) {
+ const { index } = req.query;
+
+ const privateWidgetOptions = await getPrivateWidgetOptions("glances", index);
+
+ try {
+ const quicklookData = await retrieveFromGlancesAPI(privateWidgetOptions, "quicklook");
+
+ const data = {
+ quicklook: quicklookData
+ }
+
+ data.uptime = await retrieveFromGlancesAPI(privateWidgetOptions, "uptime");
+
+ data.sensors = await retrieveFromGlancesAPI(privateWidgetOptions, "sensors");
+
+ return res.status(200).send(data);
+ } catch (e) {
+ return res.status(400).json({ error: e.message });
+ }
}
diff --git a/src/pages/api/widgets/resources.js b/src/pages/api/widgets/resources.js
index baa507a4..ae53bf14 100644
--- a/src/pages/api/widgets/resources.js
+++ b/src/pages/api/widgets/resources.js
@@ -1,15 +1,16 @@
import { existsSync } from "fs";
-import { cpu, drive, mem } from "node-os-utils";
+const si = require('systeminformation');
export default async function handler(req, res) {
const { type, target } = req.query;
if (type === "cpu") {
+ const load = await si.currentLoad();
return res.status(200).json({
cpu: {
- usage: await cpu.usage(1000),
- load: cpu.loadavgTime(5),
+ usage: load.currentLoad,
+ load: load.avgLoad,
},
});
}
@@ -21,14 +22,29 @@ export default async function handler(req, res) {
});
}
+ const fsSize = await si.fsSize();
+
return res.status(200).json({
- drive: await drive.info(target || "/"),
+ drive: fsSize.find(fs => fs.mount === target) ?? fsSize.find(fs => fs.mount === "/")
});
}
if (type === "memory") {
return res.status(200).json({
- memory: await mem.info(),
+ memory: await si.mem(),
+ });
+ }
+
+ if (type === "cputemp") {
+ return res.status(200).json({
+ cputemp: await si.cpuTemperature(),
+ });
+ }
+
+ if (type === "uptime") {
+ const timeData = await si.time();
+ return res.status(200).json({
+ uptime: timeData.uptime
});
}