diff --git a/src/components/widgets/peanut/peanut.jsx b/src/components/widgets/peanut/peanut.jsx
new file mode 100644
index 00000000..fb446190
--- /dev/null
+++ b/src/components/widgets/peanut/peanut.jsx
@@ -0,0 +1,60 @@
+import useSWR from "swr";
+import { FaCarBattery } from "react-icons/fa";
+import { useTranslation } from "next-i18next";
+
+import Error from "../widget/error";
+import Resource from "../widget/resource";
+
+export default function Widget({ options }) {
+ const { t } = useTranslation();
+ const { expanded } = options;
+ let { refresh } = options;
+ if (!refresh) refresh = 1500;
+
+ const { data, error } = useSWR(
+ `/api/widgets/peanut?${new URLSearchParams({ ...options }).toString()}`,
+ {
+ refreshInterval: refresh,
+ },
+ );
+
+ if (error || data?.error) {
+ return ;
+ }
+
+ if (!data) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/src/components/widgets/widget.jsx b/src/components/widgets/widget.jsx
index b4fdb143..93dd2968 100644
--- a/src/components/widgets/widget.jsx
+++ b/src/components/widgets/widget.jsx
@@ -12,6 +12,7 @@ const widgetMappings = {
logo: dynamic(() => import("components/widgets/logo/logo"), { ssr: false }),
unifi_console: dynamic(() => import("components/widgets/unifi_console/unifi_console")),
glances: dynamic(() => import("components/widgets/glances/glances")),
+ peanut: dynamic(() => import("components/widgets/peanut/peanut")),
openmeteo: dynamic(() => import("components/widgets/openmeteo/openmeteo")),
longhorn: dynamic(() => import("components/widgets/longhorn/longhorn")),
kubernetes: dynamic(() => import("components/widgets/kubernetes/kubernetes")),
diff --git a/src/pages/api/widgets/peanut.js b/src/pages/api/widgets/peanut.js
new file mode 100644
index 00000000..508aeda4
--- /dev/null
+++ b/src/pages/api/widgets/peanut.js
@@ -0,0 +1,55 @@
+import { httpProxy } from "utils/proxy/http";
+import createLogger from "utils/logger";
+import { getPrivateWidgetOptions } from "utils/config/widget-helpers";
+
+const logger = createLogger("peanut");
+
+async function retrieveFromPeanutAPI(privateWidgetOptions, ups) {
+ let errorMessage;
+ const url = privateWidgetOptions?.options?.url;
+ if (!url) {
+ errorMessage = "Missing PeaNUT URL";
+ logger.error(errorMessage);
+ throw new Error(errorMessage);
+ }
+
+ const apiUrl = `${url}/api/v1/devices/${ups || privateWidgetOptions?.options?.key}`;
+ const headers = {
+ "Accept-Encoding": "application/json",
+ };
+ const params = { method: "GET", headers };
+
+ const [status, , data] = await httpProxy(apiUrl, params);
+
+ if (status === 401) {
+ errorMessage = `Authorization failure getting data from peanut API. Data: ${data.toString()}`;
+ logger.error(errorMessage);
+ throw new Error(errorMessage);
+ }
+
+ if (status !== 200) {
+ errorMessage = `HTTP ${status} getting data from peanut 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 { key } = req.query;
+
+ const privateWidgets = await getPrivateWidgetOptions();
+ const privateWidgetOptions = privateWidgets.find((o) => o.type === "peanut");
+
+ try {
+ const upsData = await retrieveFromPeanutAPI(privateWidgetOptions, key);
+ const data = {
+ ...upsData
+ };
+
+ return res.status(200).send(data);
+ } catch (e) {
+ return res.status(400).json({ error: e.message });
+ }
+}