Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement: resources network widget #4327

Merged
merged 13 commits into from
Nov 25, 2024
3 changes: 2 additions & 1 deletion docs/installation/k8s.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ data:
expanded: true
cpu: true
memory: true
network: default
- search:
provider: duckduckgo
target: _blank
Expand Down Expand Up @@ -370,7 +371,7 @@ prevent unnecessary re-renders on page loads and window / tab focusing. The
procedure for enabling sticky sessions depends on your Ingress controller. Below
is an example using Traefik as the Ingress controller.

```
```yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
Expand Down
3 changes: 2 additions & 1 deletion docs/widgets/info/resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ _Note: unfortunately, the package used for getting CPU temp ([systeminformation]
tempmin: 0 # optional, minimum cpu temp
tempmax: 100 # optional, maximum cpu temp
uptime: true
units: imperial # only used by cpu temp
units: imperial # only used by cpu temp, options: 'imperial' or 'metric'
refresh: 3000 # optional, in ms
diskUnits: bytes # optional, bytes (default) or bbytes. Only applies to disk
network: true # uses 'default' or specify a network interface name
```

You can also pass a `label` option, which allows you to group resources under named sections,
Expand Down
71 changes: 71 additions & 0 deletions src/components/widgets/resources/network.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import useSWR from "swr";
import { FaNetworkWired, FaAngleUp, FaAngleDown } from "react-icons/fa";
import { useTranslation } from "next-i18next";

import Resource from "../widget/resource";
import Error from "../widget/error";

export default function Network({ options, refresh = 1500 }) {
const { t } = useTranslation();
if (options.network === true) options.network = "default";

const { data, error } = useSWR(`/api/widgets/resources?type=network&interfaceName=${options.network}`, {
refreshInterval: refresh,
});

if (error || data?.error) {
return <Error />;
}

if (!data) {
return (
<Resource
icon={FaNetworkWired}
value="-"
label={<FaAngleUp />}
expandedValue="-"
expandedLabel={<FaAngleDown />}
percentage="0"
expanded="true"
/>
);
}

return (
<>
<Resource
icon={FaNetworkWired}
value={t("common.bits", { value: data?.network?.tx_sec })}
label={<FaAngleUp />}
expandedValue={t("common.bits", { value: data?.network?.rx_sec })}
expandedLabel={<FaAngleDown />}
percentage="0"
expanded="true"
iconChildren={
<span class="bg-theme-100 text-theme-800 text-xs font-medium px-1 py-1 mt-1 rounded dark:bg-theme-700 dark:text-theme-300 text-center">
{data.interface}
</span>
}
>
<div className="pt-1 text-theme-800 dark:text-theme-200 text-xs text-center">{t("pyload.speed")}</div>
</Resource>

<Resource
icon={FaNetworkWired}
value={t("common.bbytes", { value: data?.network?.tx_bytes })}
label={<FaAngleUp />}
expandedValue={t("common.bbytes", { value: data?.network?.rx_bytes })}
expandedLabel={<FaAngleDown />}
percentage="0"
expanded="true"
iconChildren={
<span class="bg-theme-100 text-theme-800 text-xs font-medium px-1 py-1 mt-1 rounded dark:bg-theme-700 dark:text-theme-300 text-center">
{data.interface}
</span>
}
>
<div className="pt-1 text-theme-800 dark:text-theme-200 text-xs text-center">{t("pyload.total")}</div>
</Resource>
</>
);
}
2 changes: 2 additions & 0 deletions src/components/widgets/resources/resources.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Cpu from "./cpu";
import Memory from "./memory";
import CpuTemp from "./cputemp";
import Uptime from "./uptime";
import Network from "./network";

export default function Resources({ options }) {
const { expanded, units, diskUnits, tempmin, tempmax } = options;
Expand All @@ -23,6 +24,7 @@ export default function Resources({ options }) {
<Disk key={disk} options={{ disk }} expanded={expanded} diskUnits={diskUnits} refresh={refresh} />
))
: options.disk && <Disk options={options} expanded={expanded} diskUnits={diskUnits} refresh={refresh} />}
{options.network && <Network options={options} refresh={refresh} />}
{options.cputemp && (
<CpuTemp expanded={expanded} units={units} refresh={refresh} tempmin={tempmin} tempmax={tempmax} />
)}
Expand Down
6 changes: 5 additions & 1 deletion src/components/widgets/widget/resource.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import UsageBar from "../resources/usage-bar";
export default function Resource({
children,
icon,
iconChildren,
value,
label,
expandedValue = "",
Expand All @@ -17,7 +18,10 @@ export default function Resource({
<div
className={`flex-none flex flex-row items-center mr-3 py-1.5 information-widget-resource ${additionalClassNames}`}
>
<Icon className="text-theme-800 dark:text-theme-200 w-5 h-5 resource-icon" />
<div className="flex flex-col items-center">
<Icon className="text-theme-800 dark:text-theme-200 w-5 h-5 resource-icon" />
{iconChildren}
</div>
<div className={`flex flex-col ml-3 text-left min-w-[85px] ${expanded ? " expanded" : ""}`}>
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5">{value}</div>
Expand Down
28 changes: 27 additions & 1 deletion src/pages/api/widgets/resources.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const logger = createLogger("resources");
const si = require("systeminformation");

export default async function handler(req, res) {
const { type, target } = req.query;
const { type, target, interfaceName = "default" } = req.query;

if (type === "cpu") {
const load = await si.currentLoad();
Expand Down Expand Up @@ -57,6 +57,32 @@ export default async function handler(req, res) {
});
}

if (type === "network") {
let networkData = await si.networkStats();
let interfaceDefault;
logger.debug("networkData:", JSON.stringify(networkData));
if (interfaceName && interfaceName !== "default") {
networkData = networkData.filter((network) => network.iface === interfaceName)["0"];
if (!networkData) {
return res.status(404).json({
error: "Interface not found",
});
}
} else {
interfaceDefault = await si.networkInterfaceDefault();
networkData = networkData.filter((network) => network.iface === interfaceDefault)["0"];
if (!networkData) {
return res.status(404).json({
error: "Interface not found! Please specify a valid interface name.",
});
}
}
return res.status(200).json({
network: networkData,
interface: interfaceName !== "default" ? interfaceName : interfaceDefault,
});
}

return res.status(400).json({
error: "invalid type",
});
Expand Down