diff --git a/docs/widgets/services/gatus.md b/docs/widgets/services/gatus.md
new file mode 100644
index 00000000..3918b9f3
--- /dev/null
+++ b/docs/widgets/services/gatus.md
@@ -0,0 +1,12 @@
+---
+title: Gatus
+description: Gatus Widget Configuration
+---
+
+Allowed fields: `["up", "down", "uptime"]`.
+
+```yaml
+widget:
+ type: gatus
+ url: http://gatus.host.or.ip:port
+```
diff --git a/mkdocs.yml b/mkdocs.yml
index a9beba06..572d36b4 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -55,6 +55,7 @@ nav:
- widgets/services/freshrss.md
- widgets/services/fritzbox.md
- widgets/services/gamedig.md
+ - widgets/services/gatus.md
- widgets/services/ghostfolio.md
- widgets/services/glances.md
- widgets/services/gluetun.md
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 2192a7de..b0f60a6d 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -694,6 +694,11 @@
"targets_down": "Targets Down",
"targets_total": "Total Targets"
},
+ "gatus": {
+ "up": "Sites Up",
+ "down": "Sites Down",
+ "uptime": "Uptime"
+ },
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "One year",
diff --git a/src/widgets/components.js b/src/widgets/components.js
index 3cdfc261..497d6407 100644
--- a/src/widgets/components.js
+++ b/src/widgets/components.js
@@ -29,6 +29,7 @@ const components = {
freshrss: dynamic(() => import("./freshrss/component")),
fritzbox: dynamic(() => import("./fritzbox/component")),
gamedig: dynamic(() => import("./gamedig/component")),
+ gatus: dynamic(() => import("./gatus/component")),
ghostfolio: dynamic(() => import("./ghostfolio/component")),
glances: dynamic(() => import("./glances/component")),
gluetun: dynamic(() => import("./gluetun/component")),
diff --git a/src/widgets/gatus/component.jsx b/src/widgets/gatus/component.jsx
new file mode 100644
index 00000000..86b85ff3
--- /dev/null
+++ b/src/widgets/gatus/component.jsx
@@ -0,0 +1,51 @@
+import { useTranslation } from "next-i18next";
+
+import Container from "components/services/widget/container";
+import useWidgetAPI from "utils/proxy/use-widget-api";
+import Block from "components/services/widget/block";
+
+export default function Component({ service }) {
+ const { t } = useTranslation();
+
+ const { widget } = service;
+
+ const { data: statusData, error: statusError } = useWidgetAPI(widget, "status");
+
+ if (statusError) {
+ return ;
+ }
+
+ if (!statusData) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ let sitesUp = 0;
+ let sitesDown = 0;
+ Object.values(statusData).forEach((site) => {
+ const lastResult = site.results[site.results.length - 1];
+ if (lastResult?.success === true) {
+ sitesUp += 1;
+ } else {
+ sitesDown += 1;
+ }
+ });
+
+ // Adapted from https://github.com/bastienwirtz/homer/blob/b7cd8f9482e6836a96b354b11595b03b9c3d67cd/src/components/services/UptimeKuma.vue#L105
+ const resultsList = Object.values(statusData).reduce((a, b) => a.concat(b.results), []);
+ const percent = resultsList.reduce((a, b) => a + (b?.success === true ? 1 : 0), 0) / resultsList.length;
+ const uptime = (percent * 100).toFixed(1);
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/src/widgets/gatus/widget.js b/src/widgets/gatus/widget.js
new file mode 100644
index 00000000..8963ac19
--- /dev/null
+++ b/src/widgets/gatus/widget.js
@@ -0,0 +1,15 @@
+// import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
+import genericProxyHandler from "utils/proxy/handlers/generic";
+
+const widget = {
+ api: "{url}/{endpoint}",
+ proxyHandler: genericProxyHandler,
+
+ mappings: {
+ status: {
+ endpoint: "api/v1/endpoints/statuses",
+ },
+ },
+};
+
+export default widget;
diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js
index 128a7ded..553ce626 100644
--- a/src/widgets/widgets.js
+++ b/src/widgets/widgets.js
@@ -23,6 +23,7 @@ import flood from "./flood/widget";
import freshrss from "./freshrss/widget";
import fritzbox from "./fritzbox/widget";
import gamedig from "./gamedig/widget";
+import gatus from "./gatus/widget";
import ghostfolio from "./ghostfolio/widget";
import glances from "./glances/widget";
import gluetun from "./gluetun/widget";
@@ -128,6 +129,7 @@ const widgets = {
freshrss,
fritzbox,
gamedig,
+ gatus,
ghostfolio,
glances,
gluetun,