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,