prusalink widget

prusalink widget

simple prusalink widget to track print progress
This commit is contained in:
Anton K. (ai Doge) 2024-01-23 18:39:00 -05:00
parent d3a6ad1b2e
commit 532cb2e161
7 changed files with 166 additions and 0 deletions

View File

@ -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
```

View File

@ -808,5 +808,10 @@
"netdata": { "netdata": {
"warnings": "Warnings", "warnings": "Warnings",
"criticals": "Criticals" "criticals": "Criticals"
},
"prusalink": {
"print_progress": "Progress",
"print_time": "Print time",
"print_time_left": "Remaining time"
} }
} }

View File

@ -110,6 +110,7 @@ const components = {
watchtower: dynamic(() => import("./watchtower/component")), watchtower: dynamic(() => import("./watchtower/component")),
whatsupdocker: dynamic(() => import("./whatsupdocker/component")), whatsupdocker: dynamic(() => import("./whatsupdocker/component")),
xteve: dynamic(() => import("./xteve/component")), xteve: dynamic(() => import("./xteve/component")),
prusalink: dynamic(() => import("./prusalink/component")),
}; };
export default components; export default components;

View File

@ -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 <Container service={service} error={prusalinkError} />;
}
if (!isPrinting) {
return (
<Container service={service}>
<Block label="prusalink.print_progress" value="-" />
<Block label="prusalink.print_time" value="-" />
<Block label="prusalink.print_time_left" value="-" />
</Container>
);
}
const progress = prusalinkStats.progress * 100;
const printTime = secondsToTimeObj(prusalinkStats.printTime);
const printTimeLeft = secondsToTimeObj(prusalinkStats.printTimeLeft);
return (
<Container service={service}>
<Block label="prusalink.print_progress" value={t("common.percent", { value: progress })} />
<Block
label="prusalink.print_time"
value={`${t("common.number", {
value: printTime.hours,
maximumFractionDigits: 0,
style: "unit",
unit: "hour",
})} ${t("common.number", {
value: printTime.minutes,
maximumFractionDigits: 0,
style: "unit",
unit: "minute",
})}`}
/>
<Block
label="prusalink.print_time_left"
value={`${t("common.number", {
value: printTimeLeft.hours,
maximumFractionDigits: 0,
style: "unit",
unit: "hour",
})} ${t("common.number", {
value: printTimeLeft.minutes,
maximumFractionDigits: 0,
style: "unit",
unit: "minute",
})}`}
/>
</Container>
);
}

View File

@ -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 } });
}
}

View File

@ -0,0 +1,8 @@
import prusalinkProxyHandler from "./proxy";
const widget = {
api: "{url}/api/job",
proxyHandler: prusalinkProxyHandler,
};
export default widget;

View File

@ -103,6 +103,7 @@ import whatsupdocker from "./whatsupdocker/widget";
import xteve from "./xteve/widget"; import xteve from "./xteve/widget";
import urbackup from "./urbackup/widget"; import urbackup from "./urbackup/widget";
import romm from "./romm/widget"; import romm from "./romm/widget";
import prusalink from "./prusalink/widget";
const widgets = { const widgets = {
adguard, adguard,
@ -212,6 +213,7 @@ const widgets = {
watchtower, watchtower,
whatsupdocker, whatsupdocker,
xteve, xteve,
prusalink,
}; };
export default widgets; export default widgets;