diff --git a/docs/widgets/services/garage.md b/docs/widgets/services/garage.md
new file mode 100644
index 00000000..ecf98887
--- /dev/null
+++ b/docs/widgets/services/garage.md
@@ -0,0 +1,17 @@
+---
+title: Garage
+description: Garage Widget Configuration
+---
+
+Learn more about [Garage](https://git.deuxfleurs.fr/Deuxfleurs/garage).
+
+Use the admin token key. Information about the token can be found in the [Garage configuration file documentation](https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/).
+
+Allowed fields: `["status", "knownNodes", "connectedNodes", "storageNodes", "storageNodesOk", "partitions", "partitionsQuorum", "partitionsAllOk"]`.
+
+```yaml
+widget:
+ type: garage
+ url: http://garage.host.or.ip:port
+ key: token
+```
diff --git a/docs/widgets/services/index.md b/docs/widgets/services/index.md
index ae506f08..8f1cf631 100644
--- a/docs/widgets/services/index.md
+++ b/docs/widgets/services/index.md
@@ -38,6 +38,7 @@ You can also find a list of all available service widgets in the sidebar navigat
- [Frigate](frigate.md)
- [Fritz!Box](fritzbox.md)
- [GameDig](gamedig.md)
+- [Garage](garage.md)
- [Gatus](gatus.md)
- [Ghostfolio](ghostfolio.md)
- [Gitea](gitea.md)
diff --git a/mkdocs.yml b/mkdocs.yml
index 1e9d59cc..7e8cd0f8 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -61,6 +61,7 @@ nav:
- widgets/services/frigate.md
- widgets/services/fritzbox.md
- widgets/services/gamedig.md
+ - widgets/services/garage.md
- widgets/services/gatus.md
- widgets/services/ghostfolio.md
- widgets/services/gitea.md
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index ab7dcfc9..29a09e13 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -998,5 +998,18 @@
"progressing": "Progressing",
"missing": "Missing",
"suspended": "Suspended"
+ },
+ "garage": {
+ "status": "Status",
+ "healthy": "Healthy",
+ "degraded": "Degraded",
+ "unavailable": "Unavailable",
+ "knownNodes": "Known nodes",
+ "connectedNodes": "Connected nodes",
+ "storageNodes": "Storage nodes",
+ "storageNodesOk": "Storage OK",
+ "partitions": "Total partitions",
+ "partitionsQuorum": "Quorum reached",
+ "partitionsAllOk": "Partitions OK"
}
}
diff --git a/public/locales/fr/common.json b/public/locales/fr/common.json
index 20b76e93..4f56cb88 100644
--- a/public/locales/fr/common.json
+++ b/public/locales/fr/common.json
@@ -953,5 +953,18 @@
"reminders": "Reminders",
"nextReminder": "Next Reminder",
"none": "None"
+ },
+ "garage": {
+ "status": "Statut",
+ "healthy": "En bonne santé",
+ "degraded": "Dégradé",
+ "unavailable": "Indisponible",
+ "knownNodes": "Nœuds connus",
+ "connectedNodes": "Nœuds connectés",
+ "storageNodes": "Nœuds de stockage",
+ "storageNodesOk": "Stockage OK",
+ "partitions": "Partitions totales",
+ "partitionsQuorum": "Quorum atteint",
+ "partitionsAllOk": "Partitions OK"
}
}
diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js
index 8d4340c2..4b1de3cf 100644
--- a/src/utils/proxy/handlers/credentialed.js
+++ b/src/utils/proxy/handlers/credentialed.js
@@ -48,6 +48,7 @@ export default async function credentialedProxyHandler(req, res, map) {
"tandoor",
"pterodactyl",
"vikunja",
+ "garage",
].includes(widget.type)
) {
headers.Authorization = `Bearer ${widget.key}`;
diff --git a/src/widgets/components.js b/src/widgets/components.js
index aa476c46..2e1e5d72 100644
--- a/src/widgets/components.js
+++ b/src/widgets/components.js
@@ -35,6 +35,7 @@ const components = {
frigate: dynamic(() => import("./frigate/component")),
fritzbox: dynamic(() => import("./fritzbox/component")),
gamedig: dynamic(() => import("./gamedig/component")),
+ garage: dynamic(() => import("./garage/component")),
gatus: dynamic(() => import("./gatus/component")),
ghostfolio: dynamic(() => import("./ghostfolio/component")),
gitea: dynamic(() => import("./gitea/component")),
diff --git a/src/widgets/garage/component.jsx b/src/widgets/garage/component.jsx
new file mode 100644
index 00000000..6cd01e9a
--- /dev/null
+++ b/src/widgets/garage/component.jsx
@@ -0,0 +1,45 @@
+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";
+
+export default function Component({ service }) {
+ const { t } = useTranslation();
+
+ const { widget } = service;
+
+ const { data, error } = useWidgetAPI(widget, "health");
+
+ if (error) {
+ return ;
+ }
+
+ if (!data) {
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/widgets/garage/widget.js b/src/widgets/garage/widget.js
new file mode 100644
index 00000000..d44bfc50
--- /dev/null
+++ b/src/widgets/garage/widget.js
@@ -0,0 +1,14 @@
+import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
+
+const widget = {
+ api: "{url}/v1/{endpoint}",
+ proxyHandler: credentialedProxyHandler,
+
+ mappings: {
+ health: {
+ endpoint: "health",
+ },
+ },
+};
+
+export default widget;
diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js
index 0cad5346..e542d61d 100644
--- a/src/widgets/widgets.js
+++ b/src/widgets/widgets.js
@@ -29,6 +29,7 @@ import freshrss from "./freshrss/widget";
import frigate from "./frigate/widget";
import fritzbox from "./fritzbox/widget";
import gamedig from "./gamedig/widget";
+import garage from "./garage/widget";
import gatus from "./gatus/widget";
import ghostfolio from "./ghostfolio/widget";
import gitea from "./gitea/widget";
@@ -160,6 +161,7 @@ const widgets = {
frigate,
fritzbox,
gamedig,
+ garage,
gatus,
ghostfolio,
gitea,