From b9b9449cd33f42f86bed269babe6ead24b5f5cb9 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 5 Oct 2022 21:36:43 -0700 Subject: [PATCH 01/15] Add unifi_console widget, API endpoint --- package.json | 1 + pnpm-lock.yaml | 101 ++++++++++++++++++ public/locales/en/common.json | 7 ++ .../widgets/unifi_console/unifi_console.jsx | 96 +++++++++++++++++ src/components/widgets/widget.jsx | 1 + src/pages/api/widgets/unifi.js | 53 +++++++++ 6 files changed, 259 insertions(+) create mode 100644 src/components/widgets/unifi_console/unifi_console.jsx create mode 100644 src/pages/api/widgets/unifi.js diff --git a/package.json b/package.json index 3dd2ed0d1..0ee4cf2b4 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "next": "^12.3.1", "next-i18next": "^12.0.1", "node-os-utils": "^1.3.7", + "node-unifi": "^2.1.0", "pretty-bytes": "^6.0.0", "raw-body": "^2.5.1", "react": "^18.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 49394c8ea..c6cbeb989 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,6 +24,7 @@ specifiers: next: ^12.3.1 next-i18next: ^12.0.1 node-os-utils: ^1.3.7 + node-unifi: ^2.1.0 postcss: ^8.4.16 prettier: ^2.7.1 pretty-bytes: ^6.0.0 @@ -54,6 +55,7 @@ dependencies: next: 12.3.1_biqbaboplfbrettd7655fr4n2y next-i18next: 12.0.1_azq6kxkn3od7qdylwkyksrwopy node-os-utils: 1.3.7 + node-unifi: 2.1.0 pretty-bytes: 6.0.0 raw-body: 2.5.1 react: 18.2.0 @@ -458,6 +460,15 @@ packages: hasBin: true dev: true + /agent-base/6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + /ajv/6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: @@ -577,6 +588,15 @@ packages: engines: {node: '>=4'} dev: true + /axios/0.27.2: + resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} + dependencies: + follow-redirects: 1.15.2 + form-data: 4.0.0 + transitivePeerDependencies: + - debug + dev: false + /axobject-query/2.2.0: resolution: {integrity: sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==} dev: true @@ -1394,6 +1414,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /eventemitter2/6.4.9: + resolution: {integrity: sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==} + dev: false + /fast-deep-equal/3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true @@ -1488,6 +1512,15 @@ packages: mime-types: 2.1.35 dev: false + /form-data/4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + /fraction.js/4.2.0: resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} dev: true @@ -1647,6 +1680,25 @@ packages: void-elements: 3.1.0 dev: false + /http-cookie-agent/4.0.2_tough-cookie@4.1.2: + resolution: {integrity: sha512-noTmxdH5CuytTnLj/Qv3Z84e/YFq8yLXAw3pqIYZ25Edhb9pQErIAC+ednw40Cic6Le/h9ryph5/TqsvkOaUCw==} + engines: {node: '>=14.18.0 <15.0.0 || >=16.0.0'} + peerDependencies: + deasync: ^0.1.26 + tough-cookie: ^4.0.0 + undici: ^5.1.1 + peerDependenciesMeta: + deasync: + optional: true + undici: + optional: true + dependencies: + agent-base: 6.0.2 + tough-cookie: 4.1.2 + transitivePeerDependencies: + - supports-color + dev: false + /http-errors/2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -2090,6 +2142,25 @@ packages: resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==} dev: true + /node-unifi/2.1.0: + resolution: {integrity: sha512-vawHGIFEc5XgCXo2I98h72pykVklemI5eE1d50oRZOLpfnYLVDNWF2RfdhvaRSHtVpPjFRshqJP2zuOSWnq4+A==} + engines: {node: '>=14.18.0 <15.0.0 || >=16.0.0'} + dependencies: + axios: 0.27.2 + eventemitter2: 6.4.9 + http-cookie-agent: 4.0.2_tough-cookie@4.1.2 + tough-cookie: 4.1.2 + url: 0.11.0 + ws: 8.9.0 + transitivePeerDependencies: + - bufferutil + - deasync + - debug + - supports-color + - undici + - utf-8-validate + dev: false + /normalize-path/3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -2365,10 +2436,20 @@ packages: once: 1.4.0 dev: false + /punycode/1.3.2: + resolution: {integrity: sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==} + dev: false + /punycode/2.1.1: resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} engines: {node: '>=6'} + /querystring/0.2.0: + resolution: {integrity: sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==} + engines: {node: '>=0.4.x'} + deprecated: The querystring API is considered Legacy. new code should use the URLSearchParams API instead. + dev: false + /querystringify/2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} dev: false @@ -2921,6 +3002,13 @@ packages: requires-port: 1.0.0 dev: false + /url/0.11.0: + resolution: {integrity: sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==} + dependencies: + punycode: 1.3.2 + querystring: 0.2.0 + dev: false + /use-sync-external-store/1.2.0_react@18.2.0: resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} peerDependencies: @@ -3000,6 +3088,19 @@ packages: /wrappy/1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + /ws/8.9.0: + resolution: {integrity: sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: false + /xtend/4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} diff --git a/public/locales/en/common.json b/public/locales/en/common.json index dfc1a5ba6..f19120087 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -31,6 +31,13 @@ "used": "Used", "load": "Load" }, + "unifi": { + "users": "Users", + "status": "Status", + "days": "Days", + "wan": "WAN", + "wait": "Please wait" + }, "docker": { "rx": "RX", "tx": "TX", diff --git a/src/components/widgets/unifi_console/unifi_console.jsx b/src/components/widgets/unifi_console/unifi_console.jsx new file mode 100644 index 000000000..7e0ff7fc0 --- /dev/null +++ b/src/components/widgets/unifi_console/unifi_console.jsx @@ -0,0 +1,96 @@ +import useSWR from "swr"; +import { BiError, BiWifi, BiCheckCircle } from "react-icons/bi"; +import { MdSettingsEthernet } from "react-icons/md"; +import { useTranslation } from "next-i18next"; +import { SiUbiquiti } from "react-icons/si"; + +export default function Widget({ options }) { + const { t, i18n } = useTranslation(); + + const { data, error } = useSWR( + `/api/widgets/unifi?${new URLSearchParams({ lang: i18n.language, ...options }).toString()}` + ); + + if (error || data?.error) { + return ( +
+
+
+ +
+ {t("widget.api_error")} + - +
+
+
+
+ ); + } + + if (!data) { + return ( +
+
+
+ +
+
+ {t("unifi.wait")} +
+
+
+ ); + } + + return ( +
+
+
+ +
+ {data.name} +
+
+
+
+
+ {t("common.number", { + value: data.uptime / 86400, + maximumFractionDigits: 1, + })} +
+
{t("unifi.days")}
+
+
+
{t("unifi.wan")}
+ +
+
+
+
+
+ +
+
+ {t("common.number", { + value: data.wlan.users, + maximumFractionDigits: 0, + })} +
+
+
+
+ +
+
+ {t("common.number", { + value: data.lan.users, + maximumFractionDigits: 0, + })} +
+
+
+
+
+ ); +} diff --git a/src/components/widgets/widget.jsx b/src/components/widgets/widget.jsx index de3f7a352..8daa87246 100644 --- a/src/components/widgets/widget.jsx +++ b/src/components/widgets/widget.jsx @@ -9,6 +9,7 @@ const widgetMappings = { search: dynamic(() => import("components/widgets/search/search")), greeting: dynamic(() => import("components/widgets/greeting/greeting")), datetime: dynamic(() => import("components/widgets/datetime/datetime")), + unifi_console: dynamic(() => import("components/widgets/unifi_console/unifi_console")), }; export default function Widget({ widget }) { diff --git a/src/pages/api/widgets/unifi.js b/src/pages/api/widgets/unifi.js new file mode 100644 index 000000000..f8cbcc1e6 --- /dev/null +++ b/src/pages/api/widgets/unifi.js @@ -0,0 +1,53 @@ +import { Controller } from "node-unifi"; + +export default async function handler(req, res) { + const { host, port, username, password } = req.query; + + if (!host) { + return res.status(400).json({ error: "Missing host" }); + } + + if (!username) { + return res.status(400).json({ error: "Missing username" }); + } + + if (!password) { + return res.status(400).json({ error: "Missing password" }); + } + + const controller = new Controller({ + host: host, + port: port, + sslverify: false + }); + + try { + //login to the controller + await controller.login(username, password); + + //retrieve sites + const sites = await controller.getSitesStats(); + const default_site = sites.find(s => s.name == "default"); + const wan = default_site.health.find(h => h.subsystem == "wan"); + const lan = default_site.health.find(h => h.subsystem == "lan"); + const wlan = default_site.health.find(h => h.subsystem == "wlan"); + + return res.status(200).json({ + name: wan.gw_name, + uptime: wan['gw_system-stats']['uptime'], + up: wan.status == 'ok', + wlan: { + users: wlan.num_user, + status: wlan.status + }, + lan: { + users: lan.num_user, + status: lan.status + } + }); + } catch (e) { + return res.status(400).json({ + error: `Error communicating with UniFi Console: ${e.message}` + }) + } +} From 5ba75bc62d80d7761791e85aa2d7bb854c6ecba4 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 5 Oct 2022 21:41:39 -0700 Subject: [PATCH 02/15] support wan down too --- src/components/widgets/unifi_console/unifi_console.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/widgets/unifi_console/unifi_console.jsx b/src/components/widgets/unifi_console/unifi_console.jsx index 7e0ff7fc0..ee8cfa450 100644 --- a/src/components/widgets/unifi_console/unifi_console.jsx +++ b/src/components/widgets/unifi_console/unifi_console.jsx @@ -1,5 +1,5 @@ import useSWR from "swr"; -import { BiError, BiWifi, BiCheckCircle } from "react-icons/bi"; +import { BiError, BiWifi, BiCheckCircle, BiXCircle } from "react-icons/bi"; import { MdSettingsEthernet } from "react-icons/md"; import { useTranslation } from "next-i18next"; import { SiUbiquiti } from "react-icons/si"; @@ -63,7 +63,10 @@ export default function Widget({ options }) {
{t("unifi.wan")}
- + { data.up + ? + : + }
From 952f0295cc69da7cbfb7b90072e8ed8692aa8a0b Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 5 Oct 2022 21:44:15 -0700 Subject: [PATCH 03/15] uptime label --- public/locales/en/common.json | 2 +- src/components/widgets/unifi_console/unifi_console.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/locales/en/common.json b/public/locales/en/common.json index f19120087..af8fc1974 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -33,7 +33,7 @@ }, "unifi": { "users": "Users", - "status": "Status", + "uptime": "System Uptime", "days": "Days", "wan": "WAN", "wait": "Please wait" diff --git a/src/components/widgets/unifi_console/unifi_console.jsx b/src/components/widgets/unifi_console/unifi_console.jsx index ee8cfa450..484982c8c 100644 --- a/src/components/widgets/unifi_console/unifi_console.jsx +++ b/src/components/widgets/unifi_console/unifi_console.jsx @@ -52,7 +52,7 @@ export default function Widget({ options }) {
-
+
{t("common.number", { value: data.uptime / 86400, From ac4dcd32222650113aac19d56c4f8261f0264ae8 Mon Sep 17 00:00:00 2001 From: Jason Fischer Date: Fri, 7 Oct 2022 17:12:29 -0700 Subject: [PATCH 04/15] Remove node-unifi package dependency - Add custom Unifi proxy built on existing cookie jar and httpProxy - Change formatApiCall to emit empty string instead of undefined on missing key --- package.json | 1 - pnpm-lock.yaml | 101 ----------------- .../widgets/unifi_console/unifi_console.jsx | 34 ++++-- src/pages/api/widgets/unifi.js | 53 --------- src/utils/proxy/api-helpers.js | 2 +- src/widgets/unifi/proxy.js | 103 ++++++++++++++++++ src/widgets/unifi/widget.js | 14 +++ src/widgets/widgets.js | 3 + 8 files changed, 148 insertions(+), 163 deletions(-) delete mode 100644 src/pages/api/widgets/unifi.js create mode 100644 src/widgets/unifi/proxy.js create mode 100644 src/widgets/unifi/widget.js diff --git a/package.json b/package.json index 0ee4cf2b4..3dd2ed0d1 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "next": "^12.3.1", "next-i18next": "^12.0.1", "node-os-utils": "^1.3.7", - "node-unifi": "^2.1.0", "pretty-bytes": "^6.0.0", "raw-body": "^2.5.1", "react": "^18.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c6cbeb989..49394c8ea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,7 +24,6 @@ specifiers: next: ^12.3.1 next-i18next: ^12.0.1 node-os-utils: ^1.3.7 - node-unifi: ^2.1.0 postcss: ^8.4.16 prettier: ^2.7.1 pretty-bytes: ^6.0.0 @@ -55,7 +54,6 @@ dependencies: next: 12.3.1_biqbaboplfbrettd7655fr4n2y next-i18next: 12.0.1_azq6kxkn3od7qdylwkyksrwopy node-os-utils: 1.3.7 - node-unifi: 2.1.0 pretty-bytes: 6.0.0 raw-body: 2.5.1 react: 18.2.0 @@ -460,15 +458,6 @@ packages: hasBin: true dev: true - /agent-base/6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} - dependencies: - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - dev: false - /ajv/6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: @@ -588,15 +577,6 @@ packages: engines: {node: '>=4'} dev: true - /axios/0.27.2: - resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} - dependencies: - follow-redirects: 1.15.2 - form-data: 4.0.0 - transitivePeerDependencies: - - debug - dev: false - /axobject-query/2.2.0: resolution: {integrity: sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==} dev: true @@ -1414,10 +1394,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /eventemitter2/6.4.9: - resolution: {integrity: sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==} - dev: false - /fast-deep-equal/3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true @@ -1512,15 +1488,6 @@ packages: mime-types: 2.1.35 dev: false - /form-data/4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} - engines: {node: '>= 6'} - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - dev: false - /fraction.js/4.2.0: resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} dev: true @@ -1680,25 +1647,6 @@ packages: void-elements: 3.1.0 dev: false - /http-cookie-agent/4.0.2_tough-cookie@4.1.2: - resolution: {integrity: sha512-noTmxdH5CuytTnLj/Qv3Z84e/YFq8yLXAw3pqIYZ25Edhb9pQErIAC+ednw40Cic6Le/h9ryph5/TqsvkOaUCw==} - engines: {node: '>=14.18.0 <15.0.0 || >=16.0.0'} - peerDependencies: - deasync: ^0.1.26 - tough-cookie: ^4.0.0 - undici: ^5.1.1 - peerDependenciesMeta: - deasync: - optional: true - undici: - optional: true - dependencies: - agent-base: 6.0.2 - tough-cookie: 4.1.2 - transitivePeerDependencies: - - supports-color - dev: false - /http-errors/2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -2142,25 +2090,6 @@ packages: resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==} dev: true - /node-unifi/2.1.0: - resolution: {integrity: sha512-vawHGIFEc5XgCXo2I98h72pykVklemI5eE1d50oRZOLpfnYLVDNWF2RfdhvaRSHtVpPjFRshqJP2zuOSWnq4+A==} - engines: {node: '>=14.18.0 <15.0.0 || >=16.0.0'} - dependencies: - axios: 0.27.2 - eventemitter2: 6.4.9 - http-cookie-agent: 4.0.2_tough-cookie@4.1.2 - tough-cookie: 4.1.2 - url: 0.11.0 - ws: 8.9.0 - transitivePeerDependencies: - - bufferutil - - deasync - - debug - - supports-color - - undici - - utf-8-validate - dev: false - /normalize-path/3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -2436,20 +2365,10 @@ packages: once: 1.4.0 dev: false - /punycode/1.3.2: - resolution: {integrity: sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==} - dev: false - /punycode/2.1.1: resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} engines: {node: '>=6'} - /querystring/0.2.0: - resolution: {integrity: sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==} - engines: {node: '>=0.4.x'} - deprecated: The querystring API is considered Legacy. new code should use the URLSearchParams API instead. - dev: false - /querystringify/2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} dev: false @@ -3002,13 +2921,6 @@ packages: requires-port: 1.0.0 dev: false - /url/0.11.0: - resolution: {integrity: sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==} - dependencies: - punycode: 1.3.2 - querystring: 0.2.0 - dev: false - /use-sync-external-store/1.2.0_react@18.2.0: resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} peerDependencies: @@ -3088,19 +3000,6 @@ packages: /wrappy/1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - /ws/8.9.0: - resolution: {integrity: sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - dev: false - /xtend/4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} diff --git a/src/components/widgets/unifi_console/unifi_console.jsx b/src/components/widgets/unifi_console/unifi_console.jsx index 484982c8c..7427bd23c 100644 --- a/src/components/widgets/unifi_console/unifi_console.jsx +++ b/src/components/widgets/unifi_console/unifi_console.jsx @@ -1,17 +1,18 @@ -import useSWR from "swr"; import { BiError, BiWifi, BiCheckCircle, BiXCircle } from "react-icons/bi"; import { MdSettingsEthernet } from "react-icons/md"; import { useTranslation } from "next-i18next"; import { SiUbiquiti } from "react-icons/si"; +import useWidgetAPI from "utils/proxy/use-widget-api"; + export default function Widget({ options }) { - const { t, i18n } = useTranslation(); + const { t } = useTranslation(); - const { data, error } = useSWR( - `/api/widgets/unifi?${new URLSearchParams({ lang: i18n.language, ...options }).toString()}` - ); + // eslint-disable-next-line no-param-reassign + options.type = "unifi_console"; + const { data: statsData, error: statsError } = useWidgetAPI(options, "stat/sites"); - if (error || data?.error) { + if (statsError || statsData?.error) { return (
@@ -27,7 +28,9 @@ export default function Widget({ options }) { ); } - if (!data) { + const defaultSite = statsData?.data?.find(s => s.name === "default"); + + if (!defaultSite) { return (
@@ -42,6 +45,23 @@ export default function Widget({ options }) { ); } + const wan = defaultSite.health.find(h => h.subsystem === "wan"); + const lan = defaultSite.health.find(h => h.subsystem === "lan"); + const wlan = defaultSite.health.find(h => h.subsystem === "wlan"); + const data = { + name: wan.gw_name, + uptime: wan["gw_system-stats"].uptime, + up: wan.status === 'ok', + wlan: { + users: wlan.num_user, + status: wlan.status + }, + lan: { + users: lan.num_user, + status: lan.status + } + }; + return (
diff --git a/src/pages/api/widgets/unifi.js b/src/pages/api/widgets/unifi.js deleted file mode 100644 index f8cbcc1e6..000000000 --- a/src/pages/api/widgets/unifi.js +++ /dev/null @@ -1,53 +0,0 @@ -import { Controller } from "node-unifi"; - -export default async function handler(req, res) { - const { host, port, username, password } = req.query; - - if (!host) { - return res.status(400).json({ error: "Missing host" }); - } - - if (!username) { - return res.status(400).json({ error: "Missing username" }); - } - - if (!password) { - return res.status(400).json({ error: "Missing password" }); - } - - const controller = new Controller({ - host: host, - port: port, - sslverify: false - }); - - try { - //login to the controller - await controller.login(username, password); - - //retrieve sites - const sites = await controller.getSitesStats(); - const default_site = sites.find(s => s.name == "default"); - const wan = default_site.health.find(h => h.subsystem == "wan"); - const lan = default_site.health.find(h => h.subsystem == "lan"); - const wlan = default_site.health.find(h => h.subsystem == "wlan"); - - return res.status(200).json({ - name: wan.gw_name, - uptime: wan['gw_system-stats']['uptime'], - up: wan.status == 'ok', - wlan: { - users: wlan.num_user, - status: wlan.status - }, - lan: { - users: lan.num_user, - status: lan.status - } - }); - } catch (e) { - return res.status(400).json({ - error: `Error communicating with UniFi Console: ${e.message}` - }) - } -} diff --git a/src/utils/proxy/api-helpers.js b/src/utils/proxy/api-helpers.js index 55cd333c5..904c9e967 100644 --- a/src/utils/proxy/api-helpers.js +++ b/src/utils/proxy/api-helpers.js @@ -2,7 +2,7 @@ export function formatApiCall(url, args) { const find = /\{.*?\}/g; const replace = (match) => { const key = match.replace(/\{|\}/g, ""); - return args[key]; + return args[key] || ""; }; return url.replace(/\/+$/, "").replace(find, replace); diff --git a/src/widgets/unifi/proxy.js b/src/widgets/unifi/proxy.js new file mode 100644 index 000000000..d4d50e0d6 --- /dev/null +++ b/src/widgets/unifi/proxy.js @@ -0,0 +1,103 @@ +import { formatApiCall } from "utils/proxy/api-helpers"; +import { httpProxy } from "utils/proxy/http"; +import { addCookieToJar, setCookieHeader } from "utils/proxy/cookie-jar"; +import { getSettings } from "utils/config/config"; +import getServiceWidget from "utils/config/service-helpers"; +import createLogger from "utils/logger"; +import widgets from "widgets/widgets"; + +const logger = createLogger("unifiProxyHandler"); + +async function getWidget(req) { + const { group, service, type } = req.query; + + let widget = null; + if (type === 'unifi_console') { + const settings = getSettings(); + widget = settings.unifi_console; + if (!widget) { + logger.debug("There is no unifi_console section in settings.yaml"); + return null; + } + widget.type = "unifi"; + } else { + if (!group || !service) { + logger.debug("Invalid or missing service '%s' or group '%s'", service, group); + return null; + } + + 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 login(widget) { + logger.debug("Unifi isn't logged in or is rejecting the reqeust, logging in."); + + const loginBody = { username: widget.username, password: widget.password, remember: true }; + let loginUrl = `${widget.url}/api`; + if (widget.version === "udm-pro") { + loginUrl += "/auth" + } + loginUrl += "/login"; + + const loginParams = { method: "POST", body: JSON.stringify(loginBody) }; + const [status, contentType, data, responseHeaders] = await httpProxy(loginUrl, loginParams); + return [status, contentType, data, responseHeaders]; +} + +export default async function unifiProxyHandler(req, res) { + const widget = await getWidget(req); + if (!widget) { + return res.status(400).json({ error: "Invalid proxy service type" }); + } + + const api = widgets?.[widget.type]?.api; + if (!api) { + return res.status(403).json({ error: "Service does not support API calls" }); + } + + widget.prefx = ""; + if (widget.version === "udm-pro") { + widget.prefix = "/proxy/network" + } + + const { endpoint } = req.query; + const url = new URL(formatApiCall(api, { endpoint, ...widget })); + const params = { method: "GET", headers: {} }; + setCookieHeader(url, params); + + let [status, contentType, data, responseHeaders] = await httpProxy(url, params); + if (status === 401) { + [status, contentType, data, responseHeaders] = await login(widget); + + if (status !== 200) { + logger.error("HTTP %d logging in to Unifi. Data: %s", status, data); + return res.status(status).end(data); + } + + const json = JSON.parse(data.toString()); + if (json?.meta?.rc !== "ok") { + logger.error("Error logging in to Unifi: Data: %s", data); + return res.status(401).end(data); + } + + addCookieToJar(url, responseHeaders); + setCookieHeader(url, params); + } + + [status, contentType, data] = await httpProxy(url, params); + + if (status !== 200) { + logger.error("HTTP %d getting data from Unifi. Data: %s", status, data); + } + + if (contentType) res.setHeader("Content-Type", contentType); + return res.status(status).send(data); +} diff --git a/src/widgets/unifi/widget.js b/src/widgets/unifi/widget.js new file mode 100644 index 000000000..928ebd766 --- /dev/null +++ b/src/widgets/unifi/widget.js @@ -0,0 +1,14 @@ +import unifiProxyHandler from "./proxy"; + +const widget = { + api: "{url}{prefix}/api/{endpoint}", + proxyHandler: unifiProxyHandler, + + mappings: { + "stat/sites": { + endpoint: "stat/sites", + }, + } +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 04665c782..a4cab76b0 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -27,6 +27,7 @@ import strelaysrv from "./strelaysrv/widget"; import tautulli from "./tautulli/widget"; import traefik from "./traefik/widget"; import transmission from "./transmission/widget"; +import unifi from "./unifi/widget"; const widgets = { adguard, @@ -59,6 +60,8 @@ const widgets = { tautulli, traefik, transmission, + unifi, + unifi_console: unifi }; export default widgets; From ad1d1e751dd9edf54c951b0cb93953892443b9cb Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Fri, 7 Oct 2022 23:26:16 -0700 Subject: [PATCH 05/15] Refactor unifi proxy for udm-pro compatibility --- src/widgets/unifi/proxy.js | 51 ++++++++++++++++++++++++++----------- src/widgets/unifi/widget.js | 2 +- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/widgets/unifi/proxy.js b/src/widgets/unifi/proxy.js index d4d50e0d6..914460512 100644 --- a/src/widgets/unifi/proxy.js +++ b/src/widgets/unifi/proxy.js @@ -38,17 +38,22 @@ async function getWidget(req) { } async function login(widget) { - logger.debug("Unifi isn't logged in or is rejecting the reqeust, logging in."); + let endpoint = widget.udmp ? "auth/login" : "login"; + widget.prefix = ""; // never use prefix for login + const api = widgets?.[widget.type]?.api; + const loginUrl = new URL(formatApiCall(api, { endpoint, ...widget })); const loginBody = { username: widget.username, password: widget.password, remember: true }; - let loginUrl = `${widget.url}/api`; - if (widget.version === "udm-pro") { - loginUrl += "/auth" - } - loginUrl += "/login"; - const loginParams = { method: "POST", body: JSON.stringify(loginBody) }; - const [status, contentType, data, responseHeaders] = await httpProxy(loginUrl, loginParams); + const headers = { + "Content-Type": "application/json" + }; + + const [status, contentType, data, responseHeaders] = await httpProxy(loginUrl, { + method: "POST", + body: JSON.stringify(loginBody), + headers + }); return [status, contentType, data, responseHeaders]; } @@ -62,9 +67,22 @@ export default async function unifiProxyHandler(req, res) { if (!api) { return res.status(403).json({ error: "Service does not support API calls" }); } + + // determine if udm-pro from base url + let [status, contentType, data, responseHeaders] = await httpProxy(`https://${widget.host}`); + if (responseHeaders['x-csrf-token']) { + widget.udmp = true + } - widget.prefx = ""; - if (widget.version === "udm-pro") { + if (!widget.port) { + widget.port = 8443; + if (widget.udmp) { + widget.port = 443 + } + } + + widget.prefix = ""; + if (widget.udmp) { widget.prefix = "/proxy/network" } @@ -73,29 +91,32 @@ export default async function unifiProxyHandler(req, res) { const params = { method: "GET", headers: {} }; setCookieHeader(url, params); - let [status, contentType, data, responseHeaders] = await httpProxy(url, params); + [status, contentType, data, responseHeaders] = await httpProxy(url, params); if (status === 401) { + logger.debug("Unifi isn't logged in or rejected the reqeust, attempting login."); [status, contentType, data, responseHeaders] = await login(widget); if (status !== 200) { - logger.error("HTTP %d logging in to Unifi. Data: %s", status, data); + logger.error("HTTP %d logging in to Unifi. Data: %s", status, data); return res.status(status).end(data); } const json = JSON.parse(data.toString()); - if (json?.meta?.rc !== "ok") { + if (!(json?.meta?.rc === "ok" || json.login_time)) { logger.error("Error logging in to Unifi: Data: %s", data); return res.status(401).end(data); } addCookieToJar(url, responseHeaders); setCookieHeader(url, params); + + logger.debug("Retrying Unifi reqeust after login."); + [status, contentType, data, responseHeaders] = await httpProxy(url, params); } - [status, contentType, data] = await httpProxy(url, params); if (status !== 200) { - logger.error("HTTP %d getting data from Unifi. Data: %s", status, data); + logger.error("HTTP %d getting data from Unifi endpoint %s. Data: %s", status, url.href, data); } if (contentType) res.setHeader("Content-Type", contentType); diff --git a/src/widgets/unifi/widget.js b/src/widgets/unifi/widget.js index 928ebd766..f4b1366fe 100644 --- a/src/widgets/unifi/widget.js +++ b/src/widgets/unifi/widget.js @@ -1,7 +1,7 @@ import unifiProxyHandler from "./proxy"; const widget = { - api: "{url}{prefix}/api/{endpoint}", + api: "https://{host}:{port}{prefix}/api/{endpoint}", proxyHandler: unifiProxyHandler, mappings: { From e03822df6eabc2ab98272c656112cb9823e0e5a4 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Sat, 8 Oct 2022 00:45:00 -0700 Subject: [PATCH 06/15] Add UniFI console service widget --- public/locales/en/common.json | 4 +++ src/widgets/components.js | 1 + src/widgets/unifi/component.jsx | 56 +++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 src/widgets/unifi/component.jsx diff --git a/public/locales/en/common.json b/public/locales/en/common.json index af8fc1974..669ebd276 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -36,6 +36,10 @@ "uptime": "System Uptime", "days": "Days", "wan": "WAN", + "lan": "LAN", + "wlan": "WLAN", + "up": "UP", + "down": "DOWN", "wait": "Please wait" }, "docker": { diff --git a/src/widgets/components.js b/src/widgets/components.js index 5357c0704..0ff197afb 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -32,6 +32,7 @@ const components = { tautulli: dynamic(() => import("./tautulli/component")), traefik: dynamic(() => import("./traefik/component")), transmission: dynamic(() => import("./transmission/component")), + unifi_console: dynamic(() => import("./unifi/component")), }; export default components; diff --git a/src/widgets/unifi/component.jsx b/src/widgets/unifi/component.jsx new file mode 100644 index 000000000..82b339415 --- /dev/null +++ b/src/widgets/unifi/component.jsx @@ -0,0 +1,56 @@ +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 { t } = useTranslation(); + + const { widget } = service; + + const { data: statsData, error: statsError } = useWidgetAPI(widget, "stat/sites"); + + if (statsError || statsData?.error) { + return ; + } + + const defaultSite = statsData?.data?.find(s => s.name === "default"); + + if (!defaultSite) { + return ( + + + + + + + ); + } + + const wan = defaultSite.health.find(h => h.subsystem === "wan"); + const lan = defaultSite.health.find(h => h.subsystem === "lan"); + const wlan = defaultSite.health.find(h => h.subsystem === "wlan"); + const data = { + name: wan.gw_name, + uptime: wan["gw_system-stats"].uptime, + up: wan.status === 'ok', + wlan: { + users: wlan.num_user, + status: wlan.status + }, + lan: { + users: lan.num_user, + status: lan.status + } + }; + + return ( + + + + + + + ); +} From 2bd9c8eddcca78bc30817e33f1388d17fd0231b6 Mon Sep 17 00:00:00 2001 From: Jason Fischer Date: Sat, 8 Oct 2022 13:52:07 -0700 Subject: [PATCH 07/15] Cache console version check result --- src/widgets/unifi/component.jsx | 10 +++++-- src/widgets/unifi/proxy.js | 52 ++++++++++++++++----------------- src/widgets/unifi/widget.js | 2 +- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/src/widgets/unifi/component.jsx b/src/widgets/unifi/component.jsx index 82b339415..0ab862bd6 100644 --- a/src/widgets/unifi/component.jsx +++ b/src/widgets/unifi/component.jsx @@ -45,12 +45,16 @@ export default function Component({ service }) { } }; + const uptime = `${t("common.number", { value: data.uptime / 86400, maximumFractionDigits: 1 })} ${t("unifi.days")}`; + const lanUsers = `${t("common.number", { value: data.lan.users })} ${t("unifi.users")}`; + const wanUsers = `${t("common.number", { value: data.wlan.users })} ${t("unifi.users")}`; + return ( - + - - + + ); } diff --git a/src/widgets/unifi/proxy.js b/src/widgets/unifi/proxy.js index 914460512..181f43d7a 100644 --- a/src/widgets/unifi/proxy.js +++ b/src/widgets/unifi/proxy.js @@ -1,3 +1,5 @@ +import cache from "memory-cache"; + import { formatApiCall } from "utils/proxy/api-helpers"; import { httpProxy } from "utils/proxy/http"; import { addCookieToJar, setCookieHeader } from "utils/proxy/cookie-jar"; @@ -6,7 +8,10 @@ import getServiceWidget from "utils/config/service-helpers"; import createLogger from "utils/logger"; import widgets from "widgets/widgets"; -const logger = createLogger("unifiProxyHandler"); +const udmpPrefix = "/proxy/network"; +const proxyName = "unifiProxyHandler"; +const prefixCacheKey = `${proxyName}__prefix`; +const logger = createLogger(proxyName); async function getWidget(req) { const { group, service, type } = req.query; @@ -38,21 +43,18 @@ async function getWidget(req) { } async function login(widget) { - let endpoint = widget.udmp ? "auth/login" : "login"; - widget.prefix = ""; // never use prefix for login - const api = widgets?.[widget.type]?.api; - const loginUrl = new URL(formatApiCall(api, { endpoint, ...widget })); + let loginUrl = `${widget.url}/api`; + if (widget.prefix === udmpPrefix) { + loginUrl += "/auth" + } + loginUrl += "/login"; const loginBody = { username: widget.username, password: widget.password, remember: true }; - - const headers = { - "Content-Type": "application/json" - }; - + const headers = { "Content-Type": "application/json" }; const [status, contentType, data, responseHeaders] = await httpProxy(loginUrl, { method: "POST", body: JSON.stringify(loginBody), - headers + headers, }); return [status, contentType, data, responseHeaders]; } @@ -67,24 +69,21 @@ export default async function unifiProxyHandler(req, res) { if (!api) { return res.status(403).json({ error: "Service does not support API calls" }); } - - // determine if udm-pro from base url - let [status, contentType, data, responseHeaders] = await httpProxy(`https://${widget.host}`); - if (responseHeaders['x-csrf-token']) { - widget.udmp = true - } - if (!widget.port) { - widget.port = 8443; - if (widget.udmp) { - widget.port = 443 + let [status, contentType, data, responseHeaders] = []; + let prefix = cache.get(prefixCacheKey); + if (prefix === null) { + // auto detect if we're talking to a UDM Pro, and cache the result so that we + // don't make two requests each time data from Unifi is required + [status, contentType, data, responseHeaders] = await httpProxy(widget.url); + prefix = ""; + if (responseHeaders["x-csrf-token"]) { + prefix = udmpPrefix; } + cache.put(prefixCacheKey, prefix); } - widget.prefix = ""; - if (widget.udmp) { - widget.prefix = "/proxy/network" - } + widget.prefix = prefix; const { endpoint } = req.query; const url = new URL(formatApiCall(api, { endpoint, ...widget })); @@ -110,11 +109,10 @@ export default async function unifiProxyHandler(req, res) { addCookieToJar(url, responseHeaders); setCookieHeader(url, params); - logger.debug("Retrying Unifi reqeust after login."); + logger.debug("Retrying Unifi request after login."); [status, contentType, data, responseHeaders] = await httpProxy(url, params); } - if (status !== 200) { logger.error("HTTP %d getting data from Unifi endpoint %s. Data: %s", status, url.href, data); } diff --git a/src/widgets/unifi/widget.js b/src/widgets/unifi/widget.js index f4b1366fe..928ebd766 100644 --- a/src/widgets/unifi/widget.js +++ b/src/widgets/unifi/widget.js @@ -1,7 +1,7 @@ import unifiProxyHandler from "./proxy"; const widget = { - api: "https://{host}:{port}{prefix}/api/{endpoint}", + api: "{url}{prefix}/api/{endpoint}", proxyHandler: unifiProxyHandler, mappings: { From 7ee3113d8a2525f3b46f5c3fa9d98bfc67e86001 Mon Sep 17 00:00:00 2001 From: Jason Fischer Date: Sat, 8 Oct 2022 13:58:19 -0700 Subject: [PATCH 08/15] Rename unifi_console service widget to unifi - This rename is necessary as the Unifi service widet gets its config data from a different location than the unifi_console information widget --- src/widgets/components.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/components.js b/src/widgets/components.js index 0ff197afb..c2a6705ed 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -32,7 +32,7 @@ const components = { tautulli: dynamic(() => import("./tautulli/component")), traefik: dynamic(() => import("./traefik/component")), transmission: dynamic(() => import("./transmission/component")), - unifi_console: dynamic(() => import("./unifi/component")), + unifi: dynamic(() => import("./unifi/component")), }; export default components; From 42da3eca28c9744eb99264b4e658120c1918d4b7 Mon Sep 17 00:00:00 2001 From: Jason Fischer Date: Sat, 8 Oct 2022 13:59:09 -0700 Subject: [PATCH 09/15] Standardize on double quoted strings --- src/widgets/unifi/proxy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/unifi/proxy.js b/src/widgets/unifi/proxy.js index 181f43d7a..43a2e46fa 100644 --- a/src/widgets/unifi/proxy.js +++ b/src/widgets/unifi/proxy.js @@ -17,7 +17,7 @@ async function getWidget(req) { const { group, service, type } = req.query; let widget = null; - if (type === 'unifi_console') { + if (type === "unifi_console") { const settings = getSettings(); widget = settings.unifi_console; if (!widget) { From fe1064b173be9a9093ed2afbfbdb0b4793c5c56e Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Sat, 8 Oct 2022 14:02:12 -0700 Subject: [PATCH 10/15] Rename unifi_console info widget to unifi --- .../{unifi_console/unifi_console.jsx => unifi/unifi.jsx} | 2 +- src/components/widgets/widget.jsx | 2 +- src/widgets/unifi/proxy.js | 6 +++--- src/widgets/widgets.js | 1 - 4 files changed, 5 insertions(+), 6 deletions(-) rename src/components/widgets/{unifi_console/unifi_console.jsx => unifi/unifi.jsx} (99%) diff --git a/src/components/widgets/unifi_console/unifi_console.jsx b/src/components/widgets/unifi/unifi.jsx similarity index 99% rename from src/components/widgets/unifi_console/unifi_console.jsx rename to src/components/widgets/unifi/unifi.jsx index 7427bd23c..ae7f1a7f3 100644 --- a/src/components/widgets/unifi_console/unifi_console.jsx +++ b/src/components/widgets/unifi/unifi.jsx @@ -9,7 +9,7 @@ export default function Widget({ options }) { const { t } = useTranslation(); // eslint-disable-next-line no-param-reassign - options.type = "unifi_console"; + options.type = "unifi"; const { data: statsData, error: statsError } = useWidgetAPI(options, "stat/sites"); if (statsError || statsData?.error) { diff --git a/src/components/widgets/widget.jsx b/src/components/widgets/widget.jsx index ac5353eb9..fc5a72a53 100644 --- a/src/components/widgets/widget.jsx +++ b/src/components/widgets/widget.jsx @@ -10,7 +10,7 @@ const widgetMappings = { greeting: dynamic(() => import("components/widgets/greeting/greeting")), datetime: dynamic(() => import("components/widgets/datetime/datetime")), logo: dynamic(() => import("components/widgets/logo/logo"), { ssr: false }), - unifi_console: dynamic(() => import("components/widgets/unifi_console/unifi_console")), + unifi: dynamic(() => import("components/widgets/unifi/unifi")), }; export default function Widget({ widget }) { diff --git a/src/widgets/unifi/proxy.js b/src/widgets/unifi/proxy.js index 43a2e46fa..61682ef8a 100644 --- a/src/widgets/unifi/proxy.js +++ b/src/widgets/unifi/proxy.js @@ -17,11 +17,11 @@ async function getWidget(req) { const { group, service, type } = req.query; let widget = null; - if (type === "unifi_console") { + if (type === "unifi") { const settings = getSettings(); - widget = settings.unifi_console; + widget = settings.unifi; if (!widget) { - logger.debug("There is no unifi_console section in settings.yaml"); + logger.debug("There is no unifi section in settings.yaml"); return null; } widget.type = "unifi"; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index a4cab76b0..c922a9458 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -61,7 +61,6 @@ const widgets = { traefik, transmission, unifi, - unifi_console: unifi }; export default widgets; From d1b6dad14dca425b94c8acfc82d5559ed718c9b0 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Sat, 8 Oct 2022 14:36:08 -0700 Subject: [PATCH 11/15] Infer unifi port, use api widget property for login --- src/widgets/unifi/proxy.js | 16 ++++++++++------ src/widgets/unifi/widget.js | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/widgets/unifi/proxy.js b/src/widgets/unifi/proxy.js index 61682ef8a..601e42421 100644 --- a/src/widgets/unifi/proxy.js +++ b/src/widgets/unifi/proxy.js @@ -43,12 +43,9 @@ async function getWidget(req) { } async function login(widget) { - let loginUrl = `${widget.url}/api`; - if (widget.prefix === udmpPrefix) { - loginUrl += "/auth" - } - loginUrl += "/login"; - + const endpoint = (widget.prefix === udmpPrefix) ? "auth/login" : "login"; + const api = widgets?.[widget.type]?.api?.replace("{prefix}", ""); // no prefix for login url + const loginUrl = new URL(formatApiCall(api, { endpoint, ...widget })); const loginBody = { username: widget.username, password: widget.password, remember: true }; const headers = { "Content-Type": "application/json" }; const [status, contentType, data, responseHeaders] = await httpProxy(loginUrl, { @@ -85,6 +82,13 @@ export default async function unifiProxyHandler(req, res) { widget.prefix = prefix; + if (!widget.port) { + widget.port = 8443; + if (widget.prefix == udmpPrefix) { + widget.port = 443 + } + } + const { endpoint } = req.query; const url = new URL(formatApiCall(api, { endpoint, ...widget })); const params = { method: "GET", headers: {} }; diff --git a/src/widgets/unifi/widget.js b/src/widgets/unifi/widget.js index 928ebd766..0ac3c311e 100644 --- a/src/widgets/unifi/widget.js +++ b/src/widgets/unifi/widget.js @@ -1,7 +1,7 @@ import unifiProxyHandler from "./proxy"; const widget = { - api: "{url}{prefix}/api/{endpoint}", + api: "{url}:{port}{prefix}/api/{endpoint}", proxyHandler: unifiProxyHandler, mappings: { From fbac27f504f07ce2a3557d8f70e6fe424a8ecbad Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Sat, 8 Oct 2022 17:47:52 -0700 Subject: [PATCH 12/15] Fix unifi service lan/wlan labels --- src/widgets/unifi/component.jsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/widgets/unifi/component.jsx b/src/widgets/unifi/component.jsx index 0ab862bd6..aa4204e4f 100644 --- a/src/widgets/unifi/component.jsx +++ b/src/widgets/unifi/component.jsx @@ -15,6 +15,9 @@ export default function Component({ service }) { return ; } + const wlanLabel = t("unifi.wlan") + " " + t("unifi.users") + const lanLabel = t("unifi.lan") + " " + t("unifi.users") + const defaultSite = statsData?.data?.find(s => s.name === "default"); if (!defaultSite) { @@ -22,8 +25,8 @@ export default function Component({ service }) { - - + + ); } @@ -46,15 +49,13 @@ export default function Component({ service }) { }; const uptime = `${t("common.number", { value: data.uptime / 86400, maximumFractionDigits: 1 })} ${t("unifi.days")}`; - const lanUsers = `${t("common.number", { value: data.lan.users })} ${t("unifi.users")}`; - const wanUsers = `${t("common.number", { value: data.wlan.users })} ${t("unifi.users")}`; return ( - - + + ); } From 69c9a449b11740ddf16a0c6384b498fe99cc2f4f Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 9 Oct 2022 09:15:46 -0700 Subject: [PATCH 13/15] Revert "Rename unifi_console info widget to unifi" --- .../{unifi/unifi.jsx => unifi_console/unifi_console.jsx} | 2 +- src/components/widgets/widget.jsx | 2 +- src/widgets/unifi/proxy.js | 6 +++--- src/widgets/widgets.js | 1 + 4 files changed, 6 insertions(+), 5 deletions(-) rename src/components/widgets/{unifi/unifi.jsx => unifi_console/unifi_console.jsx} (99%) diff --git a/src/components/widgets/unifi/unifi.jsx b/src/components/widgets/unifi_console/unifi_console.jsx similarity index 99% rename from src/components/widgets/unifi/unifi.jsx rename to src/components/widgets/unifi_console/unifi_console.jsx index ae7f1a7f3..7427bd23c 100644 --- a/src/components/widgets/unifi/unifi.jsx +++ b/src/components/widgets/unifi_console/unifi_console.jsx @@ -9,7 +9,7 @@ export default function Widget({ options }) { const { t } = useTranslation(); // eslint-disable-next-line no-param-reassign - options.type = "unifi"; + options.type = "unifi_console"; const { data: statsData, error: statsError } = useWidgetAPI(options, "stat/sites"); if (statsError || statsData?.error) { diff --git a/src/components/widgets/widget.jsx b/src/components/widgets/widget.jsx index fc5a72a53..ac5353eb9 100644 --- a/src/components/widgets/widget.jsx +++ b/src/components/widgets/widget.jsx @@ -10,7 +10,7 @@ const widgetMappings = { greeting: dynamic(() => import("components/widgets/greeting/greeting")), datetime: dynamic(() => import("components/widgets/datetime/datetime")), logo: dynamic(() => import("components/widgets/logo/logo"), { ssr: false }), - unifi: dynamic(() => import("components/widgets/unifi/unifi")), + unifi_console: dynamic(() => import("components/widgets/unifi_console/unifi_console")), }; export default function Widget({ widget }) { diff --git a/src/widgets/unifi/proxy.js b/src/widgets/unifi/proxy.js index 601e42421..60ef44035 100644 --- a/src/widgets/unifi/proxy.js +++ b/src/widgets/unifi/proxy.js @@ -17,11 +17,11 @@ async function getWidget(req) { const { group, service, type } = req.query; let widget = null; - if (type === "unifi") { + if (type === "unifi_console") { const settings = getSettings(); - widget = settings.unifi; + widget = settings.unifi_console; if (!widget) { - logger.debug("There is no unifi section in settings.yaml"); + logger.debug("There is no unifi_console section in settings.yaml"); return null; } widget.type = "unifi"; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index c922a9458..a4cab76b0 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -61,6 +61,7 @@ const widgets = { traefik, transmission, unifi, + unifi_console: unifi }; export default widgets; From 86b12debc58e5c9a8ba1cc3ac25b89e2d5c33ddb Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 9 Oct 2022 09:17:50 -0700 Subject: [PATCH 14/15] Revert "Infer unifi port" --- src/widgets/unifi/proxy.js | 7 ------- src/widgets/unifi/widget.js | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/widgets/unifi/proxy.js b/src/widgets/unifi/proxy.js index 60ef44035..53ee49f03 100644 --- a/src/widgets/unifi/proxy.js +++ b/src/widgets/unifi/proxy.js @@ -82,13 +82,6 @@ export default async function unifiProxyHandler(req, res) { widget.prefix = prefix; - if (!widget.port) { - widget.port = 8443; - if (widget.prefix == udmpPrefix) { - widget.port = 443 - } - } - const { endpoint } = req.query; const url = new URL(formatApiCall(api, { endpoint, ...widget })); const params = { method: "GET", headers: {} }; diff --git a/src/widgets/unifi/widget.js b/src/widgets/unifi/widget.js index 0ac3c311e..928ebd766 100644 --- a/src/widgets/unifi/widget.js +++ b/src/widgets/unifi/widget.js @@ -1,7 +1,7 @@ import unifiProxyHandler from "./proxy"; const widget = { - api: "{url}:{port}{prefix}/api/{endpoint}", + api: "{url}{prefix}/api/{endpoint}", proxyHandler: unifiProxyHandler, mappings: { From 04da8f3925f43f9695805d6b1118e18b4cba37ed Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 9 Oct 2022 15:13:27 -0700 Subject: [PATCH 15/15] lint --- src/widgets/unifi/component.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widgets/unifi/component.jsx b/src/widgets/unifi/component.jsx index aa4204e4f..8a654a516 100644 --- a/src/widgets/unifi/component.jsx +++ b/src/widgets/unifi/component.jsx @@ -15,8 +15,8 @@ export default function Component({ service }) { return ; } - const wlanLabel = t("unifi.wlan") + " " + t("unifi.users") - const lanLabel = t("unifi.lan") + " " + t("unifi.users") + const wlanLabel = `${t("unifi.wlan")} ${t("unifi.users")}` + const lanLabel = `${t("unifi.lan")} ${t("unifi.users")}` const defaultSite = statsData?.data?.find(s => s.name === "default");