Add TrueNAS info widget for resource monitoring

This commit is contained in:
Derek Stotz 2024-01-29 18:39:25 -06:00
parent fc431d9309
commit 9dfd567f29
4 changed files with 148 additions and 0 deletions

View File

@ -0,0 +1,19 @@
---
title: TrueNAS
description: TrueNAS Information Widget Configuration
---
_(Find the TrueNAS service widget [here](../services/truenas.md))_
The TrueNAS widget allows you to monitor the resources (CPU/memory) of your TrueNAS hosts, and is designed to match the `kubernetes` info widget. You can have multiple instances by adding another configuration block.
```yaml
- truenas:
url: http://host.or.ip:port
username: user # not required if using api key
password: pass # not required if using api key
key: yourtruenasapikey # not required if using username / password
label: My TrueNAS # optional
icon: si-truenas # optional, defaults to si-truenas
refresh: 5000 # optional, in ms
```

View File

@ -0,0 +1,38 @@
import useSWR from "swr";
import { useTranslation } from "next-i18next";
import Error from "../widget/error";
import ServiceResource from "../resources/serviceResource";
import ResolvedIcon from "components/resolvedicon";
export default function Widget({ options }) {
const { i18n } = useTranslation();
const { icon: iconVal, label, refresh } = options;
const { data, error } = useSWR(
`/api/widgets/truenas?${new URLSearchParams({ lang: i18n.language, ...options }).toString()}`,
{
refreshInterval: refresh ?? 5000,
},
);
const icon = <ResolvedIcon icon={iconVal ?? "si-truenas"} />;
if (error || data?.error) {
return <Error options={options} />;
}
const memUsagePercent = Math.round(((data?.memory?.used ?? 0) / (data?.memory?.total ?? 1)) * 100);
return (
<ServiceResource
icon={icon}
label={label}
cpuPercent={data?.cpu?.load}
memFree={data?.memory?.free}
memPercent={memUsagePercent}
error={data?.error}
/>
);
}

View File

@ -15,6 +15,7 @@ const widgetMappings = {
openmeteo: dynamic(() => import("components/widgets/openmeteo/openmeteo")),
longhorn: dynamic(() => import("components/widgets/longhorn/longhorn")),
kubernetes: dynamic(() => import("components/widgets/kubernetes/kubernetes")),
truenas: dynamic(() => import("components/widgets/truenas/truenas")),
};
export default function Widget({ widget, style }) {

View File

@ -0,0 +1,90 @@
import { httpProxy } from "utils/proxy/http";
import createLogger from "utils/logger";
import { getPrivateWidgetOptions } from "utils/config/widget-helpers";
const logger = createLogger("truenas");
async function retrieveFromTruenasAPI(privateWidgetOptions, endpoint, method, body) {
let errorMessage;
const url = privateWidgetOptions?.url;
if (!url) {
errorMessage = "Missing Truenas URL";
logger.error(errorMessage);
throw new Error(errorMessage);
}
const apiUrl = `${url}/api/v2.0/${endpoint}`;
const headers = {
"Accept-Encoding": "application/json",
};
if (privateWidgetOptions.username && privateWidgetOptions.password) {
headers.Authorization = `Basic ${Buffer.from(
`${privateWidgetOptions.username}:${privateWidgetOptions.password}`,
).toString("base64")}`;
} else if (privateWidgetOptions.key) {
headers.Authorization = `Bearer ${privateWidgetOptions.key}`;
} else {
errorMessage = "Missing TrueNAS credentials";
logger.error(errorMessage);
throw new Error(errorMessage);
}
const params = { method, headers, body };
const [status, , data] = await httpProxy(apiUrl, params);
if (status === 401) {
errorMessage = `Authorization failure getting data from TrueNAS API. Data: ${data.toString()}`;
logger.error(errorMessage);
throw new Error(errorMessage);
}
if (status !== 200) {
errorMessage = `HTTP ${status} getting data from TrueNAS API. Data: ${data.toString()}`;
logger.error(errorMessage);
throw new Error(errorMessage);
}
return JSON.parse(Buffer.from(data).toString());
}
export default async function handler(req, res) {
const { index } = req.query;
const privateWidgetOptions = await getPrivateWidgetOptions("truenas", index);
try {
const systemInfo = await retrieveFromTruenasAPI(privateWidgetOptions, "system/info");
const memoryInfo = await retrieveFromTruenasAPI(
privateWidgetOptions,
"reporting/get_data",
"POST",
JSON.stringify({
graphs: [{ name: "memory" }],
reporting_query: {
start: Math.round((new Date() - 30000) / 1000), // 30 seconds ago
end: "NOW",
aggregate: true,
},
}),
);
const [used, free, cached, buffered] = memoryInfo?.[0]?.aggregations?.mean || [];
const data = {
cpu: {
load: systemInfo?.loadavg?.[0],
},
memory: {
used,
free,
cached,
buffered,
total: used + free, // Using used + free instead of total memory to match TrueNAS dashboard
},
};
return res.status(200).send(data);
} catch (e) {
return res.status(400).json({ error: e.message });
}
}