diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 3aea07eb..109e179a 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -953,5 +953,13 @@
"reminders": "Reminders",
"nextReminder": "Next Reminder",
"none": "None"
+ },
+ "twingate": {
+ "networks": "Networks",
+ "connectors": "Connectors",
+ "resources": "Resources",
+ "devices": "Devices",
+ "users": "Users",
+ "groups": "Groups"
}
}
diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js
index 39822075..ccad04f1 100644
--- a/src/utils/proxy/handlers/credentialed.js
+++ b/src/utils/proxy/handlers/credentialed.js
@@ -90,6 +90,9 @@ export default async function credentialedProxyHandler(req, res, map) {
}
} else if (widget.type === "wgeasy") {
headers.Authorization = widget.password;
+ } else if (widget.type == "twingate") {
+ headers["Content-Type"] = "application/x-www-form-urlencoded";
+ headers["X-API-Key"] = `${widget.key}`;
} else {
headers["X-API-Key"] = `${widget.key}`;
}
diff --git a/src/widgets/components.js b/src/widgets/components.js
index 0a5a815c..2eeca0a9 100644
--- a/src/widgets/components.js
+++ b/src/widgets/components.js
@@ -120,6 +120,7 @@ const components = {
transmission: dynamic(() => import("./transmission/component")),
tubearchivist: dynamic(() => import("./tubearchivist/component")),
truenas: dynamic(() => import("./truenas/component")),
+ twingate: dynamic(() => import("./twingate/component")),
unifi: dynamic(() => import("./unifi/component")),
unmanic: dynamic(() => import("./unmanic/component")),
uptimekuma: dynamic(() => import("./uptimekuma/component")),
diff --git a/src/widgets/twingate/component.jsx b/src/widgets/twingate/component.jsx
new file mode 100644
index 00000000..601fef0b
--- /dev/null
+++ b/src/widgets/twingate/component.jsx
@@ -0,0 +1,63 @@
+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 { widget } = service;
+
+ const { data: statsData, error: statsError } = useWidgetAPI(widget, "stats", {
+ query:
+ "{ connectors { totalCount states: edges { node { state } } } groups { totalCount states: edges { node { state: isActive } } } users { totalCount states: edges { node { state } } } remoteNetworks { totalCount states: edges { node { state: isActive } } } devices { totalCount states: edges { node { state: activeState } } } resources { totalCount states: edges { node { state: isActive } } } }",
+ });
+
+ console.log(statsData);
+
+ if (statsError) {
+ return ;
+ }
+
+ const { connectors, groups, users, remoteNetworks, devices, resources } = statsData || {};
+
+ const activeConnectors = connectors?.states?.filter(({ node }) => node.state === "ALIVE").length;
+ const activeGroups = groups?.states?.filter(({ node }) => node.state).length;
+ const activeUsers = users?.states?.filter(({ node }) => node.state === "ACTIVE").length;
+ const activeNetworks = remoteNetworks?.states?.filter(({ node }) => node.state).length;
+ const activeDevices = devices?.states?.filter(({ node }) => node.state === "ACTIVE").length;
+ const activeResources = resources?.states?.filter(({ node }) => node.state).length;
+
+ // Provide a default if not set in the config
+ if (!widget.fields) {
+ widget.fields = ["networks", "connectors", "resources"];
+ }
+
+ // Limit to a maximum of 4 at a time
+ if (widget.fields.length > 4) {
+ widget.fields = widget.fields.slice(0, 4);
+ }
+
+ if (!statsData) {
+ return (
+
+
+
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/widgets/twingate/widget.js b/src/widgets/twingate/widget.js
new file mode 100644
index 00000000..81c197ae
--- /dev/null
+++ b/src/widgets/twingate/widget.js
@@ -0,0 +1,20 @@
+import { asJson } from "utils/proxy/api-helpers";
+import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
+
+const widget = {
+ api: "{url}/api/{endpoint}",
+ proxyHandler: credentialedProxyHandler,
+
+ mappings: {
+ stats: {
+ method: "GET",
+ endpoint: "graphql",
+ map: (data) => asJson(data).data,
+ params:[
+ "query"
+ ]
+ },
+ },
+};
+
+export default widget;
diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js
index 3334e47e..36216618 100644
--- a/src/widgets/widgets.js
+++ b/src/widgets/widgets.js
@@ -111,6 +111,7 @@ import traefik from "./traefik/widget";
import transmission from "./transmission/widget";
import tubearchivist from "./tubearchivist/widget";
import truenas from "./truenas/widget";
+import twingate from "./twingate/widget";
import unifi from "./unifi/widget";
import unmanic from "./unmanic/widget";
import uptimekuma from "./uptimekuma/widget";
@@ -240,6 +241,7 @@ const widgets = {
transmission,
tubearchivist,
truenas,
+ twingate,
unifi,
unifi_console: unifi,
unmanic,