mirror of
https://github.com/DI0IK/homepage-plus.git
synced 2025-07-13 16:30:31 +00:00
Merge pull request #341 from shamoon/main
Feature: UniFi Console Info & Service Widgets
This commit is contained in:
commit
85df467fdb
9 changed files with 330 additions and 1 deletions
|
@ -32,6 +32,7 @@ const components = {
|
|||
tautulli: dynamic(() => import("./tautulli/component")),
|
||||
traefik: dynamic(() => import("./traefik/component")),
|
||||
transmission: dynamic(() => import("./transmission/component")),
|
||||
unifi: dynamic(() => import("./unifi/component")),
|
||||
};
|
||||
|
||||
export default components;
|
||||
|
|
61
src/widgets/unifi/component.jsx
Normal file
61
src/widgets/unifi/component.jsx
Normal file
|
@ -0,0 +1,61 @@
|
|||
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 <Container error={t("widget.api_error")} />;
|
||||
}
|
||||
|
||||
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) {
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="unifi.uptime" />
|
||||
<Block label="unifi.wan" />
|
||||
<Block label={ lanLabel } />
|
||||
<Block label={ wlanLabel } />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
const uptime = `${t("common.number", { value: data.uptime / 86400, maximumFractionDigits: 1 })} ${t("unifi.days")}`;
|
||||
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="unifi.uptime" value={ uptime } />
|
||||
<Block label="unifi.wan" value={ data.up ? t("unifi.up") : t("unifi.down") } />
|
||||
<Block label={ lanLabel } value={t("common.number", { value: data.lan.users })} />
|
||||
<Block label={ wlanLabel } value={t("common.number", { value: data.wlan.users })} />
|
||||
</Container>
|
||||
);
|
||||
}
|
119
src/widgets/unifi/proxy.js
Normal file
119
src/widgets/unifi/proxy.js
Normal file
|
@ -0,0 +1,119 @@
|
|||
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";
|
||||
import { getSettings } from "utils/config/config";
|
||||
import getServiceWidget from "utils/config/service-helpers";
|
||||
import createLogger from "utils/logger";
|
||||
import widgets from "widgets/widgets";
|
||||
|
||||
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;
|
||||
|
||||
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) {
|
||||
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, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(loginBody),
|
||||
headers,
|
||||
});
|
||||
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" });
|
||||
}
|
||||
|
||||
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 = prefix;
|
||||
|
||||
const { endpoint } = req.query;
|
||||
const url = new URL(formatApiCall(api, { endpoint, ...widget }));
|
||||
const params = { method: "GET", headers: {} };
|
||||
setCookieHeader(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);
|
||||
return res.status(status).end(data);
|
||||
}
|
||||
|
||||
const json = JSON.parse(data.toString());
|
||||
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 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);
|
||||
}
|
||||
|
||||
if (contentType) res.setHeader("Content-Type", contentType);
|
||||
return res.status(status).send(data);
|
||||
}
|
14
src/widgets/unifi/widget.js
Normal file
14
src/widgets/unifi/widget.js
Normal file
|
@ -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;
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue