diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index c46e6c38..c6d44949 100755
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -576,5 +576,11 @@
"people_home": "People Home",
"lights_on": "Lights On",
"switches_on": "Switches On"
+ },
+ "nomad": {
+ "nodes": "Nodes",
+ "jobs": "Jobs",
+ "volumes": "Volumes",
+ "services": "Services"
}
}
diff --git a/public/locales/zh-CN/common.json b/public/locales/zh-CN/common.json
index fd77e685..9518f9dc 100644
--- a/public/locales/zh-CN/common.json
+++ b/public/locales/zh-CN/common.json
@@ -567,5 +567,11 @@
"people_home": "People Home",
"lights_on": "Lights On",
"switches_on": "Switches On"
+ },
+ "nomad": {
+ "nodes": "节点",
+ "jobs": "任务",
+ "volumes": "挂载",
+ "services": "服务"
}
}
diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js
index 93cdb995..84808b76 100644
--- a/src/utils/proxy/handlers/credentialed.js
+++ b/src/utils/proxy/handlers/credentialed.js
@@ -54,6 +54,8 @@ export default async function credentialedProxyHandler(req, res, map) {
} else {
headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`;
}
+ } else if (widget.type === "nomad") {
+ headers["X-Nomad-Token"] = `${widget.key}`;
} else {
headers["X-API-Key"] = `${widget.key}`;
}
@@ -78,7 +80,7 @@ export default async function credentialedProxyHandler(req, res, map) {
if (status >= 400) {
logger.error("HTTP Error %d calling %s", status, url.toString());
}
-
+
if (status === 200) {
if (!validateWidgetData(widget, endpoint, resultData)) {
return res.status(500).json({error: {message: "Invalid data", url: sanitizeErrorURL(url), data: resultData}});
diff --git a/src/widgets/components.js b/src/widgets/components.js
index ec7be376..9ebef242 100644
--- a/src/widgets/components.js
+++ b/src/widgets/components.js
@@ -42,6 +42,7 @@ const components = {
navidrome: dynamic(() => import("./navidrome/component")),
nextcloud: dynamic(() => import("./nextcloud/component")),
nextdns: dynamic(() => import("./nextdns/component")),
+ nomad: dynamic(() => import("./nomad/component")),
npm: dynamic(() => import("./npm/component")),
nzbget: dynamic(() => import("./nzbget/component")),
octoprint: dynamic(() => import("./octoprint/component")),
diff --git a/src/widgets/nomad/component.jsx b/src/widgets/nomad/component.jsx
new file mode 100644
index 00000000..7ad2a822
--- /dev/null
+++ b/src/widgets/nomad/component.jsx
@@ -0,0 +1,62 @@
+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";
+
+function calcRunningService(total, current) {
+ return current.Services.length + total;
+}
+
+function calcReadyNode(total, current) {
+ return current.Status === "ready" ? total + 1 : total;
+}
+
+function calcRunningJob(total, current) {
+ return current.Status === "running" ? total + 1 : total;
+}
+
+export default function Component({ service }) {
+ const { t } = useTranslation();
+ const { widget } = service;
+
+ const { data: nodeData, error: nodeError } = useWidgetAPI(widget, "nodes");
+ const { data: jobData, error: jobError } = useWidgetAPI(widget, "jobs");
+ const { data: serviceData, error: serviceError } = useWidgetAPI(widget, "services");
+ const { data: volumeData, error: volumeError } = useWidgetAPI(widget, "volumes");
+ const { data: csiVolumeData, error: csiVolumeError } = useWidgetAPI(widget, "csi_volumes");
+
+ if (nodeError || jobError || serviceError || volumeError || csiVolumeError) {
+ const finalError = nodeError ?? jobError ?? serviceError ?? volumeError ?? csiVolumeError;
+ return ;
+ }
+
+ if (!nodeData || !jobData || !serviceData || !volumeData || !csiVolumeData) {
+ return (
+
+
+
+
+
+
+ );
+ }
+
+ const nodes = nodeData || [];
+ const readyNodes = nodes.reduce(calcReadyNode, 0);
+
+ const jobs = jobData || [];
+ const runningJobs = jobs.reduce(calcRunningJob, 0);
+
+ const volumeTotal = volumeData.length + csiVolumeData.length;
+ const runningServices = (serviceData || []).reduce(calcRunningService, 0);
+
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/src/widgets/nomad/widget.js b/src/widgets/nomad/widget.js
new file mode 100644
index 00000000..363c95d4
--- /dev/null
+++ b/src/widgets/nomad/widget.js
@@ -0,0 +1,26 @@
+import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
+
+const widget = {
+ api: "{url}/v1/{endpoint}",
+ proxyHandler: credentialedProxyHandler,
+
+ mappings: {
+ nodes: {
+ endpoint: "nodes",
+ },
+ jobs: {
+ endpoint: "jobs",
+ },
+ services: {
+ endpoint: "services",
+ },
+ volumes: {
+ endpoint: "volumes",
+ },
+ csi_volumes: {
+ endpoint: "volumes?type=csi",
+ }
+ },
+};
+
+export default widget;
diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js
index a8ce5282..9e2c5f7f 100644
--- a/src/widgets/widgets.js
+++ b/src/widgets/widgets.js
@@ -37,6 +37,7 @@ import navidrome from "./navidrome/widget";
import nextcloud from "./nextcloud/widget";
import nextdns from "./nextdns/widget";
import npm from "./npm/widget";
+import nomad from "./nomad/widget";
import nzbget from "./nzbget/widget";
import octoprint from "./octoprint/widget";
import omada from "./omada/widget";
@@ -116,6 +117,7 @@ const widgets = {
nextcloud,
nextdns,
npm,
+ nomad,
nzbget,
octoprint,
omada,