diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 50e796f5..6bb3377e 100755 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -517,5 +517,9 @@ "active_workers": "Active Workers", "total_workers": "Total Workers", "records_total": "Queue Length" + }, + "pterodactyl": { + "servers": "Servers", + "nodes": "Nodes" } -} \ No newline at end of file +} diff --git a/src/widgets/components.js b/src/widgets/components.js index cfd4d01a..28a70755 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -52,6 +52,7 @@ const components = { portainer: dynamic(() => import("./portainer/component")), prowlarr: dynamic(() => import("./prowlarr/component")), proxmox: dynamic(() => import("./proxmox/component")), + pterodactyl: dynamic(() => import("./pterodactyl/component")), pyload: dynamic(() => import("./pyload/component")), qbittorrent: dynamic(() => import("./qbittorrent/component")), radarr: dynamic(() => import("./radarr/component")), @@ -76,4 +77,4 @@ const components = { uptimekuma: dynamic(() => import("./uptimekuma/component")), }; -export default components; \ No newline at end of file +export default components; diff --git a/src/widgets/pterodactyl/component.jsx b/src/widgets/pterodactyl/component.jsx new file mode 100644 index 00000000..faa236ba --- /dev/null +++ b/src/widgets/pterodactyl/component.jsx @@ -0,0 +1,30 @@ + +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 {widget} = service; + + const {data: datasData, error: datasError} = useWidgetAPI(widget); + + if (datasError) { + return ; + } + + if (!datasData) { + return ( + + + + + ); + } + return ( + + + + + ); +} diff --git a/src/widgets/pterodactyl/proxy.js b/src/widgets/pterodactyl/proxy.js new file mode 100644 index 00000000..0c36da4a --- /dev/null +++ b/src/widgets/pterodactyl/proxy.js @@ -0,0 +1,97 @@ + +import { httpProxy } from "utils/proxy/http"; +import getServiceWidget from "utils/config/service-helpers"; +import createLogger from "utils/logger"; + +const proxyName = "pterodactylProxyHandler"; + +const logger = createLogger(proxyName); + +export default async function pterodactylProxyHandler(req, res) { + const { group, service } = req.query; + + if (group && service) { + const widget = await getServiceWidget(group, service); + + if (widget) { + + const { url } = widget; + + const nodesURL = `${url}/api/application/nodes?include=servers`; + + let [status, contentType, data] = await httpProxy(nodesURL, { + headers: { + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": `Bearer ${widget.key}` + }, + }); + + if (status !== 200) { + logger.error("Unable to retrieve Pterodactyl nodes' list"); + return res.status(status).json({error: {message: `HTTP Error ${status}`, url: nodesURL, data}}); + } + + const nodesData = JSON.parse(data); + const nodesTotal = nodesData.data.length; + let nodesOnline = 0; + let total = 0; + + const serversRequests = []; + const nodesRequests = []; + + for (let nodeid = 0; nodeid < nodesData.data.length; nodeid += 1) { + // check if node is online + const nodeURL = `${nodesData.data[nodeid].attributes.scheme}://${nodesData.data[nodeid].attributes.fqdn}:${nodesData.data[nodeid].attributes.daemon_listen}/api/system`; + + nodesRequests.push(httpProxy(nodeURL)); + + for (let serverid = 0; serverid < nodesData.data[nodeid].attributes.relationships.servers.data.length; serverid += 1) { + total += 1; + const serverURL = `${url}/api/client/servers/${nodesData.data[nodeid].attributes.relationships.servers.data[serverid].attributes.identifier}/resources`; + serversRequests.push(httpProxy(serverURL, { + headers: { + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": `Bearer ${widget.key}` + }, + })); + } + } + + const nodesList = await Promise.all(nodesRequests); + + for (let nodeid = 0; nodeid < nodesList.length; nodeid += 1) { + // eslint-disable-next-line no-unused-vars + [status, contentType, data] = nodesList[nodeid]; + if (status === 401) { + nodesOnline += 1; + } + } + + let online = 0; + + const serversList = await Promise.all(serversRequests); + for (let serverid = 0; serverid < serversList.length; serverid += 1) { + // eslint-disable-next-line no-unused-vars + [status, contentType, data] = serversList[serverid]; + if (status === 200) { + const serverData = JSON.parse(data); + if (serverData.attributes.current_state === "running") { + online += 1; + } + } + } + + const servers = `${online}/${total}`; + const nodes = `${nodesOnline}/${nodesTotal}`; + + return res.send(JSON.stringify({ + nodes, + servers + })); + } + } + + return res.status(400).json({ error: "Invalid proxy service type" }); +} diff --git a/src/widgets/pterodactyl/widget.js b/src/widgets/pterodactyl/widget.js new file mode 100644 index 00000000..63e1e499 --- /dev/null +++ b/src/widgets/pterodactyl/widget.js @@ -0,0 +1,8 @@ + +import pterodactylProxyHandler from "./proxy"; + +const widget = { + proxyHandler: pterodactylProxyHandler, +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 7df12776..5d4b74a2 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -46,6 +46,7 @@ import plex from "./plex/widget"; import portainer from "./portainer/widget"; import prowlarr from "./prowlarr/widget"; import proxmox from "./proxmox/widget"; +import pterodactyl from "./pterodactyl/widget"; import pyload from "./pyload/widget"; import qbittorrent from "./qbittorrent/widget"; import radarr from "./radarr/widget"; @@ -119,6 +120,7 @@ const widgets = { portainer, prowlarr, proxmox, + pterodactyl, pyload, qbittorrent, radarr, @@ -144,4 +146,4 @@ const widgets = { uptimekuma, }; -export default widgets; \ No newline at end of file +export default widgets;