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;