From 3cce7e40062751a7b1b6acdd30329b280788f1c4 Mon Sep 17 00:00:00 2001 From: Sparky Date: Wed, 4 Dec 2024 20:59:18 -0800 Subject: [PATCH] Adding support for RackNerd widget. --- docs/widgets/services/index.md | 1 + docs/widgets/services/racknerd.md | 22 +++++++ mkdocs.yml | 1 + public/locales/en/common.json | 8 +++ src/widgets/components.js | 1 + src/widgets/racknerd/component.jsx | 71 ++++++++++++++++++++ src/widgets/racknerd/proxy.js | 100 +++++++++++++++++++++++++++++ src/widgets/racknerd/widget.js | 16 +++++ src/widgets/widgets.js | 2 + 9 files changed, 222 insertions(+) create mode 100644 docs/widgets/services/racknerd.md create mode 100644 src/widgets/racknerd/component.jsx create mode 100644 src/widgets/racknerd/proxy.js create mode 100644 src/widgets/racknerd/widget.js diff --git a/docs/widgets/services/index.md b/docs/widgets/services/index.md index 894a31f6..9e2019d3 100644 --- a/docs/widgets/services/index.md +++ b/docs/widgets/services/index.md @@ -108,6 +108,7 @@ You can also find a list of all available service widgets in the sidebar navigat - [PyLoad](pyload.md) - [qBittorrent](qbittorrent.md) - [QNAP](qnap.md) +- [RackNerd](racknerd.md) - [Radarr](radarr.md) - [Readarr](readarr.md) - [ROMM](romm.md) diff --git a/docs/widgets/services/racknerd.md b/docs/widgets/services/racknerd.md new file mode 100644 index 00000000..772887c2 --- /dev/null +++ b/docs/widgets/services/racknerd.md @@ -0,0 +1,22 @@ +--- +title: RackNerd +description: RackNerd Widget Configuration +--- + +Learn more about [RackNerd](https://racknerd.com). + +Use key & hash. Information about the key & hash can be found under the [VPS](https://nerdvm.racknerd.com) control panel in the API section. + +Allowed fields: `["ipAddress", "hddtotal", "bandwidthfree", "bandwidthused"]`. + +Note `"memoryusage"` is deprecated as v1 of their API result will be always be 0. +Note `"status"` is not fully implemented. + +Note hard drive free/used/percentage isn't functioning in v1 of their API result. +```yaml +widget: + type: racknerd + url: https://nerdvm.racknerd.com + key: token + hash: token +``` diff --git a/mkdocs.yml b/mkdocs.yml index fa2188ad..55eb0f0b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -131,6 +131,7 @@ nav: - widgets/services/pyload.md - widgets/services/qbittorrent.md - widgets/services/qnap.md + - widgets/services/racknerd.md - widgets/services/radarr.md - widgets/services/readarr.md - widgets/services/romm.md diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 484f76b5..6a6a1556 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -716,6 +716,14 @@ "numfiles": "Files", "numshares": "Shared Items" }, + "racknerd": { + "ipAddress": "IP Address", + "memoryusage": "Memory Usage", + "hddtotal": "Total Space", + "bandwidthtotal": "Bandwidth Total", + "bandwidthused": "Bandwidth Used", + "bandwidthfree": "Bandwidth Free" + }, "kopia": { "status": "Status", "size": "Size", diff --git a/src/widgets/components.js b/src/widgets/components.js index 19f41d4a..bc5a8a46 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -104,6 +104,7 @@ const components = { pyload: dynamic(() => import("./pyload/component")), qbittorrent: dynamic(() => import("./qbittorrent/component")), qnap: dynamic(() => import("./qnap/component")), + racknerd: dynamic(() => import("./racknerd/component")), radarr: dynamic(() => import("./radarr/component")), readarr: dynamic(() => import("./readarr/component")), romm: dynamic(() => import("./romm/component")), diff --git a/src/widgets/racknerd/component.jsx b/src/widgets/racknerd/component.jsx new file mode 100644 index 00000000..6b945356 --- /dev/null +++ b/src/widgets/racknerd/component.jsx @@ -0,0 +1,71 @@ +import { useTranslation } from "next-i18next"; +import { useMemo } from "react"; + +import Container from "components/services/widget/container"; +import Block from "components/services/widget/block"; +import useWidgetAPI from "utils/proxy/use-widget-api"; + +export const racknerdDefaultFields = ["ipAddress", "hddtotal", "bandwidthusage"]; + +export default function Component({ service }) { + const { t } = useTranslation(); + + const { widget } = service; + const params = { + key: widget.key, + hash: widget.hash, + }; + const { data: racknerdData, error: racknerdError } = useWidgetAPI(widget, "serverinfo", {...params, action: 'info'}); + // Support for fields (harddriveusage, memoryusage, bandwidthusage) + const [showIpAddress, showMemoryUsage, showHardDriveUsage, showBandwidthUsed, showBandwidthFree] = useMemo(() => { + // Default values if fields is not set + if (!widget.fields) return [true, false, true, true, true]; + + const hasIpAddress = widget.fields?.includes("ipAddress") || false; + const hasMemoryUsage = widget.fields?.includes("memoryusage") || false; + const hasHardDriveUsage = widget.fields?.includes("hddtotal") || false; + const hasBandwidthUsed = widget.fields?.includes("bandwidthused") || false; + const hasBandwidthFree = widget.fields?.includes("bandwidthfree") || false; + return [hasIpAddress, hasMemoryUsage, hasHardDriveUsage, hasBandwidthUsed, hasBandwidthFree]; + }, [widget.fields]); + if (racknerdError) { + return ; + } + + if (!racknerdData) { + return ( + + {showIpAddress && } + {showMemoryUsage && } + {showHardDriveUsage && } + {showBandwidthUsed && } + {showBandwidthFree && } + + ); + } + const { racknerd: racknerdInfo } = racknerdData; + return ( + + {showIpAddress && ()} + {showMemoryUsage && ()} + {showHardDriveUsage && ()} + {showBandwidthUsed && ()} + {showBandwidthFree && ()} + + ); +} diff --git a/src/widgets/racknerd/proxy.js b/src/widgets/racknerd/proxy.js new file mode 100644 index 00000000..a7c3c718 --- /dev/null +++ b/src/widgets/racknerd/proxy.js @@ -0,0 +1,100 @@ +import { xml2json } from "xml-js"; + +import { racknerdDefaultFields } from "./component"; + +import { httpProxy } from "utils/proxy/http"; +import getServiceWidget from "utils/config/service-helpers"; +import createLogger from "utils/logger"; + +const logger = createLogger("racknerdProxyHandler"); + +async function requestEndpoint(apiBaseUrl, action, params) { + const request = { + method: "POST" + }; + let qs = ""; + Object.entries(params).forEach(([key, value]) => { + qs += `&${key}=${value}`; + }); + const apiUrl = `${apiBaseUrl}?action=${action}${qs}`; + const [status, , data] = await httpProxy(apiUrl, request); + if (status !== 200) { + logger.debug(`HTTP ${status} performing XMLRequest for ${action}`, data); + throw new Error(`Failed fetching '${action}'`); + } + const response = {}; + try { + const jsonData = JSON.parse(xml2json(`${data}`, {compact: true})); + const responseElements = jsonData?.root || {}; + Object.entries(responseElements).forEach(([responseKey, responseValue]) => { + /* eslint no-underscore-dangle: ["error", { "allow": ["_text"] }] */ + response[responseKey] = responseValue?._text || ""; + }); + } catch (e) { + logger.debug(`Failed parsing ${action} response:`, data); + throw new Error(`Failed parsing '${action}' response`); + } + + return response; +} + +export default async function racknerdProxyHandler(req, res) { + const { group, service, index } = req.query; + const serviceWidget = await getServiceWidget(group, service, index); + + if (!serviceWidget) { + res.status(500).json({ error: { message: "Service widget not found" } }); + return; + } + + if (!serviceWidget.url) { + res.status(500).json({ error: { message: "Service widget url not configured" } }); + return; + } + + const serviceWidgetUrl = new URL(serviceWidget.url); + const apiBaseUrl = `${serviceWidgetUrl.protocol}//${serviceWidgetUrl.hostname}/api/client/command.php`; + + if (!serviceWidget.fields?.length > 0) { + serviceWidget.fields = racknerdDefaultFields; + } + const requestStatus = ["status"].some((field) => serviceWidget.fields?.includes(field)); + const requestInfo = ["bandwidthused", "memoryusage", "hddtotal", "ipAddress"].some((field) => serviceWidget.fields?.includes(field)); + const params = { + bw: serviceWidget.fields?.includes('bandwidthused') || serviceWidget.fields?.includes('bandwidthfree'), + hdd: serviceWidget.fields?.includes('hddtotal'), + ipAddr: serviceWidget.fields?.includes('ipAddress'), + mem: serviceWidget.fields?.includes('memoryusage'), + key: serviceWidget.key, + hash: serviceWidget.hash, + }; + + await Promise.all([ + requestStatus ? requestEndpoint(apiBaseUrl, "status", params) : null, + requestInfo ? requestEndpoint(apiBaseUrl, "info", params) : null, + ]) + .then(([statusResponse, infoResponse]) => { + const memoryItems = infoResponse.mem?.split(','); + const hddItems = infoResponse.hdd?.split(','); + const bandwidthItems = infoResponse.bw?.split(','); + res.status(200).json({ + racknerd: { + ipAddress: infoResponse.ipaddress || undefined, + system: { + status: statusResponse ? statusResponse.statusmsg : undefined, + memoryused: memoryItems ? parseFloat(memoryItems[1], 10) : undefined, + hdd_total: hddItems ? parseFloat(hddItems[0], 10) : undefined, + bandwidth_total: bandwidthItems ? parseFloat(bandwidthItems[0], 10) : undefined, + bandwidth_used: bandwidthItems ? parseFloat(bandwidthItems[1], 10) : undefined, + bandwidth_free: bandwidthItems ? parseFloat(bandwidthItems[2], 10) : undefined, + mem_total: memoryItems ? parseFloat(memoryItems[0], 10) : undefined, + mem_free: memoryItems ? parseFloat(memoryItems[2], 10) : undefined, + mem_percent: memoryItems ? parseFloat(memoryItems[3], 10) : undefined, + } + } + }); + }) + .catch((error) => { + res.status(500).json({ error: { message: error.message } }); + }); +} diff --git a/src/widgets/racknerd/widget.js b/src/widgets/racknerd/widget.js new file mode 100644 index 00000000..76b5d6e1 --- /dev/null +++ b/src/widgets/racknerd/widget.js @@ -0,0 +1,16 @@ +import racknerdProxyHandler from "./proxy"; + +const widget = { + api: "{url}/{endpoint}", + proxyHandler: racknerdProxyHandler, + + mappings: { + serverinfo: { + endpoint: "api/client/command.php", + params: ["key", "hash"], + optionalParams: ["bw", "mem", "hdd", "ipaddr"], + }, + }, +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 9d4bb935..2744408d 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -96,6 +96,7 @@ import pterodactyl from "./pterodactyl/widget"; import pyload from "./pyload/widget"; import qbittorrent from "./qbittorrent/widget"; import qnap from "./qnap/widget"; +import racknerd from "./racknerd/widget"; import radarr from "./radarr/widget"; import readarr from "./readarr/widget"; import rutorrent from "./rutorrent/widget"; @@ -232,6 +233,7 @@ const widgets = { pyload, qbittorrent, qnap, + racknerd, radarr, readarr, romm,