From 3b207f3250881b3a6e453e8e615ab9f1cf4ec433 Mon Sep 17 00:00:00 2001
From: hapylestat <3234762+hapylestat@users.noreply.github.com>
Date: Mon, 16 Dec 2024 14:01:38 +0000
Subject: [PATCH] Aria2 Service Widget
---
docs/widgets/services/aria2.md | 18 +++++++++
public/locales/en/common.json | 6 +++
src/widgets/aria2c/component.jsx | 37 ++++++++++++++++++
src/widgets/aria2c/proxy.js | 65 ++++++++++++++++++++++++++++++++
src/widgets/aria2c/widget.js | 8 ++++
src/widgets/components.js | 1 +
src/widgets/widgets.js | 2 +
7 files changed, 137 insertions(+)
create mode 100644 docs/widgets/services/aria2.md
create mode 100644 src/widgets/aria2c/component.jsx
create mode 100644 src/widgets/aria2c/proxy.js
create mode 100644 src/widgets/aria2c/widget.js
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,