From a9cc0100f6f337d459546dd37ca8cb3bed0f9d91 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 21 Dec 2022 12:08:34 -0800 Subject: [PATCH 1/4] Squashed commit of the following from initial Omada widget: commit ad3e664b56ad2a9024684f2141f8f21ead59177e Author: Benoit <oupsman@oupsman.fr> Date: Tue Dec 13 19:54:54 2022 +0100 Add .idea to .gitignore commit 7e51a093845c6bcda4fac78a2c174adbcc7701b6 Merge: 93d8035 7dd0b0e Author: Benoit SERRA <oupsman@oupsman.fr> Date: Tue Dec 13 18:38:51 2022 +0100 Merge branch 'benphelps:main' into main commit 93d80350b1d4519ac217b880568ccabbef43f03f Author: Benoit <oupsman@oupsman.fr> Date: Tue Dec 13 18:15:20 2022 +0100 Omada widget : One widget, shows only the number alerts, the number of connected AP, the number of connected devices to Wifi, the number of connected switches and gatewawys. commit a1babd860ce8a2dd6981b7fef6e055e249cbccb2 Author: Benoit <oupsman@oupsman.fr> Date: Tue Dec 13 09:33:50 2022 +0100 Omada widget : spliting widget between WLAN and LAN/WAN fields to have no more than 5 fields per widget. commit e12cc65c7703f2f1930fc7646c1f4386b49e73fb Merge: 331f31f 146326f Author: Benoit SERRA <oupsman@oupsman.fr> Date: Sun Dec 11 14:39:27 2022 +0100 Merge branch 'benphelps:main' into main commit 331f31fc2be80e0738869fd050b3034638979350 Merge: 37154e3 ccc1229 Author: Benoit SERRA <oupsman@oupsman.fr> Date: Sat Dec 10 17:56:44 2022 +0100 Merge branch 'benphelps:main' into main commit 37154e327af7d3fe66e7638ba79851ef789d3649 Author: Benoit <oupsman@oupsman.fr> Date: Sat Dec 10 17:11:30 2022 +0100 Omada widget : Improved error handling Omada widget: handling power as common.power in translation commit 1f484914067e514f22c1f250b62f589fedefe1fd Author: Benoit <oupsman@oupsman.fr> Date: Sat Dec 10 10:24:55 2022 +0100 Omada widget : adding stats for isolated aps, connected gateways, connected switches, available ports, power consumption commit f375f0b815bf6697f7044995203084427b14ea69 Merge: 467b678 775b511 Author: Benoit <oupsman@oupsman.fr> Date: Fri Dec 9 21:06:38 2022 +0100 Merge branch 'main' of https://github.com/Oupsman/homepage into main commit 467b67802a7b8dface01703b6035951dcaa1e069 Author: Benoit <oupsman@oupsman.fr> Date: Fri Dec 9 21:06:09 2022 +0100 Omada widget : v3 v4 and v5 versions don't use the same fields for the same stats, I've corrected the code to make it more reliable commit 775b5111e13072a18edb33f30a4d4f0589bc2af0 Merge: 8d66756 88c4375 Author: Benoit SERRA <oupsman@oupsman.fr> Date: Thu Dec 8 15:38:20 2022 +0100 Merge branch 'benphelps:main' into main commit 8d66756a7d8f9e0b43c8abde2f2e6f2fd86a1098 Author: Benoit <oupsman@oupsman.fr> Date: Thu Dec 8 12:45:44 2022 +0100 Omada Widget : code cleanup commit 282a6d0592c53a39184d63bba517f482a84f5b36 Author: Benoit <oupsman@oupsman.fr> Date: Thu Dec 8 12:42:41 2022 +0100 Omada Widget : code cleanup commit c3e9b8f87075e834ea1a520fd8c93708afb15ac0 Author: Benoit <oupsman@oupsman.fr> Date: Thu Dec 8 12:37:10 2022 +0100 Omada Widget : No more legacy variable, the code detects the controller version and adapts the requests. Logic is not duplicated anymore commit eafcc205975cc1bd04f063f1627d380f2a2b7f04 Author: Benoit <oupsman@oupsman.fr> Date: Wed Dec 7 15:46:00 2022 +0100 V2 API is working commit bcc2864ee2e1f0f1d2f4c009df1ba8a1a7244f80 Author: Benoit <oupsman@oupsman.fr> Date: Wed Dec 7 10:01:26 2022 +0100 Code fore v2 API is not working but V1 code is. commit ea8e297e849c2ef5659bfec94d76d2fff8677c4c Author: Benoit <oupsman@oupsman.fr> Date: Tue Dec 6 14:28:05 2022 +0100 Errors handling commit ab6d51a88c8737654dd31bec46106d7c49ed39d2 Author: Benoit <oupsman@oupsman.fr> Date: Tue Dec 6 09:50:14 2022 +0100 Adding alerts commit 047db2cce867c0207be7fe0827b24107fdd84923 Author: Benoit <oupsman@oupsman.fr> Date: Mon Dec 5 22:53:43 2022 +0100 Fixed translation system commit 42c5a3e6658f22662b1c58f54cba31dc90bfbc61 Author: Benoit <oupsman@oupsman.fr> Date: Mon Dec 5 22:34:34 2022 +0100 Translation system is still * up commit c80eac9d5bd5491ec4a61da38cdaf82f0ea1cc2f Author: Benoit <oupsman@oupsman.fr> Date: Mon Dec 5 22:33:50 2022 +0100 Translation system is still * up commit f8ba6b02454d66eb96e7e6ebd8022492ff79e690 Author: Benoit <oupsman@oupsman.fr> Date: Mon Dec 5 22:32:22 2022 +0100 Translation system is still * up commit dec7eec6de26298eb7c90fd01e1c0fd23b6469a4 Author: Benoit <oupsman@oupsman.fr> Date: Mon Dec 5 22:16:13 2022 +0100 Translation system is * up commit cc840cf7ccb40509f1da3f521a4a1b3e26498ac0 Author: Benoit <oupsman@oupsman.fr> Date: Mon Dec 5 21:33:00 2022 +0100 First working version commit 54b65e619e41c9963a614a177df3a4af68ebe77d Author: Benoit <oupsman@oupsman.fr> Date: Mon Dec 5 18:59:09 2022 +0100 Using getGlobalStat method commit 7ebc8500da9d52bd2911a620179fb6585f044c47 Author: Benoit <oupsman@oupsman.fr> Date: Mon Dec 5 14:33:37 2022 +0100 Working on Omada Widget : NOT WORKING FOR NOW commit 04eaf28cae1be0935cb190e50ae5b75c19254403 Merge: 61065ac 826fe15 Author: Benoit <oupsman@oupsman.fr> Date: Mon Dec 5 10:32:30 2022 +0100 Merge branch 'main' of https://github.com/Oupsman/homepage into main commit 61065ace2887c3c1d6486001d34ce6f58d58958d Author: Benoit <oupsman@oupsman.fr> Date: Mon Dec 5 10:24:57 2022 +0100 Working on Omada Widget remove idea Co-Authored-By: Benoit SERRA <11260343+oupsman@users.noreply.github.com> --- public/locales/en/common.json | 10 ++ src/widgets/components.js | 1 + src/widgets/omada/component.jsx | 41 +++++ src/widgets/omada/proxy.js | 272 ++++++++++++++++++++++++++++++++ src/widgets/omada/widget.js | 15 ++ src/widgets/widgets.js | 2 + 6 files changed, 341 insertions(+) create mode 100644 src/widgets/omada/component.jsx create mode 100644 src/widgets/omada/proxy.js create mode 100644 src/widgets/omada/widget.js diff --git a/public/locales/en/common.json b/public/locales/en/common.json index d8bfb039..78c5dce8 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -88,6 +88,16 @@ "bitrate": "Bitrate", "no_active": "No Active Streams" }, + "omada": { + "activeUser": "Active devices", + "alerts": "Alerts", + "connectedAp": "Connected APs", + "isolatedAp": "Isolated APs", + "powerConsumption": "Power consumption", + "availablePorts" : "Available ports", + "connectedGateway": "Connected gateways", + "connectedSwitches": "Connected switches" + }, "nzbget": { "rate": "Rate", "remaining": "Remaining", diff --git a/src/widgets/components.js b/src/widgets/components.js index eb7c686f..9f3011b3 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -26,6 +26,7 @@ const components = { nextdns: dynamic(() => import("./nextdns/component")), npm: dynamic(() => import("./npm/component")), nzbget: dynamic(() => import("./nzbget/component")), + omada: dynamic(() => import("./omada/component")), ombi: dynamic(() => import("./ombi/component")), overseerr: dynamic(() => import("./overseerr/component")), paperlessngx: dynamic(() => import("./paperlessngx/component")), diff --git a/src/widgets/omada/component.jsx b/src/widgets/omada/component.jsx new file mode 100644 index 00000000..d499da36 --- /dev/null +++ b/src/widgets/omada/component.jsx @@ -0,0 +1,41 @@ +import { useTranslation } from "next-i18next"; + +import useWidgetAPI from "../../utils/proxy/use-widget-api"; +import Container from "../../components/services/widget/container"; +import Block from "../../components/services/widget/block"; + +export default function Component({ service }) { + const { t } = useTranslation(); + + const { widget } = service; + + const { data: omadaData, error: omadaAPIError } = useWidgetAPI(widget, "stats", { + refreshInterval: 5000, + }); + + if (omadaAPIError) { + return <Container error={omadaAPIError} />; + } + + if (!omadaData) { + return ( + <Container service={service}> + <Block label="omada.connectedAp" /> + <Block label="omada.activeUser" /> + <Block label="omada.alerts" /> + <Block label="omada.connectedGateway" /> + <Block label="omada.connectedSwitches" /> + </Container> + ); + } + + return ( + <Container service={service}> + <Block label="omada.connectedAp" value={t( "common.number", { value: omadaData.connectedAp})} /> + <Block label="omada.activeUser" value={t( "common.number", { value: omadaData.activeUser })} /> + <Block label="omada.alerts" value={t( "common.number", { value: omadaData.alerts })} /> + { omadaData.connectedGateways > 0 && <Block label="omada.connectedGateway" value={t("common.number", { value: omadaData.connectedGateways})} /> } + { omadaData.connectedSwitches > 0 && <Block label="omada.connectedSwitches" value={t("common.number", { value: omadaData.connectedSwitches})} /> } + </Container> + ); +} diff --git a/src/widgets/omada/proxy.js b/src/widgets/omada/proxy.js new file mode 100644 index 00000000..58263052 --- /dev/null +++ b/src/widgets/omada/proxy.js @@ -0,0 +1,272 @@ + +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 = "omadaProxyHandler"; + +const logger = createLogger(proxyName); + + +async function login(loginUrl, username, password, cversion) { + let params; + if (cversion < "4.0.0") { + // change the parameters of the query string + params = JSON.stringify({ + "method": "login", + "params": { + "name": username, + "password": password + } + }); + } else { + params = JSON.stringify({ + "username": username, + "password": password + }); + } + const authResponse = await httpProxy(loginUrl, { + method: "POST", + body: params, + headers: { + "Content-Type": "application/json", + }, + }); + + const data = JSON.parse(authResponse[2]); + const status = authResponse[0]; + let token; + if (data.errorCode === 0) { + token = data.result.token; + } else { + token = null; + } + return [status, token ?? data]; +} + + +export default async function omadaProxyHandler(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 cid; + let cversion; + let connectedAp; + let activeuser; + let connectedSwitches; + let connectedGateways; + + let alerts; + let loginUrl; + let siteName; + let requestresponse; + + const {url} = widget; + + const controllerInfoUrl = `${widget.url}/api/info`; + + const cInfoResponse = await httpProxy(controllerInfoUrl, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + + if (cInfoResponse[0] === 500) { + logger.debug("Getting controller version ends with Error 500"); + return res.status(cInfoResponse[0]).json({error: {message: "HTTP Error", controllerInfoUrl, data: cInfoResponse[2]}}); + + } + const cidresult = cInfoResponse[2]; + + try { + cid = JSON.parse(cidresult).result.omadacId; + cversion = JSON.parse(cidresult).result.controllerVer; + } catch (e) { + cversion = "3.2.17" + } + if (cversion < "4.0.0") { + loginUrl = `${widget.url}/api/user/login?ajax`; + } else if (cversion < "5.0.0") { + loginUrl = `${widget.url}/api/v2/login`; + } else { + loginUrl = `${widget.url}/${cid}/api/v2/login`; + } + requestresponse = await login(loginUrl, widget.username, widget.password, cversion); + + if (requestresponse[1].errorCode) { + return res.status(requestresponse[0]).json({error: {message: "Error logging in", url, data: requestresponse[1]}}); + } + + const token = requestresponse[1]; + // Switching to the site we want to gather stats from + // First, we get the list of sites + let sitesUrl; + let body; + let params; + let headers; + let method; + let sitetoswitch; + if (cversion < "4.0.0") { + sitesUrl = `${widget.url}/web/v1/controller?ajax=&token=${token}`; + body = JSON.stringify({ + "method": "getUserSites", + "params": { + "userName": widget.username + }}); + params = { "token": token }; + headers = { }; + method = "POST"; + } else if (cversion < "5.0.0") { + sitesUrl = `${widget.url}/api/v2/sites?token=${token}¤tPage=1¤tPageSize=1000`; + body = {}; + params = {"token": token}; + headers = {"Csrf-Token": token }; + method = "GET"; + + } else { + sitesUrl = `${widget.url}/${cid}/api/v2/sites?token=${token}¤tPage=1¤tPageSize=1000`; + body = {}; + headers = { "Csrf-Token": token }; + method = "GET"; + params = { }; + } + requestresponse = await httpProxy(sitesUrl, { + method, + params, + body: body.toString(), + headers, + }); + const listresult = JSON.parse(requestresponse[2]); + if (listresult.errorCode !== 0) { + logger.debug(`HTTTP ${requestresponse[0]} getting sites list: ${requestresponse[2].msg}`); + return res.status(requestresponse[0]).json({error: {message: "Error getting sites list", url, data: requestresponse[2]}}); + } + + // Switching site is really needed only for Omada 3.x.x controllers + + let switchUrl; + + if (cversion < "4.0.0") { + sitetoswitch = listresult.result.siteList.filter(site => site.name === widget.site); + siteName = sitetoswitch[0].siteName; + switchUrl = `${widget.url}/web/v1/controller?ajax=&token=${token}`; + method = "POST"; + body = JSON.stringify({ + "method": "switchSite", + "params": { + "siteName": siteName, + "userName": widget.username + } + }); + headers = { "Content-Type": "application/json" }; + params = { "token": token }; + requestresponse = await httpProxy(switchUrl, { + method, + params, + body: body.toString(), + headers, + }); + const switchresult = JSON.parse(requestresponse[2]); + if (switchresult.errorCode !== 0) { + logger.debug(`HTTTP ${requestresponse[0]} getting sites list: ${requestresponse[2]}`); + return res.status(requestresponse[0]).json({error: {message: "Error switching site", url, data: requestresponse[2]}}); + } + } + + // OK now we are on the correct site. Let's get the stats + // on modern controller, we need to call two different endpoints + // on older controller, we can call one endpoint + if (cversion < "4.0.0") { + const statsUrl = `${widget.url}/web/v1/controller?getGlobalStat=&token=${token}`; + const statResponse = await httpProxy(statsUrl, { + method: "POST", + params: { "token": token }, + body: JSON.stringify({ + "method": "getGlobalStat", + }), + headers: { + "Content-Type": "application/json", + }, + }); + + const data = JSON.parse(statResponse[2]); + + if (data.errorCode !== 0) { + return res.status(statResponse[0]).json({error: {message: "Error getting stats", url, data: statResponse[2]}}); + } + connectedAp = data.result.connectedAp; + activeuser = data.result.activeUser; + alerts = data.result.alerts; + + } else { + let siteStatsUrl; + let response; + sitetoswitch = listresult.result.data.filter(site => site.name === widget.site); + + if (sitetoswitch.length === 0) { + return res.status(requestresponse[0]).json({error: {message: `Site ${widget.site} is not found`, url, data: requestresponse[2]}}); + } + + // On 5.0.0, the field we need is id, on 4.x.x, it's key ... + siteName = sitetoswitch[0].id ?? sitetoswitch[0].key; + if (cversion < "5.0.0") { + siteStatsUrl = `${url}/api/v2/sites/${siteName}/dashboard/overviewDiagram?token=${token}¤tPage=1¤tPageSize=1000`; + } else { + siteStatsUrl = `${url}/${cid}/api/v2/sites/${siteName}/dashboard/overviewDiagram?token=${token}¤tPage=1¤tPageSize=1000`; + } + response = await httpProxy(siteStatsUrl, { + method: "GET", + headers: { + "Csrf-Token": token, + }, + }); + + const clientresult = JSON.parse(response[2]); + if (clientresult.errorCode !== 0) { + logger.debug(`HTTTP ${listresult.errorCode} getting clients stats for site ${widget.site} with message ${listresult.msg}`); + return res.status(500).send(response[2]); + } + + activeuser = clientresult.result.totalClientNum; + connectedAp = clientresult.result.connectedApNum; + connectedGateways = clientresult.result.connectedGatewayNum; + connectedSwitches = clientresult.result.connectedSwitchNum; + + + let alertUrl; + if (cversion >= "5.0.0") { + alertUrl = `${url}/${cid}/api/v2/sites/${siteName}/alerts/num?token=${token}¤tPage=1¤tPageSize=1000`; + } else { + alertUrl = `${url}/api/v2/sites/${siteName}/alerts/num?token=${token}¤tPage=1¤tPageSize=1000`; + } + response = await httpProxy(alertUrl, { + method: "GET", + headers: { + "Csrf-Token": token, + }, + }); + const alertresult = JSON.parse(response[2]); + alerts = alertresult.result.alertNum; + } + + return res.send(JSON.stringify({ + "connectedAp": connectedAp, + "activeUser": activeuser, + "alerts": alerts, + "connectedGateways": connectedGateways, + "connectedSwitches": connectedSwitches, + })); + } + } + return res.status(400).json({ error: "Invalid proxy service type" }); +} diff --git a/src/widgets/omada/widget.js b/src/widgets/omada/widget.js new file mode 100644 index 00000000..0ef4177e --- /dev/null +++ b/src/widgets/omada/widget.js @@ -0,0 +1,15 @@ +import omadaProxyHandler from "./proxy"; +// import genericProxyHandler from "../../utils/proxy/handlers/generic"; + +const widget = { + api: "{url}/web/v1/{endpoint}", + proxyHandler: omadaProxyHandler, + + mappings: { + stats: { + endpoint: "controller", + } + } +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 2b45e55a..c68dfe3e 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -21,6 +21,7 @@ import navidrome from "./navidrome/widget"; import nextdns from "./nextdns/widget"; import npm from "./npm/widget"; import nzbget from "./nzbget/widget"; +import omada from "./omada/widget"; import ombi from "./ombi/widget"; import overseerr from "./overseerr/widget"; import paperlessngx from "./paperlessngx/widget"; @@ -73,6 +74,7 @@ const widgets = { nextdns, npm, nzbget, + omada, ombi, overseerr, paperlessngx, From 4a3f836020b8461c4baaa39d5e0fc24d3e08144f Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 21 Dec 2022 12:20:17 -0800 Subject: [PATCH 2/4] Refactor Omada proxy for v4/v5 --- src/widgets/omada/proxy.js | 291 ++++++++++++++++++------------------- 1 file changed, 139 insertions(+), 152 deletions(-) diff --git a/src/widgets/omada/proxy.js b/src/widgets/omada/proxy.js index 58263052..d5f4d9ba 100644 --- a/src/widgets/omada/proxy.js +++ b/src/widgets/omada/proxy.js @@ -9,40 +9,26 @@ const proxyName = "omadaProxyHandler"; const logger = createLogger(proxyName); -async function login(loginUrl, username, password, cversion) { - let params; - if (cversion < "4.0.0") { - // change the parameters of the query string - params = JSON.stringify({ - "method": "login", - "params": { - "name": username, - "password": password - } - }); - } else { - params = JSON.stringify({ - "username": username, - "password": password - }); +async function login(loginUrl, username, password, controllerVersionMajor) { + const params = { + username: username, + password: password } - const authResponse = await httpProxy(loginUrl, { - method: "POST", - body: params, - headers: { - "Content-Type": "application/json", - }, - }); - const data = JSON.parse(authResponse[2]); - const status = authResponse[0]; - let token; - if (data.errorCode === 0) { - token = data.result.token; - } else { - token = null; + if (controllerVersionMajor < 4) { + params.method = "login"; + params.name = username; } - return [status, token ?? data]; + + const [status, contentType, data] = await httpProxy(loginUrl, { + method: "POST", + body: JSON.stringify(params), + headers: { + "Content-Type": "application/json", + }, + }); + + return [status, JSON.parse(data.toString())]; } @@ -57,108 +43,121 @@ export default async function omadaProxyHandler(req, res) { } if (widget) { - let cid; - let cversion; - let connectedAp; - let activeuser; - let connectedSwitches; - let connectedGateways; - - let alerts; - let loginUrl; - let siteName; - let requestresponse; const {url} = widget; - const controllerInfoUrl = `${widget.url}/api/info`; + const controllerInfoURL = `${widget.url}/api/info`; - const cInfoResponse = await httpProxy(controllerInfoUrl, { - method: "GET", + let [status, contentType, data] = await httpProxy(controllerInfoURL, { headers: { "Content-Type": "application/json", }, }); - - if (cInfoResponse[0] === 500) { - logger.debug("Getting controller version ends with Error 500"); - return res.status(cInfoResponse[0]).json({error: {message: "HTTP Error", controllerInfoUrl, data: cInfoResponse[2]}}); - + if (status !== 200) { + logger.error("Unable to retrieve Omada controller info"); + return res.status(status).json({error: {message: `HTTP Error ${status}`, url: controllerInfoURL, data: data}}); } - const cidresult = cInfoResponse[2]; + + const cId = JSON.parse(data).result.omadacId; + let controllerVersion; try { - cid = JSON.parse(cidresult).result.omadacId; - cversion = JSON.parse(cidresult).result.controllerVer; + controllerVersion = JSON.parse(data).result.controllerVer; } catch (e) { - cversion = "3.2.17" - } - if (cversion < "4.0.0") { - loginUrl = `${widget.url}/api/user/login?ajax`; - } else if (cversion < "5.0.0") { - loginUrl = `${widget.url}/api/v2/login`; - } else { - loginUrl = `${widget.url}/${cid}/api/v2/login`; - } - requestresponse = await login(loginUrl, widget.username, widget.password, cversion); - - if (requestresponse[1].errorCode) { - return res.status(requestresponse[0]).json({error: {message: "Error logging in", url, data: requestresponse[1]}}); + // fallback to this random version? + controllerVersion = "3.2.17" } - const token = requestresponse[1]; - // Switching to the site we want to gather stats from - // First, we get the list of sites + const controllerVersionMajor = parseInt(controllerVersion.split('.')[0], 10) + + if (![3,4,5].includes(controllerVersionMajor)) { + return res.status(500).json({error: {message: "Error determining controller version", data}}); + } + + let loginUrl; + + switch (controllerVersionMajor) { + case 3: + loginUrl = `${widget.url}/api/user/login?ajax`; + break; + case 4: + loginUrl = `${widget.url}/api/v2/login`; + break; + case 5: + loginUrl = `${widget.url}/${cId}/api/v2/login`; + break; + default: + break; + } + + const [loginStatus, loginResponseData] = await login(loginUrl, widget.username, widget.password, controllerVersionMajor); + + if (loginStatus !== 200 || loginResponseData.errorCode > 0) { + return res.status(requestresponse[0]).json({error: {message: "Error logging in to Oamda controller", url: loginUrl, data: loginResponseData}}); + } + + const token = loginResponseData.result.token; + + // List sites let sitesUrl; - let body; - let params; - let headers; - let method; - let sitetoswitch; - if (cversion < "4.0.0") { - sitesUrl = `${widget.url}/web/v1/controller?ajax=&token=${token}`; - body = JSON.stringify({ - "method": "getUserSites", - "params": { - "userName": widget.username - }}); - params = { "token": token }; - headers = { }; - method = "POST"; - } else if (cversion < "5.0.0") { - sitesUrl = `${widget.url}/api/v2/sites?token=${token}¤tPage=1¤tPageSize=1000`; - body = {}; - params = {"token": token}; - headers = {"Csrf-Token": token }; - method = "GET"; + let body = {}; + let params = { token }; + let headers = { "Csrf-Token": token }; + let method = "GET"; - } else { - sitesUrl = `${widget.url}/${cid}/api/v2/sites?token=${token}¤tPage=1¤tPageSize=1000`; - body = {}; - headers = { "Csrf-Token": token }; - method = "GET"; - params = { }; + switch (controllerVersionMajor) { + case 3: + sitesUrl = `${widget.url}/web/v1/controller?ajax=&token=${token}`; + body = { + "method": "getUserSites", + "params": { + "userName": widget.username + } + }; + method = "POST"; + break; + case 4: + sitesUrl = `${widget.url}/api/v2/sites?token=${token}¤tPage=1¤tPageSize=1000`; + break; + case 5: + sitesUrl = `${widget.url}/${cId}/api/v2/sites?token=${token}¤tPage=1¤tPageSize=1000`; + break; } - requestresponse = await httpProxy(sitesUrl, { + + [status, contentType, data] = await httpProxy(sitesUrl, { method, params, - body: body.toString(), + body: JSON.stringify(body), headers, }); - const listresult = JSON.parse(requestresponse[2]); - if (listresult.errorCode !== 0) { - logger.debug(`HTTTP ${requestresponse[0]} getting sites list: ${requestresponse[2].msg}`); - return res.status(requestresponse[0]).json({error: {message: "Error getting sites list", url, data: requestresponse[2]}}); + + const sitesResponseData = JSON.parse(data); + + let site; + + let connectedAp; + let activeUser; + let connectedSwitches; + let connectedGateways; + let alerts; + + if (sitesResponseData.errorCode > 0) { + logger.debug(`HTTTP ${status} getting sites list: ${requestresponse[2].msg}`); + return res.status(status).json({error: {message: "Error getting sites list", url, data: requestresponse[2]}}); } - // Switching site is really needed only for Omada 3.x.x controllers + // on modern controller, we need to call two different endpoints + // on older controller, we can call one endpoint - let switchUrl; + if (controllerVersionMajor === 3) { - if (cversion < "4.0.0") { - sitetoswitch = listresult.result.siteList.filter(site => site.name === widget.site); - siteName = sitetoswitch[0].siteName; + // Switching site is really needed only for Omada 3.x.x controllers + + let switchUrl; + + site = listresult.result.siteList.filter(site => site.name === widget.site); + siteName = site[0].siteName; switchUrl = `${widget.url}/web/v1/controller?ajax=&token=${token}`; method = "POST"; body = JSON.stringify({ @@ -181,12 +180,7 @@ export default async function omadaProxyHandler(req, res) { logger.debug(`HTTTP ${requestresponse[0]} getting sites list: ${requestresponse[2]}`); return res.status(requestresponse[0]).json({error: {message: "Error switching site", url, data: requestresponse[2]}}); } - } - - // OK now we are on the correct site. Let's get the stats - // on modern controller, we need to call two different endpoints - // on older controller, we can call one endpoint - if (cversion < "4.0.0") { + const statsUrl = `${widget.url}/web/v1/controller?getGlobalStat=&token=${token}`; const statResponse = await httpProxy(statsUrl, { method: "POST", @@ -205,68 +199,61 @@ export default async function omadaProxyHandler(req, res) { return res.status(statResponse[0]).json({error: {message: "Error getting stats", url, data: statResponse[2]}}); } connectedAp = data.result.connectedAp; - activeuser = data.result.activeUser; + activeUser = data.result.activeUser; alerts = data.result.alerts; - } else { - let siteStatsUrl; - let response; - sitetoswitch = listresult.result.data.filter(site => site.name === widget.site); + } else if (controllerVersionMajor === 4 || controllerVersionMajor === 5) { + site = sitesResponseData.result.data.find(site => site.name === widget.site); - if (sitetoswitch.length === 0) { - return res.status(requestresponse[0]).json({error: {message: `Site ${widget.site} is not found`, url, data: requestresponse[2]}}); + if (site.length === 0) { + return res.status(requestresponse[0]).json({error: {message: `Site ${widget.site} is not found`, url, data}}); } - // On 5.0.0, the field we need is id, on 4.x.x, it's key ... - siteName = sitetoswitch[0].id ?? sitetoswitch[0].key; - if (cversion < "5.0.0") { - siteStatsUrl = `${url}/api/v2/sites/${siteName}/dashboard/overviewDiagram?token=${token}¤tPage=1¤tPageSize=1000`; - } else { - siteStatsUrl = `${url}/${cid}/api/v2/sites/${siteName}/dashboard/overviewDiagram?token=${token}¤tPage=1¤tPageSize=1000`; - } - response = await httpProxy(siteStatsUrl, { - method: "GET", + const siteName = (controllerVersionMajor === 5) ? site.id : site.key; + const siteStatsUrl = (controllerVersionMajor === 4) ? + `${url}/api/v2/sites/${siteName}/dashboard/overviewDiagram?token=${token}¤tPage=1¤tPageSize=1000` : + `${url}/${cId}/api/v2/sites/${siteName}/dashboard/overviewDiagram?token=${token}¤tPage=1¤tPageSize=1000`; + + [status, contentType, data] = await httpProxy(siteStatsUrl, { headers: { "Csrf-Token": token, }, }); - const clientresult = JSON.parse(response[2]); - if (clientresult.errorCode !== 0) { - logger.debug(`HTTTP ${listresult.errorCode} getting clients stats for site ${widget.site} with message ${listresult.msg}`); - return res.status(500).send(response[2]); + const siteResponseData = JSON.parse(data); + + if (status !== 200 || siteResponseData.errorCode > 0) { + logger.debug(`HTTP ${status} getting stats for site ${widget.site} with message ${listresult.msg}`); + return res.status(500).send(data); } - activeuser = clientresult.result.totalClientNum; - connectedAp = clientresult.result.connectedApNum; - connectedGateways = clientresult.result.connectedGatewayNum; - connectedSwitches = clientresult.result.connectedSwitchNum; + activeUser = siteResponseData.result.totalClientNum; + connectedAp = siteResponseData.result.connectedApNum; + connectedGateways = siteResponseData.result.connectedGatewayNum; + connectedSwitches = siteResponseData.result.connectedSwitchNum; + const alertUrl = (controllerVersionMajor === 4) ? + `${url}/api/v2/sites/${siteName}/alerts/num?token=${token}¤tPage=1¤tPageSize=1000` : + `${url}/${cId}/api/v2/sites/${siteName}/alerts/num?token=${token}¤tPage=1¤tPageSize=1000`; - let alertUrl; - if (cversion >= "5.0.0") { - alertUrl = `${url}/${cid}/api/v2/sites/${siteName}/alerts/num?token=${token}¤tPage=1¤tPageSize=1000`; - } else { - alertUrl = `${url}/api/v2/sites/${siteName}/alerts/num?token=${token}¤tPage=1¤tPageSize=1000`; - } - response = await httpProxy(alertUrl, { - method: "GET", + [status, contentType, data] = await httpProxy(alertUrl, { headers: { "Csrf-Token": token, }, }); - const alertresult = JSON.parse(response[2]); - alerts = alertresult.result.alertNum; + const alertResponseData = JSON.parse(data); + alerts = alertResponseData.result.alertNum; } return res.send(JSON.stringify({ - "connectedAp": connectedAp, - "activeUser": activeuser, - "alerts": alerts, - "connectedGateways": connectedGateways, - "connectedSwitches": connectedSwitches, + connectedAp, + activeUser, + alerts, + connectedGateways, + connectedSwitches, })); } } + return res.status(400).json({ error: "Invalid proxy service type" }); } From b01e6eaf5661e94112fed7f012f387a6839167a2 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 21 Dec 2022 12:51:20 -0800 Subject: [PATCH 3/4] Refactor Omada proxy for api v3 --- src/widgets/omada/proxy.js | 100 ++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 52 deletions(-) diff --git a/src/widgets/omada/proxy.js b/src/widgets/omada/proxy.js index d5f4d9ba..d867e112 100644 --- a/src/widgets/omada/proxy.js +++ b/src/widgets/omada/proxy.js @@ -15,9 +15,12 @@ async function login(loginUrl, username, password, controllerVersionMajor) { password: password } - if (controllerVersionMajor < 4) { - params.method = "login"; - params.name = username; + if (controllerVersionMajor === 3) { + params["method"] = "login"; + params["params"] = { + name: username, + password + }; } const [status, contentType, data] = await httpProxy(loginUrl, { @@ -59,14 +62,14 @@ export default async function omadaProxyHandler(req, res) { return res.status(status).json({error: {message: `HTTP Error ${status}`, url: controllerInfoURL, data: data}}); } - const cId = JSON.parse(data).result.omadacId; + let cId; let controllerVersion; try { + cId = JSON.parse(data).result.omadacId; controllerVersion = JSON.parse(data).result.controllerVer; } catch (e) { - // fallback to this random version? - controllerVersion = "3.2.17" + controllerVersion = "3.2.x" } const controllerVersionMajor = parseInt(controllerVersion.split('.')[0], 10) @@ -134,7 +137,20 @@ export default async function omadaProxyHandler(req, res) { const sitesResponseData = JSON.parse(data); - let site; + if (sitesResponseData.errorCode > 0) { + logger.debug(`HTTTP ${status} getting sites list: ${requestresponse[2].msg}`); + return res.status(status).json({error: {message: "Error getting sites list", url, data: requestresponse[2]}}); + } + + const site = (controllerVersionMajor === 3) ? + sitesResponseData.result.siteList.find(site => site.name === widget.site): + sitesResponseData.result.data.find(site => site.name === widget.site); + + if (!site) { + return res.status(requestresponse[0]).json({error: {message: `Site ${widget.site} is not found`, url, data}}); + } + + let siteResponseData; let connectedAp; let activeUser; @@ -142,73 +158,53 @@ export default async function omadaProxyHandler(req, res) { let connectedGateways; let alerts; - if (sitesResponseData.errorCode > 0) { - logger.debug(`HTTTP ${status} getting sites list: ${requestresponse[2].msg}`); - return res.status(status).json({error: {message: "Error getting sites list", url, data: requestresponse[2]}}); - } - - // on modern controller, we need to call two different endpoints - // on older controller, we can call one endpoint - if (controllerVersionMajor === 3) { - - // Switching site is really needed only for Omada 3.x.x controllers - - let switchUrl; - - site = listresult.result.siteList.filter(site => site.name === widget.site); - siteName = site[0].siteName; - switchUrl = `${widget.url}/web/v1/controller?ajax=&token=${token}`; + // Omada 3.x.x controller requires switching site + const switchUrl = `${widget.url}/web/v1/controller?ajax=&token=${token}`; method = "POST"; - body = JSON.stringify({ + body = { "method": "switchSite", "params": { - "siteName": siteName, + "siteName": site.siteName, "userName": widget.username } - }); + }; headers = { "Content-Type": "application/json" }; params = { "token": token }; - requestresponse = await httpProxy(switchUrl, { + + [status, contentType, data] = await httpProxy(switchUrl, { method, params, - body: body.toString(), + body: JSON.stringify(body), headers, }); - const switchresult = JSON.parse(requestresponse[2]); - if (switchresult.errorCode !== 0) { - logger.debug(`HTTTP ${requestresponse[0]} getting sites list: ${requestresponse[2]}`); - return res.status(requestresponse[0]).json({error: {message: "Error switching site", url, data: requestresponse[2]}}); + + const switchResponseData = JSON.parse(data); + if (status !== 200 || switchResponseData.errorCode > 0) { + logger.error(`HTTP ${status} getting sites list: ${data}`); + return res.status(status).json({error: {message: "Error switching site", url: switchUrl, data}}); } const statsUrl = `${widget.url}/web/v1/controller?getGlobalStat=&token=${token}`; - const statResponse = await httpProxy(statsUrl, { - method: "POST", - params: { "token": token }, + [status, contentType, data] = await httpProxy(statsUrl, { + method, + params, body: JSON.stringify({ "method": "getGlobalStat", }), - headers: { - "Content-Type": "application/json", - }, + headers }); - const data = JSON.parse(statResponse[2]); + siteResponseData = JSON.parse(data); - if (data.errorCode !== 0) { - return res.status(statResponse[0]).json({error: {message: "Error getting stats", url, data: statResponse[2]}}); + if (status !== 200 || siteResponseData.errorCode > 0) { + return res.status(status).json({error: {message: "Error getting stats", url: statsUrl, data}}); } - connectedAp = data.result.connectedAp; - activeUser = data.result.activeUser; - alerts = data.result.alerts; + connectedAp = siteResponseData.result.connectedAp; + activeUser = siteResponseData.result.activeUser; + alerts = siteResponseData.result.alerts; } else if (controllerVersionMajor === 4 || controllerVersionMajor === 5) { - site = sitesResponseData.result.data.find(site => site.name === widget.site); - - if (site.length === 0) { - return res.status(requestresponse[0]).json({error: {message: `Site ${widget.site} is not found`, url, data}}); - } - const siteName = (controllerVersionMajor === 5) ? site.id : site.key; const siteStatsUrl = (controllerVersionMajor === 4) ? `${url}/api/v2/sites/${siteName}/dashboard/overviewDiagram?token=${token}¤tPage=1¤tPageSize=1000` : @@ -220,7 +216,7 @@ export default async function omadaProxyHandler(req, res) { }, }); - const siteResponseData = JSON.parse(data); + siteResponseData = JSON.parse(data); if (status !== 200 || siteResponseData.errorCode > 0) { logger.debug(`HTTP ${status} getting stats for site ${widget.site} with message ${listresult.msg}`); @@ -254,6 +250,6 @@ export default async function omadaProxyHandler(req, res) { })); } } - + return res.status(400).json({ error: "Invalid proxy service type" }); } From 952305492cf28fe523a6afac121bbfdd2d320608 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 21 Dec 2022 13:04:14 -0800 Subject: [PATCH 4/4] More Omada widget cleanup --- public/locales/en/common.json | 13 +++---- src/widgets/omada/component.jsx | 4 +-- src/widgets/omada/proxy.js | 61 ++++++++++++++++----------------- src/widgets/omada/widget.js | 8 ----- 4 files changed, 35 insertions(+), 51 deletions(-) diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 78c5dce8..9f5637bf 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -89,14 +89,11 @@ "no_active": "No Active Streams" }, "omada": { - "activeUser": "Active devices", - "alerts": "Alerts", - "connectedAp": "Connected APs", - "isolatedAp": "Isolated APs", - "powerConsumption": "Power consumption", - "availablePorts" : "Available ports", - "connectedGateway": "Connected gateways", - "connectedSwitches": "Connected switches" + "connectedAp": "Connected APs", + "activeUser": "Active devices", + "alerts": "Alerts", + "connectedGateway": "Connected gateways", + "connectedSwitches": "Connected switches" }, "nzbget": { "rate": "Rate", diff --git a/src/widgets/omada/component.jsx b/src/widgets/omada/component.jsx index d499da36..dee60842 100644 --- a/src/widgets/omada/component.jsx +++ b/src/widgets/omada/component.jsx @@ -9,7 +9,7 @@ export default function Component({ service }) { const { widget } = service; - const { data: omadaData, error: omadaAPIError } = useWidgetAPI(widget, "stats", { + const { data: omadaData, error: omadaAPIError } = useWidgetAPI(widget, { refreshInterval: 5000, }); @@ -23,8 +23,6 @@ export default function Component({ service }) { <Block label="omada.connectedAp" /> <Block label="omada.activeUser" /> <Block label="omada.alerts" /> - <Block label="omada.connectedGateway" /> - <Block label="omada.connectedSwitches" /> </Container> ); } diff --git a/src/widgets/omada/proxy.js b/src/widgets/omada/proxy.js index d867e112..e89ad81d 100644 --- a/src/widgets/omada/proxy.js +++ b/src/widgets/omada/proxy.js @@ -2,27 +2,26 @@ 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 = "omadaProxyHandler"; const logger = createLogger(proxyName); - async function login(loginUrl, username, password, controllerVersionMajor) { const params = { - username: username, - password: password + username, + password } if (controllerVersionMajor === 3) { - params["method"] = "login"; - params["params"] = { + params.method = "login"; + params.params = { name: username, password }; } - + + // eslint-disable-next-line no-unused-vars const [status, contentType, data] = await httpProxy(loginUrl, { method: "POST", body: JSON.stringify(params), @@ -41,10 +40,6 @@ export default async function omadaProxyHandler(req, res) { 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) { const {url} = widget; @@ -59,7 +54,7 @@ export default async function omadaProxyHandler(req, res) { if (status !== 200) { logger.error("Unable to retrieve Omada controller info"); - return res.status(status).json({error: {message: `HTTP Error ${status}`, url: controllerInfoURL, data: data}}); + return res.status(status).json({error: {message: `HTTP Error ${status}`, url: controllerInfoURL, data}}); } let cId; @@ -97,12 +92,11 @@ export default async function omadaProxyHandler(req, res) { const [loginStatus, loginResponseData] = await login(loginUrl, widget.username, widget.password, controllerVersionMajor); if (loginStatus !== 200 || loginResponseData.errorCode > 0) { - return res.status(requestresponse[0]).json({error: {message: "Error logging in to Oamda controller", url: loginUrl, data: loginResponseData}}); + return res.status(status).json({error: {message: "Error logging in to Oamda controller", url: loginUrl, data: loginResponseData}}); } - const token = loginResponseData.result.token; + const { token } = loginResponseData.result; - // List sites let sitesUrl; let body = {}; let params = { token }; @@ -126,6 +120,8 @@ export default async function omadaProxyHandler(req, res) { case 5: sitesUrl = `${widget.url}/${cId}/api/v2/sites?token=${token}¤tPage=1¤tPageSize=1000`; break; + default: + break; } [status, contentType, data] = await httpProxy(sitesUrl, { @@ -138,16 +134,16 @@ export default async function omadaProxyHandler(req, res) { const sitesResponseData = JSON.parse(data); if (sitesResponseData.errorCode > 0) { - logger.debug(`HTTTP ${status} getting sites list: ${requestresponse[2].msg}`); - return res.status(status).json({error: {message: "Error getting sites list", url, data: requestresponse[2]}}); + logger.debug(`HTTTP ${status} getting sites list: ${sitesResponseData.msg}`); + return res.status(status).json({error: {message: "Error getting sites list", url, data: sitesResponseData}}); } const site = (controllerVersionMajor === 3) ? - sitesResponseData.result.siteList.find(site => site.name === widget.site): - sitesResponseData.result.data.find(site => site.name === widget.site); + sitesResponseData.result.siteList.find(s => s.name === widget.site): + sitesResponseData.result.data.find(s => s.name === widget.site); if (!site) { - return res.status(requestresponse[0]).json({error: {message: `Site ${widget.site} is not found`, url, data}}); + return res.status(status).json({error: {message: `Site ${widget.site} is not found`, url, data}}); } let siteResponseData; @@ -159,18 +155,18 @@ export default async function omadaProxyHandler(req, res) { let alerts; if (controllerVersionMajor === 3) { - // Omada 3.x.x controller requires switching site + // Omada v3 controller requires switching site const switchUrl = `${widget.url}/web/v1/controller?ajax=&token=${token}`; method = "POST"; body = { - "method": "switchSite", - "params": { - "siteName": site.siteName, - "userName": widget.username + method: "switchSite", + params: { + siteName: site.siteName, + userName: widget.username } }; headers = { "Content-Type": "application/json" }; - params = { "token": token }; + params = { token }; [status, contentType, data] = await httpProxy(switchUrl, { method, @@ -219,25 +215,26 @@ export default async function omadaProxyHandler(req, res) { siteResponseData = JSON.parse(data); if (status !== 200 || siteResponseData.errorCode > 0) { - logger.debug(`HTTP ${status} getting stats for site ${widget.site} with message ${listresult.msg}`); + logger.debug(`HTTP ${status} getting stats for site ${widget.site} with message ${siteResponseData.msg}`); return res.status(500).send(data); } - activeUser = siteResponseData.result.totalClientNum; - connectedAp = siteResponseData.result.connectedApNum; - connectedGateways = siteResponseData.result.connectedGatewayNum; - connectedSwitches = siteResponseData.result.connectedSwitchNum; - const alertUrl = (controllerVersionMajor === 4) ? `${url}/api/v2/sites/${siteName}/alerts/num?token=${token}¤tPage=1¤tPageSize=1000` : `${url}/${cId}/api/v2/sites/${siteName}/alerts/num?token=${token}¤tPage=1¤tPageSize=1000`; + // eslint-disable-next-line no-unused-vars [status, contentType, data] = await httpProxy(alertUrl, { headers: { "Csrf-Token": token, }, }); const alertResponseData = JSON.parse(data); + + activeUser = siteResponseData.result.totalClientNum; + connectedAp = siteResponseData.result.connectedApNum; + connectedGateways = siteResponseData.result.connectedGatewayNum; + connectedSwitches = siteResponseData.result.connectedSwitchNum; alerts = alertResponseData.result.alertNum; } diff --git a/src/widgets/omada/widget.js b/src/widgets/omada/widget.js index 0ef4177e..5e32edad 100644 --- a/src/widgets/omada/widget.js +++ b/src/widgets/omada/widget.js @@ -1,15 +1,7 @@ import omadaProxyHandler from "./proxy"; -// import genericProxyHandler from "../../utils/proxy/handlers/generic"; const widget = { - api: "{url}/web/v1/{endpoint}", proxyHandler: omadaProxyHandler, - - mappings: { - stats: { - endpoint: "controller", - } - } }; export default widget;