forked from gethomepage/homepage
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature: OpenWRT service widget (gethomepage#2782)
* Feat: OpenWRT widget implementation * Update proxy.js * fixes from review --------- Co-authored-by: shamoon <[email protected]>
- Loading branch information
Showing
10 changed files
with
280 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
--- | ||
title: OpenWRT | ||
description: OpenWRT widget configuration | ||
--- | ||
|
||
Learn more about [OpenWRT](https://openwrt.org/). | ||
|
||
Provides information from OpenWRT | ||
|
||
```yaml | ||
widget: | ||
type: openwrt | ||
url: http://host.or.ip | ||
username: homepage | ||
password: pass | ||
interfaceName: eth0 # optional | ||
``` | ||
## Interface | ||
Setting `interfaceName` (e.g. eth0) will display information for that particular device, otherwise the widget will display general system info. | ||
|
||
## Authorization | ||
|
||
In order for homepage to access the OpenWRT RPC endpoints you will need to [create an ACL](https://openwrt.org/docs/techref/ubus#acls) and [new user](https://openwrt.org/docs/techref/ubus#authentication) in OpenWRT. | ||
|
||
Create an ACL named `homepage.json` in `/usr/share/rpcd/acl.d/`, the following permissions will suffice: | ||
|
||
``` | ||
{ | ||
"homepage": { | ||
"description": "Homepage widget", | ||
"read": { | ||
"ubus": { | ||
"network.interface.wan": ["status"], | ||
"network.interface.lan": ["status"], | ||
"network.device": ["status"] | ||
"system": ["info"] | ||
} | ||
}, | ||
} | ||
} | ||
``` | ||
|
||
Then add a user that will use that ACL in `/etc/config/rpc`: | ||
|
||
```config login | ||
option username 'homepage' | ||
option password '<password>' | ||
list read homepage | ||
list write '*' | ||
``` | ||
|
||
This username and password will be used in Homepage's services.yaml to grant access. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import Interface from "./methods/interface"; | ||
import System from "./methods/system"; | ||
|
||
export default function Component({ service }) { | ||
if (service.widget.interfaceName) { | ||
return <Interface service={service} />; | ||
} | ||
return <System service={service} />; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
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 { data, error } = useWidgetAPI(service.widget); | ||
|
||
if (error) { | ||
return <Container service={service} error={error} />; | ||
} | ||
|
||
if (!data) { | ||
return null; | ||
} | ||
|
||
const { up, bytesTx, bytesRx } = data; | ||
|
||
return ( | ||
<Container service={service}> | ||
<Block | ||
label="widget.status" | ||
value={ | ||
up ? ( | ||
<span className="text-green-500">{t("openwrt.up")}</span> | ||
) : ( | ||
<span className="text-red-500">{t("openwrt.down")}</span> | ||
) | ||
} | ||
/> | ||
<Block label="openwrt.bytesTx" value={t("common.bytes", { value: bytesTx })} /> | ||
<Block label="openwrt.bytesRx" value={t("common.bytes", { value: bytesRx })} /> | ||
</Container> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
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 { data, error } = useWidgetAPI(service.widget); | ||
|
||
if (error) { | ||
return <Container service={service} error={error} />; | ||
} | ||
|
||
if (!data) { | ||
return null; | ||
} | ||
|
||
const { uptime, cpuLoad } = data; | ||
|
||
return ( | ||
<Container service={service}> | ||
<Block label="openwrt.uptime" value={t("common.uptime", { value: uptime })} /> | ||
<Block label="openwrt.cpuLoad" value={cpuLoad} /> | ||
</Container> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import { sendJsonRpcRequest } from "utils/proxy/handlers/jsonrpc"; | ||
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 PROXY_NAME = "OpenWRTProxyHandler"; | ||
const logger = createLogger(PROXY_NAME); | ||
const LOGIN_PARAMS = ["00000000000000000000000000000000", "session", "login"]; | ||
const RPC_METHOD = "call"; | ||
|
||
let authToken = "00000000000000000000000000000000"; | ||
|
||
const PARAMS = { | ||
system: ["system", "info", {}], | ||
device: ["network.device", "status", {}], | ||
}; | ||
|
||
async function getWidget(req) { | ||
const { group, service } = req.query; | ||
|
||
if (!group || !service) { | ||
logger.debug("Invalid or missing service '%s' or group '%s'", service, group); | ||
return null; | ||
} | ||
|
||
const 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; | ||
} | ||
|
||
function isUnauthorized(data) { | ||
const json = JSON.parse(data.toString()); | ||
return json?.error?.code === -32002; | ||
} | ||
|
||
async function login(url, username, password) { | ||
const response = await sendJsonRpcRequest(url, RPC_METHOD, [...LOGIN_PARAMS, { username, password }]); | ||
|
||
if (response[0] === 200) { | ||
const responseData = JSON.parse(response[2]); | ||
authToken = responseData[1].ubus_rpc_session; | ||
} | ||
|
||
return response; | ||
} | ||
|
||
async function fetchInterface(url, interfaceName) { | ||
const [, contentType, data] = await sendJsonRpcRequest(url, RPC_METHOD, [authToken, ...PARAMS.device]); | ||
if (isUnauthorized(data)) { | ||
return [401, contentType, data]; | ||
} | ||
const response = JSON.parse(data.toString())[1]; | ||
const networkInterface = response[interfaceName]; | ||
if (!networkInterface) { | ||
return [404, contentType, { error: "Interface not found" }]; | ||
} | ||
|
||
const interfaceInfo = { | ||
up: networkInterface.up, | ||
bytesRx: networkInterface.statistics.rx_bytes, | ||
bytesTx: networkInterface.statistics.tx_bytes, | ||
}; | ||
return [200, contentType, interfaceInfo]; | ||
} | ||
|
||
async function fetchSystem(url) { | ||
const [, contentType, data] = await sendJsonRpcRequest(url, RPC_METHOD, [authToken, ...PARAMS.system]); | ||
if (isUnauthorized(data)) { | ||
return [401, contentType, data]; | ||
} | ||
const systemResponse = JSON.parse(data.toString())[1]; | ||
const response = { | ||
uptime: systemResponse.uptime, | ||
cpuLoad: systemResponse.load[1], | ||
}; | ||
return [200, contentType, response]; | ||
} | ||
|
||
async function fetchData(url, widget) { | ||
let response; | ||
if (widget.interfaceName) { | ||
response = await fetchInterface(url, widget.interfaceName); | ||
} else { | ||
response = await fetchSystem(url); | ||
} | ||
return response; | ||
} | ||
|
||
export default async function proxyHandler(req, res) { | ||
const { group, service } = 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 getWidget(req); | ||
|
||
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 api = widgets?.[widget.type]?.api; | ||
const url = new URL(formatApiCall(api, { ...widget })); | ||
|
||
let [status, , data] = await fetchData(url, widget); | ||
|
||
if (status === 401) { | ||
const [loginStatus, , loginData] = await login(url, widget.username, widget.password); | ||
if (loginStatus !== 200) { | ||
return res.status(loginStatus).end(loginData); | ||
} | ||
[status, , data] = await fetchData(url, widget); | ||
|
||
if (status === 401) { | ||
return res.status(401).json({ error: "Unauthorized" }); | ||
} | ||
} | ||
|
||
return res.status(200).end(JSON.stringify(data)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import proxyHandler from "./proxy"; | ||
|
||
const widget = { | ||
api: "{url}/ubus", | ||
proxyHandler, | ||
}; | ||
|
||
export default widget; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters