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")),
|
openmeteo: dynamic(() => import("components/widgets/openmeteo/openmeteo")),
|
||||||
longhorn: dynamic(() => import("components/widgets/longhorn/longhorn")),
|
longhorn: dynamic(() => import("components/widgets/longhorn/longhorn")),
|
||||||
kubernetes: dynamic(() => import("components/widgets/kubernetes/kubernetes")),
|
kubernetes: dynamic(() => import("components/widgets/kubernetes/kubernetes")),
|
||||||
|
truenas: dynamic(() => import("components/widgets/truenas/truenas")),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Widget({ widget, style }) {
|
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