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] Squashed commit of the following from initial Omada widget: commit ad3e664b56ad2a9024684f2141f8f21ead59177e Author: Benoit Date: Tue Dec 13 19:54:54 2022 +0100 Add .idea to .gitignore commit 7e51a093845c6bcda4fac78a2c174adbcc7701b6 Merge: 93d8035 7dd0b0e Author: Benoit SERRA Date: Tue Dec 13 18:38:51 2022 +0100 Merge branch 'benphelps:main' into main commit 93d80350b1d4519ac217b880568ccabbef43f03f Author: Benoit 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 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 Date: Sun Dec 11 14:39:27 2022 +0100 Merge branch 'benphelps:main' into main commit 331f31fc2be80e0738869fd050b3034638979350 Merge: 37154e3 ccc1229 Author: Benoit SERRA Date: Sat Dec 10 17:56:44 2022 +0100 Merge branch 'benphelps:main' into main commit 37154e327af7d3fe66e7638ba79851ef789d3649 Author: Benoit 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 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 Date: Fri Dec 9 21:06:38 2022 +0100 Merge branch 'main' of https://github.com/Oupsman/homepage into main commit 467b67802a7b8dface01703b6035951dcaa1e069 Author: Benoit 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 Date: Thu Dec 8 15:38:20 2022 +0100 Merge branch 'benphelps:main' into main commit 8d66756a7d8f9e0b43c8abde2f2e6f2fd86a1098 Author: Benoit Date: Thu Dec 8 12:45:44 2022 +0100 Omada Widget : code cleanup commit 282a6d0592c53a39184d63bba517f482a84f5b36 Author: Benoit Date: Thu Dec 8 12:42:41 2022 +0100 Omada Widget : code cleanup commit c3e9b8f87075e834ea1a520fd8c93708afb15ac0 Author: Benoit 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 Date: Wed Dec 7 15:46:00 2022 +0100 V2 API is working commit bcc2864ee2e1f0f1d2f4c009df1ba8a1a7244f80 Author: Benoit Date: Wed Dec 7 10:01:26 2022 +0100 Code fore v2 API is not working but V1 code is. commit ea8e297e849c2ef5659bfec94d76d2fff8677c4c Author: Benoit Date: Tue Dec 6 14:28:05 2022 +0100 Errors handling commit ab6d51a88c8737654dd31bec46106d7c49ed39d2 Author: Benoit Date: Tue Dec 6 09:50:14 2022 +0100 Adding alerts commit 047db2cce867c0207be7fe0827b24107fdd84923 Author: Benoit Date: Mon Dec 5 22:53:43 2022 +0100 Fixed translation system commit 42c5a3e6658f22662b1c58f54cba31dc90bfbc61 Author: Benoit Date: Mon Dec 5 22:34:34 2022 +0100 Translation system is still * up commit c80eac9d5bd5491ec4a61da38cdaf82f0ea1cc2f Author: Benoit Date: Mon Dec 5 22:33:50 2022 +0100 Translation system is still * up commit f8ba6b02454d66eb96e7e6ebd8022492ff79e690 Author: Benoit Date: Mon Dec 5 22:32:22 2022 +0100 Translation system is still * up commit dec7eec6de26298eb7c90fd01e1c0fd23b6469a4 Author: Benoit Date: Mon Dec 5 22:16:13 2022 +0100 Translation system is * up commit cc840cf7ccb40509f1da3f521a4a1b3e26498ac0 Author: Benoit Date: Mon Dec 5 21:33:00 2022 +0100 First working version commit 54b65e619e41c9963a614a177df3a4af68ebe77d Author: Benoit Date: Mon Dec 5 18:59:09 2022 +0100 Using getGlobalStat method commit 7ebc8500da9d52bd2911a620179fb6585f044c47 Author: Benoit Date: Mon Dec 5 14:33:37 2022 +0100 Working on Omada Widget : NOT WORKING FOR NOW commit 04eaf28cae1be0935cb190e50ae5b75c19254403 Merge: 61065ac 826fe15 Author: Benoit Date: Mon Dec 5 10:32:30 2022 +0100 Merge branch 'main' of https://github.com/Oupsman/homepage into main commit 61065ace2887c3c1d6486001d34ce6f58d58958d Author: Benoit 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 ; + } + + if (!omadaData) { + return ( + + + + + + + + ); + } + + return ( + + + + + { omadaData.connectedGateways > 0 && } + { omadaData.connectedSwitches > 0 && } + + ); +} 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,