mirror of
https://github.com/karl0ss/homepage.git
synced 2025-05-02 05:23:39 +01:00
Merge pull request #1084 from benphelps/feature-updated-resources
Feature: updated resources widget with cpu temp + uptime
This commit is contained in:
commit
c649c4ef17
@ -23,7 +23,7 @@
|
|||||||
"minecraft-ping-js": "^1.0.2",
|
"minecraft-ping-js": "^1.0.2",
|
||||||
"next": "^12.3.1",
|
"next": "^12.3.1",
|
||||||
"next-i18next": "^12.0.1",
|
"next-i18next": "^12.0.1",
|
||||||
"node-os-utils": "^1.3.7",
|
"osx-temperature-sensor": "^1.0.8",
|
||||||
"pretty-bytes": "^6.0.0",
|
"pretty-bytes": "^6.0.0",
|
||||||
"raw-body": "^2.5.1",
|
"raw-body": "^2.5.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
@ -32,6 +32,7 @@
|
|||||||
"react-icons": "^4.4.0",
|
"react-icons": "^4.4.0",
|
||||||
"shvl": "^3.0.0",
|
"shvl": "^3.0.0",
|
||||||
"swr": "^1.3.0",
|
"swr": "^1.3.0",
|
||||||
|
"systeminformation": "^5.17.12",
|
||||||
"tough-cookie": "^4.1.2",
|
"tough-cookie": "^4.1.2",
|
||||||
"winston": "^3.8.2",
|
"winston": "^3.8.2",
|
||||||
"xml-js": "^1.6.11"
|
"xml-js": "^1.6.11"
|
||||||
|
24
pnpm-lock.yaml
generated
24
pnpm-lock.yaml
generated
@ -25,7 +25,7 @@ specifiers:
|
|||||||
minecraft-ping-js: ^1.0.2
|
minecraft-ping-js: ^1.0.2
|
||||||
next: ^12.3.1
|
next: ^12.3.1
|
||||||
next-i18next: ^12.0.1
|
next-i18next: ^12.0.1
|
||||||
node-os-utils: ^1.3.7
|
osx-temperature-sensor: ^1.0.8
|
||||||
postcss: ^8.4.16
|
postcss: ^8.4.16
|
||||||
prettier: ^2.7.1
|
prettier: ^2.7.1
|
||||||
pretty-bytes: ^6.0.0
|
pretty-bytes: ^6.0.0
|
||||||
@ -36,6 +36,7 @@ specifiers:
|
|||||||
react-icons: ^4.4.0
|
react-icons: ^4.4.0
|
||||||
shvl: ^3.0.0
|
shvl: ^3.0.0
|
||||||
swr: ^1.3.0
|
swr: ^1.3.0
|
||||||
|
systeminformation: ^5.17.12
|
||||||
tailwind-scrollbar: ^2.0.1
|
tailwind-scrollbar: ^2.0.1
|
||||||
tailwindcss: ^3.1.8
|
tailwindcss: ^3.1.8
|
||||||
tough-cookie: ^4.1.2
|
tough-cookie: ^4.1.2
|
||||||
@ -57,7 +58,7 @@ dependencies:
|
|||||||
minecraft-ping-js: 1.0.2
|
minecraft-ping-js: 1.0.2
|
||||||
next: 12.3.1_biqbaboplfbrettd7655fr4n2y
|
next: 12.3.1_biqbaboplfbrettd7655fr4n2y
|
||||||
next-i18next: 12.0.1_azq6kxkn3od7qdylwkyksrwopy
|
next-i18next: 12.0.1_azq6kxkn3od7qdylwkyksrwopy
|
||||||
node-os-utils: 1.3.7
|
osx-temperature-sensor: 1.0.8
|
||||||
pretty-bytes: 6.0.0
|
pretty-bytes: 6.0.0
|
||||||
raw-body: 2.5.1
|
raw-body: 2.5.1
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
@ -66,6 +67,7 @@ dependencies:
|
|||||||
react-icons: 4.4.0_react@18.2.0
|
react-icons: 4.4.0_react@18.2.0
|
||||||
shvl: 3.0.0
|
shvl: 3.0.0
|
||||||
swr: 1.3.0_react@18.2.0
|
swr: 1.3.0_react@18.2.0
|
||||||
|
systeminformation: 5.17.12
|
||||||
tough-cookie: 4.1.2
|
tough-cookie: 4.1.2
|
||||||
winston: 3.8.2
|
winston: 3.8.2
|
||||||
xml-js: 1.6.11
|
xml-js: 1.6.11
|
||||||
@ -2301,10 +2303,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
|
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/node-os-utils/1.3.7:
|
|
||||||
resolution: {integrity: sha512-fvnX9tZbR7WfCG5BAy3yO/nCLyjVWD6MghEq0z5FDfN+ZXpLWNITBdbifxQkQ25ebr16G0N7eRWJisOcMEHG3Q==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/node-releases/2.0.6:
|
/node-releases/2.0.6:
|
||||||
resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==}
|
resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -2446,6 +2444,13 @@ packages:
|
|||||||
word-wrap: 1.2.3
|
word-wrap: 1.2.3
|
||||||
dev: true
|
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:
|
/p-limit/3.1.0:
|
||||||
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@ -3063,6 +3068,13 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
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:
|
/tailwind-scrollbar/2.0.1_tailwindcss@3.1.8:
|
||||||
resolution: {integrity: sha512-OcR7qHBbux4k+k6bWqnEQFYFooLK/F4dhkBz6nvswIoaA9ancZ5h20e0tyV7ifSWLDCUBtpG+1NHRA8HMRH/wg==}
|
resolution: {integrity: sha512-OcR7qHBbux4k+k6bWqnEQFYFooLK/F4dhkBz6nvswIoaA9ancZ5h20e0tyV7ifSWLDCUBtpG+1NHRA8HMRH/wg==}
|
||||||
engines: {node: '>=12.13.0'}
|
engines: {node: '>=12.13.0'}
|
||||||
|
@ -36,7 +36,14 @@
|
|||||||
"total": "Total",
|
"total": "Total",
|
||||||
"free": "Free",
|
"free": "Free",
|
||||||
"used": "Used",
|
"used": "Used",
|
||||||
"load": "Load"
|
"load": "Load",
|
||||||
|
"temp": "TEMP",
|
||||||
|
"max": "Max",
|
||||||
|
"uptime": "UP",
|
||||||
|
"months": "mo",
|
||||||
|
"days": "d",
|
||||||
|
"hours": "h",
|
||||||
|
"minutes": "m"
|
||||||
},
|
},
|
||||||
"unifi": {
|
"unifi": {
|
||||||
"users": "Users",
|
"users": "Users",
|
||||||
@ -298,7 +305,11 @@
|
|||||||
"glances": {
|
"glances": {
|
||||||
"cpu": "CPU",
|
"cpu": "CPU",
|
||||||
"mem": "MEM",
|
"mem": "MEM",
|
||||||
"wait": "Please wait"
|
"wait": "Please wait",
|
||||||
|
"temp": "TEMP",
|
||||||
|
"uptime": "UP",
|
||||||
|
"days": "d",
|
||||||
|
"hours": "h"
|
||||||
},
|
},
|
||||||
"quicklaunch": {
|
"quicklaunch": {
|
||||||
"bookmark": "Bookmark",
|
"bookmark": "Bookmark",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { BiError } from "react-icons/bi";
|
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 { FiCpu } from "react-icons/fi";
|
||||||
import { useTranslation } from "next-i18next";
|
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 (
|
return (
|
||||||
<div className="flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap ml-4">
|
<div className="flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap ml-4">
|
||||||
<div className="flex flex-row self-center flex-wrap justify-between">
|
<div className="flex flex-row self-center flex-wrap justify-between">
|
||||||
@ -73,7 +76,7 @@ export default function Widget({ options }) {
|
|||||||
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||||
<div className="pl-0.5">
|
<div className="pl-0.5">
|
||||||
{t("common.number", {
|
{t("common.number", {
|
||||||
value: data.cpu,
|
value: data.quicklook.cpu,
|
||||||
style: "unit",
|
style: "unit",
|
||||||
unit: "percent",
|
unit: "percent",
|
||||||
maximumFractionDigits: 0,
|
maximumFractionDigits: 0,
|
||||||
@ -81,7 +84,7 @@ export default function Widget({ options }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="pr-1">{t("glances.cpu")}</div>
|
<div className="pr-1">{t("glances.cpu")}</div>
|
||||||
</div>
|
</div>
|
||||||
<UsageBar percent={data.cpu} />
|
<UsageBar percent={data.quicklook.cpu} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
|
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
|
||||||
@ -90,7 +93,7 @@ export default function Widget({ options }) {
|
|||||||
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||||
<div className="pl-0.5">
|
<div className="pl-0.5">
|
||||||
{t("common.number", {
|
{t("common.number", {
|
||||||
value: data.mem,
|
value: data.quicklook.mem,
|
||||||
style: "unit",
|
style: "unit",
|
||||||
unit: "percent",
|
unit: "percent",
|
||||||
maximumFractionDigits: 0,
|
maximumFractionDigits: 0,
|
||||||
@ -98,9 +101,38 @@ export default function Widget({ options }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="pr-1">{t("glances.mem")}</div>
|
<div className="pr-1">{t("glances.mem")}</div>
|
||||||
</div>
|
</div>
|
||||||
<UsageBar percent={data.mem} />
|
<UsageBar percent={data.quicklook.mem} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{options.cputemp &&
|
||||||
|
(<div className="flex-none flex flex-row items-center mr-3 py-1.5">
|
||||||
|
<FaThermometerHalf className="text-theme-800 dark:text-theme-200 w-5 h-5" />
|
||||||
|
<div className="flex flex-col ml-3 text-left min-w-[85px]">
|
||||||
|
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||||
|
<div className="pl-0.5">
|
||||||
|
{t("common.number", {
|
||||||
|
value: mainTemp,
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
style: "unit",
|
||||||
|
unit
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="pr-1">{t("glances.temp")}</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>)}
|
||||||
|
{options.uptime && data.uptime &&
|
||||||
|
(<div className="flex-none flex flex-row items-center mr-3 py-1.5">
|
||||||
|
<FaRegClock className="text-theme-800 dark:text-theme-200 w-5 h-5" />
|
||||||
|
<div className="flex flex-col ml-3 text-left min-w-[85px]">
|
||||||
|
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||||
|
<div className="pl-0.5">
|
||||||
|
{data.uptime.replace(" days,", t("glances.days")).replace(/:\d\d:\d\d$/g, t("glances.hours"))}
|
||||||
|
</div>
|
||||||
|
<div className="pr-1">{t("glances.uptime")}</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>)}
|
||||||
</div>
|
</div>
|
||||||
{options.label && (
|
{options.label && (
|
||||||
<div className="pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{options.label}</div>
|
<div className="pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{options.label}</div>
|
||||||
|
79
src/components/widgets/resources/cputemp.jsx
Normal file
79
src/components/widgets/resources/cputemp.jsx
Normal file
@ -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 (
|
||||||
|
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
|
||||||
|
<BiError className="text-theme-800 dark:text-theme-200 w-5 h-5" />
|
||||||
|
<div className="flex flex-col ml-3 text-left">
|
||||||
|
<span className="text-theme-800 dark:text-theme-200 text-xs">{t("widget.api_error")}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return (
|
||||||
|
<div className="flex-none flex flex-row items-center mr-3 py-1.5 animate-pulse">
|
||||||
|
<FaThermometerHalf className="text-theme-800 dark:text-theme-200 w-5 h-5" />
|
||||||
|
<div className="flex flex-col ml-3 text-left min-w-[85px]">
|
||||||
|
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||||
|
<div className="pl-0.5">-</div>
|
||||||
|
<div className="pr-1">{t("resources.temp")}</div>
|
||||||
|
</span>
|
||||||
|
{expanded && (
|
||||||
|
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||||
|
<div className="pl-0.5">-</div>
|
||||||
|
<div className="pr-1">{t("resources.max")}</div>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
|
||||||
|
<FaThermometerHalf className="text-theme-800 dark:text-theme-200 w-5 h-5" />
|
||||||
|
<div className="flex flex-col ml-3 text-left min-w-[85px]">
|
||||||
|
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||||
|
<div className="pl-0.5">
|
||||||
|
{t("common.number", {
|
||||||
|
value: mainTemp,
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
style: "unit",
|
||||||
|
unit
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="pr-1">{t("resources.temp")}</div>
|
||||||
|
</span>
|
||||||
|
{expanded && (
|
||||||
|
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||||
|
<div className="pl-0.5">
|
||||||
|
{t("common.number", {
|
||||||
|
value: maxTemp,
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
style: "unit",
|
||||||
|
unit
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="pr-1">{t("resources.max")}</div>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -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 (
|
return (
|
||||||
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
|
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
|
||||||
<FiHardDrive className="text-theme-800 dark:text-theme-200 w-5 h-5" />
|
<FiHardDrive className="text-theme-800 dark:text-theme-200 w-5 h-5" />
|
||||||
<div className="flex flex-col ml-3 text-left min-w-[85px]">
|
<div className="flex flex-col ml-3 text-left min-w-[85px]">
|
||||||
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||||
<div className="pl-0.5 pr-1">{t("common.bytes", { value: data.drive.freeGb * 1024 * 1024 * 1024 })}</div>
|
<div className="pl-0.5 pr-1">{t("common.bytes", { value: data.drive.available })}</div>
|
||||||
<div className="pr-1">{t("resources.free")}</div>
|
<div className="pr-1">{t("resources.free")}</div>
|
||||||
</span>
|
</span>
|
||||||
{expanded && (
|
{expanded && (
|
||||||
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||||
<div className="pl-0.5 pr-1">{t("common.bytes", { value: data.drive.totalGb * 1024 * 1024 * 1024 })}</div>
|
<div className="pl-0.5 pr-1">{t("common.bytes", { value: data.drive.size })}</div>
|
||||||
<div className="pr-1">{t("resources.total")}</div>
|
<div className="pr-1">{t("resources.total")}</div>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
@ -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 (
|
return (
|
||||||
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
|
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
|
||||||
@ -52,7 +52,7 @@ export default function Memory({ expanded }) {
|
|||||||
<div className="flex flex-col ml-3 text-left min-w-[85px]">
|
<div className="flex flex-col ml-3 text-left min-w-[85px]">
|
||||||
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||||
<div className="pl-0.5 pr-1">
|
<div className="pl-0.5 pr-1">
|
||||||
{t("common.bytes", { value: data.memory.freeMemMb * 1024 * 1024, maximumFractionDigits: 1, binary: true })}
|
{t("common.bytes", { value: data.memory.free, maximumFractionDigits: 1, binary: true })}
|
||||||
</div>
|
</div>
|
||||||
<div className="pr-1">{t("resources.free")}</div>
|
<div className="pr-1">{t("resources.free")}</div>
|
||||||
</span>
|
</span>
|
||||||
@ -60,7 +60,7 @@ export default function Memory({ expanded }) {
|
|||||||
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||||
<div className="pl-0.5 pr-1">
|
<div className="pl-0.5 pr-1">
|
||||||
{t("common.bytes", {
|
{t("common.bytes", {
|
||||||
value: data.memory.totalMemMb * 1024 * 1024,
|
value: data.memory.total,
|
||||||
maximumFractionDigits: 1,
|
maximumFractionDigits: 1,
|
||||||
binary: true,
|
binary: true,
|
||||||
})}
|
})}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import Disk from "./disk";
|
import Disk from "./disk";
|
||||||
import Cpu from "./cpu";
|
import Cpu from "./cpu";
|
||||||
import Memory from "./memory";
|
import Memory from "./memory";
|
||||||
|
import CpuTemp from "./cputemp";
|
||||||
|
import Uptime from "./uptime";
|
||||||
|
|
||||||
export default function Resources({ options }) {
|
export default function Resources({ options }) {
|
||||||
const { expanded } = options;
|
const { expanded, units } = options;
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap">
|
<div className="flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap">
|
||||||
<div className="flex flex-row self-center flex-wrap justify-between">
|
<div className="flex flex-row self-center flex-wrap justify-between">
|
||||||
@ -12,6 +14,8 @@ export default function Resources({ options }) {
|
|||||||
{Array.isArray(options.disk)
|
{Array.isArray(options.disk)
|
||||||
? options.disk.map((disk) => <Disk key={disk} options={{ disk }} expanded={expanded} />)
|
? options.disk.map((disk) => <Disk key={disk} options={{ disk }} expanded={expanded} />)
|
||||||
: options.disk && <Disk options={options} expanded={expanded} />}
|
: options.disk && <Disk options={options} expanded={expanded} />}
|
||||||
|
{options.cputemp && <CpuTemp expanded={expanded} units={units} />}
|
||||||
|
{options.uptime && <Uptime />}
|
||||||
</div>
|
</div>
|
||||||
{options.label && (
|
{options.label && (
|
||||||
<div className="ml-6 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{options.label}</div>
|
<div className="ml-6 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{options.label}</div>
|
||||||
|
61
src/components/widgets/resources/uptime.jsx
Normal file
61
src/components/widgets/resources/uptime.jsx
Normal file
@ -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 (
|
||||||
|
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
|
||||||
|
<BiError className="text-theme-800 dark:text-theme-200 w-5 h-5" />
|
||||||
|
<div className="flex flex-col ml-3 text-left">
|
||||||
|
<span className="text-theme-800 dark:text-theme-200 text-xs">{t("widget.api_error")}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return (
|
||||||
|
<div className="flex-none flex flex-row items-center mr-3 py-1.5 animate-pulse">
|
||||||
|
<FaRegClock className="text-theme-800 dark:text-theme-200 w-5 h-5" />
|
||||||
|
<div className="flex flex-col ml-3 text-left min-w-[85px]">
|
||||||
|
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||||
|
<div className="pl-0.5">-</div>
|
||||||
|
<div className="pr-1">{t("resources.temp")}</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
|
||||||
|
<FaRegClock className="text-theme-800 dark:text-theme-200 w-5 h-5" />
|
||||||
|
<div className="flex flex-col ml-3 text-left min-w-[85px]">
|
||||||
|
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||||
|
<div className="pl-0.5">
|
||||||
|
{uptime}
|
||||||
|
</div>
|
||||||
|
<div className="pr-1">{t("resources.uptime")}</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -4,19 +4,16 @@ import { getPrivateWidgetOptions } from "utils/config/widget-helpers";
|
|||||||
|
|
||||||
const logger = createLogger("glances");
|
const logger = createLogger("glances");
|
||||||
|
|
||||||
export default async function handler(req, res) {
|
async function retrieveFromGlancesAPI(privateWidgetOptions, endpoint) {
|
||||||
const { index } = req.query;
|
let errorMessage;
|
||||||
|
|
||||||
const privateWidgetOptions = await getPrivateWidgetOptions("glances", index);
|
|
||||||
|
|
||||||
const url = privateWidgetOptions?.url;
|
const url = privateWidgetOptions?.url;
|
||||||
if (!url) {
|
if (!url) {
|
||||||
const errorMessage = "Missing Glances URL";
|
errorMessage = "Missing Glances URL";
|
||||||
logger.error(errorMessage);
|
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 = {
|
const headers = {
|
||||||
"Accept-Encoding": "application/json"
|
"Accept-Encoding": "application/json"
|
||||||
};
|
};
|
||||||
@ -25,16 +22,41 @@ export default async function handler(req, res) {
|
|||||||
}
|
}
|
||||||
const params = { method: "GET", headers };
|
const params = { method: "GET", headers };
|
||||||
|
|
||||||
const [status, contentType, data] = await httpProxy(apiUrl, params);
|
const [status, , data] = await httpProxy(apiUrl, params);
|
||||||
|
|
||||||
if (status === 401) {
|
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) {
|
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 JSON.parse(Buffer.from(data).toString());
|
||||||
return res.status(status).send(data);
|
}
|
||||||
|
|
||||||
|
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 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import { existsSync } from "fs";
|
import { existsSync } from "fs";
|
||||||
|
|
||||||
import { cpu, drive, mem } from "node-os-utils";
|
const si = require('systeminformation');
|
||||||
|
|
||||||
export default async function handler(req, res) {
|
export default async function handler(req, res) {
|
||||||
const { type, target } = req.query;
|
const { type, target } = req.query;
|
||||||
|
|
||||||
if (type === "cpu") {
|
if (type === "cpu") {
|
||||||
|
const load = await si.currentLoad();
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
cpu: {
|
cpu: {
|
||||||
usage: await cpu.usage(1000),
|
usage: load.currentLoad,
|
||||||
load: cpu.loadavgTime(5),
|
load: load.avgLoad,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -21,14 +22,29 @@ export default async function handler(req, res) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fsSize = await si.fsSize();
|
||||||
|
|
||||||
return res.status(200).json({
|
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") {
|
if (type === "memory") {
|
||||||
return res.status(200).json({
|
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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user