diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 0bf28285e..150afbd90 100755 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -502,5 +502,10 @@ "lastrun": "Last Run", "nextrun": "Next Run", "failed": "Failed" + }, + "unmanic": { + "active_workers": "Active Workers", + "total_workers": "Total Workers", + "records_total": "Queue Length" } } \ No newline at end of file diff --git a/src/widgets/components.js b/src/widgets/components.js index e4ecb9479..fb64e2b02 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -68,6 +68,7 @@ const components = { tubearchivist: dynamic(() => import("./tubearchivist/component")), truenas: dynamic(() => import("./truenas/component")), unifi: dynamic(() => import("./unifi/component")), + unmanic: dynamic(() => import("./unmanic/component")), watchtower: dynamic(() => import("./watchtower/component")), xteve: dynamic(() => import("./xteve/component")), immich: dynamic(() => import("./immich/component")), diff --git a/src/widgets/unmanic/component.jsx b/src/widgets/unmanic/component.jsx new file mode 100644 index 000000000..1d68e7650 --- /dev/null +++ b/src/widgets/unmanic/component.jsx @@ -0,0 +1,33 @@ +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 { widget } = service; + + const { data: workersData, error: workersError } = useWidgetAPI(widget, "workers"); + const { data: pendingData, error: pendingError } = useWidgetAPI(widget, "pending"); + + if (workersError || pendingError) { + const finalError = workersError ?? pendingError; + return ; + } + + if (!workersData || !pendingData) { + return ( + + + + + + ); + } + + return ( + + + + + + ); +} diff --git a/src/widgets/unmanic/proxy.js b/src/widgets/unmanic/proxy.js new file mode 100644 index 000000000..7a8337659 --- /dev/null +++ b/src/widgets/unmanic/proxy.js @@ -0,0 +1,55 @@ +import getServiceWidget from "utils/config/service-helpers"; +import { formatApiCall } from "utils/proxy/api-helpers"; +import validateWidgetData from "utils/proxy/validate-widget-data"; +import { httpProxy } from "utils/proxy/http"; +import createLogger from "utils/logger"; +import widgets from "widgets/widgets"; + +const logger = createLogger("unmanicProxyHandler"); + +export default async function unmanicProxyHandler(req, res, map) { + const { group, service, endpoint } = req.query; + + if (group && service) { + const widget = await getServiceWidget(group, service); + + if (!widgets?.[widget.type]?.api) { + return res.status(403).json({ error: "Service does not support API calls" }); + } + + if (widget) { + const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget })); + + const [status, contentType, data] = await httpProxy(url, { + method: req.method, + body: (endpoint === "pending/tasks") ? "{}" : "", + }); + + let resultData = data; + + if (!validateWidgetData(widget, endpoint, resultData)) { + return res.status(status).json({error: {message: "Invalid data", url, data: resultData}}); + } + + if (status === 200 && map) { + resultData = map(data); + } + + if (contentType) res.setHeader("Content-Type", contentType); + + if (status === 204 || status === 304) { + return res.status(status).end(); + } + + if (status >= 400) { + logger.debug("HTTP Error %d calling %s//%s%s...", status, url.protocol, url.hostname, url.pathname); + return res.status(status).json({error: {message: "HTTP Error", url, data}}); + } + + return res.status(status).send(resultData); + } + } + + logger.debug("Invalid or missing proxy service type '%s' in group '%s'", service, group); + return res.status(400).json({ error: "Invalid proxy service type" }); +} diff --git a/src/widgets/unmanic/widget.js b/src/widgets/unmanic/widget.js new file mode 100644 index 000000000..69bc63808 --- /dev/null +++ b/src/widgets/unmanic/widget.js @@ -0,0 +1,27 @@ +import unmanicProxyHandler from "./proxy"; + +import { asJson } from "utils/proxy/api-helpers"; + +const widget = { + api: "{url}/unmanic/api/v2/{endpoint}", + proxyHandler: unmanicProxyHandler, + + mappings: { + workers: { + endpoint: "workers/status", + map: (data) => ({ + total_workers: (asJson(data).workers_status).length, + active_workers: (asJson(data).workers_status).filter(worker => !worker.idle).length, + }) + }, + pending: { + method: "POST", + endpoint: "pending/tasks", + validate: [ + "recordsTotal" + ] + }, + }, +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index bf9457c66..c9f299cba 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -66,6 +66,7 @@ import watchtower from "./watchtower/widget"; import xteve from "./xteve/widget"; import immich from "./immich/widget"; import uptimekuma from "./uptimekuma/widget"; +import unmanic from "./unmanic/widget"; const widgets = { adguard, @@ -134,6 +135,7 @@ const widgets = { truenas, unifi, unifi_console: unifi, + unmanic, watchtower, xteve, immich,