Add TrueNAS info widget for resource monitoring
This commit is contained in:
parent
fc431d9309
commit
9dfd567f29
19
docs/widgets/info/truenas.md
Normal file
19
docs/widgets/info/truenas.md
Normal 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
|
||||
```
|
||||
38
src/components/widgets/truenas/truenas.jsx
Normal file
38
src/components/widgets/truenas/truenas.jsx
Normal 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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -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 }) {
|
||||
|
||||
90
src/pages/api/widgets/truenas.js
Normal file
90
src/pages/api/widgets/truenas.js
Normal 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 });
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user