diff --git a/docs/widgets/services/aria2.md b/docs/widgets/services/aria2.md new file mode 100644 index 00000000..054cda2a --- /dev/null +++ b/docs/widgets/services/aria2.md @@ -0,0 +1,18 @@ +--- +title: Aria2 +description: Aria2 Widget Configuration +--- + +Learn more about [Aria2](https://github.com/aria2/aria2). + +Find your API key in aria2c configuration file `aria2c.conf`: `rpc-secret`. +To make it work, JSON RPC in Aria2 should be enabled. + +Optionally, `jsonrpc` endpoint path could be adjusted via `endpoint` widget config. + +```yaml +widget: + type: aria2c + url: http://aria2c.host.or.ip + key: apikey +``` diff --git a/public/locales/en/common.json b/public/locales/en/common.json index e3670e80..8684cd0b 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -1007,5 +1007,11 @@ "issues": "Issues", "merges": "Merge Requests", "projects": "Projects" + }, + "aria2c": { + "active": "Active", + "waiting": "Waiting", + "download": "↓", + "upload": "↑" } } diff --git a/src/widgets/aria2c/component.jsx b/src/widgets/aria2c/component.jsx new file mode 100644 index 00000000..1539ea4f --- /dev/null +++ b/src/widgets/aria2c/component.jsx @@ -0,0 +1,37 @@ +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: aria2cData, error: aria2cError } = useWidgetAPI(widget); + + if (aria2cError) { + return ; + } + + if (!aria2cData) { + return ( + + + + + + + ); + } + + return ( + + + + + + + ); +} diff --git a/src/widgets/aria2c/proxy.js b/src/widgets/aria2c/proxy.js new file mode 100644 index 00000000..cba9e0bb --- /dev/null +++ b/src/widgets/aria2c/proxy.js @@ -0,0 +1,65 @@ +import getServiceWidget from "utils/config/service-helpers"; +import { httpProxy } from "utils/proxy/http"; +import widgets from "widgets/widgets"; +import { formatApiCall } from "utils/proxy/api-helpers"; +import createLogger from "utils/logger"; + +const logger = createLogger("ariaProxyHandler"); + + +export default async function ariaProxyHandler(req, res) { + const { group, service, index } = req.query; + + if (group && service) { + const widget = await getServiceWidget(group, service, index); + + if (widget) { + if (widget.endpoint === undefined) { + widget.endpoint = 'jsonrpc' + } + + const api = widgets?.[widget.type]?.api; + const url = new URL(formatApiCall(api, { ...widget })); + + const headers = { + "content-type": "application/json", + accept: "application/json", + }; + if (widget.username) { + headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`; + } + + const rpcRequestBody = { + id: "homepage", + jsonrpc: "2.0", + method: "aria2.getGlobalStat", + params: [] + } + + if (widget.token !== undefined) { + rpcRequestBody.params.push(`token:${widget.token}`) + } + + const [status, , data] = await httpProxy(url, { + method: "POST", + headers, + body: JSON.stringify(rpcRequestBody), + }); + + if (status !== 200) { + logger.error("HTTP Error %d calling %s", status, url.toString()); + return res.status(status).json({ error: { message: "HTTP Error", url, data } }); + } + + try { + const rawData = JSON.parse(data); + + return res.status(200).send(rawData.result); + } catch (e) { + return res.status(500).json({ error: { message: e?.toString() ?? "Error parsing aria2c rpc data", url, data } }); + } + } + } + + return res.status(500).json({ error: "Invalid proxy service type" }); +} diff --git a/src/widgets/aria2c/widget.js b/src/widgets/aria2c/widget.js new file mode 100644 index 00000000..a90707ab --- /dev/null +++ b/src/widgets/aria2c/widget.js @@ -0,0 +1,8 @@ +import ariaProxyHandler from './proxy'; + +const widget = { + api: "{url}/{endpoint}", + proxyHandler: ariaProxyHandler, +}; + +export default widget; diff --git a/src/widgets/components.js b/src/widgets/components.js index 19f41d4a..9acae848 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -3,6 +3,7 @@ import dynamic from "next/dynamic"; const components = { adguard: dynamic(() => import("./adguard/component")), argocd: dynamic(() => import("./argocd/component")), + aria2c: dynamic(() => import("./aria2c/component")), atsumeru: dynamic(() => import("./atsumeru/component")), audiobookshelf: dynamic(() => import("./audiobookshelf/component")), authentik: dynamic(() => import("./authentik/component")), diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 9d4bb935..6b0769be 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -1,5 +1,6 @@ import adguard from "./adguard/widget"; import argocd from "./argocd/widget"; +import aria2c from "./aria2c/widget"; import atsumeru from "./atsumeru/widget"; import audiobookshelf from "./audiobookshelf/widget"; import authentik from "./authentik/widget"; @@ -134,6 +135,7 @@ import zabbix from "./zabbix/widget"; const widgets = { adguard, argocd, + aria2c, atsumeru, audiobookshelf, authentik,