diff --git a/config.yml b/config.yml index 2768328..d081870 100644 --- a/config.yml +++ b/config.yml @@ -76,10 +76,12 @@ nodes_settings: servers: false # Show server details (true/false). allocations_as_max_servers: false # Show allocations as max servers (true/false). unit: "byte" # Unit for node usage, Available types: "byte" or "percentage". + uptime: true # Enable or disable node uptime (true/false). limit: 100 # Node limit for usage statistics display. # Panel Users and Servers Settings panel_settings: + uptime: true # Enable or disable node uptime (true/false). status: true # Display panel stats under node stats (true/false). servers: true # Display servers count (true/false). users: true # Display users count (true/false). diff --git a/handlers/UptimeFormatter.js b/handlers/UptimeFormatter.js new file mode 100644 index 0000000..711d9e9 --- /dev/null +++ b/handlers/UptimeFormatter.js @@ -0,0 +1,13 @@ +module.exports = function UptimeFormatter(time) { + let text = [] + const days = Math.floor(time / 86400000); + const hours = Math.floor(time / 3600000) % 24; + const minutes = Math.floor(time / 60000) % 60; + const seconds = Math.floor(time / 1000) % 60; + if (days > 0) text.push(`${days} days`) + if (hours > 0) text.push(`${hours} hours`) + if (minutes > 0) text.push(`${minutes} minutes`) + if (text.length > 0) text.push(`and ${seconds} seconds`) + else text.push(`${seconds} seconds`) + return text.join(", ").replace(", and", " and") +} \ No newline at end of file diff --git a/handlers/app.js b/handlers/app.js index d0a94bd..c2234da 100644 --- a/handlers/app.js +++ b/handlers/app.js @@ -2,7 +2,6 @@ require("dotenv").config() const { Client, GatewayIntentBits, EmbedBuilder, time, ActivityType, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require("discord.js"); const fs = require("node:fs"); const cliColor = require("cli-color"); -const cachePath = require('node:path').join(__dirname, "../cache.json"); const config = require("./config.js"); const convertUnits = require("./convertUnits.js"); const getStats = require("./getStats.js"); @@ -20,6 +19,7 @@ module.exports = function App() { const results = await getStats(); createMessage({ panel: true, + uptime: results.uptime, nodes: results.nodes, servers: results.servers, users: results.users, @@ -27,7 +27,7 @@ module.exports = function App() { } catch { console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("Panel is currently offline.")); - fs.readFile(cachePath, (err, data) => { + fs.readFile(require('node:path').join(__dirname, "../cache.json"), (err, data) => { if (err) { createMessage({ cache: false, panel: false }); return console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("Last cache was not found!")); @@ -35,6 +35,8 @@ module.exports = function App() { try { const results = JSON.parse(data); + results.uptime = false + fs.writeFileSync("cache.json", JSON.stringify(results, null, 2), "utf8"); createMessage({ cache: true, panel: false, @@ -87,7 +89,7 @@ module.exports = function App() { startGetStatus(); }); - async function createMessage({ cache, panel, nodes, servers, users }) { + async function createMessage({ cache, panel, uptime, nodes, servers, users }) { let embed = new EmbedBuilder() .setAuthor({ name: config.embed.nodes.author.name || null, @@ -116,6 +118,7 @@ module.exports = function App() { `Disk : ${convertUnits(node.attributes.allocated_resources.disk, node.attributes.disk, config.nodes_settings.unit)}` + (node.attributes?.allocated_resources?.cpu ? `\nCPU : ${node.attributes?.allocated_resources?.cpu || 0}%` : "") + (config.nodes_settings.servers ? `\nServers: ${node.attributes.relationships.servers}${config.nodes_settings.allocations_as_max_servers ? ` / ${node.attributes.relationships.allocations}` : ""}` : "") + + (config.nodes_settings.uptime ? `\nUptime : ${node.uptime ? require("./UptimeFormatter.js")(Date.now() - node.uptime) : "N/A"}` : "") + "```" }); }); @@ -152,6 +155,7 @@ module.exports = function App() { `Nodes : ${nodes.length}\n` + (config.panel_settings.servers ? `Servers: ${servers || "Unknown"}\n` : "") + (config.panel_settings.users ? `Users : ${users || "Unknown"}\n` : "") + + (config.panel_settings.uptime ? `Uptime : ${uptime ? require("./UptimeFormatter.js")(Date.now() - uptime) : "N/A"}\n` : "") + "```" }); @@ -209,22 +213,27 @@ module.exports = function App() { } function DiscordErrorHandler(error) { - if (error.rawError?.code === 429) { - console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("Error 429 | Your IP has been rate limited by either Discord or your website. If it's a rate limit with Discord, you must wait. If it's a issue with your website, consider whitelisting your server IP.")); - } else if (error.rawError?.code === 403) { - console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("FORBIDDEN | The channel ID you provided is incorrect. Please double check you have the right ID. If you're not sure, read our documentation: https://github.com/HirziDevs/PteroStats#getting-channel-id")); - } else if (error.code === "ENOTFOUND") { - console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("ENOTFOUND | DNS Error. Ensure your network connection and DNS server are functioning correctly.")); - } else if (error.rawError?.code === 50001) { - console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("Discord Error | Your discord bot doesn't have access to see/send message/edit message in the channel!")); - } else if (error.rawError?.errors && Object?.values(error.rawError.errors)[0]._errors[0].code === "MAX_EMBED_SIZE_EXCEEDED") { - console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("Discord Error | Embed message limit exceeded! Please limit or decrease the nodes that need to be shown in the config!")); - } else if (error.rawError?.errors && Object?.values(error.rawError.errors)[0]._errors[0].code) { - console.log(Object.values(error.rawError.errors)[0]._errors[0].message); - } else { - console.error(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("Discord Error"), error); + try { + if (error.rawError?.code === 429) { + console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("Error 429 | Your IP has been rate limited by either Discord or your website. If it's a rate limit with Discord, you must wait. If it's a issue with your website, consider whitelisting your server IP.")); + } else if (error.rawError?.code === 403) { + console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("FORBIDDEN | The channel ID you provided is incorrect. Please double check you have the right ID. If you're not sure, read our documentation: https://github.com/HirziDevs/PteroStats#getting-channel-id")); + } else if (error.code === "ENOTFOUND") { + console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("ENOTFOUND | DNS Error. Ensure your network connection and DNS server are functioning correctly.")); + } else if (error.rawError?.code === 50001) { + console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("Discord Error | Your discord bot doesn't have access to see/send message/edit message in the channel!")); + } else if (error.rawError?.errors && Object?.values(error.rawError.errors)[0]?._errors[0]?.code === "MAX_EMBED_SIZE_EXCEEDED") { + console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("Discord Error | Embed message limit exceeded! Please limit or decrease the nodes that need to be shown in the config!")); + } else if (error.rawError?.errors && Object?.values(error.rawError.errors)[0]?._errors[0]?.code) { + console.log(Object.values(error.rawError.errors)[0]._errors[0].message); + } else { + console.error(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("Discord Error"), error); + } + process.exit(); + } catch (err) { + console.log(error) + process.exit(); } - process.exit(); } try { diff --git a/handlers/getStats.js b/handlers/getStats.js index cf62382..b1c4280 100644 --- a/handlers/getStats.js +++ b/handlers/getStats.js @@ -1,28 +1,40 @@ const config = require("./config.js"); const fs = require("node:fs"); -const getNodesDetails = require("./getNodesDetails.js"); -const getNodeConfiguration = require("./getNodeConfiguration.js"); -const getWingsStatus = require("./getWingsStatus.js"); -const getServers = require("./getServers.js"); -const getUsers = require("./getUsers.js"); -const promiseTimeout = require("./promiseTimeout.js"); const cliColor = require("cli-color"); module.exports = async function getStats() { + let cache = (() => { + try { + return JSON.parse(fs.readFileSync(require('node:path').join(__dirname, "../cache.json"))) + } catch { + return false + } + })() + console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow("Retrieving panel nodes...")) - const nodesStats = await getNodesDetails(); + const nodesStats = await require("./getNodesDetails.js")(); if (!nodesStats) throw new Error("Failed to get nodes attributes"); const statusPromises = nodesStats.slice(0, config.nodes_settings.limit).map(async (node) => { console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow(`Fetching ${cliColor.blueBright(node.attributes.name)} configuration...`)) - const nodeConfig = await getNodeConfiguration(node.attributes.id); + const nodeConfig = await require("./getNodeConfiguration.js")(node.attributes.id); console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow(`Checking ${cliColor.blueBright(node.attributes.name)} wings status...`)) - const nodeStatus = await promiseTimeout(getWingsStatus(node, nodeConfig.token), config.timeout * 1000); + const nodeStatus = await require("./promiseTimeout.js")(require("./getWingsStatus.js")(node, nodeConfig.token), config.timeout * 1000); - if (!nodeStatus) + let nodeUptime = cache ? (() => { + return cache.nodes.find((n) => n.attributes.id === node.attributes.id)?.uptime || Date.now() + })() : Date.now() + + if (!nodeUptime && nodeStatus) nodeUptime = Date.now() + + if (!nodeStatus) { + nodeUptime = false console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright(`Node ${cliColor.blueBright(node.attributes.name)} is currently offline.`)) + } + return { attributes: { + id: node.attributes.id, name: node.attributes.name, memory: node.attributes.memory, disk: node.attributes.disk, @@ -33,13 +45,17 @@ module.exports = async function getStats() { servers: node.attributes.relationships.servers.data.length } }, + uptime: nodeUptime, status: nodeStatus }; }); const data = { - servers: await getServers(), - users: await getUsers(), + uptime: cache ? (() => { + return cache.uptime || Date.now() + })() : Date.now(), + servers: await require("./getServers.js")(), + users: await require("./getUsers.js")(), nodes: await Promise.all(statusPromises), timestamp: Date.now() }