commit
e15ba1c82c
@ -1,56 +1,62 @@
|
|||||||
export default function Logo() {
|
import ResolvedIcon from "components/resolvedicon"
|
||||||
|
|
||||||
|
export default function Logo({ options }) {
|
||||||
return (
|
return (
|
||||||
<div className="w-12 h-12 flex flex-row items-center align-middle mr-3 self-center">
|
<div className="w-12 h-12 flex flex-row items-center align-middle mr-3 self-center">
|
||||||
<svg
|
{options.icon ?
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<ResolvedIcon icon={options.icon} width={48} height={48} /> :
|
||||||
viewBox="0 0 1024 1024"
|
// fallback to homepage logo
|
||||||
style={{
|
<svg
|
||||||
enableBackground: "new 0 0 1024 1024",
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
}}
|
viewBox="0 0 1024 1024"
|
||||||
xmlSpace="preserve"
|
style={{
|
||||||
className="w-full h-full"
|
enableBackground: "new 0 0 1024 1024",
|
||||||
>
|
}}
|
||||||
<style>
|
xmlSpace="preserve"
|
||||||
{
|
className="w-full h-full"
|
||||||
".st0{display:none}.st3{stroke-linecap:square}.st3,.st4{fill:none;stroke:#fff;stroke-miterlimit:10}.st6{display:inline;fill:#333}.st7{fill:#fff}"
|
>
|
||||||
}
|
<style>
|
||||||
</style>
|
{
|
||||||
<g id="Icon">
|
".st0{display:none}.st3{stroke-linecap:square}.st3,.st4{fill:none;stroke:#fff;stroke-miterlimit:10}.st6{display:inline;fill:#333}.st7{fill:#fff}"
|
||||||
<path
|
}
|
||||||
d="M771.9 191c27.7 0 50.1 26.5 50.1 59.3v186.4l-100.2.3V250.3c0-32.8 22.4-59.3 50.1-59.3z"
|
</style>
|
||||||
style={{
|
<g id="Icon">
|
||||||
fill: "rgba(var(--color-logo-start))",
|
<path
|
||||||
}}
|
d="M771.9 191c27.7 0 50.1 26.5 50.1 59.3v186.4l-100.2.3V250.3c0-32.8 22.4-59.3 50.1-59.3z"
|
||||||
/>
|
|
||||||
<linearGradient
|
|
||||||
id="homepage_logo_gradient"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1={200.746}
|
|
||||||
y1={225.015}
|
|
||||||
x2={764.986}
|
|
||||||
y2={789.255}
|
|
||||||
>
|
|
||||||
<stop
|
|
||||||
offset={0}
|
|
||||||
style={{
|
style={{
|
||||||
stopColor: "rgba(var(--color-logo-start))",
|
fill: "rgba(var(--color-logo-start))",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<stop
|
<linearGradient
|
||||||
offset={1}
|
id="homepage_logo_gradient"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1={200.746}
|
||||||
|
y1={225.015}
|
||||||
|
x2={764.986}
|
||||||
|
y2={789.255}
|
||||||
|
>
|
||||||
|
<stop
|
||||||
|
offset={0}
|
||||||
|
style={{
|
||||||
|
stopColor: "rgba(var(--color-logo-start))",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<stop
|
||||||
|
offset={1}
|
||||||
|
style={{
|
||||||
|
stopColor: "rgba(var(--color-logo-stop))",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</linearGradient>
|
||||||
|
<path
|
||||||
|
d="M721.8 250.3c0-32.7 22.4-59.3 50.1-59.3H253.1c-27.7 0-50.1 26.5-50.1 59.3v582.2l90.2-75.7-.1-130.3H375v61.8l88-73.8 258.8 217.9V250.6"
|
||||||
style={{
|
style={{
|
||||||
stopColor: "rgba(var(--color-logo-stop))",
|
fill: "url(#homepage_logo_gradient)",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</linearGradient>
|
</g>
|
||||||
<path
|
</svg>
|
||||||
d="M721.8 250.3c0-32.7 22.4-59.3 50.1-59.3H253.1c-27.7 0-50.1 26.5-50.1 59.3v582.2l90.2-75.7-.1-130.3H375v61.8l88-73.8 258.8 217.9V250.6"
|
}
|
||||||
style={{
|
|
||||||
fill: "url(#homepage_logo_gradient)",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
import cachedFetch from "utils/proxy/cached-fetch";
|
||||||
|
|
||||||
|
export default async function handler(req, res) {
|
||||||
|
const releasesURL = "https://api.github.com/repos/benphelps/homepage/releases";
|
||||||
|
return res.send(await cachedFetch(releasesURL, 5));
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
import diskstationProxyHandler from "./proxy";
|
import downloadstationProxyHandler from "./proxy";
|
||||||
|
|
||||||
const widget = {
|
const widget = {
|
||||||
api: "{url}/webapi/DownloadStation/task.cgi?api=SYNO.DownloadStation.Task&version=1&method={endpoint}",
|
api: "{url}/webapi/DownloadStation/task.cgi?api=SYNO.DownloadStation.Task&version=1&method={endpoint}",
|
||||||
proxyHandler: diskstationProxyHandler,
|
proxyHandler: downloadstationProxyHandler,
|
||||||
|
|
||||||
mappings: {
|
mappings: {
|
||||||
"list": {
|
"list": {
|
@ -0,0 +1,43 @@
|
|||||||
|
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, "system");
|
||||||
|
const { data: leasesData, error: leasesError } = useWidgetAPI(widget, "leases");
|
||||||
|
|
||||||
|
if (statsError || leasesError) {
|
||||||
|
const finalError = statsError ?? leasesError;
|
||||||
|
return <Container error={ finalError } />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!statsData || !leasesData) {
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="mikrotik.uptime" />
|
||||||
|
<Block label="mikrotik.cpuLoad" />
|
||||||
|
<Block label="mikrotik.memoryUsed" />
|
||||||
|
<Block label="mikrotik.numberOfLeases" />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const memoryUsed = 100 - (statsData['free-memory'] / statsData['total-memory'])*100
|
||||||
|
|
||||||
|
const numberOfLeases = leasesData.length
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="mikrotik.uptime" value={ statsData.uptime } />
|
||||||
|
<Block label="mikrotik.cpuLoad" value={t("common.percent", { value: statsData['cpu-load'] })} />
|
||||||
|
<Block label="mikrotik.memoryUsed" value={t("common.percent", { value: memoryUsed })} />
|
||||||
|
<Block label="mikrotik.numberOfLeases" value={t("common.number", { value: numberOfLeases })} />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
import genericProxyHandler from "utils/proxy/handlers/generic";
|
||||||
|
|
||||||
|
const widget = {
|
||||||
|
api: "{url}/rest/{endpoint}",
|
||||||
|
proxyHandler: genericProxyHandler,
|
||||||
|
|
||||||
|
mappings: {
|
||||||
|
system: {
|
||||||
|
endpoint: "system/resource",
|
||||||
|
validate: [
|
||||||
|
"cpu-load",
|
||||||
|
"free-memory",
|
||||||
|
"total-memory",
|
||||||
|
"uptime"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
leases: {
|
||||||
|
endpoint: "ip/dhcp-server/lease",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default widget;
|
@ -0,0 +1,33 @@
|
|||||||
|
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: minifluxData, error: minifluxError } = useWidgetAPI(widget, "counters");
|
||||||
|
|
||||||
|
if (minifluxError) {
|
||||||
|
return <Container error={minifluxError} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!minifluxData) {
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="miniflux.unread" />
|
||||||
|
<Block label="miniflux.read" />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="miniflux.unread" value={t("common.number", { value: minifluxData.unread })} />
|
||||||
|
<Block label="miniflux.read" value={t("common.number", { value: minifluxData.read })} />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import { asJson } from "utils/proxy/api-helpers";
|
||||||
|
import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
|
||||||
|
|
||||||
|
const widget = {
|
||||||
|
api: "{url}/v1/{endpoint}",
|
||||||
|
proxyHandler: credentialedProxyHandler,
|
||||||
|
|
||||||
|
mappings: {
|
||||||
|
counters: {
|
||||||
|
endpoint: "feeds/counters",
|
||||||
|
map: (data) => ({
|
||||||
|
read: Object.values(asJson(data).reads).reduce((acc, i) => acc + i, 0),
|
||||||
|
unread: Object.values(asJson(data).unreads).reduce((acc, i) => acc + i, 0)
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default widget;
|
@ -0,0 +1,39 @@
|
|||||||
|
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: nextdnsData, error: nextdnsError } = useWidgetAPI(widget, "analytics/status");
|
||||||
|
|
||||||
|
if (nextdnsError) {
|
||||||
|
return <Container error={nextdnsError} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nextdnsData) {
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block key="status" label="widget.status" value={t("nextdns.wait")} />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nextdnsData?.data?.length) {
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block key="status" label="widget.status" value={t("nextdns.no_devices")} />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
{nextdnsData.data.map(d => <Block key={d.status} label={d.status} value={t("common.number", { value: d.queries })} />)}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
|
||||||
|
|
||||||
|
const widget = {
|
||||||
|
api: "https://api.nextdns.io/profiles/{profile}/{endpoint}",
|
||||||
|
proxyHandler: credentialedProxyHandler,
|
||||||
|
|
||||||
|
mappings: {
|
||||||
|
"analytics/status": {
|
||||||
|
endpoint: "analytics/status",
|
||||||
|
validate: [
|
||||||
|
"data",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default widget;
|
@ -0,0 +1,39 @@
|
|||||||
|
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, {
|
||||||
|
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" />
|
||||||
|
</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>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,252 @@
|
|||||||
|
|
||||||
|
import { httpProxy } from "utils/proxy/http";
|
||||||
|
import getServiceWidget from "utils/config/service-helpers";
|
||||||
|
import createLogger from "utils/logger";
|
||||||
|
|
||||||
|
const proxyName = "omadaProxyHandler";
|
||||||
|
|
||||||
|
const logger = createLogger(proxyName);
|
||||||
|
|
||||||
|
async function login(loginUrl, username, password, controllerVersionMajor) {
|
||||||
|
const params = {
|
||||||
|
username,
|
||||||
|
password
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controllerVersionMajor === 3) {
|
||||||
|
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),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return [status, JSON.parse(data.toString())];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default async function omadaProxyHandler(req, res) {
|
||||||
|
const { group, service } = req.query;
|
||||||
|
|
||||||
|
if (group && service) {
|
||||||
|
const widget = await getServiceWidget(group, service);
|
||||||
|
|
||||||
|
if (widget) {
|
||||||
|
|
||||||
|
const { url } = widget;
|
||||||
|
|
||||||
|
const controllerInfoURL = `${url}/api/info`;
|
||||||
|
|
||||||
|
let [status, contentType, data] = await httpProxy(controllerInfoURL, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (status !== 200) {
|
||||||
|
logger.error("Unable to retrieve Omada controller info");
|
||||||
|
return res.status(status).json({error: {message: `HTTP Error ${status}`, url: controllerInfoURL, data}});
|
||||||
|
}
|
||||||
|
|
||||||
|
let cId;
|
||||||
|
let controllerVersion;
|
||||||
|
|
||||||
|
try {
|
||||||
|
cId = JSON.parse(data).result.omadacId;
|
||||||
|
controllerVersion = JSON.parse(data).result.controllerVer;
|
||||||
|
} catch (e) {
|
||||||
|
controllerVersion = "3.2.x"
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = `${url}/api/user/login?ajax`;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
loginUrl = `${url}/api/v2/login`;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
loginUrl = `${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(status).json({error: {message: "Error logging in to Oamda controller", url: loginUrl, data: loginResponseData}});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { token } = loginResponseData.result;
|
||||||
|
|
||||||
|
let sitesUrl;
|
||||||
|
let body = {};
|
||||||
|
let params = { token };
|
||||||
|
let headers = { "Csrf-Token": token };
|
||||||
|
let method = "GET";
|
||||||
|
|
||||||
|
switch (controllerVersionMajor) {
|
||||||
|
case 3:
|
||||||
|
sitesUrl = `${url}/web/v1/controller?ajax=&token=${token}`;
|
||||||
|
body = {
|
||||||
|
"method": "getUserSites",
|
||||||
|
"params": {
|
||||||
|
"userName": widget.username
|
||||||
|
}
|
||||||
|
};
|
||||||
|
method = "POST";
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
sitesUrl = `${url}/api/v2/sites?token=${token}¤tPage=1¤tPageSize=1000`;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
sitesUrl = `${url}/${cId}/api/v2/sites?token=${token}¤tPage=1¤tPageSize=1000`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
[status, contentType, data] = await httpProxy(sitesUrl, {
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sitesResponseData = JSON.parse(data);
|
||||||
|
|
||||||
|
if (status !== 200 || sitesResponseData.errorCode > 0) {
|
||||||
|
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(s => s.name === widget.site):
|
||||||
|
sitesResponseData.result.data.find(s => s.name === widget.site);
|
||||||
|
|
||||||
|
if (!site) {
|
||||||
|
return res.status(status).json({error: {message: `Site ${widget.site} is not found`, url: sitesUrl, data}});
|
||||||
|
}
|
||||||
|
|
||||||
|
let siteResponseData;
|
||||||
|
|
||||||
|
let connectedAp;
|
||||||
|
let activeUser;
|
||||||
|
let connectedSwitches;
|
||||||
|
let connectedGateways;
|
||||||
|
let alerts;
|
||||||
|
|
||||||
|
if (controllerVersionMajor === 3) {
|
||||||
|
// Omada v3 controller requires switching site
|
||||||
|
const switchUrl = `${url}/web/v1/controller?ajax=&token=${token}`;
|
||||||
|
method = "POST";
|
||||||
|
body = {
|
||||||
|
method: "switchSite",
|
||||||
|
params: {
|
||||||
|
siteName: site.siteName,
|
||||||
|
userName: widget.username
|
||||||
|
}
|
||||||
|
};
|
||||||
|
headers = { "Content-Type": "application/json" };
|
||||||
|
params = { token };
|
||||||
|
|
||||||
|
[status, contentType, data] = await httpProxy(switchUrl, {
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
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 = `${url}/web/v1/controller?getGlobalStat=&token=${token}`;
|
||||||
|
[status, contentType, data] = await httpProxy(statsUrl, {
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
body: JSON.stringify({
|
||||||
|
"method": "getGlobalStat",
|
||||||
|
}),
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
|
||||||
|
siteResponseData = JSON.parse(data);
|
||||||
|
|
||||||
|
if (status !== 200 || siteResponseData.errorCode > 0) {
|
||||||
|
return res.status(status).json({error: {message: "Error getting stats", url: statsUrl, data}});
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedAp = siteResponseData.result.connectedAp;
|
||||||
|
activeUser = siteResponseData.result.activeUser;
|
||||||
|
alerts = siteResponseData.result.alerts;
|
||||||
|
} else if (controllerVersionMajor === 4 || controllerVersionMajor === 5) {
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
siteResponseData = JSON.parse(data);
|
||||||
|
|
||||||
|
if (status !== 200 || siteResponseData.errorCode > 0) {
|
||||||
|
logger.debug(`HTTP ${status} getting stats for site ${widget.site} with message ${siteResponseData.msg}`);
|
||||||
|
return res.status(500).send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.send(JSON.stringify({
|
||||||
|
connectedAp,
|
||||||
|
activeUser,
|
||||||
|
alerts,
|
||||||
|
connectedGateways,
|
||||||
|
connectedSwitches,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
import omadaProxyHandler from "./proxy";
|
||||||
|
|
||||||
|
const widget = {
|
||||||
|
proxyHandler: omadaProxyHandler,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default widget;
|
@ -0,0 +1,48 @@
|
|||||||
|
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: activityData, error: activityError } = useWidgetAPI(widget, "activity");
|
||||||
|
const { data: interfaceData, error: interfaceError } = useWidgetAPI(widget, "interface");
|
||||||
|
|
||||||
|
if (activityError || interfaceError) {
|
||||||
|
const finalError = activityError ?? interfaceError;
|
||||||
|
return <Container error={ finalError } />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!activityData || !interfaceData) {
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="opnsense.cpu" />
|
||||||
|
<Block label="opnsense.memory" />
|
||||||
|
<Block label="opnsense.wanUpload" />
|
||||||
|
<Block label="opnsense.wanDownload" />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const cpuIdle = activityData.headers[2].match(/ ([0-9.]+)% idle/)[1];
|
||||||
|
const cpu = 100 - parseFloat(cpuIdle);
|
||||||
|
const memory = activityData.headers[3].match(/Mem: (.+) Active,/)[1];
|
||||||
|
|
||||||
|
const wanUpload = interfaceData.interfaces.wan['bytes transmitted'];
|
||||||
|
const wanDownload = interfaceData.interfaces.wan['bytes received'];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="opnsense.cpu" value={t("common.percent", { value: cpu.toFixed(2) })} />
|
||||||
|
<Block label="opnsense.memory" value={memory} />
|
||||||
|
<Block label="opnsense.wanUpload" value={t("common.bytes", { value: wanUpload })} />
|
||||||
|
<Block label="opnsense.wanDownload" value={t("common.bytes", { value: wanDownload })} />
|
||||||
|
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
import genericProxyHandler from "utils/proxy/handlers/generic";
|
||||||
|
|
||||||
|
const widget = {
|
||||||
|
api: "{url}/api/{endpoint}",
|
||||||
|
proxyHandler: genericProxyHandler,
|
||||||
|
|
||||||
|
mappings: {
|
||||||
|
activity: {
|
||||||
|
endpoint: "diagnostics/activity/getActivity",
|
||||||
|
validate: [
|
||||||
|
"headers"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
interface: {
|
||||||
|
endpoint: "diagnostics/traffic/interface",
|
||||||
|
validate: [
|
||||||
|
"interfaces"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default widget;
|
@ -0,0 +1,42 @@
|
|||||||
|
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: tdarrData, error: tdarrError } = useWidgetAPI(widget);
|
||||||
|
|
||||||
|
if (tdarrError) {
|
||||||
|
return <Container error={tdarrError} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tdarrData) {
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="tdarr.queue" />
|
||||||
|
<Block label="tdarr.processed" />
|
||||||
|
<Block label="tdarr.errored" />
|
||||||
|
<Block label="tdarr.saved" />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const queue = parseInt(tdarrData.table1Count, 10) + parseInt(tdarrData.table4Count, 10);
|
||||||
|
const processed = parseInt(tdarrData.table2Count, 10) + parseInt(tdarrData.table5Count, 10);
|
||||||
|
const errored = parseInt(tdarrData.table3Count, 10) + parseInt(tdarrData.table6Count, 10);
|
||||||
|
const saved = parseFloat(tdarrData.sizeDiff, 10) * 1000000000;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="tdarr.queue" value={t("common.number", { value: queue })} />
|
||||||
|
<Block label="tdarr.processed" value={t("common.number", { value: processed })} />
|
||||||
|
<Block label="tdarr.errored" value={t("common.number", { value: errored })} />
|
||||||
|
<Block label="tdarr.saved" value={t("common.bytes", { value: saved })} />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
import { httpProxy } from "utils/proxy/http";
|
||||||
|
import { formatApiCall } from "utils/proxy/api-helpers";
|
||||||
|
import getServiceWidget from "utils/config/service-helpers";
|
||||||
|
import createLogger from "utils/logger";
|
||||||
|
import widgets from "widgets/widgets";
|
||||||
|
|
||||||
|
const proxyName = "tdarrProxyHandler";
|
||||||
|
const logger = createLogger(proxyName);
|
||||||
|
|
||||||
|
export default async function tdarrProxyHandler(req, res) {
|
||||||
|
const { group, service, endpoint } = req.query;
|
||||||
|
|
||||||
|
if (!group || !service) {
|
||||||
|
logger.debug("Invalid or missing service '%s' or group '%s'", service, group);
|
||||||
|
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const widget = await getServiceWidget(group, service);
|
||||||
|
|
||||||
|
if (!widget) {
|
||||||
|
logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group);
|
||||||
|
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget }));
|
||||||
|
|
||||||
|
const [status, contentType, data] = await httpProxy(url, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
"data": {
|
||||||
|
"collection": "StatisticsJSONDB",
|
||||||
|
"mode": "getById",
|
||||||
|
"docID": "statistics"
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (status !== 200) {
|
||||||
|
logger.error("Error getting data from Tdarr: %d. Data: %s", status, data);
|
||||||
|
return res.status(500).send({error: {message:"Error getting data from Tdarr", url, data}});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentType) res.setHeader("Content-Type", contentType);
|
||||||
|
return res.status(status).send(data);
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
import tdarrProxyHandler from "./proxy";
|
||||||
|
|
||||||
|
const widget = {
|
||||||
|
api: "{url}/api/v2/cruddb",
|
||||||
|
proxyHandler: tdarrProxyHandler,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default widget;
|
@ -0,0 +1,35 @@
|
|||||||
|
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: xteveData, error: xteveError } = useWidgetAPI(widget, "api");
|
||||||
|
|
||||||
|
if (xteveError) {
|
||||||
|
return <Container error={xteveError} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!xteveData) {
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="xteve.streams_all" />
|
||||||
|
<Block label="xteve.streams_active " />
|
||||||
|
<Block label="xteve.streams_xepg" />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="xteve.streams_all" value={t("common.number", { value: xteveData["streams.all"] ?? 0 })} />
|
||||||
|
<Block label="xteve.streams_active" value={t("common.number", { value: xteveData["streams.active"] ?? 0 })} />
|
||||||
|
<Block label="xteve.streams_xepg" value={t("common.number", { value: xteveData["streams.xepg"] ?? 0 })} />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
import { formatApiCall } from "utils/proxy/api-helpers";
|
||||||
|
import { httpProxy } from "utils/proxy/http";
|
||||||
|
import createLogger from "utils/logger";
|
||||||
|
import widgets from "widgets/widgets";
|
||||||
|
import getServiceWidget from "utils/config/service-helpers";
|
||||||
|
|
||||||
|
const logger = createLogger("xteveProxyHandler");
|
||||||
|
|
||||||
|
export default async function xteveProxyHandler(req, res) {
|
||||||
|
const { group, service, endpoint } = req.query;
|
||||||
|
|
||||||
|
if (!group || !service) {
|
||||||
|
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const widget = await getServiceWidget(group, service);
|
||||||
|
const api = widgets?.[widget.type]?.api;
|
||||||
|
if (!api) {
|
||||||
|
return res.status(403).json({ error: "Service does not support API calls" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = formatApiCall(api, { endpoint, ...widget });
|
||||||
|
const method = "POST";
|
||||||
|
const payload = { cmd: "status" };
|
||||||
|
|
||||||
|
if (widget.username && widget.password) {
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const [status, contentType, data] = await httpProxy(url, {
|
||||||
|
method,
|
||||||
|
body: JSON.stringify({
|
||||||
|
cmd: "login",
|
||||||
|
username: widget.username,
|
||||||
|
password: widget.password,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (status !== 200) {
|
||||||
|
logger.debug("Error logging into xteve", status, url);
|
||||||
|
return res.status(status).json({error: {message: `HTTP Error ${status} logging into xteve`, url, data}});
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = JSON.parse(data.toString());
|
||||||
|
|
||||||
|
if (json?.status !== true) {
|
||||||
|
return res.status(401).json({error: {message: "Authentication failed", url, data}});
|
||||||
|
}
|
||||||
|
|
||||||
|
payload.token = json.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [status, contentType, data] = await httpProxy(url, {
|
||||||
|
method,
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (status !== 200) {
|
||||||
|
logger.debug("Error %d calling xteve endpoint %s", status, url);
|
||||||
|
return res.status(status).json({error: {message: `HTTP Error ${status}`, url, data}});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentType) res.setHeader("Content-Type", contentType);
|
||||||
|
return res.status(status).send(data);
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
import xteveProxyHandler from "./proxy";
|
||||||
|
|
||||||
|
const widget = {
|
||||||
|
api: "{url}/{endpoint}",
|
||||||
|
proxyHandler: xteveProxyHandler,
|
||||||
|
|
||||||
|
mappings: {
|
||||||
|
"api": {
|
||||||
|
endpoint: "api/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default widget;
|
Loading…
Reference in new issue