Feature: Fritz!Box Widget (#2387)
* Feature: Fritz!Box Widget * Use i18n * code style & formatting --------- Co-authored-by: Thorben Grove <thorben.grove@tui.de> Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>pull/2395/head
parent
b3414fc35f
commit
4c45c6453f
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
title: FRITZ!Box
|
||||||
|
description: FRITZ!Box Widget Configuration
|
||||||
|
---
|
||||||
|
|
||||||
|
Application access & UPnP must be activated on your device:
|
||||||
|
|
||||||
|
```
|
||||||
|
Home Network > Network > Network Settings > Access Settings in the Home Network
|
||||||
|
[x] Allow access for applications
|
||||||
|
[x] Transmit status information over UPnP
|
||||||
|
```
|
||||||
|
|
||||||
|
You don't need to provide any credentials.
|
||||||
|
|
||||||
|
Allowed fields (limited to a max of 4): `["connectionStatus", "upTime", "maxDown", "maxUp", "down", "up", "received", "sent", "externalIPAddress"]`.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
widget:
|
||||||
|
type: fritzbox
|
||||||
|
url: https://192.168.178.1
|
||||||
|
```
|
@ -0,0 +1,67 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
const formatUptime = (timestamp) => {
|
||||||
|
const hours = Math.floor(timestamp / 3600);
|
||||||
|
const minutes = Math.floor((timestamp % 3600) / 60);
|
||||||
|
const seconds = timestamp % 60;
|
||||||
|
|
||||||
|
const hourDuration = hours > 0 ? `${hours}h` : "00h";
|
||||||
|
const minDuration = minutes > 0 ? `${minutes}m` : "00m";
|
||||||
|
const secDuration = seconds > 0 ? `${seconds}s` : "00s";
|
||||||
|
|
||||||
|
return hourDuration + minDuration + secDuration;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Component({ service }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { widget } = service;
|
||||||
|
const { data: fritzboxData, error: fritzboxError } = useWidgetAPI(widget, "status");
|
||||||
|
|
||||||
|
if (fritzboxError) {
|
||||||
|
return <Container service={service} error={fritzboxError} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default fields
|
||||||
|
if (!widget.fields?.length > 0) {
|
||||||
|
widget.fields = ["connectionStatus", "uptime", "maxDown", "maxUp"];
|
||||||
|
}
|
||||||
|
const MAX_ALLOWED_FIELDS = 4;
|
||||||
|
// Limits max number of displayed fields
|
||||||
|
if (widget.fields?.length > MAX_ALLOWED_FIELDS) {
|
||||||
|
widget.fields = widget.fields.slice(0, MAX_ALLOWED_FIELDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fritzboxData) {
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="fritzbox.connectionStatus" />
|
||||||
|
<Block label="fritzbox.uptime" />
|
||||||
|
<Block label="fritzbox.maxDown" />
|
||||||
|
<Block label="fritzbox.maxUp" />
|
||||||
|
<Block label="fritzbox.down" />
|
||||||
|
<Block label="fritzbox.up" />
|
||||||
|
<Block label="fritzbox.received" />
|
||||||
|
<Block label="fritzbox.sent" />
|
||||||
|
<Block label="fritzbox.externalIPAddress" />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="fritzbox.connectionStatus" value={t(`fritzbox.connectionStatus${fritzboxData.connectionStatus}`)} />
|
||||||
|
<Block label="fritzbox.uptime" value={formatUptime(fritzboxData.uptime)} />
|
||||||
|
<Block label="fritzbox.maxDown" value={t("common.byterate", { value: fritzboxData.maxDown / 8, decimals: 1 })} />
|
||||||
|
<Block label="fritzbox.maxUp" value={t("common.byterate", { value: fritzboxData.maxUp / 8, decimals: 1 })} />
|
||||||
|
<Block label="fritzbox.down" value={t("common.byterate", { value: fritzboxData.down, decimals: 1 })} />
|
||||||
|
<Block label="fritzbox.up" value={t("common.byterate", { value: fritzboxData.up, decimals: 1 })} />
|
||||||
|
<Block label="fritzbox.received" value={t("common.bytes", { value: fritzboxData.received })} />
|
||||||
|
<Block label="fritzbox.sent" value={t("common.bytes", { value: fritzboxData.sent })} />
|
||||||
|
<Block label="fritzbox.externalIPAddress" value={fritzboxData.externalIPAddress} />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
import { xml2json } from "xml-js";
|
||||||
|
|
||||||
|
import { httpProxy } from "utils/proxy/http";
|
||||||
|
import getServiceWidget from "utils/config/service-helpers";
|
||||||
|
import createLogger from "utils/logger";
|
||||||
|
|
||||||
|
const logger = createLogger("fritzboxProxyHandler");
|
||||||
|
|
||||||
|
async function requestEndpoint(apiBaseUrl, service, action) {
|
||||||
|
const servicePath = service === "WANIPConnection" ? "WANIPConn1" : "WANCommonIFC1";
|
||||||
|
const params = {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "text/xml; charset='utf-8'",
|
||||||
|
SoapAction: `urn:schemas-upnp-org:service:${service}:1#${action}`,
|
||||||
|
},
|
||||||
|
body:
|
||||||
|
"<?xml version='1.0' encoding='utf-8'?>" +
|
||||||
|
"<s:Envelope s:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/' xmlns:s='http://schemas.xmlsoap.org/soap/envelope/'>" +
|
||||||
|
"<s:Body>" +
|
||||||
|
`<u:${action} xmlns:u='urn:schemas-upnp-org:service:${service}:1' />` +
|
||||||
|
"</s:Body>" +
|
||||||
|
"</s:Envelope>",
|
||||||
|
};
|
||||||
|
const apiUrl = `${apiBaseUrl}/igdupnp/control/${servicePath}`;
|
||||||
|
const [status, , data] = await httpProxy(apiUrl, params);
|
||||||
|
if (status !== 200) {
|
||||||
|
logger.debug(`HTTP ${status} performing SoapRequest for ${service}->${action}`, data);
|
||||||
|
throw new Error(`Failed fetching '${action}'`);
|
||||||
|
}
|
||||||
|
const response = {};
|
||||||
|
try {
|
||||||
|
const jsonData = JSON.parse(xml2json(data));
|
||||||
|
const responseElements = jsonData?.elements[0]?.elements[0]?.elements[0]?.elements || [];
|
||||||
|
responseElements.forEach((element) => {
|
||||||
|
response[element.name] = element.elements[0]?.text || "";
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug(`Failed parsing ${service}->${action} response:`, data);
|
||||||
|
throw new Error(`Failed parsing '${action}' response`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function fritzboxProxyHandler(req, res) {
|
||||||
|
const { group, service } = req.query;
|
||||||
|
const serviceWidget = await getServiceWidget(group, service);
|
||||||
|
if (!serviceWidget) {
|
||||||
|
res.status(500).json({ error: "Service widget not found" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!serviceWidget.url) {
|
||||||
|
res.status(500).json({ error: "Service widget url not configured" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceWidgetUrl = new URL(serviceWidget.url);
|
||||||
|
const port = serviceWidgetUrl.protocol === "https:" ? 49443 : 49000;
|
||||||
|
const apiBaseUrl = `${serviceWidgetUrl.protocol}//${serviceWidgetUrl.hostname}:${port}`;
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
requestEndpoint(apiBaseUrl, "WANIPConnection", "GetStatusInfo"),
|
||||||
|
requestEndpoint(apiBaseUrl, "WANIPConnection", "GetExternalIPAddress"),
|
||||||
|
requestEndpoint(apiBaseUrl, "WANCommonInterfaceConfig", "GetCommonLinkProperties"),
|
||||||
|
requestEndpoint(apiBaseUrl, "WANCommonInterfaceConfig", "GetAddonInfos"),
|
||||||
|
])
|
||||||
|
.then(([statusInfo, externalIPAddress, linkProperties, addonInfos]) => {
|
||||||
|
res.status(200).json({
|
||||||
|
connectionStatus: statusInfo.NewConnectionStatus,
|
||||||
|
uptime: statusInfo.NewUptime,
|
||||||
|
maxDown: linkProperties.NewLayer1DownstreamMaxBitRate,
|
||||||
|
maxUp: linkProperties.NewLayer1UpstreamMaxBitRate,
|
||||||
|
down: addonInfos.NewByteReceiveRate,
|
||||||
|
up: addonInfos.NewByteSendRate,
|
||||||
|
received: addonInfos.NewX_AVM_DE_TotalBytesReceived64,
|
||||||
|
sent: addonInfos.NewX_AVM_DE_TotalBytesSent64,
|
||||||
|
externalIPAddress: externalIPAddress.NewExternalIPAddress,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
import fritzboxProxyHandler from "./proxy";
|
||||||
|
|
||||||
|
const widget = {
|
||||||
|
proxyHandler: fritzboxProxyHandler,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default widget;
|
Loading…
Reference in new issue