diff --git a/public/locales/en/common.json b/public/locales/en/common.json index d4634e807..dfc1a5ba6 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -185,5 +185,11 @@ "users": "Users", "loginsLast24H": "Logins (24h)", "failedLoginsLast24H": "Failed Logins (24h)" + }, + "proxmox": { + "mem": "MEM", + "cpu": "CPU", + "lxc": "LXC", + "vms": "VMs" } } diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js index d14ef0e1c..7418b68c3 100644 --- a/src/utils/proxy/handlers/credentialed.js +++ b/src/utils/proxy/handlers/credentialed.js @@ -29,6 +29,8 @@ export default async function credentialedProxyHandler(req, res) { headers["X-gotify-Key"] = `${widget.key}`; } else if (widget.type === "authentik") { headers.Authorization = `Bearer ${widget.key}`; + } else if (widget.type === "proxmox") { + headers.Authorization = `PVEAPIToken=${widget.username}=${widget.password}`; } else { headers["X-API-Key"] = `${widget.key}`; } diff --git a/src/widgets/components.js b/src/widgets/components.js index fdfaa3351..5357c0704 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -20,6 +20,7 @@ const components = { pihole: dynamic(() => import("./pihole/component")), portainer: dynamic(() => import("./portainer/component")), prowlarr: dynamic(() => import("./prowlarr/component")), + proxmox: dynamic(() => import("./proxmox/component")), qbittorrent: dynamic(() => import("./qbittorrent/component")), radarr: dynamic(() => import("./radarr/component")), readarr: dynamic(() => import("./readarr/component")), diff --git a/src/widgets/proxmox/component.jsx b/src/widgets/proxmox/component.jsx new file mode 100644 index 000000000..9cdb26f7f --- /dev/null +++ b/src/widgets/proxmox/component.jsx @@ -0,0 +1,53 @@ +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 calcRunning(total, current) { + return current.status === "running" ? total + 1 : total; +} + +export default function Component({ service }) { + const { t } = useTranslation(); + + const { widget } = service; + + const { data: clusterData, error: clusterError } = useWidgetAPI(widget, "cluster/resources"); + + if (clusterError) { + return ; + } + + if (!clusterData || !clusterData.data) { + return ( + + + + + + + ); + } + + const { data } = clusterData ; + const vms = data.filter(item => item.type === "qemu") || []; + const lxc = data.filter(item => item.type === "lxc") || []; + const nodes = data.filter(item => item.type === "node") || []; + + const runningVMs = vms.reduce(calcRunning, 0); + const runningLXC = lxc.reduce(calcRunning, 0); + + // TODO: support more than one node + // TODO: better handling of cluster with zero nodes + const node = nodes.length > 0 ? nodes[0] : { cpu: 0.0, mem: 0, maxmem: 0 }; + + return ( + + + + + + + ); +} diff --git a/src/widgets/proxmox/widget.js b/src/widgets/proxmox/widget.js new file mode 100644 index 000000000..32d361e4e --- /dev/null +++ b/src/widgets/proxmox/widget.js @@ -0,0 +1,14 @@ +import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; + +const widget = { + api: "{url}/api2/json/{endpoint}", + proxyHandler: credentialedProxyHandler, + + mappings: { + "cluster/resources": { + endpoint: "cluster/resources", + }, + }, +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 953b7417b..04665c782 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -15,6 +15,7 @@ import overseerr from "./overseerr/widget"; import pihole from "./pihole/widget"; import portainer from "./portainer/widget"; import prowlarr from "./prowlarr/widget"; +import proxmox from "./proxmox/widget"; import qbittorrent from "./qbittorrent/widget"; import radarr from "./radarr/widget"; import readarr from "./readarr/widget"; @@ -46,6 +47,7 @@ const widgets = { pihole, portainer, prowlarr, + proxmox, qbittorrent, radarr, readarr,