diff --git a/public/locales/en/common.json b/public/locales/en/common.json index b44c9812..b76917be 100755 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -625,5 +625,11 @@ "repos": "Repositories", "users": "Users", "orgs": "Organizations" + }, + "seafile": { + "users": "Users", + "groups": "Groups", + "libraries": "Libraries", + "storage": "Storage" } } diff --git a/src/widgets/components.js b/src/widgets/components.js index 6505928a..1011adad 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -70,6 +70,7 @@ const components = { rutorrent: dynamic(() => import("./rutorrent/component")), sabnzbd: dynamic(() => import("./sabnzbd/component")), scrutiny: dynamic(() => import("./scrutiny/component")), + seafile: dynamic(() => import("./seafile/component")), shoko: dynamic(() => import("./shoko/component")), sonarr: dynamic(() => import("./sonarr/component")), speedtest: dynamic(() => import("./speedtest/component")), diff --git a/src/widgets/seafile/component.jsx b/src/widgets/seafile/component.jsx new file mode 100644 index 00000000..64abcb14 --- /dev/null +++ b/src/widgets/seafile/component.jsx @@ -0,0 +1,40 @@ +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: seafileData, error: seafileError } = useWidgetAPI(widget); + + if (seafileError) { + return ; + } + + if (!seafileData) { + return ( + + + + + + + ); + } + + return ( + + + + + + + ); +} diff --git a/src/widgets/seafile/proxy.js b/src/widgets/seafile/proxy.js new file mode 100644 index 00000000..7e64e0d0 --- /dev/null +++ b/src/widgets/seafile/proxy.js @@ -0,0 +1,82 @@ +import cache from "memory-cache"; + +import { formatApiCall } from "utils/proxy/api-helpers"; +import { httpProxy } from "utils/proxy/http"; +import getServiceWidget from "utils/config/service-helpers"; +import createLogger from "utils/logger"; +import widgets from "widgets/widgets"; + +const proxyName = "seafileProxyHandler"; +const sessionTokenCacheKey = `${proxyName}__sessionToken`; +const logger = createLogger(proxyName); + +async function login(widget, service) { + const endpoint = "api2/auth-token/"; + const api = widgets?.[widget.type]?.api; + const loginUrl = new URL(formatApiCall(api, { endpoint, ...widget })); + const loginParams = { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ + username: widget.username, + password: widget.password, + }).toString(), + }; + + // eslint-disable-next-line no-unused-vars + const [status, contentType, data] = await httpProxy(loginUrl, loginParams); + + try { + const { token } = JSON.parse(data.toString()); + cache.put(`${sessionTokenCacheKey}.${service}`, token); + return { token }; + } catch (e) { + logger.error("Unable to login to Seafile API: %s", e); + } + + return { token: false }; +} + +export default async function seafileProxyHandler(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 getServiceWidget(group, service); + + 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" }); + } + + if (!cache.get(`${sessionTokenCacheKey}.${service}`)) { + await login(widget, service); + } + + const endpoint = "api/v2.1/admin/sysinfo/"; + const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget })); + const params = { + method: "GET", + headers: { + Authorization: `Token ${cache.get(`${sessionTokenCacheKey}.${service}`)}`, + }, + }; + + let [status, contentType, data] = await httpProxy(url, params); + + if (status === 401) { + logger.debug("Seafile API rejected the request, attempting to obtain new session token"); + const { token } = await login(widget, service); + params.headers.Authorization = `Token ${token}`; + + [status, contentType, data] = await httpProxy(url, params); + } + + if (contentType) res.setHeader("Content-Type", contentType); + return res.status(status).send(data); +} diff --git a/src/widgets/seafile/widget.js b/src/widgets/seafile/widget.js new file mode 100644 index 00000000..a2274f9c --- /dev/null +++ b/src/widgets/seafile/widget.js @@ -0,0 +1,8 @@ +import seafileProxyHandler from "./proxy"; + +const widget = { + api: "{url}/{endpoint}", + proxyHandler: seafileProxyHandler, +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 60e1eec2..c65f60f8 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -64,6 +64,7 @@ import readarr from "./readarr/widget"; import rutorrent from "./rutorrent/widget"; import sabnzbd from "./sabnzbd/widget"; import scrutiny from "./scrutiny/widget"; +import seafile from "./seafile/widget"; import shoko from "./shoko/widget"; import sonarr from "./sonarr/widget"; import speedtest from "./speedtest/widget"; @@ -150,6 +151,7 @@ const widgets = { rutorrent, sabnzbd, scrutiny, + seafile, shoko, sonarr, speedtest,