From 721b2c4b2ffc0a5a74358501ea7335c125d8f3ad Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Sat, 12 Oct 2024 02:37:48 +0000 Subject: [PATCH] Feature: Twingate service widget --- public/locales/en/common.json | 8 +++ src/utils/proxy/handlers/credentialed.js | 3 ++ src/widgets/components.js | 1 + src/widgets/twingate/component.jsx | 63 ++++++++++++++++++++++++ src/widgets/twingate/widget.js | 20 ++++++++ src/widgets/widgets.js | 2 + 6 files changed, 97 insertions(+) create mode 100644 src/widgets/twingate/component.jsx create mode 100644 src/widgets/twingate/widget.js 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,