diff --git a/docs/widgets/services/wgeasy.md b/docs/widgets/services/wgeasy.md
new file mode 100644
index 000000000..c5442081c
--- /dev/null
+++ b/docs/widgets/services/wgeasy.md
@@ -0,0 +1,20 @@
+---
+title: Wg-Easy
+description: Wg-Easy Widget Configuration
+---
+
+Learn more about [Wg-Easy](https://github.com/wg-easy/wg-easy).
+
+Allowed fields: `["connected", "enabled", "disabled", "total"]`.
+
+Note: by default `["connected", "enabled", "total"]` are displayed.
+
+To detect if a device is connected the time since the last handshake is queried. `threshold` is the time to wait in minutes since the last handshake to consider a device connected. Default is 2 minutes.
+
+```yaml
+widget:
+ type: wgeasy
+ url: http://wg.easy.or.ip
+ password: yourwgeasypassword
+ threshold: 2 # optional
+```
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 3ac3ed0d2..15de0ee94 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -876,5 +876,11 @@
"crowdsec": {
"alerts": "Alerts",
"bans": "Bans"
+ },
+ "wgeasy": {
+ "connected": "Connected",
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "total": "Total"
}
}
diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js
index aaee636c9..8e2f12d54 100644
--- a/src/utils/config/service-helpers.js
+++ b/src/utils/config/service-helpers.js
@@ -462,6 +462,9 @@ export function cleanServiceGroups(groups) {
// unifi
site,
+
+ // wgeasy
+ threshold,
} = cleanedService.widget;
let fieldsList = fields;
@@ -596,6 +599,9 @@ export function cleanServiceGroups(groups) {
cleanedService.widget.bitratePrecision = parseInt(bitratePrecision, 10);
}
}
+ if (type === "wgeasy") {
+ if (threshold !== undefined) cleanedService.widget.threshold = parseInt(threshold, 10);
+ }
}
return cleanedService;
diff --git a/src/widgets/components.js b/src/widgets/components.js
index 500fe0ce7..1b5c4b685 100644
--- a/src/widgets/components.js
+++ b/src/widgets/components.js
@@ -117,6 +117,7 @@ const components = {
uptimerobot: dynamic(() => import("./uptimerobot/component")),
urbackup: dynamic(() => import("./urbackup/component")),
watchtower: dynamic(() => import("./watchtower/component")),
+ wgeasy: dynamic(() => import("./wgeasy/component")),
whatsupdocker: dynamic(() => import("./whatsupdocker/component")),
xteve: dynamic(() => import("./xteve/component")),
};
diff --git a/src/widgets/wgeasy/component.jsx b/src/widgets/wgeasy/component.jsx
new file mode 100644
index 000000000..0289d48c4
--- /dev/null
+++ b/src/widgets/wgeasy/component.jsx
@@ -0,0 +1,45 @@
+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 { widget } = service;
+
+ const { data: infoData, error: infoError } = useWidgetAPI(widget);
+
+ if (!widget.fields) {
+ widget.fields = ["connected", "enabled", "total"];
+ }
+
+ if (infoError) {
+ return ;
+ }
+
+ if (!infoData) {
+ return (
+
+
+
+
+
+
+ );
+ }
+
+ const enabled = infoData.filter((item) => item.enabled).length;
+ const disabled = infoData.length - enabled;
+ const connectionThreshold = widget.threshold ?? 2 * 60 * 1000;
+ const currentTime = new Date();
+ const connected = infoData.filter(
+ (item) => currentTime - new Date(item.latestHandshakeAt) < connectionThreshold,
+ ).length;
+
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/src/widgets/wgeasy/proxy.js b/src/widgets/wgeasy/proxy.js
new file mode 100644
index 000000000..ec733475e
--- /dev/null
+++ b/src/widgets/wgeasy/proxy.js
@@ -0,0 +1,70 @@
+import cache from "memory-cache";
+
+import getServiceWidget from "utils/config/service-helpers";
+import { formatApiCall } from "utils/proxy/api-helpers";
+import { httpProxy } from "utils/proxy/http";
+import widgets from "widgets/widgets";
+import createLogger from "utils/logger";
+
+const proxyName = "wgeasyProxyHandler";
+const logger = createLogger(proxyName);
+const sessionSIDCacheKey = `${proxyName}__sessionSID`;
+
+async function login(widget, service) {
+ const url = formatApiCall(widgets[widget.type].api, { ...widget, endpoint: "session" });
+ const [, , , responseHeaders] = await httpProxy(url, {
+ method: "POST",
+ body: JSON.stringify({ password: widget.password }),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+
+ try {
+ const connectSidCookie = responseHeaders["set-cookie"]
+ .find((cookie) => cookie.startsWith("connect.sid="))
+ .split(";")[0]
+ .replace("connect.sid=", "");
+ cache.put(`${sessionSIDCacheKey}.${service}`, connectSidCookie);
+ return connectSidCookie;
+ } catch (e) {
+ logger.error(`Error logging into wg-easy`);
+ cache.del(`${sessionSIDCacheKey}.${service}`);
+ return null;
+ }
+}
+
+export default async function wgeasyProxyHandler(req, res) {
+ const { group, service } = req.query;
+
+ if (group && service) {
+ const widget = await getServiceWidget(group, service);
+
+ if (!widgets?.[widget.type]?.api) {
+ return res.status(403).json({ error: "Service does not support API calls" });
+ }
+
+ if (widget) {
+ let sid = cache.get(`${sessionSIDCacheKey}.${service}`);
+ if (!sid) {
+ sid = await login(widget, service);
+ if (!sid) {
+ return res.status(500).json({ error: "Failed to authenticate with Wg-Easy" });
+ }
+ }
+ const [, , data] = await httpProxy(
+ formatApiCall(widgets[widget.type].api, { ...widget, endpoint: "wireguard/client" }),
+ {
+ headers: {
+ "Content-Type": "application/json",
+ Cookie: `connect.sid=${sid}`,
+ },
+ },
+ );
+
+ return res.json(JSON.parse(data));
+ }
+ }
+
+ return res.status(400).json({ error: "Invalid proxy service type" });
+}
diff --git a/src/widgets/wgeasy/widget.js b/src/widgets/wgeasy/widget.js
new file mode 100644
index 000000000..7f7d69d7e
--- /dev/null
+++ b/src/widgets/wgeasy/widget.js
@@ -0,0 +1,8 @@
+import wgeasyProxyHandler from "./proxy";
+
+const widget = {
+ api: "{url}/api/{endpoint}",
+ proxyHandler: wgeasyProxyHandler,
+};
+
+export default widget;
diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js
index 7ed98bfb9..d6965f50f 100644
--- a/src/widgets/widgets.js
+++ b/src/widgets/widgets.js
@@ -107,6 +107,7 @@ import unmanic from "./unmanic/widget";
import uptimekuma from "./uptimekuma/widget";
import uptimerobot from "./uptimerobot/widget";
import watchtower from "./watchtower/widget";
+import wgeasy from "./wgeasy/widget";
import whatsupdocker from "./whatsupdocker/widget";
import xteve from "./xteve/widget";
import urbackup from "./urbackup/widget";
@@ -227,6 +228,7 @@ const widgets = {
uptimerobot,
urbackup,
watchtower,
+ wgeasy,
whatsupdocker,
xteve,
};