commit
e15ba1c82c
@ -1,56 +1,62 @@
|
||||
export default function Logo() {
|
||||
import ResolvedIcon from "components/resolvedicon"
|
||||
|
||||
export default function Logo({ options }) {
|
||||
return (
|
||||
<div className="w-12 h-12 flex flex-row items-center align-middle mr-3 self-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 1024 1024"
|
||||
style={{
|
||||
enableBackground: "new 0 0 1024 1024",
|
||||
}}
|
||||
xmlSpace="preserve"
|
||||
className="w-full h-full"
|
||||
>
|
||||
<style>
|
||||
{
|
||||
".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>
|
||||
<g id="Icon">
|
||||
<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={{
|
||||
fill: "rgba(var(--color-logo-start))",
|
||||
}}
|
||||
/>
|
||||
<linearGradient
|
||||
id="homepage_logo_gradient"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1={200.746}
|
||||
y1={225.015}
|
||||
x2={764.986}
|
||||
y2={789.255}
|
||||
>
|
||||
<stop
|
||||
offset={0}
|
||||
{options.icon ?
|
||||
<ResolvedIcon icon={options.icon} width={48} height={48} /> :
|
||||
// fallback to homepage logo
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 1024 1024"
|
||||
style={{
|
||||
enableBackground: "new 0 0 1024 1024",
|
||||
}}
|
||||
xmlSpace="preserve"
|
||||
className="w-full h-full"
|
||||
>
|
||||
<style>
|
||||
{
|
||||
".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>
|
||||
<g id="Icon">
|
||||
<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={{
|
||||
stopColor: "rgba(var(--color-logo-start))",
|
||||
fill: "rgba(var(--color-logo-start))",
|
||||
}}
|
||||
/>
|
||||
<stop
|
||||
offset={1}
|
||||
<linearGradient
|
||||
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={{
|
||||
stopColor: "rgba(var(--color-logo-stop))",
|
||||
fill: "url(#homepage_logo_gradient)",
|
||||
}}
|
||||
/>
|
||||
</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={{
|
||||
fill: "url(#homepage_logo_gradient)",
|
||||
}}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</g>
|
||||
</svg>
|
||||
}
|
||||
</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 = {
|
||||
api: "{url}/webapi/DownloadStation/task.cgi?api=SYNO.DownloadStation.Task&version=1&method={endpoint}",
|
||||
proxyHandler: diskstationProxyHandler,
|
||||
proxyHandler: downloadstationProxyHandler,
|
||||
|
||||
mappings: {
|
||||
"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