From 667e759ca44c715320eccb5d1d4f0f3a12ba71ca Mon Sep 17 00:00:00 2001
From: Karl Hudgell <karl.hudgell@bjss.com>
Date: Tue, 6 Jun 2023 17:25:12 +0100
Subject: [PATCH 1/3] WGeasy initial commit

---
 public/locales/en/common.json    |   3 +
 src/widgets/components.js        |   1 +
 src/widgets/wgeasy/component.jsx |  33 +++++++++
 src/widgets/wgeasy/proxy.js      | 118 +++++++++++++++++++++++++++++++
 src/widgets/wgeasy/widget.js     |  14 ++++
 src/widgets/widgets.js           |   2 +
 6 files changed, 171 insertions(+)
 create mode 100644 src/widgets/wgeasy/component.jsx
 create mode 100644 src/widgets/wgeasy/proxy.js
 create mode 100644 src/widgets/wgeasy/widget.js

diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 37f7e233..1de843cc 100755
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -653,5 +653,8 @@
     "nextpvr": {
         "upcoming": "Upcoming",
         "ready": "Recent"
+    },
+    "wgeasy": {
+        "clients": "Total Clients"
     }
 }
\ No newline at end of file
diff --git a/src/widgets/components.js b/src/widgets/components.js
index 9916d601..3a918e62 100644
--- a/src/widgets/components.js
+++ b/src/widgets/components.js
@@ -89,6 +89,7 @@ const components = {
   uptimekuma: dynamic(() => import("./uptimekuma/component")),
   watchtower: dynamic(() => import("./watchtower/component")),
   whatsupdocker: dynamic(() => import("./whatsupdocker/component")),
+  wgeasy: dynamic(() => import("./wgeasy/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 00000000..e2434f51
--- /dev/null
+++ b/src/widgets/wgeasy/component.jsx
@@ -0,0 +1,33 @@
+import { useTranslation } from "next-i18next";
+
+import Block from "components/services/widget/block";
+import Container from "components/services/widget/container";
+import useWidgetAPI from "utils/proxy/use-widget-api";
+
+export default function Component({ service }) {
+  const { t } = useTranslation();
+
+  const { widget } = service;
+
+  const { data: wgeasyData, error: wgeasyAPIError } = useWidgetAPI(widget, "unified", {
+    refreshInterval: 5000,
+  });
+
+  if (wgeasyAPIError) {
+    return <Container service={service} error={wgeasyAPIError} />;
+  }
+
+  if (!wgeasyData) {
+    return (
+      <Container service={service}>
+        <Block label="wgeasy.clients" />
+      </Container>
+    );
+  }
+
+  return (
+    <Container service={service}>
+      <Block label="wgeasy.clients" value={t("common.number", { value: wgeasyData.clientCount })} />
+    </Container>
+  );
+}
diff --git a/src/widgets/wgeasy/proxy.js b/src/widgets/wgeasy/proxy.js
new file mode 100644
index 00000000..233e80da
--- /dev/null
+++ b/src/widgets/wgeasy/proxy.js
@@ -0,0 +1,118 @@
+/* eslint-disable no-underscore-dangle */
+import { formatApiCall } from "utils/proxy/api-helpers";
+import { httpProxy } from "utils/proxy/http";
+import getServiceWidget from "utils/config/service-helpers";
+import createLogger from "utils/logger";
+import widgets from "widgets/widgets";
+
+
+const proxyName = "wgeasyProxyHandler";
+
+const logger = createLogger(proxyName);
+let globalSid = null;
+
+async function getWidget(req) {
+    const { group, service } = req.query;
+    if (!group || !service) {
+        logger.debug("Invalid or missing service '%s' or group '%s'", service, group);
+        return null;
+    }
+    const widget = await getServiceWidget(group, service);
+    if (!widget) {
+        logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group);
+        return null;
+    }
+
+    return widget;
+}
+
+
+async function loginToWGEasy(endpoint, widget) {
+    const api = widgets?.[widget.type]?.api;
+    if (!api) {
+        return [403, null];
+    }
+    // Create new session on WgEasy
+    let url = new URL(formatApiCall(api, { endpoint, ...widget }));
+
+    let [status, contentType, data, responseHeaders] = await httpProxy(url, {
+        method: 'POST',
+        headers: {
+            'Content-Type': 'application/json',
+        },
+        body: JSON.stringify({
+            password: widget.password,
+        })
+    });
+
+    if (status !== 204) {
+        logger.error("HTTP %d communicating with NextPVR. Data: %s", status, data.toString());
+        return [status, data, responseHeaders];
+    }
+    try {
+        globalSid = responseHeaders["set-cookie"][0]
+    } catch (e) {
+        logger.error("Error decoding NextPVR API data. Data: %s", data.toString());
+        return [status, null];
+    }
+    logger.info('gettingSID')
+    return [status, true];
+}
+
+
+async function fetchDataFromWGeasy(endpoint, widget, sid) {
+    const api = widgets?.[widget.type]?.api;
+    if (!api) {
+        return [403, null];
+    }
+    const url = `${new URL(formatApiCall(api, { endpoint, ...widget }))}`
+    const [status, contentType, data] = await httpProxy(url, {
+        method: 'GET',
+        headers: {
+            'Content-Type': 'application/json',
+            'Cookie': sid
+        },
+    });
+
+    if (status !== 200) {
+        logger.error("HTTP %d communicating with WGeasy. Data: %s", status, data.toString());
+        return [status, data];
+    }
+
+    try {
+        return [status, JSON.parse(data), contentType];
+    } catch (e) {
+        logger.error("Error decoding WGeasy API data. Data: %s", data.toString());
+        return [status, null];
+    }
+}
+
+export default async function WGeasyProxyHandler(req, res) {
+    const widget = await getWidget(req);
+
+    if (!globalSid) {
+        await loginToWGEasy('session', widget);
+    }
+    if (!widget) {
+        return res.status(400).json({ error: "Invalid proxy service type" });
+    }
+
+    logger.debug("Getting data from WGeasy API");
+    // Calculate the number of clients
+    let [status, apiData] = await fetchDataFromWGeasy('wireguard/client', widget, globalSid);
+
+    if (status !== 200) {
+        return res.status(status).json({ error: { message: "HTTP error communicating with WGeasy API", data: Buffer.from(apiData).toString() } });
+    }
+    let clientCount;
+    clientCount = apiData.length;
+    
+    const data = {
+        clientCount
+    };
+
+    return res.status(status).send(data);
+
+}
+
+
diff --git a/src/widgets/wgeasy/widget.js b/src/widgets/wgeasy/widget.js
new file mode 100644
index 00000000..2496bf66
--- /dev/null
+++ b/src/widgets/wgeasy/widget.js
@@ -0,0 +1,14 @@
+import nextpvrProxyHandler from "./proxy";
+
+const widget = {
+  api: "{url}api/{endpoint}",
+  proxyHandler: nextpvrProxyHandler,
+
+  mappings: {
+    unified: {
+      endpoint: "/",
+    },
+  },
+};
+
+export default widget;
diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js
index ff770939..dc675bb8 100644
--- a/src/widgets/widgets.js
+++ b/src/widgets/widgets.js
@@ -83,6 +83,7 @@ import unmanic from "./unmanic/widget";
 import uptimekuma from "./uptimekuma/widget";
 import watchtower from "./watchtower/widget";
 import whatsupdocker from "./whatsupdocker/widget";
+import wgeasy from "./wgeasy/widget";
 import xteve from "./xteve/widget";
 
 const widgets = {
@@ -173,6 +174,7 @@ const widgets = {
   uptimekuma,
   watchtower,
   whatsupdocker,
+  wgeasy,
   xteve,
 };
 

From e6b1901c4c3683738ac4aa06ab205bdeefd242a8 Mon Sep 17 00:00:00 2001
From: Karl Hudgell <karl.hudgell@bjss.com>
Date: Tue, 6 Jun 2023 17:27:14 +0100
Subject: [PATCH 2/3] fix api

---
 src/widgets/wgeasy/widget.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/widgets/wgeasy/widget.js b/src/widgets/wgeasy/widget.js
index 2496bf66..fcb5bef6 100644
--- a/src/widgets/wgeasy/widget.js
+++ b/src/widgets/wgeasy/widget.js
@@ -1,7 +1,7 @@
 import nextpvrProxyHandler from "./proxy";
 
 const widget = {
-  api: "{url}api/{endpoint}",
+  api: "{url}/api/{endpoint}",
   proxyHandler: nextpvrProxyHandler,
 
   mappings: {

From b26e07cf26d0c43b102579d144c4949fb9ebc025 Mon Sep 17 00:00:00 2001
From: Karl Hudgell <karl.hudgell@bjss.com>
Date: Wed, 7 Jun 2023 08:54:24 +0100
Subject: [PATCH 3/3] linting fixes

---
 src/widgets/wgeasy/proxy.js | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/src/widgets/wgeasy/proxy.js b/src/widgets/wgeasy/proxy.js
index 233e80da..76f61151 100644
--- a/src/widgets/wgeasy/proxy.js
+++ b/src/widgets/wgeasy/proxy.js
@@ -33,9 +33,9 @@ async function loginToWGEasy(endpoint, widget) {
         return [403, null];
     }
     // Create new session on WgEasy
-    let url = new URL(formatApiCall(api, { endpoint, ...widget }));
+    const url = new URL(formatApiCall(api, { endpoint, ...widget }));
 
-    let [status, contentType, data, responseHeaders] = await httpProxy(url, {
+    const [status, data, , responseHeaders] = await httpProxy(url, {
         method: 'POST',
         headers: {
             'Content-Type': 'application/json',
@@ -50,7 +50,7 @@ async function loginToWGEasy(endpoint, widget) {
         return [status, data, responseHeaders];
     }
     try {
-        globalSid = responseHeaders["set-cookie"][0]
+        [ globalSid ] = responseHeaders["set-cookie"]
     } catch (e) {
         logger.error("Error decoding NextPVR API data. Data: %s", data.toString());
         return [status, null];
@@ -99,14 +99,14 @@ export default async function WGeasyProxyHandler(req, res) {
 
     logger.debug("Getting data from WGeasy API");
     // Calculate the number of clients
-    let [status, apiData] = await fetchDataFromWGeasy('wireguard/client', widget, globalSid);
+    const [status, apiData] = await fetchDataFromWGeasy('wireguard/client', widget, globalSid);
 
     if (status !== 200) {
         return res.status(status).json({ error: { message: "HTTP error communicating with WGeasy API", data: Buffer.from(apiData).toString() } });
     }
-    let clientCount;
+    let clientCount = 0;
     clientCount = apiData.length;
-    
+
     const data = {
         clientCount
     };
@@ -115,4 +115,3 @@ export default async function WGeasyProxyHandler(req, res) {
 
 }
 
-