From 532cb2e1610edc980464a47dd0e3d291a7791041 Mon Sep 17 00:00:00 2001 From: "Anton K. (ai Doge)" Date: Tue, 23 Jan 2024 18:39:00 -0500 Subject: [PATCH] prusalink widget prusalink widget simple prusalink widget to track print progress --- docs/widgets/services/prusalink.md | 15 ++++++ public/locales/en/common.json | 5 ++ src/widgets/components.js | 1 + src/widgets/prusalink/component.jsx | 72 +++++++++++++++++++++++++++++ src/widgets/prusalink/proxy.js | 63 +++++++++++++++++++++++++ src/widgets/prusalink/widget.js | 8 ++++ src/widgets/widgets.js | 2 + 7 files changed, 166 insertions(+) create mode 100644 docs/widgets/services/prusalink.md create mode 100644 src/widgets/prusalink/component.jsx create mode 100644 src/widgets/prusalink/proxy.js create mode 100644 src/widgets/prusalink/widget.js diff --git a/docs/widgets/services/prusalink.md b/docs/widgets/services/prusalink.md new file mode 100644 index 00000000..1da32c10 --- /dev/null +++ b/docs/widgets/services/prusalink.md @@ -0,0 +1,15 @@ +--- +title: PrusaLink +description: PrusaLink Widget Configuration +--- + +[PrusaLink](https://github.com/prusa3d/Prusa-Link-Web) + +Allowed fields: `["print_progress", "print_time", "print_time_left"]`. + +```yaml +widget: + type: prusalink + url: http://prusalink.host + key: mytokenhere # see https://help.prusa3d.com/article/sending-g-codes-to-printer-via-network-prusaconnect-prusalink-octoprint_196761 +``` diff --git a/public/locales/en/common.json b/public/locales/en/common.json index b0f60a6d..1542e0e8 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -808,5 +808,10 @@ "netdata": { "warnings": "Warnings", "criticals": "Criticals" + }, + "prusalink": { + "print_progress": "Progress", + "print_time": "Print time", + "print_time_left": "Remaining time" } } diff --git a/src/widgets/components.js b/src/widgets/components.js index 497d6407..1675e2f2 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -110,6 +110,7 @@ const components = { watchtower: dynamic(() => import("./watchtower/component")), whatsupdocker: dynamic(() => import("./whatsupdocker/component")), xteve: dynamic(() => import("./xteve/component")), + prusalink: dynamic(() => import("./prusalink/component")), }; export default components; diff --git a/src/widgets/prusalink/component.jsx b/src/widgets/prusalink/component.jsx new file mode 100644 index 00000000..83577337 --- /dev/null +++ b/src/widgets/prusalink/component.jsx @@ -0,0 +1,72 @@ +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"; + +function secondsToTimeObj(seconds) { + return { + seconds: seconds % 60, + minutes: Math.floor((seconds / 60) % 60), + hours: Math.floor((seconds / 3600) % 60), + }; +} + +export default function Component({ service }) { + const { t } = useTranslation(); + const { widget } = service; + const { data: prusalinkStats, error: prusalinkError } = useWidgetAPI(widget, "prusalink"); + const isPrinting = prusalinkStats?.state === "Printing"; + + if (prusalinkError) { + return ; + } + + if (!isPrinting) { + return ( + + + + + + ); + } + + const progress = prusalinkStats.progress * 100; + const printTime = secondsToTimeObj(prusalinkStats.printTime); + const printTimeLeft = secondsToTimeObj(prusalinkStats.printTimeLeft); + + return ( + + + + + + ); +} diff --git a/src/widgets/prusalink/proxy.js b/src/widgets/prusalink/proxy.js new file mode 100644 index 00000000..54e6018b --- /dev/null +++ b/src/widgets/prusalink/proxy.js @@ -0,0 +1,63 @@ +import { httpProxy } from "utils/proxy/http"; +import { formatApiCall } from "utils/proxy/api-helpers"; +import getServiceWidget from "utils/config/service-helpers"; +import createLogger from "utils/logger"; +import widgets from "widgets/widgets"; + +const proxyName = "prusalinkProxyHandler"; +const logger = createLogger(proxyName); + +async function retrieveFromAPI(url, key) { + const headers = { + "content-type": "application/json", + "X-Api-Key": key, + }; + + const [status, , data] = await httpProxy(url, { headers }); + + if (status !== 200) { + throw new Error(`Error getting data from prusalink: ${status}. Data: ${data.toString()}`); + } + + return JSON.parse(Buffer.from(data).toString()); +} + +export default async function prusalinkProxyHandler(req, res) { + const { group, service, endpoint } = 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 (!widget.key) { + logger.debug("Invalid or missing key for service '%s' in group '%s'", service, group); + return res.status(400).json({ error: "Missing widget key" }); + } + + const apiURL = widgets[widget.type].api; + + try { + const url = new URL(formatApiCall(apiURL, { endpoint, ...widget })); + const prusalinkData = await retrieveFromAPI(url, widget.key); + + const prusalinkStats = { + state: prusalinkData.state, + progress: prusalinkData.progress?.completion, + printTime: prusalinkData.progress?.printTime, + printTimeLeft: prusalinkData.progress?.printTimeLeft, + }; + + return res.status(200).send(prusalinkStats); + } catch (e) { + logger.error(e.message); + return res.status(500).send({ error: { message: e.message } }); + } +} diff --git a/src/widgets/prusalink/widget.js b/src/widgets/prusalink/widget.js new file mode 100644 index 00000000..c2b8da0f --- /dev/null +++ b/src/widgets/prusalink/widget.js @@ -0,0 +1,8 @@ +import prusalinkProxyHandler from "./proxy"; + +const widget = { + api: "{url}/api/job", + proxyHandler: prusalinkProxyHandler, +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 553ce626..de27c203 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -103,6 +103,7 @@ import whatsupdocker from "./whatsupdocker/widget"; import xteve from "./xteve/widget"; import urbackup from "./urbackup/widget"; import romm from "./romm/widget"; +import prusalink from "./prusalink/widget"; const widgets = { adguard, @@ -212,6 +213,7 @@ const widgets = { watchtower, whatsupdocker, xteve, + prusalink, }; export default widgets;