diff --git a/docs/widgets/services/truenas.md b/docs/widgets/services/truenas.md
index 6d747ef17..243504905 100644
--- a/docs/widgets/services/truenas.md
+++ b/docs/widgets/services/truenas.md
@@ -9,6 +9,8 @@ Allowed fields: `["load", "uptime", "alerts"]`.
To create an API Key, follow [the official TrueNAS documentation](https://www.truenas.com/docs/scale/scaletutorials/toptoolbar/managingapikeys/).
+A detailed pool listing is disabled by default, but can be enabled with the `enablePools` option.
+
```yaml
widget:
type: truenas
@@ -16,4 +18,5 @@ widget:
username: user # not required if using api key
password: pass # not required if using api key
key: yourtruenasapikey # not required if using username / password
+ enablePools: true # optional, defaults to false
```
diff --git a/src/components/widgets/truenas/pool.jsx b/src/components/widgets/truenas/pool.jsx
new file mode 100644
index 000000000..c9954b300
--- /dev/null
+++ b/src/components/widgets/truenas/pool.jsx
@@ -0,0 +1,31 @@
+import classNames from "classnames";
+import prettyBytes from "pretty-bytes";
+
+export default function Pool({ name, free, allocated, healthy }) {
+ const total = free + allocated;
+ const usedPercent = Math.round((allocated / total) * 100);
+ const statusColor = healthy ? "bg-green-500" : "bg-yellow-500";
+
+ return (
+
+
+
+
+
+
+
+
+ {prettyBytes(allocated)} / {prettyBytes(total)}
+
+ ({usedPercent}%)
+
+
+ );
+}
diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js
index fb6757b6f..bb6afdbcd 100644
--- a/src/utils/config/service-helpers.js
+++ b/src/utils/config/service-helpers.js
@@ -441,6 +441,9 @@ export function cleanServiceGroups(groups) {
// sonarr, radarr
enableQueue,
+ // truenas
+ enablePools,
+
// unifi
site,
} = cleanedService.widget;
@@ -510,6 +513,9 @@ export function cleanServiceGroups(groups) {
if (["sonarr", "radarr"].includes(type)) {
if (enableQueue !== undefined) cleanedService.widget.enableQueue = JSON.parse(enableQueue);
}
+ if (type === "truenas") {
+ if (enablePools !== undefined) cleanedService.widget.enablePools = JSON.parse(enablePools);
+ }
if (["diskstation", "qnap"].includes(type)) {
if (volume) cleanedService.widget.volume = volume;
}
diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js
index 0795efd52..005846cf5 100644
--- a/src/utils/proxy/handlers/credentialed.js
+++ b/src/utils/proxy/handlers/credentialed.js
@@ -70,6 +70,7 @@ export default async function credentialedProxyHandler(req, res, map) {
withCredentials: true,
credentials: "include",
headers,
+ body: req.body,
});
let resultData = data;
diff --git a/src/widgets/truenas/component.jsx b/src/widgets/truenas/component.jsx
index c1fc5c53a..0bd9ddfc8 100644
--- a/src/widgets/truenas/component.jsx
+++ b/src/widgets/truenas/component.jsx
@@ -3,6 +3,7 @@ import { useTranslation } from "next-i18next";
import Container from "components/services/widget/container";
import Block from "components/services/widget/block";
import useWidgetAPI from "utils/proxy/use-widget-api";
+import Pool from "components/widgets/truenas/pool";
export default function Component({ service }) {
const { t } = useTranslation();
@@ -11,9 +12,10 @@ export default function Component({ service }) {
const { data: alertData, error: alertError } = useWidgetAPI(widget, "alerts");
const { data: statusData, error: statusError } = useWidgetAPI(widget, "status");
+ const { data: poolsData, error: poolsError } = useWidgetAPI(widget, "pools");
- if (alertError || statusError) {
- const finalError = alertError ?? statusError;
+ if (alertError || statusError || poolsError) {
+ const finalError = alertError ?? statusError ?? poolsError;
return ;
}
@@ -27,11 +29,19 @@ export default function Component({ service }) {
);
}
+ const enablePools = widget?.enablePools && Array.isArray(poolsData) && poolsData.length > 0;
+
return (
-
-
-
-
-
+ <>
+
+
+
+
+
+ {enablePools &&
+ poolsData.map((pool) => (
+
+ ))}
+ >
);
}
diff --git a/src/widgets/truenas/proxy.js b/src/widgets/truenas/proxy.js
new file mode 100644
index 000000000..6982f9727
--- /dev/null
+++ b/src/widgets/truenas/proxy.js
@@ -0,0 +1,29 @@
+import getServiceWidget from "utils/config/service-helpers";
+import createLogger from "utils/logger";
+import genericProxyHandler from "utils/proxy/handlers/generic";
+import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
+
+const logger = createLogger("truenasProxyHandler");
+
+export default async function truenasProxyHandler(req, res, map) {
+ const { group, service } = req.query;
+
+ if (group && service) {
+ const widgetOpts = await getServiceWidget(group, service);
+ let handler;
+ if (widgetOpts.username && widgetOpts.password) {
+ handler = genericProxyHandler;
+ } else if (widgetOpts.key) {
+ handler = credentialedProxyHandler;
+ }
+
+ if (handler) {
+ return handler(req, res, map);
+ }
+
+ logger.error("Error getting data from Truenas: Username / password or API key required");
+ return res.status(500).json({ error: "Username / password or API key required" });
+ }
+
+ return res.status(500).json({ error: "Error parsing widget request" });
+}
diff --git a/src/widgets/truenas/widget.js b/src/widgets/truenas/widget.js
index 6c0f3622c..1331d98be 100644
--- a/src/widgets/truenas/widget.js
+++ b/src/widgets/truenas/widget.js
@@ -1,32 +1,10 @@
-import { jsonArrayFilter } from "utils/proxy/api-helpers";
-import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
-import genericProxyHandler from "utils/proxy/handlers/generic";
-import getServiceWidget from "utils/config/service-helpers";
+import truenasProxyHandler from "./proxy";
+
+import { asJson, jsonArrayFilter } from "utils/proxy/api-helpers";
const widget = {
api: "{url}/api/v2.0/{endpoint}",
- proxyHandler: async (req, res, map) => {
- // choose proxy handler based on widget settings
- const { group, service } = req.query;
-
- if (group && service) {
- const widgetOpts = await getServiceWidget(group, service);
- let handler;
- if (widgetOpts.username && widgetOpts.password) {
- handler = genericProxyHandler;
- } else if (widgetOpts.key) {
- handler = credentialedProxyHandler;
- }
-
- if (handler) {
- return handler(req, res, map);
- }
-
- return res.status(500).json({ error: "Username / password or API key required" });
- }
-
- return res.status(500).json({ error: "Error parsing widget request" });
- },
+ proxyHandler: truenasProxyHandler,
mappings: {
alerts: {
@@ -39,6 +17,17 @@ const widget = {
endpoint: "system/info",
validate: ["loadavg", "uptime_seconds"],
},
+ pools: {
+ endpoint: "pool",
+ map: (data) =>
+ asJson(data).map((entry) => ({
+ id: entry.name,
+ name: entry.name,
+ healthy: entry.healthy,
+ allocated: entry.allocated,
+ free: entry.free,
+ })),
+ },
},
};