diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 9f5637bf..f70792a7 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -206,7 +206,7 @@ "processed": "Processed", "errored": "Errored", "saved": "Saved" - }, + }, "traefik": { "routers": "Routers", "services": "Services", @@ -395,5 +395,10 @@ "nextdns": { "wait": "Please Wait", "no_devices": "No Device Data Received" + }, + "xteve": { + "streams_all": "All Streams", + "streams_active": "Active Streams", + "streams_xepg": "XEPG Channels" } } diff --git a/src/widgets/components.js b/src/widgets/components.js index 9f3011b3..e1b11018 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -53,6 +53,7 @@ const components = { truenas: dynamic(() => import("./truenas/component")), unifi: dynamic(() => import("./unifi/component")), watchtower: dynamic(() => import("./watchtower/component")), + xteve: dynamic(() => import("./xteve/component")), }; export default components; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index c68dfe3e..3ed4b604 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -48,6 +48,7 @@ import tubearchivist from "./tubearchivist/widget"; import truenas from "./truenas/widget"; import unifi from "./unifi/widget"; import watchtower from './watchtower/widget' +import xteve from './xteve/widget' const widgets = { adguard, @@ -102,6 +103,7 @@ const widgets = { unifi, unifi_console: unifi, watchtower, + xteve, }; export default widgets; diff --git a/src/widgets/xteve/component.jsx b/src/widgets/xteve/component.jsx new file mode 100644 index 00000000..2568301a --- /dev/null +++ b/src/widgets/xteve/component.jsx @@ -0,0 +1,35 @@ +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: xteveData, error: xteveError } = useWidgetAPI(widget, "xteve"); + + if (xteveError) { + return ; + } + + if (!xteveData) { + return ( + + + + + + ); + } + + return ( + + + + + + ); +} diff --git a/src/widgets/xteve/proxy.js b/src/widgets/xteve/proxy.js new file mode 100644 index 00000000..fdf15272 --- /dev/null +++ b/src/widgets/xteve/proxy.js @@ -0,0 +1,66 @@ +import { formatApiCall } from "utils/proxy/api-helpers"; +import { httpProxy } from "utils/proxy/http"; +import createLogger from "utils/logger"; +import widgets from "widgets/widgets"; +import getServiceWidget from "utils/config/service-helpers"; + +const logger = createLogger("xteveProxyHandler"); + +export default async function xteveProxyHandler(req, res) { + const { group, service, endpoint } = req.query; + + if (!group || !service) { + return res.status(400).json({ error: "Invalid proxy service type" }); + } + + const widget = await getServiceWidget(group, service); + const api = widgets?.[widget.type]?.api; + if (!api) { + return res.status(403).json({ error: "Service does not support API calls" }); + } + + const url = formatApiCall(api, { endpoint, ...widget }); + const method = "POST"; + let payload = { "cmd": "status" }; + + if (widget.username && widget.password) { + const body = JSON.stringify({ + "cmd": "login", + "username": widget.username, + "password": widget.password, + }); + + let [status, contentType, data] = await httpProxy(url, { + method, + body, + }); + + if (status !== 200) { + return [status, contentType, data]; + } + + const json = JSON.parse(data.toString()); + + if (json?.status !== true) { + let message = "Authentication failed."; + return res.status(401).end(JSON.stringify({error: { message: message } })); + } + + payload["token"] = json.token; + } + + const body = JSON.stringify(payload); + + let [status, contentType, data] = await httpProxy(url, { + method, + body, + }); + + if (status !== 200) { + logger.debug("Error %d calling endpoint %s", status, url); + return res.status(status, data); + } + + if (contentType) res.setHeader("Content-Type", contentType); + return res.status(status).send(data); +} diff --git a/src/widgets/xteve/widget.js b/src/widgets/xteve/widget.js new file mode 100644 index 00000000..7e65b8b6 --- /dev/null +++ b/src/widgets/xteve/widget.js @@ -0,0 +1,14 @@ +import xteveProxyHandler from "./proxy"; + +const widget = { + api: "{url}/{endpoint}", + proxyHandler: xteveProxyHandler, + + mappings: { + "xteve": { + endpoint: "api/", + }, + }, +}; + +export default widget;