From d18d3ca8fcab7f8010a2f7c487c090cabce7a4b0 Mon Sep 17 00:00:00 2001 From: Karl Hudgell Date: Mon, 14 Apr 2025 19:53:19 +0100 Subject: [PATCH] rework portainer to list all env's if none specified in config --- src/widgets/portainer/component.jsx | 12 +-- src/widgets/portainer/proxy.js | 120 ++++++++++++++++++++++++++++ src/widgets/portainer/widget.js | 11 ++- 3 files changed, 129 insertions(+), 14 deletions(-) create mode 100644 src/widgets/portainer/proxy.js diff --git a/src/widgets/portainer/component.jsx b/src/widgets/portainer/component.jsx index f8a89507..aba8c7db 100644 --- a/src/widgets/portainer/component.jsx +++ b/src/widgets/portainer/component.jsx @@ -28,16 +28,12 @@ export default function Component({ service }) { // containersData can be itself an error object e.g. if environment fails return ; } - - const running = containersData.filter((c) => c.State === "running").length; - const stopped = containersData.filter((c) => c.State === "exited").length; - const total = containersData.length; - + return ( - - - + + + ); } diff --git a/src/widgets/portainer/proxy.js b/src/widgets/portainer/proxy.js new file mode 100644 index 00000000..baaec7cf --- /dev/null +++ b/src/widgets/portainer/proxy.js @@ -0,0 +1,120 @@ +/* eslint-disable no-underscore-dangle */ +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 = "portainerProxyHandler"; + +const logger = createLogger(proxyName); + +async function getWidget(req) { + const { group, service } = req.query; + if (!group || !service) { + logger.debug("Invalid or missing service '%s' or group '%s'", service, group); + return null; + } + const widget = await getServiceWidget(group, service); + if (!widget) { + logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); + return null; + } + return widget; +} + + +async function getAllEnvIds(widget) { + const api = widgets?.[widget.type]?.api; + if (!api) { + return [403, null]; + } + const endpoint = 'endpoints' + let url = new URL(formatApiCall(api, { endpoint, ...widget })); + let [status, contentType, data] = await httpProxy(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + 'x-api-key': widget.key, + } + }); + + if (status !== 200) { + logger.error("HTTP %d communicating with NextPVR. Data: %s", status, data.toString()); + return [status, data]; + } + let dataAsJson; + try { + const dataDecoded = data.toString(); + dataAsJson = JSON.parse(dataDecoded); + } catch (e) { + logger.error("Error decoding NextPVR API data. Data: %s", data.toString()); + return [status, null]; + } + const ids = await dataAsJson.map(item => item.Id); + + return ids; +} + + +async function getAllContainers(ids, widget) { + let items = [] + const api = widgets?.[widget.type]?.api; + if (!api) { + return [403, null]; + } + for (let i = 0; i < ids.length; i++) { + const endpoint = "endpoints/" + ids[i] + "/docker/containers/json?all=1" + let url = new URL(formatApiCall(api, { endpoint, ...widget })); + let [status, contentType, data] = await httpProxy(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + 'x-api-key': widget.key, + } + }); + + if (status !== 200) { + logger.error("HTTP %d communicating with NextPVR. Data: %s", status, data.toString()); + return [status, data]; + } + let dataAsJson; + try { + const dataDecoded = data.toString(); + dataAsJson = JSON.parse(dataDecoded); + items.push(dataAsJson) + } catch (e) { + logger.error("Error decoding NextPVR API data. Data: %s", data.toString()); + return [status, null]; + } + } + return items.flat(); +} + +export default async function portainerProxyHandler(req, res) { + try { + const widget = await getWidget(req); + let ids + + if (('env' in widget)) { + ids = [widget.env]; + } else { + ids = await getAllEnvIds(widget); + } + const data = await getAllContainers(ids, widget); + + const containerList = Object.values(data); + + const running = containerList.filter((c) => c.State === "running").length; + const stopped = containerList.filter((c) => c.State === "exited").length; + const total = containerList.length; + + return res.status(200).send({ running, stopped, total }); + + } catch (error) { + console.error("portainerProxyHandler error:", error); + return res.status(500).send({ error: "Internal Server Error" }); + } +} + + diff --git a/src/widgets/portainer/widget.js b/src/widgets/portainer/widget.js index ca3d5bb0..726572ce 100644 --- a/src/widgets/portainer/widget.js +++ b/src/widgets/portainer/widget.js @@ -1,14 +1,13 @@ -import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; +import portainerProxyHandler from "./proxy"; const widget = { - api: "{url}/api/endpoints/{env}/{endpoint}", - proxyHandler: credentialedProxyHandler, + api: "{url}/api/{endpoint}", + proxyHandler: portainerProxyHandler, mappings: { "docker/containers/json": { - endpoint: "docker/containers/json", - params: ["all"], - }, + endpoint: "endpoints/{env}/docker/containers/json" + } }, };