From f1c6beacf29092f66f470d7c7b765cd92291ae02 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 27 Jun 2025 12:38:06 +0800 Subject: [PATCH 001/259] use super-simple-queue --- server/index.js | 26 +- server/package-lock.json | 1614 ++++++++++------- server/package.json | 1 + .../SuperSimpleQueue/SuperSimpleQueue.js | 146 ++ .../SuperSimpleQueueHelper.js | 99 + server/service/networkService.js | 2 +- 6 files changed, 1185 insertions(+), 703 deletions(-) mode change 100755 => 100644 server/package-lock.json create mode 100644 server/service/SuperSimpleQueue/SuperSimpleQueue.js create mode 100644 server/service/SuperSimpleQueue/SuperSimpleQueueHelper.js diff --git a/server/index.js b/server/index.js index 4b74ef46c..a4fe8cd7f 100755 --- a/server/index.js +++ b/server/index.js @@ -53,6 +53,9 @@ import { Queue, Worker } from "bullmq"; import PulseQueue from "./service/PulseQueue/PulseQueue.js"; import PulseQueueHelper from "./service/PulseQueue/PulseQueueHelper.js"; +import SuperSimpleQueue from "./service/SuperSimpleQueue/SuperSimpleQueue.js"; +import SuperSimpleQueueHelper from "./service/SuperSimpleQueue/SuperSimpleQueueHelper.js"; + //Network service and dependencies import NetworkService from "./service/networkService.js"; import axios from "axios"; @@ -210,23 +213,38 @@ const startApp = async () => { // stringService, // }); - const pulseQueueHelper = new PulseQueueHelper({ + // const pulseQueueHelper = new PulseQueueHelper({ + // db, + // logger, + // networkService, + // statusService, + // notificationService, + // }); + // const pulseQueue = await PulseQueue.create({ + // appSettings, + // db, + // pulseQueueHelper, + // logger, + // }); + const superSimpleQueueHelper = new SuperSimpleQueueHelper({ db, logger, networkService, statusService, notificationService, }); - const pulseQueue = await PulseQueue.create({ + + const superSimpleQueue = await SuperSimpleQueue.create({ appSettings, db, - pulseQueueHelper, logger, + helper: superSimpleQueueHelper, }); // Register services // ServiceRegistry.register(JobQueue.SERVICE_NAME, jobQueue); - ServiceRegistry.register(JobQueue.SERVICE_NAME, pulseQueue); + // ServiceRegistry.register(JobQueue.SERVICE_NAME, pulseQueue); + ServiceRegistry.register(JobQueue.SERVICE_NAME, superSimpleQueue); ServiceRegistry.register(MongoDB.SERVICE_NAME, db); ServiceRegistry.register(SettingsService.SERVICE_NAME, settingsService); ServiceRegistry.register(EmailService.SERVICE_NAME, emailService); diff --git a/server/package-lock.json b/server/package-lock.json old mode 100755 new mode 100644 index 3fd675af3..427356e90 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -33,6 +33,7 @@ "ping": "0.4.4", "sharp": "0.33.5", "ssl-checker": "2.0.10", + "super-simple-scheduler": "1.0.7", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, @@ -51,36 +52,33 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/runtime": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", - "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, "engines": { "node": ">=6.9.0" } @@ -92,9 +90,9 @@ "license": "Apache-2.0" }, "node_modules/@bcoe/v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.1.tgz", - "integrity": "sha512-W+a0/JpU28AqH4IKtwUPcEUnUyXMDLALcn5/JLczGGT9fHE2sIby/xP/oQnx3nxkForzgzPy201RAKcB4xPAFQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, "license": "MIT", "engines": { @@ -122,9 +120,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", - "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", + "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", "license": "MIT", "optional": true, "dependencies": { @@ -132,10 +130,11 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "dev": true, + "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" }, @@ -154,6 +153,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -166,17 +166,19 @@ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/config-array": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", - "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", + "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.5", + "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -184,34 +186,20 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/@eslint/config-helpers": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", + "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==", "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "Apache-2.0", "engines": { - "node": "*" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", - "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -222,10 +210,11 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", - "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -244,22 +233,12 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -267,56 +246,60 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/@eslint/js": { - "version": "9.20.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.20.0.tgz", - "integrity": "sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==", + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", + "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", - "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", - "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", + "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.10.0", + "@eslint/core": "^0.15.1", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@grpc/grpc-js": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.12.5.tgz", - "integrity": "sha512-d3iiHxdpg5+ZcJ6jnDSOT8Z0O0VMVGy34jAnYLUX8yd36b1qn8f1TwOA/Lc7TsOh03IkPJ38eGI5qD2EjNkoEA==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz", + "integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==", "license": "Apache-2.0", "dependencies": { "@grpc/proto-loader": "^0.7.13", @@ -327,9 +310,9 @@ } }, "node_modules/@grpc/proto-loader": { - "version": "0.7.13", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", - "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", "license": "Apache-2.0", "dependencies": { "lodash.camelcase": "^4.3.0", @@ -364,6 +347,7 @@ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=18.18.0" } @@ -373,6 +357,7 @@ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" @@ -386,6 +371,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -399,6 +385,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -408,10 +395,11 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -853,9 +841,9 @@ } }, "node_modules/@mongodb-js/saslprep": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", - "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz", + "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==", "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" @@ -1027,23 +1015,6 @@ "mongodb": "^6.5.0" } }, - "node_modules/@pulsecron/pulse/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/@scarf/scarf": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", @@ -1131,10 +1102,11 @@ } }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", @@ -1147,15 +1119,16 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.4.tgz", - "integrity": "sha512-99l6wv4HEzBQhvaU/UGoeBoCK61SCROQaCCGyQSgX2tEQ3rKkNZ2S7CEWnS/4s1LV+8ODdK21UeyR1fHP2mXug==", + "version": "24.0.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.4.tgz", + "integrity": "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA==", "license": "MIT", "dependencies": { - "undici-types": "~6.20.0" + "undici-types": "~7.8.0" } }, "node_modules/@types/triple-beam": { @@ -1192,11 +1165,21 @@ "node": ">= 0.6" } }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -1209,20 +1192,18 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", "license": "MIT", - "dependencies": { - "debug": "4" - }, "engines": { - "node": ">= 6.0.0" + "node": ">= 14" } }, "node_modules/ajv": { @@ -1230,6 +1211,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1340,9 +1322,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz", - "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", + "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -1463,12 +1445,14 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "node_modules/braces": { @@ -1491,9 +1475,9 @@ "license": "ISC" }, "node_modules/browserslist": { - "version": "4.24.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", - "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", "funding": [ { "type": "opencollective", @@ -1510,10 +1494,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -1523,9 +1507,9 @@ } }, "node_modules/bson": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.1.tgz", - "integrity": "sha512-P92xmHDQjSKPLHqFxefqMxASNq/aWJMEZugpCjf+AF/pgcUpMMQCg7t7+ewko0/u8AapvF3luf/FoehddEK+sA==", + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", "license": "Apache-2.0", "engines": { "node": ">=16.20.1" @@ -1646,9 +1630,9 @@ } }, "node_modules/call-bind-apply-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", - "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -1659,13 +1643,13 @@ } }, "node_modules/call-bound": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", - "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "get-intrinsic": "^1.2.6" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -1709,9 +1693,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001690", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", - "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", "funding": [ { "type": "opencollective", @@ -1834,6 +1818,24 @@ "fsevents": "~2.3.2" } }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -2063,15 +2065,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/compression/node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2318,13 +2311,13 @@ } }, "node_modules/cssnano": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.0.6.tgz", - "integrity": "sha512-54woqx8SCbp8HwvNZYn68ZFAepuouZW4lTwiMVnBErM3VkO7/Sd4oTOt3Zz3bPx3kxQ36aISppyXj2Md4lg8bw==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.0.7.tgz", + "integrity": "sha512-evKu7yiDIF7oS+EIpwFlMF730ijRyLFaM2o5cTxRGJR9OKHKkc+qP443ZEVR9kZG0syaAJJCPJyfv5pbrxlSng==", "license": "MIT", "dependencies": { - "cssnano-preset-default": "^7.0.6", - "lilconfig": "^3.1.2" + "cssnano-preset-default": "^7.0.7", + "lilconfig": "^3.1.3" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" @@ -2334,63 +2327,63 @@ "url": "https://opencollective.com/cssnano" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/cssnano-preset-default": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.6.tgz", - "integrity": "sha512-ZzrgYupYxEvdGGuqL+JKOY70s7+saoNlHSCK/OGn1vB2pQK8KSET8jvenzItcY+kA7NoWvfbb/YhlzuzNKjOhQ==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.7.tgz", + "integrity": "sha512-jW6CG/7PNB6MufOrlovs1TvBTEVmhY45yz+bd0h6nw3h6d+1e+/TX+0fflZ+LzvZombbT5f+KC063w9VoHeHow==", "license": "MIT", "dependencies": { - "browserslist": "^4.23.3", + "browserslist": "^4.24.5", "css-declaration-sorter": "^7.2.0", - "cssnano-utils": "^5.0.0", - "postcss-calc": "^10.0.2", - "postcss-colormin": "^7.0.2", - "postcss-convert-values": "^7.0.4", - "postcss-discard-comments": "^7.0.3", - "postcss-discard-duplicates": "^7.0.1", - "postcss-discard-empty": "^7.0.0", - "postcss-discard-overridden": "^7.0.0", - "postcss-merge-longhand": "^7.0.4", - "postcss-merge-rules": "^7.0.4", - "postcss-minify-font-values": "^7.0.0", - "postcss-minify-gradients": "^7.0.0", - "postcss-minify-params": "^7.0.2", - "postcss-minify-selectors": "^7.0.4", - "postcss-normalize-charset": "^7.0.0", - "postcss-normalize-display-values": "^7.0.0", - "postcss-normalize-positions": "^7.0.0", - "postcss-normalize-repeat-style": "^7.0.0", - "postcss-normalize-string": "^7.0.0", - "postcss-normalize-timing-functions": "^7.0.0", - "postcss-normalize-unicode": "^7.0.2", - "postcss-normalize-url": "^7.0.0", - "postcss-normalize-whitespace": "^7.0.0", - "postcss-ordered-values": "^7.0.1", - "postcss-reduce-initial": "^7.0.2", - "postcss-reduce-transforms": "^7.0.0", - "postcss-svgo": "^7.0.1", - "postcss-unique-selectors": "^7.0.3" + "cssnano-utils": "^5.0.1", + "postcss-calc": "^10.1.1", + "postcss-colormin": "^7.0.3", + "postcss-convert-values": "^7.0.5", + "postcss-discard-comments": "^7.0.4", + "postcss-discard-duplicates": "^7.0.2", + "postcss-discard-empty": "^7.0.1", + "postcss-discard-overridden": "^7.0.1", + "postcss-merge-longhand": "^7.0.5", + "postcss-merge-rules": "^7.0.5", + "postcss-minify-font-values": "^7.0.1", + "postcss-minify-gradients": "^7.0.1", + "postcss-minify-params": "^7.0.3", + "postcss-minify-selectors": "^7.0.5", + "postcss-normalize-charset": "^7.0.1", + "postcss-normalize-display-values": "^7.0.1", + "postcss-normalize-positions": "^7.0.1", + "postcss-normalize-repeat-style": "^7.0.1", + "postcss-normalize-string": "^7.0.1", + "postcss-normalize-timing-functions": "^7.0.1", + "postcss-normalize-unicode": "^7.0.3", + "postcss-normalize-url": "^7.0.1", + "postcss-normalize-whitespace": "^7.0.1", + "postcss-ordered-values": "^7.0.2", + "postcss-reduce-initial": "^7.0.3", + "postcss-reduce-transforms": "^7.0.1", + "postcss-svgo": "^7.0.2", + "postcss-unique-selectors": "^7.0.4" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/cssnano-utils": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.0.tgz", - "integrity": "sha512-Uij0Xdxc24L6SirFr25MlwC2rCFX6scyUmuKpzI+JQ7cyqDEwD42fJ0xfB3yLfOnRDU5LKGgjQ9FA6LYh76GWQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.1.tgz", + "integrity": "sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg==", "license": "MIT", "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/csso": { @@ -2426,6 +2419,15 @@ "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", "license": "CC0-1.0" }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/date.js": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/date.js/-/date.js-0.3.3.tgz", @@ -2451,9 +2453,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2494,7 +2496,8 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/delayed-stream": { "version": "1.0.0", @@ -2534,9 +2537,9 @@ } }, "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", "license": "Apache-2.0", "engines": { "node": ">=8" @@ -2646,9 +2649,9 @@ } }, "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", @@ -2660,9 +2663,9 @@ } }, "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -2707,9 +2710,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.74", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.74.tgz", - "integrity": "sha512-ck3//9RC+6oss/1Bh9tiAVFy5vfSKbRHAFh7Z3/eTRkEqJeWgymloShB17Vg3Z4nmDNp35vAd1BZ6CMW4Wt6Iw==", + "version": "1.5.175", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.175.tgz", + "integrity": "sha512-Nqpef9mOVo7pZfl9NIUhj7tgtRTsMzCzRTJDP1ccim4Wb4YHOz3Le87uxeZq68OCNwau2iQ/X7UwdAZ3ReOkmg==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -2734,9 +2737,9 @@ } }, "node_modules/encoding-sniffer": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", - "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", "license": "MIT", "dependencies": { "iconv-lite": "^0.6.3", @@ -2759,9 +2762,9 @@ } }, "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "license": "MIT", "dependencies": { "once": "^1.4.0" @@ -2816,9 +2819,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -2827,6 +2830,21 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2868,22 +2886,23 @@ } }, "node_modules/eslint": { - "version": "9.20.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.20.1.tgz", - "integrity": "sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==", + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", + "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.11.0", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.20.0", - "@eslint/plugin-kit": "^0.2.5", + "@eslint/config-array": "^0.20.1", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.29.0", + "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.1", + "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -2891,9 +2910,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -2932,6 +2951,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.5.0.tgz", "integrity": "sha512-F2ALmQVPT1GoP27O1JTZGrV9Pqg8k79OeIuvw63UxMtQKREZtmkK1NFgkZQ2TW7L2JSSFKHFPTtHu5z8R9QNRw==", "dev": true, + "license": "MIT", "dependencies": { "eslint-utils": "^3.0.0", "globals": "^13.24.0", @@ -2949,6 +2969,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -2960,10 +2981,11 @@ } }, "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -2980,6 +3002,7 @@ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", "dev": true, + "license": "MIT", "dependencies": { "eslint-visitor-keys": "^2.0.0" }, @@ -2998,15 +3021,17 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=10" } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -3014,56 +3039,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/@eslint/core": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.11.0.tgz", - "integrity": "sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/esm": { "version": "3.2.25", "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", @@ -3075,14 +3050,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3096,6 +3072,7 @@ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -3108,6 +3085,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -3120,6 +3098,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -3129,6 +3108,7 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -3213,19 +3193,22 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fecha": { "version": "4.2.3", @@ -3233,11 +3216,35 @@ "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", "license": "MIT" }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" }, @@ -3322,6 +3329,7 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "license": "MIT", "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" @@ -3331,10 +3339,11 @@ } }, "node_modules/flatted": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", - "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", - "dev": true + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" }, "node_modules/fn.name": { "version": "1.1.0", @@ -3363,12 +3372,12 @@ } }, "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { @@ -3379,19 +3388,33 @@ } }, "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { "node": ">= 6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -3440,18 +3463,19 @@ } }, "node_modules/gaxios": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", - "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", + "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", - "node-fetch": "^2.6.9" + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" }, "engines": { - "node": ">=12" + "node": ">=14" } }, "node_modules/get-caller-file": { @@ -3464,21 +3488,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", - "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "dunder-proto": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", + "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "math-intrinsics": "^1.0.0" + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -3487,6 +3511,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -3508,15 +3545,40 @@ } }, "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "license": "ISC", "dependencies": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 6" + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/globals": { @@ -3587,6 +3649,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -3610,9 +3687,9 @@ } }, "node_modules/helmet": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.0.0.tgz", - "integrity": "sha512-VyusHLEIIO5mjQPUI1wpOAEu+wl6Q0998jzTxqUYGE45xCIcAxy3MsbEK/yyJUJ3ADeMoB6MornPH6GMWAf+Pw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", + "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -3626,19 +3703,18 @@ "license": "MIT" }, "node_modules/htmlnano": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.1.tgz", - "integrity": "sha512-kAERyg/LuNZYmdqgCdYvugyLWNFAm8MWXpQMz1pLpetmCbFwoMxvkSoaAMlFrOC4OKTWI4KlZGT/RsNxg4ghOw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.2.tgz", + "integrity": "sha512-8Fst+0bhAfU362S6oHVb4wtJj/UYEFr0qiCLAEi8zioqmp1JYBQx5crZAADlFVX0Ly/6s/IQz6G7PL9/hgoJaQ==", "license": "MIT", "dependencies": { "cosmiconfig": "^9.0.0", - "posthtml": "^0.16.5", - "timsort": "^0.3.0" + "posthtml": "^0.16.5" }, "peerDependencies": { "cssnano": "^7.0.0", "postcss": "^8.3.11", - "purgecss": "^6.0.0", + "purgecss": "^7.0.2", "relateurl": "^0.2.7", "srcset": "5.0.1", "svgo": "^3.0.2", @@ -3708,16 +3784,16 @@ } }, "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "license": "MIT", "dependencies": { - "agent-base": "6", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/human-interval": { @@ -3766,6 +3842,7 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -3778,9 +3855,9 @@ "license": "ISC" }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -3798,6 +3875,7 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.19" } @@ -3809,9 +3887,9 @@ "license": "ISC" }, "node_modules/ioredis": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.5.0.tgz", - "integrity": "sha512-7CutT89g23FfSa8MDoIFs2GYYa0PaNiW/OrT+nRyjRXHDZd17HmIgy+reOQ/yhh72NznNjGuS8kbCAcA4Ro4mw==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz", + "integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==", "license": "MIT", "dependencies": { "@ioredis/commands": "^1.1.1", @@ -3952,13 +4030,31 @@ "license": "ISC" }, "node_modules/isomorphic-unfetch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz", - "integrity": "sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-4.0.2.tgz", + "integrity": "sha512-1Yd+CF/7al18/N2BDbsLBcp6RO3tucSW+jcLq24dqdX5MNbCNTw1z4BsGsp4zNmjr/Izm2cs/cEqZPp4kvWSCA==", "license": "MIT", "dependencies": { - "node-fetch": "^2.6.1", - "unfetch": "^4.2.0" + "node-fetch": "^3.2.0", + "unfetch": "^5.0.0" + } + }, + "node_modules/isomorphic-unfetch/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, "node_modules/istanbul-lib-coverage": { @@ -3986,22 +4082,6 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-report/node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/istanbul-reports": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", @@ -4075,7 +4155,8 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -4087,13 +4168,15 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/jsonwebtoken": { "version": "9.0.2", @@ -4118,13 +4201,14 @@ } }, "node_modules/juice": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/juice/-/juice-11.0.0.tgz", - "integrity": "sha512-sGF8hPz9/Wg+YXbaNDqc1Iuoaw+J/P9lBHNQKXAGc9pPNjCd4fyPai0Zxj7MRtdjMr0lcgk5PjEIkP2b8R9F3w==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/juice/-/juice-11.0.1.tgz", + "integrity": "sha512-R3KLud4l/sN9AMmFZs0QY7cugGSiKvPhGyIsufCV5nJ0MjSlngUE7k80TmFeK9I62wOXrjWBtYA1knVs2OkF8w==", "license": "MIT", "dependencies": { "cheerio": "^1.0.0", "commander": "^12.1.0", + "entities": "^4.5.0", "mensch": "^0.3.4", "slick": "^1.12.2", "web-resource-inliner": "^7.0.0" @@ -4137,21 +4221,21 @@ } }, "node_modules/juice/node_modules/cheerio": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", - "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.0.tgz", + "integrity": "sha512-+0hMx9eYhJvWbgpKV9hN7jg0JcwydpopZE4hgi+KvQtByZXPp04NiCWU0LzcAbP63abZckIHkTQaXVF52mX3xQ==", "license": "MIT", "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", - "domutils": "^3.1.0", + "domutils": "^3.2.2", "encoding-sniffer": "^0.2.0", - "htmlparser2": "^9.1.0", - "parse5": "^7.1.2", - "parse5-htmlparser2-tree-adapter": "^7.0.0", + "htmlparser2": "^10.0.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", - "undici": "^6.19.5", + "undici": "^7.10.0", "whatwg-mimetype": "^4.0.0" }, "engines": { @@ -4162,9 +4246,9 @@ } }, "node_modules/juice/node_modules/htmlparser2": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", - "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", + "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -4176,8 +4260,20 @@ "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", - "domutils": "^3.1.0", - "entities": "^4.5.0" + "domutils": "^3.2.1", + "entities": "^6.0.0" + } + }, + "node_modules/juice/node_modules/htmlparser2/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/just-extend": { @@ -4188,12 +4284,12 @@ "license": "MIT" }, "node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", "license": "MIT", "dependencies": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } @@ -4222,6 +4318,7 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -4237,6 +4334,7 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -4301,6 +4399,7 @@ "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", "dev": true, "license": "MIT" }, @@ -4356,7 +4455,8 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.once": { "version": "4.1.1", @@ -4405,15 +4505,15 @@ } }, "node_modules/long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "license": "Apache-2.0" }, "node_modules/loupe": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", - "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", + "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==", "dev": true, "license": "MIT" }, @@ -4424,25 +4524,41 @@ "license": "ISC" }, "node_modules/luxon": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz", - "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz", + "integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==", "license": "MIT", "engines": { "node": ">=12" } }, "node_modules/mailersend": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/mailersend/-/mailersend-2.3.0.tgz", - "integrity": "sha512-pe498Ry7VaAb+oqcYqmPw1V7FlECG/mcqahQ3SiK54en4ZkyRwjyxoQwA9VU4s3npB+I44LlQGUudObZQe4/jA==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mailersend/-/mailersend-2.6.0.tgz", + "integrity": "sha512-YX2Gyc6Wyw4Q4IsJ4np2Reof8nFWQ2OP/yXZTZcGCz4B7B1BOAYs71Kjb1uNRTTfDChP7rzzWBoOaX6iNlvPAg==", "license": "MIT", "dependencies": { - "gaxios": "^5.0.1", - "isomorphic-unfetch": "^3.1.0", + "gaxios": "^6.0.0", + "isomorphic-unfetch": "^4.0.0", "qs": "^6.11.0" } }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -4510,9 +4626,9 @@ } }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -4530,19 +4646,26 @@ "node": ">= 0.6" } }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "*" } }, "node_modules/minimist": { @@ -4643,6 +4766,30 @@ "mjml-cli": "bin/mjml" } }, + "node_modules/mjml-cli/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mjml-cli/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/mjml-column": { "version": "5.0.0-alpha.6", "resolved": "https://registry.npmjs.org/mjml-column/-/mjml-column-5.0.0-alpha.6.tgz", @@ -5022,6 +5169,16 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/mocha/node_modules/minimatch": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", @@ -5073,13 +5230,13 @@ } }, "node_modules/mongodb": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.13.0.tgz", - "integrity": "sha512-KeESYR5TEaFxOuwRqkOm3XOsMqCSkdeDMjaW5u2nuKfX7rqaofp7JQGoi7sVqQcNJTKuveNbzZtWMstb8ABP6Q==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz", + "integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==", "license": "Apache-2.0", "dependencies": { "@mongodb-js/saslprep": "^1.1.9", - "bson": "^6.10.1", + "bson": "^6.10.4", "mongodb-connection-string-url": "^3.0.0" }, "engines": { @@ -5119,24 +5276,24 @@ } }, "node_modules/mongodb-connection-string-url": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz", - "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", "license": "Apache-2.0", "dependencies": { "@types/whatwg-url": "^11.0.2", - "whatwg-url": "^13.0.0" + "whatwg-url": "^14.1.0 || ^13.0.0" } }, "node_modules/mongoose": { - "version": "8.10.1", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.10.1.tgz", - "integrity": "sha512-5beTeBZnJNndRXU9rxPol0JmTWZMAtgkPbooROkGilswvrZALDERY4cJrGZmgGwDS9dl0mxiB7si+Mv9Yms2fg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.0.tgz", + "integrity": "sha512-gLuAZsbwY0PHjrvfuXvUkUq9tXjyAjN3ioXph5Y6Seu7/Uo8xJaM+rrMbL/x34K4T3UTgtXRyfoq1YU16qKyIw==", "license": "MIT", "dependencies": { - "bson": "^6.10.1", + "bson": "^6.10.4", "kareem": "2.6.3", - "mongodb": "~6.13.0", + "mongodb": "~6.17.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -5178,9 +5335,9 @@ "license": "MIT" }, "node_modules/msgpackr": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.2.tgz", - "integrity": "sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==", + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.4.tgz", + "integrity": "sha512-uaff7RG9VIC4jacFW9xzL3jc0iM32DNHe4jYVycBcjUePT/Klnfj7pqtWJt9khvDFizmjN2TlYniYmSS2LIaZg==", "license": "MIT", "optionalDependencies": { "msgpackr-extract": "^3.0.2" @@ -5209,9 +5366,10 @@ } }, "node_modules/multer": { - "version": "1.4.5-lts.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", - "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "version": "1.4.5-lts.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", + "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", + "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.", "license": "MIT", "dependencies": { "append-field": "^1.0.0", @@ -5227,16 +5385,16 @@ } }, "node_modules/nan": { - "version": "2.22.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", - "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==", + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz", + "integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==", "license": "MIT", "optional": true }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", @@ -5255,12 +5413,13 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -5302,6 +5461,26 @@ "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", "license": "MIT" }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -5366,9 +5545,9 @@ "license": "MIT" }, "node_modules/nodemailer": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz", - "integrity": "sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==", + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz", + "integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==", "license": "MIT-0", "engines": { "node": ">=6.0.0" @@ -5403,17 +5582,6 @@ "url": "https://opencollective.com/nodemon" } }, - "node_modules/nodemon/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/nodemon/node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -5424,19 +5592,6 @@ "node": ">=4" } }, - "node_modules/nodemon/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/nodemon/node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -5487,9 +5642,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -5542,6 +5697,7 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "license": "MIT", "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -5593,9 +5749,9 @@ "license": "BlueOak-1.0.0" }, "node_modules/papaparse": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.2.tgz", - "integrity": "sha512-PZXg8UuAc4PcVwLosEEDYjPyfWnTEhOrUfdv+3Bx+NuAb+5NhDmXzg5fHWmdCh1mP5p7JAZfFr3IMQfcntNAdA==", + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz", + "integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==", "license": "MIT" }, "node_modules/parent-module": { @@ -5629,12 +5785,12 @@ } }, "node_modules/parse5": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", - "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", "license": "MIT", "dependencies": { - "entities": "^4.5.0" + "entities": "^6.0.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" @@ -5665,6 +5821,18 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -5716,9 +5884,9 @@ "license": "MIT" }, "node_modules/pathval": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", - "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", "dev": true, "license": "MIT", "engines": { @@ -5753,9 +5921,9 @@ } }, "node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "funding": [ { "type": "opencollective", @@ -5772,7 +5940,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -5781,12 +5949,12 @@ } }, "node_modules/postcss-calc": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.0.2.tgz", - "integrity": "sha512-DT/Wwm6fCKgpYVI7ZEWuPJ4az8hiEHtCUeYjZXqU7Ou4QqYh1Df2yCQ7Ca6N7xqKPFkxN3fhf+u9KSoOCJNAjg==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz", + "integrity": "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==", "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.1.2", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -5797,12 +5965,12 @@ } }, "node_modules/postcss-colormin": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.2.tgz", - "integrity": "sha512-YntRXNngcvEvDbEjTdRWGU606eZvB5prmHG4BF0yLmVpamXbpsRJzevyy6MZVyuecgzI2AWAlvFi8DAeCqwpvA==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.3.tgz", + "integrity": "sha512-xZxQcSyIVZbSsl1vjoqZAcMYYdnJsIyG8OvqShuuqf12S88qQboxxEy0ohNCOLwVPXTU+hFHvJPACRL2B5ohTA==", "license": "MIT", "dependencies": { - "browserslist": "^4.23.3", + "browserslist": "^4.24.5", "caniuse-api": "^3.0.0", "colord": "^2.9.3", "postcss-value-parser": "^4.2.0" @@ -5811,114 +5979,114 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-convert-values": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.4.tgz", - "integrity": "sha512-e2LSXPqEHVW6aoGbjV9RsSSNDO3A0rZLCBxN24zvxF25WknMPpX8Dm9UxxThyEbaytzggRuZxaGXqaOhxQ514Q==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.5.tgz", + "integrity": "sha512-0VFhH8nElpIs3uXKnVtotDJJNX0OGYSZmdt4XfSfvOMrFw1jKfpwpZxfC4iN73CTM/MWakDEmsHQXkISYj4BXw==", "license": "MIT", "dependencies": { - "browserslist": "^4.23.3", + "browserslist": "^4.24.5", "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-discard-comments": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.3.tgz", - "integrity": "sha512-q6fjd4WU4afNhWOA2WltHgCbkRhZPgQe7cXF74fuVB/ge4QbM9HEaOIzGSiMvM+g/cOsNAUGdf2JDzqA2F8iLA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.4.tgz", + "integrity": "sha512-6tCUoql/ipWwKtVP/xYiFf1U9QgJ0PUvxN7pTcsQ8Ns3Fnwq1pU5D5s1MhT/XySeLq6GXNvn37U46Ded0TckWg==", "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.1.2" + "postcss-selector-parser": "^7.1.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-discard-duplicates": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.1.tgz", - "integrity": "sha512-oZA+v8Jkpu1ct/xbbrntHRsfLGuzoP+cpt0nJe5ED2FQF8n8bJtn7Bo28jSmBYwqgqnqkuSXJfSUEE7if4nClQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.2.tgz", + "integrity": "sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w==", "license": "MIT", "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-discard-empty": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.0.tgz", - "integrity": "sha512-e+QzoReTZ8IAwhnSdp/++7gBZ/F+nBq9y6PomfwORfP7q9nBpK5AMP64kOt0bA+lShBFbBDcgpJ3X4etHg4lzA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.1.tgz", + "integrity": "sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg==", "license": "MIT", "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-discard-overridden": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.0.tgz", - "integrity": "sha512-GmNAzx88u3k2+sBTZrJSDauR0ccpE24omTQCVmaTTZFz1du6AasspjaUPMJ2ud4RslZpoFKyf+6MSPETLojc6w==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.1.tgz", + "integrity": "sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg==", "license": "MIT", "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-merge-longhand": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.4.tgz", - "integrity": "sha512-zer1KoZA54Q8RVHKOY5vMke0cCdNxMP3KBfDerjH/BYHh4nCIh+1Yy0t1pAEQF18ac/4z3OFclO+ZVH8azjR4A==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.5.tgz", + "integrity": "sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^7.0.4" + "stylehacks": "^7.0.5" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-merge-rules": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.4.tgz", - "integrity": "sha512-ZsaamiMVu7uBYsIdGtKJ64PkcQt6Pcpep/uO90EpLS3dxJi6OXamIobTYcImyXGoW0Wpugh7DSD3XzxZS9JCPg==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.5.tgz", + "integrity": "sha512-ZonhuSwEaWA3+xYbOdJoEReKIBs5eDiBVLAGpYZpNFPzXZcEE5VKR7/qBEQvTZpiwjqhhqEQ+ax5O3VShBj9Wg==", "license": "MIT", "dependencies": { - "browserslist": "^4.23.3", + "browserslist": "^4.24.5", "caniuse-api": "^3.0.0", - "cssnano-utils": "^5.0.0", - "postcss-selector-parser": "^6.1.2" + "cssnano-utils": "^5.0.1", + "postcss-selector-parser": "^7.1.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-minify-font-values": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.0.tgz", - "integrity": "sha512-2ckkZtgT0zG8SMc5aoNwtm5234eUx1GGFJKf2b1bSp8UflqaeFzR50lid4PfqVI9NtGqJ2J4Y7fwvnP/u1cQog==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.1.tgz", + "integrity": "sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -5927,75 +6095,75 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-minify-gradients": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.0.tgz", - "integrity": "sha512-pdUIIdj/C93ryCHew0UgBnL2DtUS3hfFa5XtERrs4x+hmpMYGhbzo6l/Ir5de41O0GaKVpK1ZbDNXSY6GkXvtg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.1.tgz", + "integrity": "sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A==", "license": "MIT", "dependencies": { "colord": "^2.9.3", - "cssnano-utils": "^5.0.0", + "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-minify-params": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.2.tgz", - "integrity": "sha512-nyqVLu4MFl9df32zTsdcLqCFfE/z2+f8GE1KHPxWOAmegSo6lpV2GNy5XQvrzwbLmiU7d+fYay4cwto1oNdAaQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.3.tgz", + "integrity": "sha512-vUKV2+f5mtjewYieanLX0xemxIp1t0W0H/D11u+kQV/MWdygOO7xPMkbK+r9P6Lhms8MgzKARF/g5OPXhb8tgg==", "license": "MIT", "dependencies": { - "browserslist": "^4.23.3", - "cssnano-utils": "^5.0.0", + "browserslist": "^4.24.5", + "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-minify-selectors": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.4.tgz", - "integrity": "sha512-JG55VADcNb4xFCf75hXkzc1rNeURhlo7ugf6JjiiKRfMsKlDzN9CXHZDyiG6x/zGchpjQS+UAgb1d4nqXqOpmA==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.5.tgz", + "integrity": "sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", - "postcss-selector-parser": "^6.1.2" + "postcss-selector-parser": "^7.1.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-charset": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.0.tgz", - "integrity": "sha512-ABisNUXMeZeDNzCQxPxBCkXexvBrUHV+p7/BXOY+ulxkcjUZO0cp8ekGBwvIh2LbCwnWbyMPNJVtBSdyhM2zYQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.1.tgz", + "integrity": "sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ==", "license": "MIT", "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-display-values": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.0.tgz", - "integrity": "sha512-lnFZzNPeDf5uGMPYgGOw7v0BfB45+irSRz9gHQStdkkhiM0gTfvWkWB5BMxpn0OqgOQuZG/mRlZyJxp0EImr2Q==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.1.tgz", + "integrity": "sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -6004,13 +6172,13 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-positions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.0.tgz", - "integrity": "sha512-I0yt8wX529UKIGs2y/9Ybs2CelSvItfmvg/DBIjTnoUSrPxSV7Z0yZ8ShSVtKNaV/wAY+m7bgtyVQLhB00A1NQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.1.tgz", + "integrity": "sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -6019,13 +6187,13 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-repeat-style": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.0.tgz", - "integrity": "sha512-o3uSGYH+2q30ieM3ppu9GTjSXIzOrRdCUn8UOMGNw7Af61bmurHTWI87hRybrP6xDHvOe5WlAj3XzN6vEO8jLw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.1.tgz", + "integrity": "sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -6034,13 +6202,13 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-string": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.0.tgz", - "integrity": "sha512-w/qzL212DFVOpMy3UGyxrND+Kb0fvCiBBujiaONIihq7VvtC7bswjWgKQU/w4VcRyDD8gpfqUiBQ4DUOwEJ6Qg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.1.tgz", + "integrity": "sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -6049,13 +6217,13 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-timing-functions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.0.tgz", - "integrity": "sha512-tNgw3YV0LYoRwg43N3lTe3AEWZ66W7Dh7lVEpJbHoKOuHc1sLrzMLMFjP8SNULHaykzsonUEDbKedv8C+7ej6g==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.1.tgz", + "integrity": "sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -6064,29 +6232,29 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-unicode": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.2.tgz", - "integrity": "sha512-ztisabK5C/+ZWBdYC+Y9JCkp3M9qBv/XFvDtSw0d/XwfT3UaKeW/YTm/MD/QrPNxuecia46vkfEhewjwcYFjkg==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.3.tgz", + "integrity": "sha512-EcoA29LvG3F+EpOh03iqu+tJY3uYYKzArqKJHxDhUYLa2u58aqGq16K6/AOsXD9yqLN8O6y9mmePKN5cx6krOw==", "license": "MIT", "dependencies": { - "browserslist": "^4.23.3", + "browserslist": "^4.24.5", "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-url": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.0.tgz", - "integrity": "sha512-+d7+PpE+jyPX1hDQZYG+NaFD+Nd2ris6r8fPTBAjE8z/U41n/bib3vze8x7rKs5H1uEw5ppe9IojewouHk0klQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.1.tgz", + "integrity": "sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -6095,13 +6263,13 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-whitespace": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.0.tgz", - "integrity": "sha512-37/toN4wwZErqohedXYqWgvcHUGlT8O/m2jVkAfAe9Bd4MzRqlBmXrJRePH0e9Wgnz2X7KymTgTOaaFizQe3AQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.1.tgz", + "integrity": "sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -6110,45 +6278,45 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-ordered-values": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.1.tgz", - "integrity": "sha512-irWScWRL6nRzYmBOXReIKch75RRhNS86UPUAxXdmW/l0FcAsg0lvAXQCby/1lymxn/o0gVa6Rv/0f03eJOwHxw==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.2.tgz", + "integrity": "sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw==", "license": "MIT", "dependencies": { - "cssnano-utils": "^5.0.0", + "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-reduce-initial": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.2.tgz", - "integrity": "sha512-pOnu9zqQww7dEKf62Nuju6JgsW2V0KRNBHxeKohU+JkHd/GAH5uvoObqFLqkeB2n20mr6yrlWDvo5UBU5GnkfA==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.3.tgz", + "integrity": "sha512-RFvkZaqiWtGMlVjlUHpaxGqEL27lgt+Q2Ixjf83CRAzqdo+TsDyGPtJUbPx2MuYIJ+sCQc2TrOvRnhcXQfgIVA==", "license": "MIT", "dependencies": { - "browserslist": "^4.23.3", + "browserslist": "^4.24.5", "caniuse-api": "^3.0.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-reduce-transforms": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.0.tgz", - "integrity": "sha512-pnt1HKKZ07/idH8cpATX/ujMbtOGhUfE+m8gbqwJE05aTaNw8gbo34a2e3if0xc0dlu75sUOiqvwCGY3fzOHew==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.1.tgz", + "integrity": "sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -6157,13 +6325,13 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -6174,9 +6342,9 @@ } }, "node_modules/postcss-svgo": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.0.1.tgz", - "integrity": "sha512-0WBUlSL4lhD9rA5k1e5D8EN5wCEyZD6HJk0jIvRxl+FDVOMlJ7DePHYWGGVc5QRqrJ3/06FTXM0bxjmJpmTPSA==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.0.2.tgz", + "integrity": "sha512-5Dzy66JlnRM6pkdOTF8+cGsB1fnERTE8Nc+Eed++fOWo1hdsBptCsbG8UuJkgtZt75bRtMJIrPeZmtfANixdFA==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", @@ -6186,22 +6354,22 @@ "node": "^18.12.0 || ^20.9.0 || >= 18" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-unique-selectors": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.3.tgz", - "integrity": "sha512-J+58u5Ic5T1QjP/LDV9g3Cx4CNOgB5vz+kM6+OxHHhFACdcDeKhBXjQmB7fnIZM12YSTvsL0Opwco83DmacW2g==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.4.tgz", + "integrity": "sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ==", "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.1.2" + "postcss-selector-parser": "^7.1.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-value-parser": { @@ -6335,14 +6503,15 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, "node_modules/prettier": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.1.tgz", - "integrity": "sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.1.tgz", + "integrity": "sha512-5xGWRa90Sp2+x1dQtNpIpeOQpTDBs9cZDmA/qs2vDNN2i18PdapqY7CmBeyLlMuGqXJRIOPaCaVZTLNQRWUH/A==", "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -6361,9 +6530,9 @@ "license": "MIT" }, "node_modules/protobufjs": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", - "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz", + "integrity": "sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { @@ -6411,9 +6580,9 @@ "license": "MIT" }, "node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", @@ -6448,7 +6617,8 @@ "version": "7.5.0", "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.5.0.tgz", "integrity": "sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/randombytes": { "version": "2.1.0", @@ -6531,12 +6701,6 @@ "node": ">=4" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT" - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -6591,9 +6755,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -7104,19 +7268,43 @@ } }, "node_modules/stylehacks": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.4.tgz", - "integrity": "sha512-i4zfNrGMt9SB4xRK9L83rlsFCgdGANfeDAYacO1pkqcE7cRHPdWHwnKZVz7WY17Veq/FvyYsRAU++Ga+qDFIww==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.5.tgz", + "integrity": "sha512-5kNb7V37BNf0Q3w+1pxfa+oiNPS++/b4Jil9e/kPDgrk1zjEd6uR7SZeJiYaLYH6RRSC1XX2/37OTeU/4FvuIA==", "license": "MIT", "dependencies": { - "browserslist": "^4.23.3", - "postcss-selector-parser": "^6.1.2" + "browserslist": "^4.24.5", + "postcss-selector-parser": "^7.1.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" + } + }, + "node_modules/super-simple-scheduler": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.0.7.tgz", + "integrity": "sha512-kxKcBWE4rYgUP+XHgvO5m8nzyTI+5UoC8VeLkXJiC7EdSJrOwocyYUBuuDzE+wDNVKEv20UineXKhe1P0twYiA==", + "license": "MIT", + "dependencies": { + "human-interval": "2.0.1", + "uuid": "11.1.0", + "winston": "3.17.0" + } + }, + "node_modules/super-simple-scheduler/node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" } }, "node_modules/supports-color": { @@ -7167,9 +7355,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.18.2", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.18.2.tgz", - "integrity": "sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw==", + "version": "5.25.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.25.2.tgz", + "integrity": "sha512-V4JyoygUe5nCbn7bAD0fVKSC0yNcL3ROIQtGC7M0NATKuyosCSmMU6T0yDZIIuGpSxjsjZh/D2Ejb8lnF2jjxw==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" @@ -7202,12 +7390,6 @@ "tar-stream": "^2.1.4" } }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", @@ -7239,18 +7421,38 @@ "node": ">=18" } }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "license": "MIT" }, - "node_modules/timsort": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==", - "license": "MIT" - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -7283,15 +7485,15 @@ } }, "node_modules/tr46": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", - "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", "license": "MIT", "dependencies": { - "punycode": "^2.3.0" + "punycode": "^2.3.1" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/triple-beam": { @@ -7320,6 +7522,7 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -7342,6 +7545,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -7389,25 +7593,28 @@ "license": "MIT" }, "node_modules/undici": { - "version": "6.21.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", - "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.10.0.tgz", + "integrity": "sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==", "license": "MIT", "engines": { - "node": ">=18.17" + "node": ">=20.18.1" } }, "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", "license": "MIT" }, "node_modules/unfetch": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", - "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==", - "license": "MIT" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-5.0.0.tgz", + "integrity": "sha512-3xM2c89siXg0nHvlmYsQ2zkLASvVMBisZm5lF3gFDqfF2xonNStDJyMpvaOBe0a1Edxmqrf2E0HBdmy9QyZaeg==", + "license": "MIT", + "workspaces": [ + "./packages/isomorphic-unfetch" + ] }, "node_modules/unpipe": { "version": "1.0.0", @@ -7419,9 +7626,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "funding": [ { "type": "opencollective", @@ -7439,7 +7646,7 @@ "license": "MIT", "dependencies": { "escalade": "^3.2.0", - "picocolors": "^1.1.0" + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -7453,6 +7660,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -7643,6 +7851,15 @@ "node": ">=4.0.0" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -7686,16 +7903,16 @@ } }, "node_modules/whatwg-url": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", - "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", "license": "MIT", "dependencies": { - "tr46": "^4.1.1", + "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/which": { @@ -7754,6 +7971,7 @@ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } diff --git a/server/package.json b/server/package.json index 51e7cd218..561d235c9 100755 --- a/server/package.json +++ b/server/package.json @@ -40,6 +40,7 @@ "ping": "0.4.4", "sharp": "0.33.5", "ssl-checker": "2.0.10", + "super-simple-scheduler": "1.0.7", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, diff --git a/server/service/SuperSimpleQueue/SuperSimpleQueue.js b/server/service/SuperSimpleQueue/SuperSimpleQueue.js new file mode 100644 index 000000000..fab46b260 --- /dev/null +++ b/server/service/SuperSimpleQueue/SuperSimpleQueue.js @@ -0,0 +1,146 @@ +import Scheduler from "super-simple-scheduler"; +const SERVICE_NAME = "SuperSimpleQueue"; + +class SuperSimpleQueue { + static SERVICE_NAME = SERVICE_NAME; + + constructor({ appSettings, db, logger, helper }) { + this.appSettings = appSettings; + this.db = db; + this.logger = logger; + this.helper = helper; + } + + static async create({ appSettings, db, logger, helper }) { + const instance = new SuperSimpleQueue({ appSettings, db, logger, helper }); + await instance.init(); + return instance; + } + + init = async () => { + this.scheduler = new Scheduler({ + logLevel: process.env.LOG_LEVEL, + debug: process.env.NODE_ENV, + }); + this.scheduler.start(); + this.scheduler.addTemplate("test", this.helper.getMonitorJob()); + const monitors = await this.db.getAllMonitors(); + for (const monitor of monitors) { + await this.addJob(monitor._id, monitor); + } + }; + + addJob = async (monitorId, monitor) => { + this.scheduler.addJob({ + id: monitorId.toString(), + template: "test", + repeat: monitor.interval, + data: monitor, + }); + }; + + deleteJob = async (monitor) => { + this.scheduler.removeJob(monitor._id.toString()); + }; + + pauseJob = async (monitor) => { + const result = this.scheduler.pauseJob(monitor._id.toString()); + if (result === false) { + throw new Error("Failed to resume monitor"); + } + this.logger.debug({ + message: `Paused monitor ${monitor._id}`, + service: SERVICE_NAME, + method: "pauseJob", + }); + }; + + resumeJob = async (monitor) => { + const result = this.scheduler.resumeJob(monitor._id.toString()); + if (result === false) { + throw new Error("Failed to resume monitor"); + } + this.logger.debug({ + message: `Resumed monitor ${monitor._id}`, + service: SERVICE_NAME, + method: "resumeJob", + }); + }; + + updateJob = async (monitor) => { + this.scheduler.updateJob(monitor._id.toString(), monitor.interval); + }; + + shutdown = async () => { + this.scheduler.stop(); + }; + + getMetrics = async () => { + const jobs = this.scheduler.getJobs(); + const metrics = jobs.reduce( + (acc, job) => { + acc.totalRuns += job.runCount || 0; + acc.totalFailures += job.failCount || 0; + acc.jobs++; + if (job.failCount > 0 && job.lastFailedAt >= job.lsatRunAt) { + acc.failingJobs++; + } + + if (job.lockedAt) { + acc.activeJobs++; + } + + if (job.failCount > 0) { + acc.jobsWithFailures.push({ + monitorId: job.id, + monitorUrl: job?.data?.url || null, + monitorType: job?.data?.type || null, + failedAt: job.lastFailedAt, + failCount: job.failCount, + failReason: job.lastFailReason, + }); + } + return acc; + }, + { + jobs: 0, + activeJobs: 0, + failingJobs: 0, + jobsWithFailures: [], + totalRuns: 0, + totalFailures: 0, + } + ); + return metrics; + }; + + getJobs = async () => { + const jobs = this.scheduler.getJobs(); + return jobs.map((job) => { + return { + monitorId: job.id, + monitorUrl: job?.data?.url || null, + monitorType: job?.data?.type || null, + active: job.active, + lockedAt: job.lockedAt, + runCount: job.runCount || 0, + failCount: job.failCount || 0, + failReason: job.lastFailReason, + lastRunAt: job.lastRunAt, + lastFinishedAt: job.lastFinishedAt, + lastRunTook: job.lockedAt ? null : job.lastFinishedAt - job.lastRunAt, + lastFailedAt: job.lastFailedAt, + }; + }); + }; + + flushQueues = async () => { + console.log("flush not implemented"); + }; + + obliterate = async () => { + console.log("obliterate not implemented"); + }; +} + +export default SuperSimpleQueue; diff --git a/server/service/SuperSimpleQueue/SuperSimpleQueueHelper.js b/server/service/SuperSimpleQueue/SuperSimpleQueueHelper.js new file mode 100644 index 000000000..45c5978b9 --- /dev/null +++ b/server/service/SuperSimpleQueue/SuperSimpleQueueHelper.js @@ -0,0 +1,99 @@ +const SERVICE_NAME = "SuperSimpleQueueHelper"; + +class SuperSimpleQueueHelper { + constructor({ db, logger, networkService, statusService, notificationService }) { + this.db = db; + this.logger = logger; + this.networkService = networkService; + this.statusService = statusService; + this.notificationService = notificationService; + } + + getMonitorJob = () => { + return async (monitor) => { + try { + const monitorId = monitor._id; + if (!monitorId) { + throw new Error("No monitor id"); + } + + const maintenanceWindowActive = await this.isInMaintenanceWindow(monitorId); + if (maintenanceWindowActive) { + this.logger.info({ + message: `Monitor ${monitorId} is in maintenance window`, + service: SERVICE_NAME, + method: "getMonitorJob", + }); + return; + } + const networkResponse = await this.networkService.getStatus(monitor); + if (!networkResponse) { + throw new Error("No network response"); + } + + const { + monitor: updatedMonitor, + statusChanged, + prevStatus, + } = await this.statusService.updateStatus(networkResponse); + + this.notificationService + .handleNotifications({ + ...networkResponse, + monitor: updatedMonitor, + prevStatus, + statusChanged, + }) + .catch((error) => { + this.logger.error({ + message: error.message, + service: SERVICE_NAME, + method: "getMonitorJob", + details: `Error sending notifications for job ${monitor._id}: ${error.message}`, + stack: error.stack, + }); + }); + } catch (error) { + this.logger.warn({ + message: error.message, + service: error.service || SERVICE_NAME, + method: error.method || "getMonitorJob", + stack: error.stack, + }); + throw error; + } + }; + }; + + async isInMaintenanceWindow(monitorId) { + const maintenanceWindows = await this.db.getMaintenanceWindowsByMonitorId(monitorId); + // Check for active maintenance window: + const maintenanceWindowIsActive = maintenanceWindows.reduce((acc, window) => { + if (window.active) { + const start = new Date(window.start); + const end = new Date(window.end); + const now = new Date(); + const repeatInterval = window.repeat || 0; + + // If start is < now and end > now, we're in maintenance + if (start <= now && end >= now) return true; + + // If maintenance window was set in the past with a repeat, + // we need to advance start and end to see if we are in range + + while (start < now && repeatInterval !== 0) { + start.setTime(start.getTime() + repeatInterval); + end.setTime(end.getTime() + repeatInterval); + if (start <= now && end >= now) { + return true; + } + } + return false; + } + return acc; + }, false); + return maintenanceWindowIsActive; + } +} + +export default SuperSimpleQueueHelper; diff --git a/server/service/networkService.js b/server/service/networkService.js index 08b2f065e..01f72d37d 100755 --- a/server/service/networkService.js +++ b/server/service/networkService.js @@ -256,7 +256,7 @@ class NetworkService { async requestPagespeed(monitor) { try { const url = monitor.url; - const updatedMonitor = { ...monitor }; + const updatedMonitor = monitor.toObject(); let pagespeedUrl = `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance`; const dbSettings = await this.settingsService.getDBSettings(); From ca13185d846be17ec60e914f1953917dec8d1ca6 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 27 Jun 2025 13:40:41 +0800 Subject: [PATCH 002/259] convert monitor to object --- server/service/SuperSimpleQueue/SuperSimpleQueue.js | 2 +- server/service/networkService.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/service/SuperSimpleQueue/SuperSimpleQueue.js b/server/service/SuperSimpleQueue/SuperSimpleQueue.js index fab46b260..ec6c16ea9 100644 --- a/server/service/SuperSimpleQueue/SuperSimpleQueue.js +++ b/server/service/SuperSimpleQueue/SuperSimpleQueue.js @@ -35,7 +35,7 @@ class SuperSimpleQueue { id: monitorId.toString(), template: "test", repeat: monitor.interval, - data: monitor, + data: monitor.toObject(), }); }; diff --git a/server/service/networkService.js b/server/service/networkService.js index 01f72d37d..08b2f065e 100755 --- a/server/service/networkService.js +++ b/server/service/networkService.js @@ -256,7 +256,7 @@ class NetworkService { async requestPagespeed(monitor) { try { const url = monitor.url; - const updatedMonitor = monitor.toObject(); + const updatedMonitor = { ...monitor }; let pagespeedUrl = `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance`; const dbSettings = await this.settingsService.getDBSettings(); From 38f5c41272552249d8781615b5585ef6229ebee9 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 27 Jun 2025 13:45:18 +0800 Subject: [PATCH 003/259] implement flush --- .../SuperSimpleQueue/SuperSimpleQueue.js | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/server/service/SuperSimpleQueue/SuperSimpleQueue.js b/server/service/SuperSimpleQueue/SuperSimpleQueue.js index ec6c16ea9..578f9dd3f 100644 --- a/server/service/SuperSimpleQueue/SuperSimpleQueue.js +++ b/server/service/SuperSimpleQueue/SuperSimpleQueue.js @@ -18,15 +18,26 @@ class SuperSimpleQueue { } init = async () => { - this.scheduler = new Scheduler({ - logLevel: process.env.LOG_LEVEL, - debug: process.env.NODE_ENV, - }); - this.scheduler.start(); - this.scheduler.addTemplate("test", this.helper.getMonitorJob()); - const monitors = await this.db.getAllMonitors(); - for (const monitor of monitors) { - await this.addJob(monitor._id, monitor); + try { + this.scheduler = new Scheduler({ + logLevel: process.env.LOG_LEVEL, + debug: process.env.NODE_ENV, + }); + this.scheduler.start(); + this.scheduler.addTemplate("test", this.helper.getMonitorJob()); + const monitors = await this.db.getAllMonitors(); + for (const monitor of monitors) { + await this.addJob(monitor._id, monitor); + } + return true; + } catch (error) { + this.logger.error({ + message: "Failed to initialize SuperSimpleQueue", + service: SERVICE_NAME, + method: "init", + details: error, + }); + return false; } }; @@ -135,7 +146,12 @@ class SuperSimpleQueue { }; flushQueues = async () => { - console.log("flush not implemented"); + const stopRes = this.scheduler.stop(); + const flushRes = this.scheduler.flushJobs(); + const initRes = await this.init(); + return { + success: stopRes && flushRes && initRes, + }; }; obliterate = async () => { From ce103c6b599bba20a46946f220ec666e8ce91bd6 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 27 Jun 2025 13:51:39 +0800 Subject: [PATCH 004/259] format --- server/templates/addReview.mjml | 2 +- server/templates/employeeActivation.mjml | 6 ++++-- server/templates/hardwareIncident.mjml | 6 +++--- server/templates/noIncidentsThisWeek.mjml | 2 +- server/templates/passwordReset.mjml | 4 ++-- server/templates/serverIsDown.mjml | 8 ++++---- server/templates/serverIsUp.mjml | 8 ++++---- server/templates/welcomeEmail.mjml | 2 +- 8 files changed, 20 insertions(+), 18 deletions(-) diff --git a/server/templates/addReview.mjml b/server/templates/addReview.mjml index 9d00be7d0..644970f6e 100755 --- a/server/templates/addReview.mjml +++ b/server/templates/addReview.mjml @@ -30,7 +30,7 @@ -

Hello {{name}}!

+

Hello {{ name }}!

We hope you’re finding Checkmate helpful in monitoring your infrastructure. Your support means a lot to us, and we truly appreciate having you as diff --git a/server/templates/employeeActivation.mjml b/server/templates/employeeActivation.mjml index 3e35db6e8..317832c1f 100755 --- a/server/templates/employeeActivation.mjml +++ b/server/templates/employeeActivation.mjml @@ -29,10 +29,12 @@ -

Hello {{name}}!

+

Hello {{ name }}!

One of the admins created an account for you on the Checkmate server.

You can go ahead and create your account using this link.

-

{{link}}

+

+ {{ link }} +

Thank you.

diff --git a/server/templates/hardwareIncident.mjml b/server/templates/hardwareIncident.mjml index 19fb589fc..3f50a3d58 100755 --- a/server/templates/hardwareIncident.mjml +++ b/server/templates/hardwareIncident.mjml @@ -47,10 +47,10 @@ -

Hello {{name}}!

-

{{monitor}} at {{url}} has the following infrastructure alerts:

+

Hello {{ name }}!

+

{{ monitor }} at {{ url }} has the following infrastructure alerts:

{{#each alerts}} -

• {{this}}

+

• {{ this }}

{{/each}}
diff --git a/server/templates/noIncidentsThisWeek.mjml b/server/templates/noIncidentsThisWeek.mjml index d31d63d10..d160e44de 100755 --- a/server/templates/noIncidentsThisWeek.mjml +++ b/server/templates/noIncidentsThisWeek.mjml @@ -45,7 +45,7 @@ -

Hello {{name}}!

+

Hello {{ name }}!

There were no incidents this week. Good job!

Current monitors:

Google: 100% availability

diff --git a/server/templates/passwordReset.mjml b/server/templates/passwordReset.mjml index e0b85a4f4..5da29b341 100755 --- a/server/templates/passwordReset.mjml +++ b/server/templates/passwordReset.mjml @@ -31,10 +31,10 @@ -

Hello {{name}}!

+

Hello {{ name }}!

You are receiving this email because a password reset request has been made - for {{email}}. Please use the link below on the site to reset your password. + for {{ email }}. Please use the link below on the site to reset your password.

Reset Password

If you didn't request this, please ignore this email.

diff --git a/server/templates/serverIsDown.mjml b/server/templates/serverIsDown.mjml index b75d970e6..d46011070 100755 --- a/server/templates/serverIsDown.mjml +++ b/server/templates/serverIsDown.mjml @@ -36,7 +36,7 @@ font-size="18px" color="red" > - {{monitor}} is down + {{ monitor }} is down
-

Hello {{name}}!

+

Hello {{ name }}!

We detected an incident on one of your monitors. Your service is currently down. We'll send a message to you once it is up again.

-

Monitor name: {{monitor}}

-

URL: {{url}}

+

Monitor name: {{ monitor }}

+

URL: {{ url }}

diff --git a/server/templates/serverIsUp.mjml b/server/templates/serverIsUp.mjml index beb34481d..074c5e3dd 100755 --- a/server/templates/serverIsUp.mjml +++ b/server/templates/serverIsUp.mjml @@ -36,7 +36,7 @@ font-size="18px" color="green" > - {{monitor}} is up + {{ monitor }} is up
-

Hello {{name}}!

+

Hello {{ name }}!

Your latest incident is resolved and your monitored service is up again.

-

Monitor name: {{monitor}}

-

URL: {{url}}

+

Monitor name: {{ monitor }}

+

URL: {{ url }}

diff --git a/server/templates/welcomeEmail.mjml b/server/templates/welcomeEmail.mjml index 663518ec2..372e4c8ae 100755 --- a/server/templates/welcomeEmail.mjml +++ b/server/templates/welcomeEmail.mjml @@ -30,7 +30,7 @@ -

Hello {{name}}!

+

Hello {{ name }}!

Thank you for trying out Checkmate! We developed it with great care to meet our own needs, and we're excited to share it with you. From c3f5d249a08bcc3db4994488cb098ff0a645f81e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 7 Jul 2025 08:55:57 -0700 Subject: [PATCH 005/259] use latest library --- server/package-lock.json | 104 +++++++++--------- server/package.json | 2 +- .../SuperSimpleQueue/SuperSimpleQueue.js | 4 +- 3 files changed, 55 insertions(+), 55 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index 427356e90..a7396bee2 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -33,7 +33,7 @@ "ping": "0.4.4", "sharp": "0.33.5", "ssl-checker": "2.0.10", - "super-simple-scheduler": "1.0.7", + "super-simple-scheduler": "1.0.12", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, @@ -172,9 +172,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", - "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -187,9 +187,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", - "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -247,9 +247,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.29.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", - "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", + "version": "9.30.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.1.tgz", + "integrity": "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==", "dev": true, "license": "MIT", "engines": { @@ -813,16 +813,16 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1123,9 +1123,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.0.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.4.tgz", - "integrity": "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA==", + "version": "24.0.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz", + "integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==", "license": "MIT", "dependencies": { "undici-types": "~7.8.0" @@ -2258,9 +2258,9 @@ } }, "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", @@ -2287,9 +2287,9 @@ } }, "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", "license": "BSD-2-Clause", "engines": { "node": ">= 6" @@ -2663,9 +2663,9 @@ } }, "node_modules/dotenv": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", - "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -2710,9 +2710,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.175", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.175.tgz", - "integrity": "sha512-Nqpef9mOVo7pZfl9NIUhj7tgtRTsMzCzRTJDP1ccim4Wb4YHOz3Le87uxeZq68OCNwau2iQ/X7UwdAZ3ReOkmg==", + "version": "1.5.179", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.179.tgz", + "integrity": "sha512-UWKi/EbBopgfFsc5k61wFpV7WrnnSlSzW/e2XcBmS6qKYTivZlLtoll5/rdqRTxGglGHkmkW0j0pFNJG10EUIQ==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -2886,19 +2886,19 @@ } }, "node_modules/eslint": { - "version": "9.29.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", - "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", + "version": "9.30.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.1.tgz", + "integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.1", - "@eslint/config-helpers": "^0.2.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.29.0", + "@eslint/js": "9.30.1", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -5286,9 +5286,9 @@ } }, "node_modules/mongoose": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.0.tgz", - "integrity": "sha512-gLuAZsbwY0PHjrvfuXvUkUq9tXjyAjN3ioXph5Y6Seu7/Uo8xJaM+rrMbL/x34K4T3UTgtXRyfoq1YU16qKyIw==", + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.1.tgz", + "integrity": "sha512-Q+0TC+KLdY4SYE+u9gk9pdW1tWu/pl0jusyEkMGTgBoAbvwQdfy4f9IM8dmvCwb/blSfp7IfLkob7v76x6ZGpQ==", "license": "MIT", "dependencies": { "bson": "^6.10.4", @@ -6509,9 +6509,9 @@ } }, "node_modules/prettier": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.1.tgz", - "integrity": "sha512-5xGWRa90Sp2+x1dQtNpIpeOQpTDBs9cZDmA/qs2vDNN2i18PdapqY7CmBeyLlMuGqXJRIOPaCaVZTLNQRWUH/A==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -7284,9 +7284,9 @@ } }, "node_modules/super-simple-scheduler": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.0.7.tgz", - "integrity": "sha512-kxKcBWE4rYgUP+XHgvO5m8nzyTI+5UoC8VeLkXJiC7EdSJrOwocyYUBuuDzE+wDNVKEv20UineXKhe1P0twYiA==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.0.12.tgz", + "integrity": "sha512-DQeSfnHYKkaaMW6OhZ1+AGneGR94H3/ux+EuHKd0oOnIzhjYUcC613boGWUjcleEQSbXaEW40TO8HWXyi76M1Q==", "license": "MIT", "dependencies": { "human-interval": "2.0.1", @@ -7355,9 +7355,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.25.2", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.25.2.tgz", - "integrity": "sha512-V4JyoygUe5nCbn7bAD0fVKSC0yNcL3ROIQtGC7M0NATKuyosCSmMU6T0yDZIIuGpSxjsjZh/D2Ejb8lnF2jjxw==", + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.26.0.tgz", + "integrity": "sha512-U8m1LruHrk33gIIT5qDKhXMygT4FonRGBE92zMbxP4i9ULolPlKISy5Pd3RCES8pWdbGzXhvm/Q6jdA/HsrClg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" @@ -7593,9 +7593,9 @@ "license": "MIT" }, "node_modules/undici": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.10.0.tgz", - "integrity": "sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.11.0.tgz", + "integrity": "sha512-heTSIac3iLhsmZhUCjyS3JQEkZELateufzZuBaVM5RHXdSBMb1LPMQf5x+FH7qjsZYDP0ttAc3nnVpUB+wYbOg==", "license": "MIT", "engines": { "node": ">=20.18.1" diff --git a/server/package.json b/server/package.json index 561d235c9..361d8b29b 100755 --- a/server/package.json +++ b/server/package.json @@ -40,7 +40,7 @@ "ping": "0.4.4", "sharp": "0.33.5", "ssl-checker": "2.0.10", - "super-simple-scheduler": "1.0.7", + "super-simple-scheduler": "1.0.12", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, diff --git a/server/service/SuperSimpleQueue/SuperSimpleQueue.js b/server/service/SuperSimpleQueue/SuperSimpleQueue.js index 578f9dd3f..c528099b6 100644 --- a/server/service/SuperSimpleQueue/SuperSimpleQueue.js +++ b/server/service/SuperSimpleQueue/SuperSimpleQueue.js @@ -87,7 +87,7 @@ class SuperSimpleQueue { }; getMetrics = async () => { - const jobs = this.scheduler.getJobs(); + const jobs = await this.scheduler.getJobs(); const metrics = jobs.reduce( (acc, job) => { acc.totalRuns += job.runCount || 0; @@ -126,7 +126,7 @@ class SuperSimpleQueue { }; getJobs = async () => { - const jobs = this.scheduler.getJobs(); + const jobs = await this.scheduler.getJobs(); return jobs.map((job) => { return { monitorId: job.id, From cb2016a5dce749d1f5b602151583e4bdd5cf57f5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 8 Jul 2025 11:04:20 -0700 Subject: [PATCH 006/259] update queue version --- server/package-lock.json | 73 ++++++++++++------- server/package.json | 2 +- .../SuperSimpleQueue/SuperSimpleQueue.js | 11 ++- 3 files changed, 56 insertions(+), 30 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index a7396bee2..8ea8f17d9 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -33,7 +33,7 @@ "ping": "0.4.4", "sharp": "0.33.5", "ssl-checker": "2.0.10", - "super-simple-scheduler": "1.0.12", + "super-simple-scheduler": "1.0.17", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, @@ -120,9 +120,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", - "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.4.tgz", + "integrity": "sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg==", "license": "MIT", "optional": true, "dependencies": { @@ -1123,9 +1123,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.0.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz", - "integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==", + "version": "24.0.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.11.tgz", + "integrity": "sha512-CJV8eqrYnwQJGMrvcRhQmZfpyniDavB+7nAZYJc6w99hFYJyFN3INV1/2W3QfQrqM36WTLrijJ1fxxvGBmCSxA==", "license": "MIT", "dependencies": { "undici-types": "~7.8.0" @@ -1198,9 +1198,9 @@ } }, "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "license": "MIT", "engines": { "node": ">= 14" @@ -1693,9 +1693,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001726", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", - "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", + "version": "1.0.30001727", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", + "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", "funding": [ { "type": "opencollective", @@ -2710,9 +2710,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.179", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.179.tgz", - "integrity": "sha512-UWKi/EbBopgfFsc5k61wFpV7WrnnSlSzW/e2XcBmS6qKYTivZlLtoll5/rdqRTxGglGHkmkW0j0pFNJG10EUIQ==", + "version": "1.5.180", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.180.tgz", + "integrity": "sha512-ED+GEyEh3kYMwt2faNmgMB0b8O5qtATGgR4RmRsIp4T6p7B8vdMbIedYndnvZfsaXvSzegtpfqRMDNCjjiSduA==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -5286,9 +5286,9 @@ } }, "node_modules/mongoose": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.1.tgz", - "integrity": "sha512-Q+0TC+KLdY4SYE+u9gk9pdW1tWu/pl0jusyEkMGTgBoAbvwQdfy4f9IM8dmvCwb/blSfp7IfLkob7v76x6ZGpQ==", + "version": "8.16.2", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.2.tgz", + "integrity": "sha512-52T4XPhDgJL4cqooBsOORzYBH3ddMwABMEF/LV7TgvD2DEKZlnYTc2HF9ch1U2lcKjhE4pQ+WuInfLFJbguGcQ==", "license": "MIT", "dependencies": { "bson": "^6.10.4", @@ -7284,16 +7284,39 @@ } }, "node_modules/super-simple-scheduler": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.0.12.tgz", - "integrity": "sha512-DQeSfnHYKkaaMW6OhZ1+AGneGR94H3/ux+EuHKd0oOnIzhjYUcC613boGWUjcleEQSbXaEW40TO8HWXyi76M1Q==", + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.0.17.tgz", + "integrity": "sha512-PIs2dymRu6lVgvddhwjCORzCgf7jDCEoRSZu5xwEsNZIjxStMgpGeV9N1EbKzkr3T+NNojUcywAZDL4lTrNH7g==", "license": "MIT", "dependencies": { "human-interval": "2.0.1", + "mongoose": "8.16.1", "uuid": "11.1.0", "winston": "3.17.0" } }, + "node_modules/super-simple-scheduler/node_modules/mongoose": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.1.tgz", + "integrity": "sha512-Q+0TC+KLdY4SYE+u9gk9pdW1tWu/pl0jusyEkMGTgBoAbvwQdfy4f9IM8dmvCwb/blSfp7IfLkob7v76x6ZGpQ==", + "license": "MIT", + "dependencies": { + "bson": "^6.10.4", + "kareem": "2.6.3", + "mongodb": "~6.17.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, "node_modules/super-simple-scheduler/node_modules/uuid": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", @@ -7355,9 +7378,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.26.0", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.26.0.tgz", - "integrity": "sha512-U8m1LruHrk33gIIT5qDKhXMygT4FonRGBE92zMbxP4i9ULolPlKISy5Pd3RCES8pWdbGzXhvm/Q6jdA/HsrClg==", + "version": "5.26.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.26.2.tgz", + "integrity": "sha512-WmMS9iMlHQejNm/Uw5ZTo4e3M2QMmEavRz7WLWVsq7Mlx4PSHJbY+VCrLsAz9wLxyHVgrJdt7N8+SdQLa52Ykg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" diff --git a/server/package.json b/server/package.json index 361d8b29b..bc5cf4128 100755 --- a/server/package.json +++ b/server/package.json @@ -40,7 +40,7 @@ "ping": "0.4.4", "sharp": "0.33.5", "ssl-checker": "2.0.10", - "super-simple-scheduler": "1.0.12", + "super-simple-scheduler": "1.0.17", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, diff --git a/server/service/SuperSimpleQueue/SuperSimpleQueue.js b/server/service/SuperSimpleQueue/SuperSimpleQueue.js index c528099b6..d5e3e6ee6 100644 --- a/server/service/SuperSimpleQueue/SuperSimpleQueue.js +++ b/server/service/SuperSimpleQueue/SuperSimpleQueue.js @@ -20,11 +20,14 @@ class SuperSimpleQueue { init = async () => { try { this.scheduler = new Scheduler({ - logLevel: process.env.LOG_LEVEL, - debug: process.env.NODE_ENV, + storeType: "mongo", + logLevel: "debug", + debug: true, + dbUri: this.appSettings.dbConnectionString, }); this.scheduler.start(); - this.scheduler.addTemplate("test", this.helper.getMonitorJob()); + + this.scheduler.addTemplate("monitor-job", this.helper.getMonitorJob()); const monitors = await this.db.getAllMonitors(); for (const monitor of monitors) { await this.addJob(monitor._id, monitor); @@ -44,7 +47,7 @@ class SuperSimpleQueue { addJob = async (monitorId, monitor) => { this.scheduler.addJob({ id: monitorId.toString(), - template: "test", + template: "monitor-job", repeat: monitor.interval, data: monitor.toObject(), }); From 1456c4f749b2dfd67013650441009df99153c1fa Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 10 Jul 2025 16:06:38 -0700 Subject: [PATCH 007/259] bump version --- server/package-lock.json | 9 +++++---- server/package.json | 2 +- server/service/SuperSimpleQueue/SuperSimpleQueue.js | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index 8ea8f17d9..6fbb33de6 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -33,7 +33,7 @@ "ping": "0.4.4", "sharp": "0.33.5", "ssl-checker": "2.0.10", - "super-simple-scheduler": "1.0.17", + "super-simple-scheduler": "1.2.3", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, @@ -7284,12 +7284,13 @@ } }, "node_modules/super-simple-scheduler": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.0.17.tgz", - "integrity": "sha512-PIs2dymRu6lVgvddhwjCORzCgf7jDCEoRSZu5xwEsNZIjxStMgpGeV9N1EbKzkr3T+NNojUcywAZDL4lTrNH7g==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.2.3.tgz", + "integrity": "sha512-lpA/xzwRXl9KXN/+mznHfF2cMDQOXejJYna/bk3L0SPBDr28afrtx3orEKZ2JVDwF+f3Np01YlYamT51UU8OCw==", "license": "MIT", "dependencies": { "human-interval": "2.0.1", + "ioredis": "5.6.1", "mongoose": "8.16.1", "uuid": "11.1.0", "winston": "3.17.0" diff --git a/server/package.json b/server/package.json index bc5cf4128..494b81e2c 100755 --- a/server/package.json +++ b/server/package.json @@ -40,7 +40,7 @@ "ping": "0.4.4", "sharp": "0.33.5", "ssl-checker": "2.0.10", - "super-simple-scheduler": "1.0.17", + "super-simple-scheduler": "1.2.3", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, diff --git a/server/service/SuperSimpleQueue/SuperSimpleQueue.js b/server/service/SuperSimpleQueue/SuperSimpleQueue.js index d5e3e6ee6..f0b59640e 100644 --- a/server/service/SuperSimpleQueue/SuperSimpleQueue.js +++ b/server/service/SuperSimpleQueue/SuperSimpleQueue.js @@ -20,7 +20,8 @@ class SuperSimpleQueue { init = async () => { try { this.scheduler = new Scheduler({ - storeType: "mongo", + // storeType: "mongo", + // storeType: "redis", logLevel: "debug", debug: true, dbUri: this.appSettings.dbConnectionString, From 1cbec8220dff3394293391d9eebc016f3824711f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 10 Jul 2025 16:08:04 -0700 Subject: [PATCH 008/259] add diagnostic page --- .../Components/Charts/CustomGauge/index.jsx | 4 +- client/src/Hooks/logHooks.js | 77 ++++++++++++++++++- client/src/Hooks/queueHooks.js | 57 -------------- .../Diagnostics/components/gauges/index.jsx | 74 ++++++++++++++++++ .../Diagnostics/components/gauges/utils.js | 4 + client/src/Pages/Logs/Diagnostics/index.jsx | 30 ++++++++ client/src/Pages/Logs/Logs/index.jsx | 2 +- client/src/Pages/Logs/Queue/index.jsx | 2 +- client/src/Pages/Logs/index.jsx | 5 +- client/src/Utils/NetworkService.js | 8 ++ client/src/locales/en.json | 6 +- server/controllers/diagnosticController.js | 73 ++++++++++++++++++ server/routes/diagnosticRoute.js | 1 + 13 files changed, 279 insertions(+), 64 deletions(-) delete mode 100644 client/src/Hooks/queueHooks.js create mode 100644 client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx create mode 100644 client/src/Pages/Logs/Diagnostics/components/gauges/utils.js create mode 100644 client/src/Pages/Logs/Diagnostics/index.jsx diff --git a/client/src/Components/Charts/CustomGauge/index.jsx b/client/src/Components/Charts/CustomGauge/index.jsx index 3c7e13610..359b5b328 100644 --- a/client/src/Components/Charts/CustomGauge/index.jsx +++ b/client/src/Components/Charts/CustomGauge/index.jsx @@ -54,8 +54,8 @@ const CustomGauge = ({ progress = 0, radius = 70, strokeWidth = 15, threshold = const fillColor = progressWithinRange > threshold - ? theme.palette.error.lowContrast // CAIO_REVIEW - : theme.palette.accent.main; // CAIO_REVIEW + ? theme.palette.error.lowContrast + : theme.palette.accent.main; return ( { return [logs, isLoading, error]; }; -export { useFetchLogs }; +const useFetchQueueData = (trigger) => { + const [jobs, setJobs] = useState(undefined); + const [metrics, setMetrics] = useState(undefined); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(undefined); + + useEffect(() => { + const fetchJobs = async () => { + try { + setIsLoading(true); + const response = await networkService.getQueueData(); + if (response.status === 200) { + setJobs(response.data.data.jobs); + setMetrics(response.data.data.metrics); + } + } catch (error) { + setError(error); + } finally { + setIsLoading(false); + } + }; + + fetchJobs(); + }, [trigger]); + + return [jobs, metrics, isLoading, error]; +}; + +const useFlushQueue = () => { + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(undefined); + + const flushQueue = async (trigger, setTrigger) => { + try { + setIsLoading(true); + await networkService.flushQueue(); + createToast({ + body: "Queue flushed", + }); + } catch (error) { + setError(error); + createToast({ + body: error.message, + }); + } finally { + setIsLoading(false); + setTrigger(!trigger); + } + }; + return [flushQueue, isLoading, error]; +}; + +const useFetchDiagnostics = () => { + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(undefined); + const [diagnostics, setDiagnostics] = useState(undefined); + + useEffect(() => { + const fetchDiagnostics = async () => { + try { + setIsLoading(true); + const response = await networkService.getDiagnostics(); + setDiagnostics(response.data.data); + } catch (error) { + setError(error); + } finally { + setIsLoading(false); + } + }; + fetchDiagnostics(); + }, []); + + return [diagnostics, isLoading, error]; +}; + +export { useFetchLogs, useFetchQueueData, useFlushQueue, useFetchDiagnostics }; diff --git a/client/src/Hooks/queueHooks.js b/client/src/Hooks/queueHooks.js deleted file mode 100644 index f7a25b3d3..000000000 --- a/client/src/Hooks/queueHooks.js +++ /dev/null @@ -1,57 +0,0 @@ -import { useState, useEffect } from "react"; -import { networkService } from "../main"; -import { createToast } from "../Utils/toastUtils"; - -const useFetchQueueData = (trigger) => { - const [jobs, setJobs] = useState(undefined); - const [metrics, setMetrics] = useState(undefined); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(undefined); - - useEffect(() => { - const fetchJobs = async () => { - try { - setIsLoading(true); - const response = await networkService.getQueueData(); - if (response.status === 200) { - setJobs(response.data.data.jobs); - setMetrics(response.data.data.metrics); - } - } catch (error) { - setError(error); - } finally { - setIsLoading(false); - } - }; - - fetchJobs(); - }, [trigger]); - - return [jobs, metrics, isLoading, error]; -}; - -const useFlushQueue = () => { - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(undefined); - - const flushQueue = async (trigger, setTrigger) => { - try { - setIsLoading(true); - await networkService.flushQueue(); - createToast({ - body: "Queue flushed", - }); - } catch (error) { - setError(error); - createToast({ - body: error.message, - }); - } finally { - setIsLoading(false); - setTrigger(!trigger); - } - }; - return [flushQueue, isLoading, error]; -}; - -export { useFetchQueueData, useFlushQueue }; diff --git a/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx b/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx new file mode 100644 index 000000000..21d9a35d8 --- /dev/null +++ b/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx @@ -0,0 +1,74 @@ +import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; +import Gauge from "../../../../../Components/Charts/CustomGauge"; +import Typography from "@mui/material/Typography"; + +// Utils +import { useTheme } from "@emotion/react"; +import PropTypes from "prop-types"; +import { getPercentage } from "./utils"; + +const GaugeBox = ({ title, subtitle, children }) => { + const theme = useTheme(); + return ( + + {children} + + {title} + {subtitle} + + ); +}; + +const Gauges = ({ diagnostics }) => { + const heapTotalSize = getPercentage( + diagnostics?.v8HeapStats?.totalHeapSizeMb, + diagnostics?.v8HeapStats?.heapSizeLimitMb + ); + + const heapUsedSize = getPercentage( + diagnostics?.v8HeapStats?.usedHeapSizeMb, + diagnostics?.v8HeapStats?.heapSizeLimitMb + ); + + const actualHeapUsed = getPercentage( + diagnostics?.v8HeapStats?.usedHeapSizeMb, + diagnostics?.v8HeapStats?.totalHeapSizeMb + ); + const theme = useTheme(); + + return ( + + + + + + + + + + + + ); +}; + +Gauges.propTypes = { + diagnostics: PropTypes.object.isRequired, +}; + +export default Gauges; diff --git a/client/src/Pages/Logs/Diagnostics/components/gauges/utils.js b/client/src/Pages/Logs/Diagnostics/components/gauges/utils.js new file mode 100644 index 000000000..bdc570a3b --- /dev/null +++ b/client/src/Pages/Logs/Diagnostics/components/gauges/utils.js @@ -0,0 +1,4 @@ +export const getPercentage = (value, total) => { + if (!value || !total) return 0; + return (value / total) * 100; +}; diff --git a/client/src/Pages/Logs/Diagnostics/index.jsx b/client/src/Pages/Logs/Diagnostics/index.jsx new file mode 100644 index 000000000..9d7ee4639 --- /dev/null +++ b/client/src/Pages/Logs/Diagnostics/index.jsx @@ -0,0 +1,30 @@ +import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import Gauges from "./components/gauges"; + +import { useTheme } from "@emotion/react"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useFetchDiagnostics } from "../../../Hooks/logHooks"; + +const Diagnostics = () => { + // Local state + + // Hooks + const theme = useTheme(); + const { t } = useTranslation(); + const [diagnostics, isLoading, error] = useFetchDiagnostics(); + console.log(diagnostics); + // Setup + return ( + + + {t("diagnosticsPage.description")} + + + + ); +}; + +export default Diagnostics; diff --git a/client/src/Pages/Logs/Logs/index.jsx b/client/src/Pages/Logs/Logs/index.jsx index 6c5e1a301..d8bd323a3 100644 --- a/client/src/Pages/Logs/Logs/index.jsx +++ b/client/src/Pages/Logs/Logs/index.jsx @@ -51,7 +51,7 @@ const Logs = () => { return ( - {t("logsPage.description")} + {t("logsPage.description")} { const theme = useTheme(); // Local state - const [value, setValue] = useState(0); + const [value, setValue] = useState(2); // Handlers const handleChange = (event, newValue) => { @@ -31,9 +32,11 @@ const Logs = () => { > + {value === 0 && } {value === 1 && } + {value === 2 && } ); }; diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js index 7fba2ce18..895d90d30 100644 --- a/client/src/Utils/NetworkService.js +++ b/client/src/Utils/NetworkService.js @@ -1091,6 +1091,14 @@ class NetworkService { }, }); } + + async getDiagnostics() { + return this.axiosInstance.get(`/diagnostic/system`, { + headers: { + "Content-Type": "application/json", + }, + }); + } } export default NetworkService; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index ddc07f166..54f9600e2 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -451,13 +451,17 @@ }, "tabs": { "logs": "Server logs", - "queue": "Job queue" + "queue": "Job queue", + "diagnostics": "Diagnostics" }, "title": "Logs", "toast": { "fetchLogsSuccess": "Logs fetched successfully" } }, + "diagnosticsPage": { + "description": "System diagnostics" + }, "low": "low", "maintenance": "maintenance", "maintenanceRepeat": "Maintenance Repeat", diff --git a/server/controllers/diagnosticController.js b/server/controllers/diagnosticController.js index 8fa5fa42b..c4223dc04 100755 --- a/server/controllers/diagnosticController.js +++ b/server/controllers/diagnosticController.js @@ -1,6 +1,14 @@ import { handleError } from "./controllerUtils.js"; +import v8 from "v8"; +import os from "os"; + const SERVICE_NAME = "diagnosticController"; +const obs = new PerformanceObserver((items) => { + const entry = items.getEntries()[0]; + performance.clearMarks(); +}); +obs.observe({ entryTypes: ["measure"] }); class DiagnosticController { constructor(db) { this.db = db; @@ -44,5 +52,70 @@ class DiagnosticController { next(handleError(error, SERVICE_NAME, "getDbStats")); } } + + async getSystemStats(req, res, next) { + try { + // Memory Usage + const totalMemory = os.totalmem(); + const freeMemory = os.freemem(); + + const osStats = { + freeMemoryMb: freeMemory / 1024 / 1024, // MB + totalMemoryMb: totalMemory / 1024 / 1024, // MB + }; + + const used = process.memoryUsage(); + const memoryUsage = {}; + for (let key in used) { + memoryUsage[`${key}Mb`] = Math.round((used[key] / 1024 / 1024) * 100) / 100; // MB + } + + // CPU Usage + const cpuUsage = process.cpuUsage(); + const cpuMetrics = { + userUsageMs: cpuUsage.user / 1000, // ms + systemUsageMs: cpuUsage.system / 1000, // ms + }; + + // V8 Heap Statistics + const heapStats = v8.getHeapStatistics(); + const v8Metrics = { + totalHeapSizeMb: heapStats.total_heap_size / 1024 / 1024, // MB + usedHeapSizeMb: heapStats.used_heap_size / 1024 / 1024, // MB + heapSizeLimitMb: heapStats.heap_size_limit / 1024 / 1024, // MB + }; + + // Event Loop Delay + let eventLoopDelay = 0; + performance.mark("start"); + await new Promise((resolve) => setTimeout(resolve, 0)); + performance.mark("end"); + performance.measure("eventLoopDelay", "start", "end"); + const entries = performance.getEntriesByName("eventLoopDelay"); + if (entries.length > 0) { + eventLoopDelay = entries[0].duration; + } + + // Uptime + const uptime = process.uptime(); // seconds + + // Combine Metrics + const diagnostics = { + osStats, + memoryUsage, + cpuUsage: cpuMetrics, + v8HeapStats: v8Metrics, + eventLoopDelayMs: eventLoopDelay, + uptimeSeconds: uptime, + }; + + return res.success({ + msg: "OK", + data: diagnostics, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getMemoryUsage")); + } + } } export default DiagnosticController; diff --git a/server/routes/diagnosticRoute.js b/server/routes/diagnosticRoute.js index 616d01ccf..0d8f4df84 100755 --- a/server/routes/diagnosticRoute.js +++ b/server/routes/diagnosticRoute.js @@ -13,6 +13,7 @@ class DiagnosticRoutes { ); this.router.post("/db/stats", this.diagnosticController.getDbStats); + this.router.get("/system", this.diagnosticController.getSystemStats); } getRouter() { From 2c1a01bb6ac50b5fd4ca6fdd167a9ab741ab6405 Mon Sep 17 00:00:00 2001 From: shanika Jayawardane Date: Fri, 11 Jul 2025 07:44:02 -0600 Subject: [PATCH 009/259] add the All select option to monitor maintenance window for all monitors same as status pages --- .../Components/MonitorList/index.jsx | 107 ++++++++++++++++++ .../Maintenance/CreateMaintenance/index.jsx | 55 ++++++++- 2 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx diff --git a/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx new file mode 100644 index 000000000..36d6594dc --- /dev/null +++ b/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx @@ -0,0 +1,107 @@ +// Components +import { Stack, Typography } from "@mui/material"; +import ReorderRoundedIcon from "@mui/icons-material/ReorderRounded"; +import DeleteIcon from "@mui/icons-material/Delete"; + +// Utils +import { DragDropContext, Droppable, Draggable } from "@hello-pangea/dnd"; +import { useTheme } from "@emotion/react"; +import Checkbox from "../../../../../Components/Inputs/Checkbox"; +const MonitorListItem = ({ + monitor, + innerRef, + draggableProps, + dragHandleProps, + onDelete, +}) => { + const theme = useTheme(); + return ( + + + {monitor.name} + { + onDelete(monitor); + }} + /> + + ); +}; + +const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => { + const onDelete = (monitorToDelete) => { + const newMonitors = selectedMonitors.filter( + (monitor) => monitor._id !== monitorToDelete._id + ); + setSelectedMonitors(newMonitors); + }; + const reorder = (list, startIndex, endIndex) => { + const result = Array.from(list); + const [removed] = result.splice(startIndex, 1); + result.splice(endIndex, 0, removed); + + return result; + }; + + const onDragEnd = (result) => { + // dropped outside the list + if (!result.destination) { + return; + } + + const reorderedMonitors = reorder( + selectedMonitors, + result.source.index, + result.destination.index + ); + + setSelectedMonitors(reorderedMonitors); + }; + + return ( + + + {(provided, snapshot) => ( + + {selectedMonitors?.map((monitor, index) => ( + + {(provided, snapshot) => ( + + )} + + ))} + {provided.placeholder} + + )} + + + ); +}; + +export default MonitorList; diff --git a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx index e71c7112c..c4e839e02 100644 --- a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx +++ b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx @@ -9,6 +9,8 @@ import { DatePicker } from "@mui/x-date-pickers/DatePicker"; import { MobileTimePicker } from "@mui/x-date-pickers/MobileTimePicker"; import { maintenanceWindowValidation } from "../../../Validation/validation"; import { createToast } from "../../../Utils/toastUtils"; +import MonitorList from "./Components/MonitorList"; +import Checkbox from "../../../Components/Inputs/Checkbox"; import dayjs from "dayjs"; import Select from "../../../Components/Inputs/Select"; @@ -129,6 +131,7 @@ const CreateMaintenance = () => { monitors: [], }); const [errors, setErrors] = useState({}); + const [selectedMonitors, setSelectedMonitors] = useState([]); useEffect(() => { const fetchMonitors = async () => { @@ -165,6 +168,7 @@ const CreateMaintenance = () => { durationUnit, monitors: monitor ? [monitor] : [], }); + setSelectedMonitors(monitor ? [monitor] : []); } catch (error) { createToast({ body: "Failed to fetch data" }); logger.error("Failed to fetch monitors", error); @@ -175,6 +179,11 @@ const CreateMaintenance = () => { fetchMonitors(); }, [user]); + // Sync form.monitors with selectedMonitors + useEffect(() => { + setForm((prev) => ({ ...prev, monitors: selectedMonitors })); + }, [selectedMonitors]); + const handleSearch = (value) => { setSearch(value); }; @@ -212,6 +221,33 @@ const CreateMaintenance = () => { }); }; + const handleMonitorsChange = (selected) => { + if (selected.some((m) => m._id === "__all__")) { + if (selectedMonitors.length !== monitors.length) { + setSelectedMonitors(monitors); + } else { + setSelectedMonitors([]); + } + } else { + // Ensure all are objects + const selectedObjs = selected + .map((sel) => + typeof sel === "string" ? monitors.find((m) => m._id === sel) : sel + ) + .filter(Boolean); + setSelectedMonitors(selectedObjs); + } + const { error } = maintenanceWindowValidation.validate( + { monitors: selected }, + { abortEarly: false } + ); + setErrors((prev) => { + return buildErrors(prev, "monitors", error); + }); + }; + + const allSelected = selectedMonitors.length === monitors.length && monitors.length > 0; + const handleSubmit = async () => { if (hasValidationErrors(form, maintenanceWindowValidation, setErrors)) return; // Build timestamp for maintenance window from startDate and startTime @@ -267,6 +303,10 @@ const CreateMaintenance = () => { } }; + // Add Select All option to the dropdown + const selectAllOption = { _id: "__all__", name: "Select All" }; + const monitorOptions = [selectAllOption, ...monitors]; + return ( { label={t("addMonitors")} multiple={true} isAdorned={false} - options={monitors ? monitors : []} + options={monitorOptions} filteredBy="name" secondaryLabel={"type"} inputValue={search} - value={form.monitors} - handleInputChange={handleSearch} - handleChange={handleSelectMonitors} + value={selectedMonitors} + handleInputChange={setSearch} + handleChange={handleMonitorsChange} error={errors["monitors"]} - disabled={maintenanceWindowId !== undefined} + disabled={form.isAllMonitors} + /> + + Date: Fri, 11 Jul 2025 07:59:12 -0600 Subject: [PATCH 010/259] add the translation to selectAll --- client/src/Pages/Maintenance/CreateMaintenance/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx index c4e839e02..0be00e8da 100644 --- a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx +++ b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx @@ -304,7 +304,7 @@ const CreateMaintenance = () => { }; // Add Select All option to the dropdown - const selectAllOption = { _id: "__all__", name: "Select All" }; + const selectAllOption = { _id: "__all__", name: t("selectAll") }; const monitorOptions = [selectAllOption, ...monitors]; return ( From b1b4fd3f6406fa83468a8726bfe45a35e2e9a933 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 11 Jul 2025 11:23:36 -0700 Subject: [PATCH 011/259] add 1s cpu usage metric, return standard units of ms and bytes --- server/controllers/diagnosticController.js | 43 +++++++++++++++------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/server/controllers/diagnosticController.js b/server/controllers/diagnosticController.js index c4223dc04..4410a28a5 100755 --- a/server/controllers/diagnosticController.js +++ b/server/controllers/diagnosticController.js @@ -53,15 +53,34 @@ class DiagnosticController { } } - async getSystemStats(req, res, next) { + async getCPUUsage() { + try { + const startUsage = process.cpuUsage(); + await new Promise((resolve) => setTimeout(resolve, 1000)); + const endUsage = process.cpuUsage(startUsage); + const cpuUsage = { + userUsageMs: endUsage.user / 1000, + systemUsageMs: endUsage.system / 1000, + usagePercentage: ((endUsage.user + endUsage.system) / 1000 / 1000) * 100, + }; + return cpuUsage; + } catch (error) { + return { + userUsageMs: 0, + systemUsageMs: 0, + }; + } + } + + getSystemStats = async (req, res, next) => { try { // Memory Usage const totalMemory = os.totalmem(); const freeMemory = os.freemem(); const osStats = { - freeMemoryMb: freeMemory / 1024 / 1024, // MB - totalMemoryMb: totalMemory / 1024 / 1024, // MB + freeMemoryBytes: freeMemory, // bytes + totalMemoryBytes: totalMemory, // bytes }; const used = process.memoryUsage(); @@ -71,18 +90,14 @@ class DiagnosticController { } // CPU Usage - const cpuUsage = process.cpuUsage(); - const cpuMetrics = { - userUsageMs: cpuUsage.user / 1000, // ms - systemUsageMs: cpuUsage.system / 1000, // ms - }; + const cpuMetrics = await this.getCPUUsage(); // V8 Heap Statistics const heapStats = v8.getHeapStatistics(); const v8Metrics = { - totalHeapSizeMb: heapStats.total_heap_size / 1024 / 1024, // MB - usedHeapSizeMb: heapStats.used_heap_size / 1024 / 1024, // MB - heapSizeLimitMb: heapStats.heap_size_limit / 1024 / 1024, // MB + totalHeapSizeBytes: heapStats.total_heap_size, // bytes + usedHeapSizeBytes: heapStats.used_heap_size, // bytes + heapSizeLimitBytes: heapStats.heap_size_limit, // bytes }; // Event Loop Delay @@ -97,7 +112,7 @@ class DiagnosticController { } // Uptime - const uptime = process.uptime(); // seconds + const uptimeMs = process.uptime() * 1000; // ms // Combine Metrics const diagnostics = { @@ -106,7 +121,7 @@ class DiagnosticController { cpuUsage: cpuMetrics, v8HeapStats: v8Metrics, eventLoopDelayMs: eventLoopDelay, - uptimeSeconds: uptime, + uptimeMs, }; return res.success({ @@ -116,6 +131,6 @@ class DiagnosticController { } catch (error) { next(handleError(error, SERVICE_NAME, "getMemoryUsage")); } - } + }; } export default DiagnosticController; From 5b395b96cd5f7001dee9d62c5a20e03471b6993f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 11 Jul 2025 11:23:50 -0700 Subject: [PATCH 012/259] implemet stats --- .../Components/Charts/CustomGauge/index.jsx | 35 ++++++- .../Diagnostics/components/gauges/index.jsx | 73 ++++++++++---- .../Diagnostics/components/gauges/utils.js | 4 - .../Diagnostics/components/stats/index.jsx | 97 +++++++++++++++++++ client/src/Pages/Logs/Diagnostics/index.jsx | 20 +++- .../src/Pages/Logs/Diagnostics/utils/utils.js | 16 +++ client/src/Pages/Logs/Logs/index.jsx | 4 + .../Logs/Queue/components/Metrics/index.jsx | 27 +++--- client/src/Pages/Logs/Queue/index.jsx | 69 +++++++------ client/src/Utils/timeUtils.js | 4 + client/src/locales/en.json | 22 ++++- 11 files changed, 294 insertions(+), 77 deletions(-) delete mode 100644 client/src/Pages/Logs/Diagnostics/components/gauges/utils.js create mode 100644 client/src/Pages/Logs/Diagnostics/components/stats/index.jsx create mode 100644 client/src/Pages/Logs/Diagnostics/utils/utils.js diff --git a/client/src/Components/Charts/CustomGauge/index.jsx b/client/src/Components/Charts/CustomGauge/index.jsx index 359b5b328..d9718d72c 100644 --- a/client/src/Components/Charts/CustomGauge/index.jsx +++ b/client/src/Components/Charts/CustomGauge/index.jsx @@ -1,8 +1,10 @@ import { useTheme } from "@emotion/react"; import { useEffect, useState, useMemo } from "react"; +import Stack from "@mui/material/Stack"; import { Box, Typography } from "@mui/material"; import PropTypes from "prop-types"; import "./index.css"; +import CircularProgress from "@mui/material/CircularProgress"; const MINIMUM_VALUE = 0; const MAXIMUM_VALUE = 100; @@ -16,6 +18,8 @@ const MAXIMUM_VALUE = 100; * @param {number} [props.radius=60] - Radius of the gauge circle * @param {number} [props.strokeWidth=15] - Width of the gauge stroke * @param {number} [props.threshold=50] - Threshold for color change + * @param {number} [props.precision=1] - Precision of the progress percentage + * @param {string} [props.unit="%"] - Unit of progress * * @example * { +const CustomGauge = ({ + isLoading = false, + progress = 0, + radius = 70, + strokeWidth = 15, + threshold = 50, + precision = 1, + unit = "%", +}) => { const theme = useTheme(); // Calculate the length of the stroke for the circle const { circumference, totalSize, strokeLength } = useMemo( @@ -57,6 +69,20 @@ const CustomGauge = ({ progress = 0, radius = 70, strokeWidth = 15, threshold = ? theme.palette.error.lowContrast : theme.palette.accent.main; + if (isLoading) { + return ( + + + + ); + } + return ( - {`${progressWithinRange.toFixed(1)}%`} + {`${progressWithinRange.toFixed(precision)}${unit}`} ); @@ -112,8 +138,11 @@ const CustomGauge = ({ progress = 0, radius = 70, strokeWidth = 15, threshold = export default CustomGauge; CustomGauge.propTypes = { + isLoading: PropTypes.bool, progress: PropTypes.number, radius: PropTypes.number, strokeWidth: PropTypes.number, threshold: PropTypes.number, + precision: PropTypes.number, + unit: PropTypes.string, }; diff --git a/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx b/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx index 21d9a35d8..e9f235112 100644 --- a/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx +++ b/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx @@ -1,12 +1,12 @@ import Stack from "@mui/material/Stack"; -import Box from "@mui/material/Box"; import Gauge from "../../../../../Components/Charts/CustomGauge"; import Typography from "@mui/material/Typography"; // Utils import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; -import { getPercentage } from "./utils"; +import { getPercentage } from "../../utils/utils"; +import { useTranslation } from "react-i18next"; const GaugeBox = ({ title, subtitle, children }) => { const theme = useTheme(); @@ -14,6 +14,8 @@ const GaugeBox = ({ title, subtitle, children }) => { {children} @@ -23,52 +25,85 @@ const GaugeBox = ({ title, subtitle, children }) => { ); }; -const Gauges = ({ diagnostics }) => { +GaugeBox.propTypes = { + title: PropTypes.string.isRequired, + subtitle: PropTypes.string.isRequired, + children: PropTypes.node.isRequired, +}; + +const Gauges = ({ diagnostics, isLoading }) => { const heapTotalSize = getPercentage( - diagnostics?.v8HeapStats?.totalHeapSizeMb, - diagnostics?.v8HeapStats?.heapSizeLimitMb + diagnostics?.v8HeapStats?.totalHeapSizeBytes, + diagnostics?.v8HeapStats?.heapSizeLimitBytes ); const heapUsedSize = getPercentage( - diagnostics?.v8HeapStats?.usedHeapSizeMb, - diagnostics?.v8HeapStats?.heapSizeLimitMb + diagnostics?.v8HeapStats?.usedHeapSizeBytes, + diagnostics?.v8HeapStats?.heapSizeLimitBytes ); const actualHeapUsed = getPercentage( - diagnostics?.v8HeapStats?.usedHeapSizeMb, - diagnostics?.v8HeapStats?.totalHeapSizeMb + diagnostics?.v8HeapStats?.usedHeapSizeBytes, + diagnostics?.v8HeapStats?.totalHeapSizeBytes ); + const theme = useTheme(); + const { t } = useTranslation(); return ( - + - + - + + + + ); }; Gauges.propTypes = { - diagnostics: PropTypes.object.isRequired, + diagnostics: PropTypes.object, + isLoading: PropTypes.bool, }; export default Gauges; diff --git a/client/src/Pages/Logs/Diagnostics/components/gauges/utils.js b/client/src/Pages/Logs/Diagnostics/components/gauges/utils.js deleted file mode 100644 index bdc570a3b..000000000 --- a/client/src/Pages/Logs/Diagnostics/components/gauges/utils.js +++ /dev/null @@ -1,4 +0,0 @@ -export const getPercentage = (value, total) => { - if (!value || !total) return 0; - return (value / total) * 100; -}; diff --git a/client/src/Pages/Logs/Diagnostics/components/stats/index.jsx b/client/src/Pages/Logs/Diagnostics/components/stats/index.jsx new file mode 100644 index 000000000..103d1c698 --- /dev/null +++ b/client/src/Pages/Logs/Diagnostics/components/stats/index.jsx @@ -0,0 +1,97 @@ +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import CircularProgress from "@mui/material/CircularProgress"; + +import { useTheme } from "@emotion/react"; +import PropTypes from "prop-types"; + +import { getHumanReadableDuration } from "../../../../../Utils/timeUtils"; +import { formatBytes } from "../../utils/utils"; +import { useTranslation } from "react-i18next"; + +const StatsCard = ({ title, value, unit = "", isLoading }) => { + const theme = useTheme(); + return ( + + {isLoading ? ( + + + + ) : ( + + + {title} + + + {value} {unit} + + + )} + + ); +}; + +StatsCard.propTypes = { + title: PropTypes.string, + value: PropTypes.string, + unit: PropTypes.string, + isLoading: PropTypes.bool, +}; + +const Stats = ({ diagnostics, isLoading }) => { + const theme = useTheme(); + const { t } = useTranslation(); + return ( + + + + + + + + + + + ); +}; + +Stats.propTypes = { + diagnostics: PropTypes.object, + isLoading: PropTypes.bool, +}; + +export default Stats; diff --git a/client/src/Pages/Logs/Diagnostics/index.jsx b/client/src/Pages/Logs/Diagnostics/index.jsx index 9d7ee4639..e000c05bc 100644 --- a/client/src/Pages/Logs/Diagnostics/index.jsx +++ b/client/src/Pages/Logs/Diagnostics/index.jsx @@ -2,6 +2,8 @@ import Stack from "@mui/material/Stack"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import Gauges from "./components/gauges"; +import Stats from "./components/stats"; +import Divider from "@mui/material/Divider"; import { useTheme } from "@emotion/react"; import { useState } from "react"; @@ -15,14 +17,26 @@ const Diagnostics = () => { const theme = useTheme(); const { t } = useTranslation(); const [diagnostics, isLoading, error] = useFetchDiagnostics(); - console.log(diagnostics); // Setup return ( - {t("diagnosticsPage.description")} + {t("diagnosticsPage.diagnosticDescription")} - + + + + + ); }; diff --git a/client/src/Pages/Logs/Diagnostics/utils/utils.js b/client/src/Pages/Logs/Diagnostics/utils/utils.js new file mode 100644 index 000000000..a3b75631f --- /dev/null +++ b/client/src/Pages/Logs/Diagnostics/utils/utils.js @@ -0,0 +1,16 @@ +export const getPercentage = (value, total) => { + if (!value || !total) return 0; + return (value / total) * 100; +}; + +export const formatBytes = (bytes) => { + if (!bytes) return "N/A"; + + if (bytes === 0) return "0 Bytes"; + + if (bytes >= 1024 * 1024 * 1024) { + return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`; + } + + return `${(bytes / (1024 * 1024)).toFixed(2)} MB`; +}; diff --git a/client/src/Pages/Logs/Logs/index.jsx b/client/src/Pages/Logs/Logs/index.jsx index d8bd323a3..b65f9c0fd 100644 --- a/client/src/Pages/Logs/Logs/index.jsx +++ b/client/src/Pages/Logs/Logs/index.jsx @@ -2,6 +2,7 @@ import Stack from "@mui/material/Stack"; import Box from "@mui/material/Box"; import Select from "../../../Components/Inputs/Select"; import Typography from "@mui/material/Typography"; +import Divider from "@mui/material/Divider"; import { useFetchLogs } from "../../../Hooks/logHooks"; import { useTheme } from "@emotion/react"; @@ -53,10 +54,13 @@ const Logs = () => { {t("logsPage.description")} + + {t("logsPage.logLevelSelect.title")} tz._id === form.timezone) || null} + inputValue={timezoneValue} + handleInputChange={(newVal) => setTimezoneValue(newVal)} + handleChange={(newValue) => { + setTimezoneValue(""); + handleFormChange({ + target: { name: "timezone", value: newValue?._id || "" }, + }); + }} + isAdorned={false} /> From a7e26d553ff0d1a41aa6f37f0dc64b130c5c6bac Mon Sep 17 00:00:00 2001 From: Yashaswee Kesharwani Date: Sat, 12 Jul 2025 23:22:42 +0530 Subject: [PATCH 014/259] chore: format code using npm run format --- client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx index 5ed9bb122..de47cfc78 100644 --- a/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx +++ b/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx @@ -16,7 +16,6 @@ import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; import { useState } from "react"; - const TabSettings = ({ isCreate, tabValue, From 39d666df6b51c7c779adc1f27b0af794ba8d4f92 Mon Sep 17 00:00:00 2001 From: shanika Jayawardane Date: Sat, 12 Jul 2025 15:13:24 -0600 Subject: [PATCH 015/259] add the checkboxes for dropdown list and remove searchbox list inside it --- .../Maintenance/CreateMaintenance/index.jsx | 33 +++++-------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx index 0be00e8da..19cb5b16d 100644 --- a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx +++ b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx @@ -222,23 +222,14 @@ const CreateMaintenance = () => { }; const handleMonitorsChange = (selected) => { - if (selected.some((m) => m._id === "__all__")) { - if (selectedMonitors.length !== monitors.length) { - setSelectedMonitors(monitors); - } else { - setSelectedMonitors([]); - } - } else { - // Ensure all are objects - const selectedObjs = selected - .map((sel) => - typeof sel === "string" ? monitors.find((m) => m._id === sel) : sel - ) - .filter(Boolean); - setSelectedMonitors(selectedObjs); - } + // Ensure all are objects + const selectedObjs = selected + .map((sel) => (typeof sel === "string" ? monitors.find((m) => m._id === sel) : sel)) + .filter(Boolean); + setSelectedMonitors(selectedObjs); + const { error } = maintenanceWindowValidation.validate( - { monitors: selected }, + { monitors: selectedObjs }, { abortEarly: false } ); setErrors((prev) => { @@ -246,8 +237,6 @@ const CreateMaintenance = () => { }); }; - const allSelected = selectedMonitors.length === monitors.length && monitors.length > 0; - const handleSubmit = async () => { if (hasValidationErrors(form, maintenanceWindowValidation, setErrors)) return; // Build timestamp for maintenance window from startDate and startTime @@ -303,10 +292,6 @@ const CreateMaintenance = () => { } }; - // Add Select All option to the dropdown - const selectAllOption = { _id: "__all__", name: t("selectAll") }; - const monitorOptions = [selectAllOption, ...monitors]; - return ( { id={"monitors"} label={t("addMonitors")} multiple={true} - isAdorned={false} - options={monitorOptions} + isAdorned={true} + options={monitors} filteredBy="name" secondaryLabel={"type"} inputValue={search} From 2bb78181163b4d445895632d8a3beb92827ecc42 Mon Sep 17 00:00:00 2001 From: Yashaswee Kesharwani Date: Sun, 13 Jul 2025 03:52:16 +0530 Subject: [PATCH 016/259] fix: replaced remaining Selects with Search in Settings Tab - optimised with useMemo and standardised the no unit found --- client/src/Components/Inputs/Search/index.jsx | 9 ++++- .../src/Pages/Settings/SettingsTimeZone.jsx | 40 +++++++++++++++---- .../Create/Components/Tabs/Settings.jsx | 36 ++++++++++++----- client/src/locales/en.json | 4 +- 4 files changed, 69 insertions(+), 20 deletions(-) diff --git a/client/src/Components/Inputs/Search/index.jsx b/client/src/Components/Inputs/Search/index.jsx index 4624b2cba..f536bd5b5 100644 --- a/client/src/Components/Inputs/Search/index.jsx +++ b/client/src/Components/Inputs/Search/index.jsx @@ -24,6 +24,7 @@ import { useTranslation } from "react-i18next"; * @param {Function} props.handleChange - Function to call when the input changes * @param {Function} Prop.onBlur - Function to call when the input is blured * @param {Object} props.sx - Additional styles to apply to the component + * @param {string} props.unit - Label to identify type of options * @returns {JSX.Element} The rendered Search component */ @@ -68,6 +69,7 @@ const Search = ({ startAdornment, endAdornment, onBlur, + unit = "option", }) => { const theme = useTheme(); const { t } = useTranslation(); @@ -186,7 +188,12 @@ const Search = ({ ); if (filtered.length === 0) { - return [{ [filteredBy]: "No monitors found", noOptions: true }]; + return [ + { + [filteredBy]: t("general.noOptionsFound", { unit: unit }), + noOptions: true, + }, + ]; } return filtered; }} diff --git a/client/src/Pages/Settings/SettingsTimeZone.jsx b/client/src/Pages/Settings/SettingsTimeZone.jsx index 346e4e546..4b17f3f81 100644 --- a/client/src/Pages/Settings/SettingsTimeZone.jsx +++ b/client/src/Pages/Settings/SettingsTimeZone.jsx @@ -2,16 +2,37 @@ import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; import ConfigBox from "../../Components/ConfigBox"; -import Select from "../../Components/Inputs/Select"; +import Search from "../../Components/Inputs/Search"; import timezones from "../../Utils/timezones.json"; // Utils import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; import { useTranslation } from "react-i18next"; +import { useCallback, useMemo, useState } from "react"; const SettingsTimeZone = ({ HEADING_SX, handleChange, timezone }) => { const theme = useTheme(); const { t } = useTranslation(); + const [rawInput, setRawInput] = useState(""); + + const selectedTimezone = useMemo( + () => timezones.find((tz) => tz._id === timezone) ?? null, + [timezone, timezones] + ); + + const handleTimezoneChange = useCallback( + (newValue) => { + setRawInput(""); + handleChange({ + target: { + name: "timezone", + value: newValue?._id ?? "", + }, + }); + }, + [handleChange] + ); + return ( @@ -28,12 +49,17 @@ const SettingsTimeZone = ({ HEADING_SX, handleChange, timezone }) => { - - - {form.type === "http" && useAdvancedMatching && ( - <> - - - {monitor.type === "http" && useAdvancedMatching && ( - <> - + {monitor.type === "http" && ( + + )} + {monitor.type === "http" && useAdvancedMatching && ( + <> + - {monitor.type === "http" && ( - - )} - {monitor.type === "http" && useAdvancedMatching && ( - <> - + {monitor.type === "http" && ( + + )} + {monitor.type === "http" && useAdvancedMatching && ( + <> + From 35270e2761d0e904b18c882901a8f076e846409e Mon Sep 17 00:00:00 2001 From: Fernando Canchola Cruz Date: Thu, 24 Jul 2025 11:38:45 -0600 Subject: [PATCH 106/259] Update README.es.md Fixed readme.es.md --- README.es.md | 179 +++++++++++++++++++++++++++++---------------------- 1 file changed, 101 insertions(+), 78 deletions(-) diff --git a/README.es.md b/README.es.md index a13ca6a75..21909b2ed 100644 --- a/README.es.md +++ b/README.es.md @@ -7,143 +7,166 @@ ![](https://img.shields.io/github/languages/top/bluewave-labs/checkmate) ![](https://img.shields.io/github/issues/bluewave-labs/checkmate) ![](https://img.shields.io/github/issues-pr/bluewave-labs/checkmate) -[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9901/badge)](https://www.bestpractices.dev/projects/9901) -[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/bluewave-labs/checkmate) +[![OpenSSF Mejores Prácticas](https://www.bestpractices.dev/projects/9901/badge)](https://www.bestpractices.dev/projects/9901) +[![Pregunta en DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/bluewave-labs/checkmate)

Checkmate

-

Una aplicación de monitoreo de infraestructura y disponibilidad de código abierto

+

Una aplicación de código abierto para monitoreo de infraestructura y tiempo de actividad

image -Este repositorio contiene tanto el frontend como el backend de Checkmate, una herramienta de monitoreo de código abierto y autoalojada para rastrear hardware de servidores, tiempo de actividad, tiempos de respuesta e incidentes en tiempo real con visualizaciones atractivas. Checkmate verifica regularmente si un servidor/sitio web es accesible y funciona de manera óptima, proporcionando alertas y reportes en tiempo real sobre disponibilidad, tiempo de inactividad y respuesta de los servicios monitoreados. +Este repositorio contiene tanto el frontend como el backend de Checkmate, una herramienta de monitoreo de código abierto y autoalojada para rastrear hardware de servidores, tiempo de actividad, tiempos de respuesta e incidentes en tiempo real con visualizaciones atractivas. Checkmate revisa regularmente si un servidor o sitio web es accesible y funciona de manera óptima, proporcionando alertas e informes en tiempo real sobre la disponibilidad, el tiempo de inactividad y el tiempo de respuesta de los servicios monitoreados. -Checkmate también tiene un agente llamado [Capture](https://github.com/bluewave-labs/capture) para obtener datos de servidores remotos. Aunque no es obligatorio para ejecutar Checkmate, proporciona información adicional sobre el estado del CPU, RAM, disco y temperatura de tus servidores. +Checkmate también tiene un agente llamado [Capture](https://github.com/bluewave-labs/capture), para recuperar datos de servidores remotos. Aunque Capture no es obligatorio para ejecutar Checkmate, proporciona información adicional sobre el estado de la CPU, RAM, disco y temperatura de tus servidores. -Checkmate ha sido probado con más de 1000 monitores activos sin problemas significativos ni cuellos de botella en el rendimiento. +Checkmate ha sido probado con más de 1000 monitores activos sin problemas ni cuellos de botella de rendimiento. -**Si deseas patrocinar una función, [consulta este enlace](https://checkmate.so/sponsored-features).** +**Si deseas patrocinar una función, [visita este enlace](https://checkmate.so/sponsored-features).** ## 📚 Tabla de contenidos -- [📦 Demo](#-demo) -- [🔗 Guía del usuario](#-guía-del-usuario) -- [🛠️ Instalación](#️-instalación) -- [🚀 Despliegue con Helm](#-despliegue-con-helm) -- [🏁 Traducciones](#-traducciones) -- [🚀 Rendimiento](#-rendimiento) -- [💚 Preguntas e ideas](#-preguntas-e-ideas) -- [🧩 Funcionalidades](#-funcionalidades) -- [🏗️ Capturas de pantalla](#-capturas-de-pantalla) -- [🏗️ Tecnología utilizada](#-tecnología-utilizada) +- [📦 Demo](#demo) +- [🔗 Guía del usuario](#guía-del-usuario) +- [🛠️ Instalación](#instalación) +- [🏁 Traducciones](#traducciones) +- [🚀 Rendimiento](#rendimiento) +- [💚 Preguntas e ideas](#preguntas-e-ideas) +- [🧩 Características](#características) +- [🏗️ Capturas de pantalla](#capturas-de-pantalla) +- [🏗️ Tecnologías](#tecnologías) - [🔗 Enlaces útiles](#enlaces-útiles) -- [🤝 Cómo contribuir](#-cómo-contribuir) -- [💰 Patrocinadores](#-patrocinadores) +- [🤝 Contribuciones](#contribuciones) +- [💰 Patrocinadores](#patrocinadores) -## 📦 Demo +... -Puedes ver la última versión de [Checkmate](https://checkmate-demo.bluewavelabs.ca/) en acción. Usuario: `uptimedemo@demo.com`, contraseña: `Demouser1!`. (Actualizamos el servidor demo periódicamente. Si no funciona, contáctanos en el canal de Discussions). +**[Texto truncado para mantener la longitud del mensaje manejable]** -## 🔗 Guía del usuario +## Demo -Instrucciones de uso disponibles [aquí](https://docs.checkmate.so/checkmate-2.1). Aún está en desarrollo, pero se actualiza constantemente. +Puedes ver la última versión de [Checkmate](https://checkmate-demo.bluewavelabs.ca/) en acción. El usuario es uptimedemo@demo.com y la contraseña es Demouser1! (ten en cuenta que actualizamos el servidor de demostración de vez en cuando, así que si no funciona para ti, por favor contáctanos en el canal de Discusiones). -## 🛠️ Instalación +## Guía del usuario + +Las instrucciones de uso se pueden encontrar [aquí](https://docs.checkmate.so/checkmate-2.1). Todavía está en desarrollo y parte de la información puede estar desactualizada ya que continuamente añadimos funciones cada semana. ¡Ten por seguro que estamos haciendo lo mejor posible! :) + +## Instalación Consulta las instrucciones de instalación en el [portal de documentación de Checkmate](https://docs.checkmate.so/checkmate-2.1/users-guide/quickstart). -También puedes usar [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md) o [Pikapods](https://www.pikapods.com/) para desplegar rápidamente una instancia. Si quieres monitorear tu infraestructura, necesitarás el agente [Capture](https://github.com/bluewave-labs/capture). +Alternativamente, también puedes usar [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md) o [Pikapods](https://www.pikapods.com/) para desplegar rápidamente una instancia de Checkmate. Si deseas monitorear tu infraestructura de servidores, necesitarás el agente [Capture](https://github.com/bluewave-labs/capture). El repositorio de Capture también contiene las instrucciones de instalación. -## 🏁 Traducciones +## Traducciones -Si deseas usar Checkmate en otro idioma, [únete a este proyecto en Poeditor](https://poeditor.com/join/project/lRUoGZFCsJ) para colaborar con las traducciones. +Si deseas usar Checkmate en tu idioma, por favor [ve a esta página](https://poeditor.com/join/project/lRUoGZFCsJ) y regístrate para el idioma al que te gustaría traducir Checkmate. -## 🚀 Rendimiento +## Rendimiento -Gracias a optimizaciones avanzadas, Checkmate tiene un consumo de memoria muy bajo. Aquí el uso de memoria monitoreando 323 servidores cada minuto: +Gracias a extensas optimizaciones, Checkmate opera con un uso de memoria excepcionalmente bajo, requiriendo recursos mínimos de memoria y CPU. Aquí está el uso de memoria de una instancia de Node.js ejecutándose en un servidor que monitorea 323 servidores cada minuto: ![image](https://github.com/user-attachments/assets/37e04a75-d83a-488f-b25c-025511b492c9) -Uso de MongoDB y Redis en el mismo servidor (398Mb y 15Mb): +También puedes ver el consumo de memoria de MongoDB y Redis en el mismo servidor (398Mb y 15Mb) para la misma cantidad de servidores: ![image](https://github.com/user-attachments/assets/3b469e85-e675-4040-a162-3f24c1afc751) -## 💚 Preguntas e ideas +## Preguntas e Ideas -Puedes contactarnos por: +Si tienes alguna pregunta, sugerencia o comentario, tienes varias opciones: - [Canal de Discord](https://discord.gg/NAb6H3UTjK) - [Discusiones en GitHub](https://github.com/bluewave-labs/bluewave-uptime/discussions) - [Grupo en Reddit](https://www.reddit.com/r/CheckmateMonitoring/) -¡Tus ideas son bienvenidas! +¡No dudes en hacer preguntas o compartir tus ideas, nos encantaría saber de ti! -## 🧩 Funcionalidades +## Características -- Código abierto, autoalojable (Raspberry Pi incluido) -- Monitoreo de sitios web, puertos, ping, SSL -- Velocidad de página -- Monitoreo de infraestructura (requiere agente Capture) -- Monitoreo Docker -- Incidentes y mantenimiento programado -- Notificaciones (email, Discord, Slack, Telegram, Webhooks) -- Consulta JSON -- Multilenguaje +- Completamente de código abierto, desplegable en tus propios servidores o dispositivos personales (por ejemplo, Raspberry Pi 4 o 5) +- Monitoreo de sitios web +- Monitoreo de velocidad de carga +- Monitoreo de infraestructura (memoria, uso de disco, rendimiento de CPU, etc) - requiere el agente [Capture](https://github.com/bluewave-labs/capture) +- Monitoreo de Docker +- Monitoreo con ping +- Monitoreo de certificados SSL +- Monitoreo de puertos +- Incidentes en una sola vista +- Páginas de estado +- Notificaciones por correo, Webhooks, Discord, Telegram, Slack +- Mantenimiento programado +- Monitoreo de consultas JSON +- Soporte para múltiples idiomas + +**Hoja de ruta a corto plazo:** ([Milestone 2.2](https://github.com/bluewave-labs/Checkmate/milestone/8)) -**Próximas funciones (Milestone 2.2):** - Mejores notificaciones - Monitoreo de red -- Otras mejoras +- ...y algunas funciones más -## 🏗️ Capturas de pantalla +## Capturas de pantalla -(Ver imágenes en el repo original) +

+image +

+

+ image +

+

+image +

+

+image +

-## 🏗️ Tecnología utilizada +## Tecnologías -- ReactJs -- MUI -- Node.js -- MongoDB -- Recharts -- Y muchos componentes de código abierto +- [ReactJs](https://react.dev/) +- [MUI (framework de React)](https://mui.com/) +- [Node.js](https://nodejs.org/en) +- [MongoDB](https://mongodb.com) +- [Recharts](https://recharts.org) +- ¡Y muchos otros componentes de código abierto! ## Enlaces útiles -- ⭐ Dale una estrella si te gusta el proyecto -- 📌 Participa en [Discussions](https://github.com/bluewave-labs/checkmate/discussions) -- 📥 Usa [NewReleases](https://newreleases.io/) para recibir actualizaciones -- 🎥 Mira un [video de instalación](https://www.youtube.com/watch?v=GfFOc0xHIwY) +- Si deseas apoyarnos, por favor considera darle una ⭐ y haz clic en "watch". +- ¿Tienes una pregunta o sugerencia para la hoja de ruta? Revisa nuestro [canal de Discord](https://discord.gg/NAb6H3UTjK) o el foro de [Discusiones](https://github.com/bluewave-labs/checkmate/discussions). +- ¿Quieres saber cuándo hay una nueva versión? Usa [Newreleases](https://newreleases.io/), un servicio gratuito para seguir lanzamientos. +- Mira un video de instalación y uso de Checkmate [aquí](https://www.youtube.com/watch?v=GfFOc0xHIwY) -## 🤝 Cómo contribuir +## Contribuciones -Somos [Alex](http://github.com/ajhollid), [Vishnu](http://github.com/vishnusn77), [Mohadeseh](http://github.com/mohicody), [Gorkem](http://github.com/gorkem-bwl/), [Owaise](http://github.com/Owaiseimdad), [Aryaman](https://github.com/Br0wnHammer) y [Mert](https://github.com/mertssmnoglu). +Somos [Alex](http://github.com/ajhollid) (líder de equipo), [Vishnu](http://github.com/vishnusn77), [Mohadeseh](http://github.com/mohicody), [Gorkem](http://github.com/gorkem-bwl/), [Owaise](http://github.com/Owaiseimdad), [Aryaman](https://github.com/Br0wnHammer) y [Mert](https://github.com/mertssmnoglu), ayudando a personas y empresas a monitorear su infraestructura y servidores. -Checkmate tiene más de 7000 estrellas y 90+ contribuidores globales. Está marcado por empleados de Google, Microsoft, Intel, Cisco, etc. +Nos enorgullecemos de construir conexiones fuertes con contribuyentes de todos los niveles. A pesar de ser un proyecto joven, Checkmate ya ha ganado más de 7000 estrellas y ha atraído a más de 90 contribuyentes de todo el mundo. -Pasos para contribuir: +Nuestro repositorio ha sido marcado con estrella por empleados de **Google, Microsoft, Intel, Cisco, Tencent, Electronic Arts, ByteDance, JP Morgan Chase, Deloitte, Accenture, Foxconn, Broadcom, China Telecom, Barclays, Capgemini, Wipro, Cloudflare, Dassault Systèmes y NEC**, ¡así que no te detengas — participa, contribuye y aprende con nosotros! -1. ⭐ Dale estrella al repositorio -2. Lee la [guía de contribución](https://github.com/bluewave-labs/Checkmate/blob/develop/CONTRIBUTING.md) -3. Revisa la [estructura del proyecto](https://docs.checkmate.so/checkmate-2.1/developers-guide/general-project-structure) -4. Aprende más con [DeepWiki](https://deepwiki.com/bluewave-labs/Checkmate) -5. Abre un issue si encuentras errores -6. Mira las tareas con `good-first-issue` -7. Envía un PR con mejoras o nuevas funciones +Cómo contribuir: -![Contribuidores](https://contrib.rocks/image?repo=bluewave-labs/checkmate) +0. Dale una estrella al repositorio :) +1. Revisa la [guía para contribuidores](https://github.com/bluewave-labs/Checkmate/blob/develop/CONTRIBUTING.md). Se anima a los nuevos a revisar las etiquetas `good-first-issue`. +2. Consulta la [estructura del proyecto](https://docs.checkmate.so/checkmate-2.1/developers-guide/general-project-structure) y la [visión general](https://bluewavelabs.gitbook.io/checkmate/developers-guide/high-level-overview). +3. Lee una estructura detallada de [Checkmate](https://deepwiki.com/bluewave-labs/Checkmate) si deseas profundizar en la arquitectura. +4. Abre un issue si crees que has encontrado un error. +5. Revisa los issues con la etiqueta `good-first-issue` si eres nuevo. +6. Haz un pull request para añadir nuevas funciones, mejoras o correcciones. + + + + [![Historial de estrellas](https://api.star-history.com/svg?repos=bluewave-labs/checkmate&type=Date)](https://star-history.com/#bluewave-labs/bluewave-uptime&Date) -## 💰 Patrocinadores +## Patrocinadores -Gracias a [Gitbook](https://gitbook.io/) y [Poeditor](https://poeditor.com/) por sus cuentas gratuitas. +Gracias a [Gitbook](https://gitbook.io/) por darnos una cuenta gratuita para su plataforma de documentación, y a [Poeditor](https://poeditor.com/) por proporcionarnos una cuenta gratuita para servicios de traducción. Si deseas patrocinar Checkmate, por favor envía un correo a hello@bluewavelabs.ca -¿Quieres patrocinar? Escríbenos a hello@bluewavelabs.ca -También puedes [ver funciones patrocinadas](https://checkmate.so/sponsored-features) +Si deseas patrocinar una función, [visita esta página](https://checkmate.so/sponsored-features). -Otros proyectos: +También puedes revisar otros proyectos de BlueWave orientados a desarrolladores y contribuidores: -- [VerifyWise](https://github.com/bluewave-labs/verifywise): Gobernanza de IA -- [DataRoom](https://github.com/bluewave-labs/bluewave-dataroom): Compartir archivos seguro -- [Guidefox](https://github.com/bluewave-labs/guidefox): Onboarding con tours y ayudas visuales \ No newline at end of file +- [VerifyWise](https://github.com/bluewave-labs/verifywise), la primera plataforma de gobernanza de IA de código abierto. +- [DataRoom](https://github.com/bluewave-labs/bluewave-dataroom), una aplicación de intercambio de archivos seguro, también conocida como dataroom. +- [Guidefox](https://github.com/bluewave-labs/guidefox), una app que ayuda a nuevos usuarios a aprender a usar tu producto mediante pistas, tours, popups y banners. From d59e2796e7d269a54be8de14232ab66a4d024bf2 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 24 Jul 2025 13:50:21 -0700 Subject: [PATCH 107/259] refactor to monitor service --- server/controllers/monitorController.js | 444 ++++-------------- server/db/models/Monitor.js | 12 +- server/db/mongo/modules/monitorModule.js | 13 +- .../db/mongo/modules/monitorModuleQueries.js | 1 + server/index.js | 79 ++-- server/routes/monitorRoute.js | 5 - server/service/business/monitorService.js | 265 +++++++++++ server/utils/errorUtils.js | 6 +- 8 files changed, 421 insertions(+), 404 deletions(-) create mode 100644 server/service/business/monitorService.js diff --git a/server/controllers/monitorController.js b/server/controllers/monitorController.js index 9943a765a..0f0dd3e76 100755 --- a/server/controllers/monitorController.js +++ b/server/controllers/monitorController.js @@ -4,8 +4,6 @@ import { getMonitorsByTeamIdParamValidation, getMonitorsByTeamIdQueryValidation, createMonitorBodyValidation, - createMonitorsBodyValidation, - getMonitorURLByQueryValidation, editMonitorBodyValidation, pauseMonitorParamValidation, getMonitorStatsByIdParamValidation, @@ -15,21 +13,18 @@ import { getHardwareDetailsByIdQueryValidation, } from "../validation/joi.js"; import sslChecker from "ssl-checker"; -import logger from "../utils/logger.js"; -import axios from "axios"; -import seedDb from "../db/mongo/utils/seedDb.js"; -import pkg from "papaparse"; -import { asyncHandler, createServerError } from "../utils/errorUtils.js"; +import { asyncHandler } from "../utils/errorUtils.js"; import { fetchMonitorCertificate } from "./controllerUtils.js"; const SERVICE_NAME = "monitorController"; class MonitorController { - constructor(db, settingsService, jobQueue, stringService, emailService) { + constructor({ db, settingsService, jobQueue, stringService, emailService, monitorService }) { this.db = db; this.settingsService = settingsService; this.jobQueue = jobQueue; this.stringService = stringService; this.emailService = emailService; + this.monitorService = monitorService; } async verifyTeamAccess(teamId, monitorId) { @@ -41,18 +36,9 @@ class MonitorController { } } - /** - * Returns all monitors - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @param {function} next - * @returns {Promise} - * @throws {Error} - */ getAllMonitors = asyncHandler( - async (req, res, next) => { - const monitors = await this.db.getAllMonitors(); + async (req, res) => { + const monitors = await this.monitorService.getAllMonitors(); return res.success({ msg: this.stringService.monitorGetAll, data: monitors, @@ -63,56 +49,47 @@ class MonitorController { ); getUptimeDetailsById = asyncHandler( - async (req, res, next) => { - const { monitorId } = req.params; - const { dateRange, normalize } = req.query; + async (req, res) => { + const monitorId = req?.params?.monitorId; + const dateRange = req?.query?.dateRange; + const normalize = req?.query?.normalize; const teamId = req?.user?.teamId; + if (!teamId) { throw new Error("Team ID is required"); } - await this.verifyTeamAccess(teamId, monitorId); - - const data = await this.db.getUptimeDetailsById({ + const data = await this.monitorService.getUptimeDetailsById({ + teamId, monitorId, dateRange, normalize, }); return res.success({ msg: this.stringService.monitorGetByIdSuccess, - data, + data: data, }); }, SERVICE_NAME, "getUptimeDetailsById" ); - /** - * Returns monitor stats for monitor with matching ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @param {function} next - * @returns {Promise} - * @throws {Error} - */ getMonitorStatsById = asyncHandler( - async (req, res, next) => { + async (req, res) => { await getMonitorStatsByIdParamValidation.validateAsync(req.params); await getMonitorStatsByIdQueryValidation.validateAsync(req.query); let { limit, sortOrder, dateRange, numToDisplay, normalize } = req.query; - const { monitorId } = req.params; + const monitorId = req?.params?.monitorId; const teamId = req?.user?.teamId; if (!teamId) { throw new Error("Team ID is required"); } - await this.verifyTeamAccess(teamId, monitorId); - - const monitorStats = await this.db.getMonitorStatsById({ + const monitorStats = await this.monitorService.getMonitorStatsById({ + teamId, monitorId, limit, sortOrder, @@ -120,6 +97,7 @@ class MonitorController { numToDisplay, normalize, }); + return res.success({ msg: this.stringService.monitorStatsById, data: monitorStats, @@ -139,7 +117,7 @@ class MonitorController { * @throws {Error} - Throws error if monitor not found or other database errors */ getHardwareDetailsById = asyncHandler( - async (req, res, next) => { + async (req, res) => { await getHardwareDetailsByIdParamValidation.validateAsync(req.params); await getHardwareDetailsByIdQueryValidation.validateAsync(req.query); @@ -150,8 +128,12 @@ class MonitorController { throw new Error("Team ID is required"); } - await this.verifyTeamAccess(teamId, monitorId); - const monitor = await this.db.getHardwareDetailsById({ monitorId, dateRange }); + const monitor = await this.monitorService.getHardwareDetailsById({ + teamId, + monitorId, + dateRange, + }); + return res.success({ msg: this.stringService.monitorGetByIdSuccess, data: monitor, @@ -180,34 +162,17 @@ class MonitorController { "getMonitorCertificate" ); - /** - * Retrieves a monitor by its ID. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.monitorId - The ID of the monitor to be retrieved. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message, and the retrieved monitor data. - * @throws {Error} If there is an error during the process, especially if the monitor is not found (404) or if there is a validation error (422). - */ getMonitorById = asyncHandler( - async (req, res, next) => { + async (req, res) => { await getMonitorByIdParamValidation.validateAsync(req.params); await getMonitorByIdQueryValidation.validateAsync(req.query); - const monitor = await this.db.getMonitorById(req.params.monitorId); - const teamId = req?.user?.teamId; if (!teamId) { throw new Error("Team ID is required"); } - if (!monitor.teamId.equals(teamId)) { - const error = new Error("Unauthorized"); - error.status = 403; - throw error; - } + const monitor = await this.monitorService.getMonitorById({ teamId, monitorId: req?.params?.monitorId }); return res.success({ msg: this.stringService.monitorGetByIdSuccess, @@ -218,30 +183,15 @@ class MonitorController { "getMonitorById" ); - /** - * Creates a new monitor and adds it to the job queue. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.body - The body of the request. - * @property {Array} req.body.notifications - The notifications associated with the monitor. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the creation of the monitor, and the created monitor data. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ createMonitor = asyncHandler( - async (req, res, next) => { + async (req, res) => { await createMonitorBodyValidation.validateAsync(req.body); - const { _id, teamId } = req.user; - const monitor = await this.db.createMonitor({ - body: req.body, - teamId, - userId: _id, - }); + const userId = req?.user?._id; + const teamId = req?.user?.teamId; + + const monitor = await this.monitorService.createMonitor({ teamId, userId, body: req.body }); - // Add monitor to job queue - this.jobQueue.addJob(monitor._id, monitor); return res.success({ msg: this.stringService.monitorCreate, data: monitor, @@ -251,141 +201,45 @@ class MonitorController { "createMonitor" ); - /** - * Creates bulk monitors and adds them to the job queue after parsing CSV. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.file - The uploaded CSV file. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status and message. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ createBulkMonitors = asyncHandler( - async (req, res, next) => { - const { parse } = pkg; - - // validate the file + async (req, res) => { if (!req.file) { throw new Error("No file uploaded"); } - // Check if the file is a CSV if (!req.file.mimetype.includes("csv")) { throw new Error("File is not a CSV"); } - // Validate if the file is empty if (req.file.size === 0) { throw new Error("File is empty"); } - const { _id, teamId } = req.user; + const userId = req?.user?._id; + const teamId = req?.user?.teamId; - if (!_id || !teamId) { + if (!userId || !teamId) { throw new Error("Missing userId or teamId"); } - // Get file buffer from memory and convert to string - const fileData = req.file.buffer.toString("utf-8"); + const fileData = req?.file?.buffer?.toString("utf-8"); + if (!fileData) { + throw new Error("Cannot get file from buffer"); + } - // Parse the CSV data - parse(fileData, { - header: true, - skipEmptyLines: true, - transform: (value, header) => { - if (value === "") return undefined; // Empty fields become undefined + const monitors = await this.monitorService.createBulkMonitors({ fileData, userId, teamId }); - // Handle 'port' and 'interval' fields, check if they're valid numbers - if (["port", "interval"].includes(header)) { - const num = parseInt(value, 10); - if (isNaN(num)) { - throw new Error(`${header} should be a valid number, got: ${value}`); - } - return num; - } - - return value; - }, - complete: async ({ data, errors }) => { - if (errors.length > 0) { - throw createServerError("Error parsing CSV"); - } - - if (!data || data.length === 0) { - throw createServerError("CSV file contains no data rows"); - } - - const enrichedData = data.map((monitor) => ({ - userId: _id, - teamId, - ...monitor, - description: monitor.description || monitor.name || monitor.url, - name: monitor.name || monitor.url, - type: monitor.type || "http", - })); - - await createMonitorsBodyValidation.validateAsync(enrichedData); - - const monitors = await this.db.createBulkMonitors(enrichedData); - - await Promise.all( - monitors.map(async (monitor, index) => { - this.jobQueue.addJob(monitor._id, monitor); - }) - ); - - return res.success({ - msg: this.stringService.bulkMonitorsCreate, - data: monitors, - }); - }, + return res.success({ + msg: this.stringService.bulkMonitorsCreate, + data: monitors, }); }, SERVICE_NAME, "createBulkMonitors" ); - /** - * Checks if the endpoint can be resolved - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.query - The query parameters of the request. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message, and the resolution result. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ - checkEndpointResolution = asyncHandler( - async (req, res, next) => { - await getMonitorURLByQueryValidation.validateAsync(req.query); - const { monitorURL } = req.query; - const parsedUrl = new URL(monitorURL); - const response = await axios.get(parsedUrl, { - timeout: 5000, - validateStatus: () => true, - }); - return res.success({ - status: response.status, - msg: response.statusText, - }); - }, - SERVICE_NAME, - "checkEndpointResolution" - ); - /** - * Deletes a monitor by its ID and also deletes associated checks, alerts, and notifications. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.monitorId - The ID of the monitor to be deleted. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status and a message indicating the deletion of the monitor. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422) or an error in deleting associated records. - */ deleteMonitor = asyncHandler( - async (req, res, next) => { + async (req, res) => { await getMonitorByIdParamValidation.validateAsync(req.params); const monitorId = req.params.monitorId; const teamId = req?.user?.teamId; @@ -393,70 +247,31 @@ class MonitorController { throw new Error("Team ID is required"); } - await this.verifyTeamAccess(teamId, monitorId); + const deletedMonitor = await this.monitorService.deleteMonitor({ teamId, monitorId }); - const monitor = await this.db.deleteMonitor({ monitorId }); - await this.jobQueue.deleteJob(monitor); - await this.db.deleteStatusPagesByMonitorId(monitor._id); - return res.success({ msg: this.stringService.monitorDelete }); + return res.success({ msg: this.stringService.monitorDelete, data: deletedMonitor }); }, SERVICE_NAME, "deleteMonitor" ); - /** - * Deletes all monitors associated with a team. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.headers - The headers of the request. - * @property {string} req.headers.authorization - The authorization header containing the JWT token. - * @param {Object} res - The Express response object. - * @param {function} next - * @returns {Object} The response object with a success status and a message indicating the number of deleted monitors. - * @throws {Error} If there is an error during the deletion process. - */ deleteAllMonitors = asyncHandler( - async (req, res, next) => { + async (req, res) => { const teamId = req?.user?.teamId; - const { monitors, deletedCount } = await this.db.deleteAllMonitors(teamId); - await Promise.all( - monitors.map(async (monitor) => { - try { - await this.jobQueue.deleteJob(monitor); - await this.db.deleteChecks(monitor._id); - await this.db.deletePageSpeedChecksByMonitorId(monitor._id); - await this.db.deleteNotificationsByMonitorId(monitor._id); - } catch (error) { - logger.warn({ - message: `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`, - service: SERVICE_NAME, - method: "deleteAllMonitors", - stack: error.stack, - }); - } - }) - ); + if (!teamId) { + throw new Error("Team ID is required"); + } + + const deletedCount = await this.monitorService.deleteAllMonitors({ teamId }); + return res.success({ msg: `Deleted ${deletedCount} monitors` }); }, SERVICE_NAME, "deleteAllMonitors" ); - /** - * Edits a monitor by its ID, updates its notifications, and updates its job in the job queue. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.monitorId - The ID of the monitor to be edited. - * @property {Object} req.body - The body of the request. - * @property {Array} req.body.notifications - The notifications to be associated with the monitor. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the editing of the monitor, and the edited monitor data. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ editMonitor = asyncHandler( - async (req, res, next) => { + async (req, res) => { await getMonitorByIdParamValidation.validateAsync(req.params); await editMonitorBodyValidation.validateAsync(req.body); const monitorId = req?.params?.monitorId; @@ -466,11 +281,7 @@ class MonitorController { throw new Error("Team ID is required"); } - await this.verifyTeamAccess(teamId, monitorId); - - const editedMonitor = await this.db.editMonitor(monitorId, req.body); - - await this.jobQueue.updateJob(editedMonitor); + const editedMonitor = await this.monitorService.editMonitor({ teamId, monitorId, body: req.body }); return res.success({ msg: this.stringService.monitorEdit, @@ -481,19 +292,8 @@ class MonitorController { "editMonitor" ); - /** - * Pauses or resumes a monitor based on its current state. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.monitorId - The ID of the monitor to be paused or resumed. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the new state of the monitor, and the updated monitor data. - * @throws {Error} If there is an error during the process. - */ pauseMonitor = asyncHandler( - async (req, res, next) => { + async (req, res) => { await pauseMonitorParamValidation.validateAsync(req.params); const monitorId = req.params.monitorId; @@ -502,10 +302,7 @@ class MonitorController { throw new Error("Team ID is required"); } - await this.verifyTeamAccess(teamId, monitorId); - - const monitor = await this.db.pauseMonitor({ monitorId }); - monitor.isActive === true ? await this.jobQueue.resumeJob(monitor._id, monitor) : await this.jobQueue.pauseJob(monitor); + const monitor = await this.monitorService.pauseMonitor({ teamId, monitorId }); return res.success({ msg: monitor.isActive ? this.stringService.monitorResume : this.stringService.monitorPause, @@ -516,60 +313,28 @@ class MonitorController { "pauseMonitor" ); - /** - * Adds demo monitors for a team. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.headers - The headers of the request. - * @property {string} req.headers.authorization - The authorization header containing the JWT token. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the addition of demo monitors, and the number of demo monitors added. - * @throws {Error} If there is an error during the process. - */ addDemoMonitors = asyncHandler( - async (req, res, next) => { + async (req, res) => { const { _id, teamId } = req.user; - const demoMonitors = await this.db.addDemoMonitors(_id, teamId); - await Promise.all(demoMonitors.map((monitor) => this.jobQueue.addJob(monitor._id, monitor))); + const demoMonitors = await this.monitorService.addDemoMonitors({ userId: _id, teamId }); return res.success({ msg: this.stringService.monitorDemoAdded, - data: demoMonitors.length, + data: demoMonitors?.length ?? 0, }); }, SERVICE_NAME, "addDemoMonitors" ); - /** - * Sends a test email to verify email delivery functionality. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.body - The body of the request. - * @property {string} req.body.to - The email address to send the test email to. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status and the email delivery message ID. - * @throws {Error} If there is an error while sending the test email. - */ sendTestEmail = asyncHandler( - async (req, res, next) => { + async (req, res) => { const { to } = req.body; if (!to || typeof to !== "string") { throw new Error(this.stringService.errorForValidEmailAddress); } - const subject = this.stringService.testEmailSubject; - const context = { testName: "Monitoring System" }; - - const html = await this.emailService.buildEmail("testEmailTemplate", context); - const messageId = await this.emailService.sendEmail(to, subject, html); - - if (!messageId) { - throw createServerError("Failed to send test email."); - } - + const messageId = await this.monitorService.sendTestEmail({ to }); return res.success({ msg: this.stringService.sendTestEmail, data: { messageId }, @@ -585,18 +350,10 @@ class MonitorController { await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); let { limit, type, page, rowsPerPage, filter, field, order } = req.query; - const teamId = req.user.teamId; + const teamId = req?.user?.teamId; + + const monitors = await this.monitorService.getMonitorsByTeamId({ teamId, limit, type, page, rowsPerPage, filter, field, order }); - const monitors = await this.db.getMonitorsByTeamId({ - limit, - type, - page, - rowsPerPage, - filter, - field, - order, - teamId, - }); return res.success({ msg: this.stringService.monitorGetByTeamId, data: monitors, @@ -607,19 +364,19 @@ class MonitorController { ); getMonitorsAndSummaryByTeamId = asyncHandler( - async (req, res, next) => { + async (req, res) => { await getMonitorsByTeamIdParamValidation.validateAsync(req.params); await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); - const { explain } = req; - const { type } = req.query; - const { teamId } = req.user; + const explain = req?.query?.explain; + const type = req?.query?.type; + const teamId = req?.user?.teamId; + if (!teamId) { + throw new Error("Team ID is required"); + } + + const result = await this.monitorService.getMonitorsAndSummaryByTeamId({ teamId, type, explain }); - const result = await this.db.getMonitorsAndSummaryByTeamId({ - type, - explain, - teamId, - }); return res.success({ msg: "OK", // TODO data: result, @@ -630,15 +387,19 @@ class MonitorController { ); getMonitorsWithChecksByTeamId = asyncHandler( - async (req, res, next) => { + async (req, res) => { await getMonitorsByTeamIdParamValidation.validateAsync(req.params); await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); - const { explain } = req; + const explain = req?.query?.explain; let { limit, type, page, rowsPerPage, filter, field, order } = req.query; - const { teamId } = req.user; + const teamId = req?.user?.teamId; + if (!teamId) { + throw new Error("Team ID is required"); + } - const result = await this.db.getMonitorsWithChecksByTeamId({ + const monitors = await this.monitorService.getMonitorsWithChecksByTeamId({ + teamId, limit, type, page, @@ -646,51 +407,26 @@ class MonitorController { filter, field, order, - teamId, explain, }); + return res.success({ msg: "OK", - data: result, + data: monitors, }); }, SERVICE_NAME, "getMonitorsWithChecksByTeamId" ); - seedDb = asyncHandler( - async (req, res, next) => { - const { _id, teamId } = req.user; - await seedDb(_id, teamId); - res.success({ msg: "Database seeded" }); - }, - SERVICE_NAME, - "seedDb" - ); - exportMonitorsToCSV = asyncHandler( - async (req, res, next) => { - const { teamId } = req.user; - - const monitors = await this.db.getMonitorsByTeamId({ teamId }); - if (!monitors || monitors.length === 0) { - return res.success({ - msg: this.stringService.noMonitorsFound, - data: null, - }); + async (req, res) => { + const teamId = req?.user?.teamId; + if (!teamId) { + throw new Error("Team ID is required"); } - const csvData = monitors?.filteredMonitors?.map((monitor) => ({ - name: monitor.name, - description: monitor.description, - type: monitor.type, - url: monitor.url, - interval: monitor.interval, - port: monitor.port, - ignoreTlsErrors: monitor.ignoreTlsErrors, - isActive: monitor.isActive, - })); - const csv = pkg.unparse(csvData); + const csv = await this.monitorService.exportMonitorsToCSV({ teamId }); return res.file({ data: csv, diff --git a/server/db/models/Monitor.js b/server/db/models/Monitor.js index e8e19ac29..266cebeef 100755 --- a/server/db/models/Monitor.js +++ b/server/db/models/Monitor.js @@ -126,18 +126,22 @@ MonitorSchema.pre("findOneAndDelete", async function (next) { try { const doc = await this.model.findOne(this.getFilter()); - if (doc.type === "pagespeed") { + if (!doc) { + throw new Error("Monitor not found"); + } + + if (doc?.type === "pagespeed") { await PageSpeedCheck.deleteMany({ monitorId: doc._id }); - } else if (doc.type === "hardware") { + } else if (doc?.type === "hardware") { await HardwareCheck.deleteMany({ monitorId: doc._id }); } else { await Check.deleteMany({ monitorId: doc._id }); } // Deal with status pages - await StatusPage.updateMany({ monitors: doc._id }, { $pull: { monitors: doc._id } }); + await StatusPage.updateMany({ monitors: doc?._id }, { $pull: { monitors: doc?._id } }); - await MonitorStats.deleteMany({ monitorId: doc._id.toString() }); + await MonitorStats.deleteMany({ monitorId: doc?._id.toString() }); next(); } catch (error) { next(error); diff --git a/server/db/mongo/modules/monitorModule.js b/server/db/mongo/modules/monitorModule.js index 18cb3c63f..6e2e80f49 100755 --- a/server/db/mongo/modules/monitorModule.js +++ b/server/db/mongo/modules/monitorModule.js @@ -279,7 +279,6 @@ const calculateGroupStats = (group) => { * @throws {Error} */ const getUptimeDetailsById = async ({ monitorId, dateRange, normalize }) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); try { const dates = getDateRange(dateRange); const formatLookup = { @@ -595,16 +594,16 @@ const createBulkMonitors = async (req) => { * @returns {Promise} * @throws {Error} */ -const deleteMonitor = async ({ monitorId }) => { +const deleteMonitor = async ({ teamId, monitorId }) => { const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); try { - const monitor = await Monitor.findByIdAndDelete(monitorId); + const deletedMonitor = await Monitor.findOneAndDelete({ _id: monitorId, teamId }); - if (!monitor) { + if (!deletedMonitor) { throw new Error(stringService.getDbFindMonitorById(monitorId)); } - return monitor; + return deletedMonitor; } catch (error) { error.service = SERVICE_NAME; error.method = "deleteMonitor"; @@ -654,9 +653,9 @@ const deleteMonitorsByUserId = async (userId) => { * @returns {Promise} * @throws {Error} */ -const editMonitor = async (candidateId, candidateMonitor) => { +const editMonitor = async ({ monitorId, body }) => { try { - const editedMonitor = await Monitor.findByIdAndUpdate(candidateId, candidateMonitor, { + const editedMonitor = await Monitor.findByIdAndUpdate(monitorId, body, { new: true, }); return editedMonitor; diff --git a/server/db/mongo/modules/monitorModuleQueries.js b/server/db/mongo/modules/monitorModuleQueries.js index 422362f30..d96e128fa 100755 --- a/server/db/mongo/modules/monitorModuleQueries.js +++ b/server/db/mongo/modules/monitorModuleQueries.js @@ -138,6 +138,7 @@ const buildUptimeDetailsPipeline = (monitorId, dates, dateString) => { { $project: { _id: 1, + teamId: 1, name: 1, status: 1, interval: 1, diff --git a/server/index.js b/server/index.js index 9c7778a99..541f9ae14 100755 --- a/server/index.js +++ b/server/index.js @@ -2,6 +2,7 @@ import path from "path"; import fs from "fs"; import swaggerUi from "swagger-ui-express"; import jwt from "jsonwebtoken"; +import papaparse from "papaparse"; import express from "express"; import helmet from "helmet"; @@ -63,6 +64,7 @@ import CheckService from "./service/business/checkService.js"; import DiagnosticService from "./service/business/diagnosticService.js"; import InviteService from "./service/business/inviteService.js"; import MaintenanceWindowService from "./service/business/maintenanceWindowService.js"; +import MonitorService from "./service/business/monitorService.js"; //Network service and dependencies import NetworkService from "./service/infrastructure/networkService.js"; @@ -185,32 +187,6 @@ const startApp = async () => { const redisService = new RedisService({ Redis: IORedis, logger }); - // Business services - const userService = new UserService({ - db, - emailService, - settingsService, - logger, - stringService, - jwt, - }); - const checkService = new CheckService({ - db, - settingsService, - stringService, - }); - const diagnosticService = new DiagnosticService(); - const inviteService = new InviteService({ - db, - settingsService, - emailService, - stringService, - }); - const maintenanceWindowService = new MaintenanceWindowService({ - db, - settingsService, - stringService, - }); // const jobQueueHelper = new JobQueueHelper({ // redisService, // Queue, @@ -256,6 +232,41 @@ const startApp = async () => { helper: superSimpleQueueHelper, }); + // Business services + const userService = new UserService({ + db, + emailService, + settingsService, + logger, + stringService, + jwt, + }); + const checkService = new CheckService({ + db, + settingsService, + stringService, + }); + const diagnosticService = new DiagnosticService(); + const inviteService = new InviteService({ + db, + settingsService, + emailService, + stringService, + }); + const maintenanceWindowService = new MaintenanceWindowService({ + db, + settingsService, + stringService, + }); + const monitorService = new MonitorService({ + db, + settingsService, + jobQueue: superSimpleQueue, + stringService, + emailService, + papaparse, + logger, + }); // Register services // ServiceRegistry.register(JobQueue.SERVICE_NAME, jobQueue); // ServiceRegistry.register(JobQueue.SERVICE_NAME, pulseQueue); @@ -273,6 +284,7 @@ const startApp = async () => { ServiceRegistry.register(CheckService.SERVICE_NAME, checkService); ServiceRegistry.register(InviteService.SERVICE_NAME, inviteService); ServiceRegistry.register(MaintenanceWindowService.SERVICE_NAME, maintenanceWindowService); + ServiceRegistry.register(MonitorService.SERVICE_NAME, monitorService); await translationService.initialize(); @@ -296,13 +308,14 @@ const startApp = async () => { userService: ServiceRegistry.get(UserService.SERVICE_NAME), }); - const monitorController = new MonitorController( - ServiceRegistry.get(MongoDB.SERVICE_NAME), - ServiceRegistry.get(SettingsService.SERVICE_NAME), - ServiceRegistry.get(JobQueue.SERVICE_NAME), - ServiceRegistry.get(StringService.SERVICE_NAME), - ServiceRegistry.get(EmailService.SERVICE_NAME) - ); + const monitorController = new MonitorController({ + db: ServiceRegistry.get(MongoDB.SERVICE_NAME), + settingsService: ServiceRegistry.get(SettingsService.SERVICE_NAME), + jobQueue: ServiceRegistry.get(JobQueue.SERVICE_NAME), + stringService: ServiceRegistry.get(StringService.SERVICE_NAME), + emailService: ServiceRegistry.get(EmailService.SERVICE_NAME), + monitorService: ServiceRegistry.get(MonitorService.SERVICE_NAME), + }); const settingsController = new SettingsController({ db: ServiceRegistry.get(MongoDB.SERVICE_NAME), diff --git a/server/routes/monitorRoute.js b/server/routes/monitorRoute.js index 15553fc1c..0124c209d 100755 --- a/server/routes/monitorRoute.js +++ b/server/routes/monitorRoute.js @@ -2,9 +2,6 @@ import { Router } from "express"; import { isAllowed } from "../middleware/isAllowed.js"; import multer from "multer"; import { fetchMonitorCertificate } from "../controllers/controllerUtils.js"; -import Monitor from "../db/models/Monitor.js"; -import { verifyOwnership } from "../middleware/verifyOwnership.js"; -import { verifyTeamAccess } from "../middleware/verifyTeamAccess.js"; const upload = multer({ storage: multer.memoryStorage(), // Store file in memory as Buffer }); @@ -33,7 +30,6 @@ class MonitorRoutes { this.router.get("/stats/:monitorId", this.monitorController.getMonitorStatsById); // Util routes - this.router.get("/resolution/url", isAllowed(["admin", "superadmin"]), this.monitorController.checkEndpointResolution); this.router.get("/certificate/:monitorId", (req, res, next) => { this.monitorController.getMonitorCertificate(req, res, next, fetchMonitorCertificate); }); @@ -46,7 +42,6 @@ class MonitorRoutes { // Other static routes this.router.post("/demo", isAllowed(["admin", "superadmin"]), this.monitorController.addDemoMonitors); this.router.get("/export", isAllowed(["admin", "superadmin"]), this.monitorController.exportMonitorsToCSV); - this.router.post("/seed", isAllowed(["superadmin"]), this.monitorController.seedDb); this.router.post("/bulk", isAllowed(["admin", "superadmin"]), upload.single("csvFile"), this.monitorController.createBulkMonitors); this.router.post("/test-email", isAllowed(["admin", "superadmin"]), this.monitorController.sendTestEmail); diff --git a/server/service/business/monitorService.js b/server/service/business/monitorService.js new file mode 100644 index 000000000..d0e8f92ca --- /dev/null +++ b/server/service/business/monitorService.js @@ -0,0 +1,265 @@ +import { createMonitorsBodyValidation } from "../../validation/joi.js"; +import { createServerError } from "../../utils/errorUtils.js"; +import logger from "../../utils/logger.js"; + +const SERVICE_NAME = "MonitorService"; +class MonitorService { + SERVICE_NAME = SERVICE_NAME; + + constructor({ db, settingsService, jobQueue, stringService, emailService, papaparse }) { + this.db = db; + this.settingsService = settingsService; + this.jobQueue = jobQueue; + this.stringService = stringService; + this.emailService = emailService; + this.papaparse = papaparse; + this.logger = logger; + } + + verifyTeamAccess = async ({ teamId, monitorId }) => { + const monitor = await this.db.getMonitorById(monitorId); + if (!monitor?.teamId?.equals(teamId)) { + const error = new Error("Unauthorized"); + error.status = 403; + throw error; + } + }; + + getAllMonitors = async () => { + const monitors = await this.db.getAllMonitors(); + return monitors; + }; + + getUptimeDetailsById = async ({ teamId, monitorId, dateRange, normalize }) => { + await this.verifyTeamAccess({ teamId, monitorId }); + const data = await this.db.getUptimeDetailsById({ + monitorId, + dateRange, + normalize, + }); + + return data; + }; + + getMonitorStatsById = async ({ teamId, monitorId, limit, sortOrder, dateRange, numToDisplay, normalize }) => { + await this.verifyTeamAccess({ teamId, monitorId }); + const monitorStats = await this.db.getMonitorStatsById({ + monitorId, + limit, + sortOrder, + dateRange, + numToDisplay, + normalize, + }); + + return monitorStats; + }; + + getHardwareDetailsById = async ({ teamId, monitorId, dateRange }) => { + await this.verifyTeamAccess({ teamId, monitorId }); + const monitor = await this.db.getHardwareDetailsById({ monitorId, dateRange }); + + return monitor; + }; + + getMonitorById = async ({ teamId, monitorId }) => { + await this.verifyTeamAccess({ teamId, monitorId }); + const monitor = await this.db.getMonitorById(monitorId); + + return monitor; + }; + + createMonitor = async ({ teamId, userId, body }) => { + const monitor = await this.db.createMonitor({ + body, + teamId, + userId, + }); + + this.jobQueue.addJob(monitor._id, monitor); + }; + + createBulkMonitors = async ({ fileData, userId, teamId }) => { + const { parse } = this.papaparse; + + return new Promise((resolve, reject) => { + parse(fileData, { + header: true, + skipEmptyLines: true, + transform: (value, header) => { + if (value === "") return undefined; // Empty fields become undefined + + // Handle 'port' and 'interval' fields, check if they're valid numbers + if (["port", "interval"].includes(header)) { + const num = parseInt(value, 10); + if (isNaN(num)) { + throw new Error(`${header} should be a valid number, got: ${value}`); + } + return num; + } + + return value; + }, + complete: async ({ data, errors }) => { + try { + if (errors.length > 0) { + throw createServerError("Error parsing CSV"); + } + + if (!data || data.length === 0) { + throw createServerError("CSV file contains no data rows"); + } + + const enrichedData = data.map((monitor) => ({ + userId, + teamId, + ...monitor, + description: monitor.description || monitor.name || monitor.url, + name: monitor.name || monitor.url, + type: monitor.type || "http", + })); + + await createMonitorsBodyValidation.validateAsync(enrichedData); + + const monitors = await this.db.createBulkMonitors(enrichedData); + + await Promise.all( + monitors.map(async (monitor) => { + this.jobQueue.addJob(monitor._id, monitor); + }) + ); + + resolve(monitors); + } catch (error) { + reject(error); + } + }, + }); + }); + }; + + deleteMonitor = async ({ teamId, monitorId }) => { + await this.verifyTeamAccess({ teamId, monitorId }); + const monitor = await this.db.deleteMonitor({ teamId, monitorId }); + await this.jobQueue.deleteJob(monitor); + await this.db.deleteStatusPagesByMonitorId(monitor._id); + return monitor; + }; + + deleteAllMonitors = async ({ teamId }) => { + const { monitors, deletedCount } = await this.db.deleteAllMonitors(teamId); + await Promise.all( + monitors.map(async (monitor) => { + try { + await this.jobQueue.deleteJob(monitor); + await this.db.deleteChecks(monitor._id); + await this.db.deletePageSpeedChecksByMonitorId(monitor._id); + await this.db.deleteNotificationsByMonitorId(monitor._id); + } catch (error) { + logger.warn({ + message: `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`, + service: SERVICE_NAME, + method: "deleteAllMonitors", + stack: error.stack, + }); + } + }) + ); + return deletedCount; + }; + + editMonitor = async ({ teamId, monitorId, body }) => { + await this.verifyTeamAccess({ teamId, monitorId }); + const editedMonitor = await this.db.editMonitor({ monitorId, body }); + await this.jobQueue.updateJob(editedMonitor); + }; + + pauseMonitor = async ({ teamId, monitorId }) => { + await this.verifyTeamAccess({ teamId, monitorId }); + const monitor = await this.db.pauseMonitor({ monitorId }); + monitor.isActive === true ? await this.jobQueue.resumeJob(monitor._id, monitor) : await this.jobQueue.pauseJob(monitor); + return monitor; + }; + + addDemoMonitors = async ({ userId, teamId }) => { + const demoMonitors = await this.db.addDemoMonitors(userId, teamId); + await Promise.all(demoMonitors.map((monitor) => this.jobQueue.addJob(monitor._id, monitor))); + return demoMonitors; + }; + + sendTestEmail = async ({ to }) => { + const subject = this.stringService.testEmailSubject; + const context = { testName: "Monitoring System" }; + + const html = await this.emailService.buildEmail("testEmailTemplate", context); + const messageId = await this.emailService.sendEmail(to, subject, html); + + if (!messageId) { + throw createServerError("Failed to send test email."); + } + + return messageId; + }; + + getMonitorsByTeamId = async ({ teamId, limit, type, page, rowsPerPage, filter, field, order }) => { + const monitors = await this.db.getMonitorsByTeamId({ + limit, + type, + page, + rowsPerPage, + filter, + field, + order, + teamId, + }); + return monitors; + }; + + getMonitorsAndSummaryByTeamId = async ({ teamId, type, explain }) => { + const result = await this.db.getMonitorsAndSummaryByTeamId({ + type, + explain, + teamId, + }); + return result; + }; + + getMonitorsWithChecksByTeamId = async ({ teamId, limit, type, page, rowsPerPage, filter, field, order, explain }) => { + const result = await this.db.getMonitorsWithChecksByTeamId({ + limit, + type, + page, + rowsPerPage, + filter, + field, + order, + teamId, + explain, + }); + return result; + }; + + exportMonitorsToCSV = async ({ teamId }) => { + const monitors = await this.monitorService.getMonitorsByTeamId({ teamId }); + + if (!monitors || monitors.length === 0) { + throw new Error("No monitors to export"); + } + + const csvData = monitors?.filteredMonitors?.map((monitor) => ({ + name: monitor.name, + description: monitor.description, + type: monitor.type, + url: monitor.url, + interval: monitor.interval, + port: monitor.port, + ignoreTlsErrors: monitor.ignoreTlsErrors, + isActive: monitor.isActive, + })); + + const csv = this.papaparse.unparse(csvData); + return csv; + }; +} + +export default MonitorService; diff --git a/server/utils/errorUtils.js b/server/utils/errorUtils.js index f913746b3..7d9a2959d 100644 --- a/server/utils/errorUtils.js +++ b/server/utils/errorUtils.js @@ -65,12 +65,16 @@ export const asyncHandler = (fn, serviceName, methodName) => { return next(appError); } + if (error.status) { + const appError = createError(error.message, error.status, serviceName, methodName, { originalError: error.message, stack: error.stack }); + return next(appError); + } + // For unknown errors, create a server error const appError = createServerError(error.message || "An unexpected error occurred", { originalError: error.message, stack: error.stack }); appError.service = serviceName; appError.method = methodName; appError.stack = error.stack; // Preserve original stack - return next(appError); } }; From 19d3ae119c7391bdd7928a2060713f8cc18a44b8 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 24 Jul 2025 13:51:00 -0700 Subject: [PATCH 108/259] added safe var access --- client/src/Hooks/useMonitorUtils.js | 14 +++--- client/src/Pages/Uptime/Configure/index.jsx | 48 ++++++++++----------- client/src/Utils/NetworkService.js | 24 ----------- 3 files changed, 31 insertions(+), 55 deletions(-) diff --git a/client/src/Hooks/useMonitorUtils.js b/client/src/Hooks/useMonitorUtils.js index 417c6eadd..b57c11a12 100644 --- a/client/src/Hooks/useMonitorUtils.js +++ b/client/src/Hooks/useMonitorUtils.js @@ -6,18 +6,18 @@ const useMonitorUtils = () => { let uptimePercentage = ""; let percentageColor = ""; - if (monitor.uptimePercentage !== undefined) { + if (monitor?.uptimePercentage !== undefined) { uptimePercentage = - monitor.uptimePercentage === 0 + monitor?.uptimePercentage === 0 ? "0" - : (monitor.uptimePercentage * 100).toFixed(2); + : (monitor?.uptimePercentage * 100).toFixed(2); percentageColor = - monitor.uptimePercentage < 0.25 + monitor?.uptimePercentage < 0.25 ? theme.palette.error.main - : monitor.uptimePercentage < 0.5 + : monitor?.uptimePercentage < 0.5 ? theme.palette.warning.main - : monitor.uptimePercentage < 0.75 + : monitor?.uptimePercentage < 0.75 ? theme.palette.success.main : theme.palette.success.main; } @@ -32,7 +32,7 @@ const useMonitorUtils = () => { const determineState = useCallback((monitor) => { if (typeof monitor === "undefined") return "pending"; - if (monitor.isActive === false) return "paused"; + if (monitor?.isActive === false) return "paused"; if (monitor?.status === undefined) return "pending"; return monitor?.status == true ? "up" : "down"; }, []); diff --git a/client/src/Pages/Uptime/Configure/index.jsx b/client/src/Pages/Uptime/Configure/index.jsx index 2dacc26ab..061db6ac0 100644 --- a/client/src/Pages/Uptime/Configure/index.jsx +++ b/client/src/Pages/Uptime/Configure/index.jsx @@ -163,18 +163,18 @@ const Configure = () => { e.preventDefault(); const toSubmit = { - _id: form._id, - url: form.url, - name: form.name, - type: form.type, - matchMethod: form.matchMethod, - expectedValue: form.expectedValue, - jsonPath: form.jsonPath, - interval: form.interval, - teamId: form.teamId, - userId: form.userId, - port: form.port, - ignoreTlsErrors: form.ignoreTlsErrors, + _id: form?._id, + url: form?.url, + name: form?.name, + type: form?.type, + matchMethod: form?.matchMethod, + expectedValue: form?.expectedValue, + jsonPath: form?.jsonPath, + interval: form?.interval, + teamId: form?.teamId, + userId: form?.userId, + port: form?.port, + ignoreTlsErrors: form?.ignoreTlsErrors, }; if (!useAdvancedMatching) { @@ -197,7 +197,7 @@ const Configure = () => { return; } - toSubmit.notifications = form.notifications; + toSubmit.notifications = form?.notifications; await updateMonitor({ monitor: toSubmit, redirect: "/uptime" }); }; @@ -235,7 +235,7 @@ const Configure = () => { component="h1" variant="monitorName" > - {form.name} + {form?.name} { component="h2" variant="monitorUrl" > - {form.url?.replace(/^https?:\/\//, "") || "..."} + {form?.url?.replace(/^https?:\/\//, "") || "..."} { type="number" label={t("portToMonitor")} placeholder="5173" - value={form.port || ""} + value={form?.port || ""} onChange={onChange} error={errors["port"] ? true : false} helperText={errors["port"]} - hidden={form.type !== "port"} + hidden={form?.type !== "port"} /> { @@ -398,7 +398,7 @@ const Configure = () => { control={ @@ -430,12 +430,12 @@ const Configure = () => { isChecked={useAdvancedMatching} onChange={onChange} /> - {form.type === "http" && useAdvancedMatching && ( + {form?.type === "http" && useAdvancedMatching && ( <> )} [ { value: "ping", label: "Ping" }, { value: "docker", label: "Docker" }, { value: "port", label: "Port" }, + { value: "game", label: "Game" }, ]; // These functions were moved inline to ensure translations are applied correctly diff --git a/client/src/Pages/Uptime/Monitors/index.jsx b/client/src/Pages/Uptime/Monitors/index.jsx index 4ebac94d3..607092785 100644 --- a/client/src/Pages/Uptime/Monitors/index.jsx +++ b/client/src/Pages/Uptime/Monitors/index.jsx @@ -34,7 +34,7 @@ import { } from "../../../Hooks/monitorHooks"; import { useTranslation } from "react-i18next"; -const TYPES = ["http", "ping", "docker", "port"]; +const TYPES = ["http", "ping", "docker", "port", "game"]; const CreateMonitorButton = ({ shouldRender }) => { // Utils const navigate = useNavigate(); diff --git a/client/src/Utils/games.js b/client/src/Utils/games.js new file mode 100644 index 000000000..465dca2ce --- /dev/null +++ b/client/src/Utils/games.js @@ -0,0 +1,3450 @@ +export const GAMES = { + abioticfactor: { + name: 'Abiotic Factor', + release_year: 2024, + options: { + port: 27015, + protocol: 'valve' + } + }, + actionsource: { + name: 'Action: Source', + release_year: 2019, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'as' + } + }, + ahl: { + name: 'Action Half-Life', + release_year: 2009, + options: { + port: 27015, + protocol: 'valve' + } + }, + aoc: { + name: 'Age of Chivalry', + release_year: 2007, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'ageofchivalry' + } + }, + aoe2: { + name: 'Age of Empires 2', + release_year: 2009, + options: { + port_query: 27224, + protocol: 'ase' + } + }, + alienarena: { + name: 'Alien Arena', + release_year: 2004, + options: { + port_query: 27910, + protocol: 'quake2' + } + }, + alienswarm: { + name: 'Alien Swarm', + release_year: 2004, + options: { + port: 27015, + protocol: 'valve' + } + }, + ase: { + name: 'Ark: Survival Evolved', + release_year: 2017, + options: { + port: 7777, + port_query: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'arkse' + } + }, + asa: { + name: 'Ark: Survival Ascended', + release_year: 2023, + options: { + port: 7777, + protocol: 'asa' + } + }, + assettocorsa: { + name: 'Assetto Corsa', + release_year: 2014, + options: { + port: 9610, + protocol: 'assettocorsa' + } + }, + atlas: { + name: 'Atlas', + release_year: 2018, + options: { + port: 5761, + port_query_offset: 51800, + protocol: 'valve' + } + }, + avorion: { + name: 'Avorion', + release_year: 2020, + options: { + port: 27000, + port_query_offset: 20, + protocol: 'valve' + } + }, + avp2: { + name: 'Aliens versus Predator 2', + release_year: 2001, + options: { + port: 27888, + protocol: 'gamespy1' + } + }, + avp2010: { + name: 'Aliens vs. Predator 2010', + release_year: 2010, + options: { + port: 27015, + protocol: 'valve' + } + }, + americasarmy: { + name: "America's Army", + release_year: 2002, + options: { + port: 1716, + port_query_offset: 1, + protocol: 'gamespy2' + } + }, + americasarmy2: { + name: "America's Army 2", + release_year: 2003, + options: { + port: 1716, + port_query_offset: 1, + protocol: 'gamespy2' + } + }, + americasarmy3: { + name: "America's Army 3", + release_year: 2009, + options: { + port: 8777, + port_query: 27020, + protocol: 'valve' + } + }, + aapg: { + name: "America's Army: Proving Grounds", + release_year: 2015, + options: { + port: 8777, + port_query: 27020, + protocol: 'valve' + }, + extra: { + old_id: 'americasarmypg' + } + }, + asr08: { + name: "Arca Sim Racing '08", + release_year: 2008, + options: { + port: 34397, + port_query_offset: -100, + protocol: 'rfactor' + }, + extra: { + old_id: 'arcasimracing' + } + }, + aaa: { + name: 'ARMA: Armed Assault', + release_year: 2006, + options: { + port: 2302, + protocol: 'gamespy2' + }, + extra: { + old_id: 'arma' + } + }, + arma2: { + name: 'ARMA 2', + release_year: 2009, + options: { + port: 2302, + port_query_offset: 1, + protocol: 'valve' + } + }, + a2oa: { + name: 'ARMA 2: Operation Arrowhead', + release_year: 2010, + options: { + port: 2302, + port_query_offset: 1, + protocol: 'valve' + }, + extra: { + old_id: 'arma2oa' + } + }, + acwa: { + name: 'ARMA: Cold War Assault', + release_year: 2011, + options: { + port: 2302, + port_query_offset: 1, + protocol: 'gamespy1' + }, + extra: { + old_id: 'armacwa' + } + }, + armaresistance: { + name: 'ARMA: Resistance', + release_year: 2011, + options: { + port: 2302, + port_query_offset: 1, + protocol: 'gamespy1' + }, + extra: { + old_id: 'armar' + } + }, + arma3: { + name: 'ARMA 3', + release_year: 2013, + options: { + port: 2302, + port_query_offset: 1, + protocol: 'valve' + } + }, + armareforger: { + name: 'ARMA: Reforger', + release_year: 2022, + options: { + port: 2001, + port_query: 17777, + protocol: 'valve' + }, + extra: { + old_id: 'armare', + doc_notes: 'armareforger' + } + }, + armagetronadvanced: { + name: 'Armagetron Advanced', + release_year: 2001, + options: { + port: 4534, + protocol: 'armagetron' + }, + extra: { + old_id: 'armagetron' + } + }, + baldursgate: { + name: "Baldur's Gate", + release_year: 1998, + options: { + port: 6073, + port_query: 1470, + protocol: 'gamespy1' + } + }, + ballisticoverkill: { + name: 'Ballistic Overkill', + release_year: 2017, + options: { + port: 27015, + port_query_offset: 1, + protocol: 'valve' + } + }, + barotrauma: { + name: 'Barotrauma', + release_year: 2019, + options: { + port: 27015, + port_query_offset: 1, + protocol: 'valve' + } + }, + battalion1944: { + name: 'Battalion 1944', + release_year: 2018, + options: { + port: 7777, + port_query_offset: 3, + protocol: 'valve' + }, + extra: { + old_id: 'bat1944' + } + }, + beammp: { + name: 'BeamMP (2021)', + options: { + port: 30814, + protocol: 'beammp' + } + }, + battlefield1942: { + name: 'Battlefield 1942', + release_year: 2002, + options: { + port: 14567, + port_query: 23000, + protocol: 'gamespy1' + }, + extra: { + old_id: 'bf1942' + } + }, + battlefieldvietnam: { + name: 'Battlefield Vietnam', + release_year: 2004, + options: { + port: 15567, + port_query: 23000, + protocol: 'gamespy2' + }, + extra: { + old_id: 'bfv' + } + }, + battlefield2: { + name: 'Battlefield 2', + release_year: 2005, + options: { + port: 16567, + port_query: 29900, + protocol: 'gamespy3' + }, + extra: { + old_id: 'bf2' + } + }, + battlefield2142: { + name: 'Battlefield 2142', + release_year: 2006, + options: { + port: 16567, + port_query: 29900, + protocol: 'gamespy3' + }, + extra: { + old_id: 'bf2142' + } + }, + bbc2: { + name: 'Battlefield: Bad Company 2', + release_year: 2010, + options: { + port: 19567, + port_query: 48888, + protocol: 'battlefield' + }, + extra: { + old_id: 'bfbc2' + } + }, + battlefield3: { + name: 'Battlefield 3', + release_year: 2011, + options: { + port: 25200, + port_query_offset: 22000, + protocol: 'battlefield' + }, + extra: { + old_id: 'bf3' + } + }, + battlefield4: { + name: 'Battlefield 4', + release_year: 2013, + options: { + port: 25200, + port_query_offset: 22000, + protocol: 'battlefield' + }, + extra: { + old_id: 'bf4' + } + }, + battlefieldhardline: { + name: 'Battlefield Hardline', + release_year: 2015, + options: { + port: 25200, + port_query_offset: 22000, + protocol: 'battlefield' + }, + extra: { + old_id: 'bfh' + } + }, + blackmesa: { + name: 'Black Mesa', + release_year: 2020, + options: { + port: 27015, + protocol: 'valve' + } + }, + brainbread2: { + name: 'BrainBread 2', + release_year: 2022, + options: { + port: 27015, + protocol: 'valve' + } + }, + brainbread: { + name: 'BrainBread', + release_year: 2007, + options: { + port: 27015, + protocol: 'valve' + } + }, + breach: { + name: 'Breach', + release_year: 2011, + options: { + port: 27016, + protocol: 'valve' + } + }, + breed: { + name: 'Breed', + release_year: 2004, + options: { + port: 7649, + protocol: 'gamespy2' + } + }, + brink: { + name: 'Brink', + release_year: 2011, + options: { + port_query_offset: 1, + protocol: 'valve' + } + }, + brokeprotocol: { + name: 'BROKE PROTOCOL', + release_year: 2024, + options: { + protocol: 'brokeprotocol' + }, + extra: { + doc_notes: 'brokeprotocol' + } + }, + basedefense: { + name: 'Base Defense', + release_year: 2017, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'bd' + } + }, + bladesymphony: { + name: 'Blade Symphony', + release_year: 2014, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'bs' + } + }, + bas: { + name: 'Build and Shoot', + release_year: 2012, + options: { + port: 32887, + port_query_offset: -1, + protocol: 'buildandshoot' + }, + extra: { + old_id: 'buildandshoot', + doc_notes: 'aosc-buildandshoot' + } + }, + aosc: { + name: 'Ace of Spades Classic', + release_year: 2012, + options: { + port: 32887, + port_query_offset: -1, + protocol: 'buildandshoot' + }, + extra: { + doc_notes: 'aosc-buildandshoot' + } + }, + cod: { + name: 'Call of Duty', + release_year: 2003, + options: { + port: 28960, + protocol: 'quake3' + } + }, + coduo: { + name: 'Call of Duty: United Offensive', + release_year: 2004, + options: { + port: 28960, + protocol: 'quake3' + } + }, + cod2: { + name: 'Call of Duty 2', + release_year: 2005, + options: { + port: 28960, + protocol: 'quake3' + } + }, + cod3: { + name: 'Call of Duty 3', + release_year: 2006, + options: { + port: 28960, + protocol: 'quake3' + } + }, + cod4mw: { + name: 'Call of Duty 4: Modern Warfare', + release_year: 2007, + options: { + port: 28960, + protocol: 'quake3' + }, + extra: { + old_id: 'cod4' + } + }, + codbo3: { + name: 'Call of Duty: Black Ops 3', + release_year: 2015, + options: { + port: 27017, + protocol: 'valve' + } + }, + codwaw: { + name: 'Call of Duty: World at War', + release_year: 2008, + options: { + port: 28960, + protocol: 'quake3' + } + }, + codmw2: { + name: 'Call of Duty: Modern Warfare 2', + release_year: 2009, + options: { + port: 28960, + protocol: 'quake3' + } + }, + codmw3: { + name: 'Call of Duty: Modern Warfare 3', + release_year: 2011, + options: { + port_query_offset: 2, + protocol: 'valve' + } + }, + coj: { + name: 'Call of Juarez', + release_year: 2006, + options: { + port_query: 26000, + protocol: 'ase' + }, + extra: { + old_id: 'callofjuarez' + } + }, + chaser: { + name: 'Chaser', + release_year: 2003, + options: { + port: 3000, + port_query_offset: 123, + protocol: 'ase' + } + }, + cmw: { + name: 'Chivalry: Medieval Warfare', + release_year: 2012, + options: { + port: 7777, + port_query_offset: 2, + protocol: 'valve' + }, + extra: { + old_id: 'chivalry' + } + }, + chrome: { + name: 'Chrome', + release_year: 2003, + options: { + port: 27015, + port_query_offset: 123, + protocol: 'ase' + } + }, + codenamecure: { + name: 'Codename CURE', + release_year: 2017, + options: { + port: 27015, + protocol: 'valve' + } + }, + codenameeagle: { + name: 'Codename Eagle', + release_year: 2000, + options: { + port_query: 4711, + protocol: 'gamespy1' + } + }, + colonysurvival: { + name: 'Colony Survival', + release_year: 2017, + options: { + port: 27004, + protocol: 'valve' + } + }, + c3db: { + name: 'Commandos 3: Destination Berlin', + release_year: 2003, + options: { + port_query: 6500, + protocol: 'gamespy1' + }, + extra: { + old_id: 'commandos3' + } + }, + cacr: { + name: 'Command and Conquer: Renegade', + release_year: 2002, + options: { + port: 4848, + port_query: 25300, + protocol: 'gamespy1' + }, + extra: { + old_id: 'cacrenegade' + } + }, + conanexiles: { + name: 'Conan Exiles', + release_year: 2018, + options: { + port: 7777, + port_query: 27015, + protocol: 'valve' + }, + extra: { + doc_notes: 'conanexiles' + } + }, + contagion: { + name: 'Contagion', + release_year: 2011, + options: { + port: 27015, + protocol: 'valve' + } + }, + contractjack: { + name: 'Contract J.A.C.K.', + release_year: 2003, + options: { + port_query: 27888, + protocol: 'gamespy1' + }, + extra: { + old_id: 'contactjack' + } + }, + corekeeper: { + name: 'Core Keeper', + release_year: 2022, + options: { + port: 1234, + port_query_offset: 1, + protocol: 'valve' + } + }, + counterstrike15: { + name: 'Counter-Strike 1.5', + release_year: 2002, + options: { + port: 27015, + protocol: 'goldsrc' + }, + extra: { + old_id: 'cs15' + } + }, + counterstrike16: { + name: 'Counter-Strike 1.6', + release_year: 2003, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'cs16' + } + }, + c2d: { + name: 'CS2D', + release_year: 2004, + options: { + port: 36963, + protocol: 'cs2d' + }, + extra: { + old_id: 'cs2d' + } + }, + cscz: { + name: 'Counter-Strike: Condition Zero', + release_year: 2004, + options: { + port: 27015, + protocol: 'valve' + } + }, + css: { + name: 'Counter-Strike: Source', + release_year: 2004, + options: { + port: 27015, + protocol: 'valve' + } + }, + csgo: { + name: 'Counter-Strike: Global Offensive', + release_year: 2012, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + doc_notes: 'csgo' + } + }, + counterstrike2: { + name: 'Counter-Strike 2', + release_year: 2023, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'cs2', + doc_notes: 'cs2' + } + }, + creativerse: { + name: 'Creativerse', + release_year: 2017, + options: { + port: 26900, + port_query_offset: 1, + protocol: 'valve' + } + }, + crce: { + name: 'Cross Racing Championship Extreme', + release_year: 2005, + options: { + port: 12321, + port_query_offset: 123, + protocol: 'ase' + }, + extra: { + old_id: 'crossracing' + } + }, + crysis: { + name: 'Crysis', + release_year: 2007, + options: { + port: 64087, + protocol: 'gamespy3' + } + }, + crysiswars: { + name: 'Crysis Wars', + release_year: 2008, + options: { + port: 64100, + protocol: 'gamespy3' + } + }, + crysis2: { + name: 'Crysis 2', + release_year: 2011, + options: { + port: 64000, + protocol: 'gamespy3' + } + }, + dab: { + name: 'Double Action: Boogaloo', + release_year: 2014, + options: { + port: 27015, + protocol: 'valve' + } + }, + daikatana: { + name: 'Daikatana', + release_year: 2000, + options: { + port: 27982, + port_query_offset: 10, + protocol: 'quake2' + } + }, + dmomam: { + name: 'Dark Messiah of Might and Magic', + release_year: 2006, + options: { + port: 27015, + protocol: 'valve' + } + }, + dhe4445: { + name: "Darkest Hour: Europe '44-'45", + release_year: 2008, + options: { + port: 7757, + port_query_offset: 1, + protocol: 'unreal2' + }, + extra: { + old_id: 'darkesthour' + } + }, + dayofdragons: { + name: 'Day of Dragons', + release_year: 2019, + options: { + port: 7777, + port_query: 27015, + protocol: 'valve' + } + }, + dow: { + name: 'Days of War', + release_year: 2017, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'daysofwar' + } + }, + dayz: { + name: 'DayZ', + release_year: 2018, + options: { + port: 2302, + port_query_offset: 24714, + protocol: 'dayz' + }, + extra: { + doc_notes: 'dayz' + } + }, + dayzmod: { + name: 'DayZ Mod', + release_year: 2013, + options: { + port: 2302, + port_query_offset: 1, + protocol: 'valve' + } + }, + ddpt: { + name: 'Deadly Dozen: Pacific Theater', + release_year: 2002, + options: { + port_query: 25300, + protocol: 'gamespy1' + }, + extra: { + old_id: 'deadlydozenpt' + } + }, + deerhunter2005: { + name: 'Deer Hunter 2005', + release_year: 2004, + options: { + port: 23459, + port_query: 34567, + protocol: 'gamespy2' + }, + extra: { + old_id: 'dh2005' + } + }, + descent3: { + name: 'Descent 3', + release_year: 1999, + options: { + port: 2092, + port_query: 20142, + protocol: 'gamespy1' + } + }, + deusex: { + name: 'Deus Ex', + release_year: 2000, + options: { + port: 7791, + port_query_offset: 1, + protocol: 'gamespy1' + } + }, + devastation: { + name: 'Devastation', + release_year: 2003, + options: { + port: 7777, + port_query_offset: 1, + protocol: 'unreal2' + } + }, + ddd: { + name: 'Dino D-Day', + release_year: 2011, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'dinodday' + } + }, + dtr2: { + name: 'Dirt Track Racing 2', + release_year: 2002, + options: { + port: 32240, + port_query_offset: -100, + protocol: 'gamespy1' + }, + extra: { + old_id: 'dirttrackracing2' + } + }, + discord: { + name: 'Discord', + release_year: 2015, + options: { + protocol: 'discord' + }, + extra: { + doc_notes: 'discord' + } + }, + deathmatchclassic: { + name: 'Deathmatch Classic', + release_year: 2001, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'dmc' + } + }, + dal: { + name: 'Dark and Light', + release_year: 2017, + options: { + port: 7777, + port_query: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'dnl' + } + }, + dnf2001: { + name: 'Duke Nukem Forever 2001', + release_year: 2022, + options: { + port: 7777, + port_query_offset: 1, + protocol: 'gamespy1' + } + }, + dod: { + name: 'Day of Defeat', + release_year: 2003, + options: { + port: 27015, + protocol: 'valve' + } + }, + dods: { + name: 'Day of Defeat: Source', + release_year: 2005, + options: { + port: 27015, + protocol: 'valve' + } + }, + doi: { + name: 'Day of Infamy', + release_year: 2017, + options: { + port: 27015, + protocol: 'valve' + } + }, + doom3: { + name: 'Doom 3', + release_year: 2004, + options: { + port: 27666, + protocol: 'doom3' + } + }, + dota2: { + name: 'Dota 2', + release_year: 2013, + options: { + port: 27015, + protocol: 'valve' + } + }, + dootf: { + name: 'Drakan: Order of the Flame', + release_year: 1999, + options: { + port: 27045, + port_query_offset: 1, + protocol: 'gamespy1' + }, + extra: { + old_id: 'drakan' + } + }, + dst: { + name: "Don't Starve Together", + release_year: 2016, + options: { + port: 10999, + port_query: 27016, + protocol: 'valve' + } + }, + dystopia: { + name: 'Dystopia', + release_year: 2005, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'dys' + } + }, + eco: { + name: 'Eco', + release_year: 2018, + options: { + port: 3000, + port_query_offset: 1, + protocol: 'eco' + } + }, + eldewrito: { + name: 'Halo Online - ElDewrito', + options: { + port: 11775, + protocol: 'eldewrito' + } + }, + empiresmod: { + name: 'Empires Mod', + release_year: 2008, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'em' + } + }, + egs: { + name: 'Empyrion - Galactic Survival', + release_year: 2015, + options: { + port: 30000, + port_query_offset: 1, + protocol: 'valve' + }, + extra: { + old_id: 'empyrion' + } + }, + enshrouded: { + name: 'enshrouded', + release_year: 2024, + options: { + port: 15636, + port_query: 15637, + protocol: 'valve' + } + }, + etqw: { + name: 'Enemy Territory: Quake Wars', + release_year: 2007, + options: { + port: 3074, + port_query: 27733, + protocol: 'doom3' + } + }, + ets2: { + name: 'Euro Truck Simulator 2', + release_year: 2012, + options: { + port: 27015, + port_query_offset: 1, + protocol: 'valve' + } + }, + exfil: { + name: 'Exfil', + release_year: 2024, + options: { + port: 7777, + port_query: 27015, + protocol: 'valve' + } + }, + fear: { + name: 'F.E.A.R.', + release_year: 2005, + options: { + port_query: 27888, + protocol: 'gamespy2' + } + }, + formulaone2002: { + name: 'Formula One 2002', + release_year: 2002, + options: { + port_query: 3297, + protocol: 'gamespy1' + }, + extra: { + old_id: 'f12002' + } + }, + f1c9902: { + name: "F1 Challenge '99-'02", + release_year: 2002, + options: { + port_query: 34397, + protocol: 'gamespy1' + } + }, + factorio: { + name: 'Factorio', + release_year: 2016, + options: { + port_query: 34197, + protocol: 'factorio' + } + }, + farcry: { + name: 'Far Cry', + release_year: 2004, + options: { + port: 49001, + port_query_offset: 123, + protocol: 'ase' + } + }, + farcry2: { + name: 'Far Cry 2', + release_year: 2008, + options: { + port_query: 14001, + protocol: 'ase' + } + }, + farmingsimulator19: { + name: 'Farming Simulator 19', + release_year: 2018, + options: { + port: 8080, + protocol: 'farmingsimulator' + } + }, + farmingsimulator22: { + name: 'Farming Simulator 22', + release_year: 2021, + options: { + port: 8080, + protocol: 'farmingsimulator' + } + }, + farmingsimulator25: { + name: 'Farming Simulator 25', + release_year: 2024, + options: { + port: 8080, + protocol: 'farmingsimulator' + } + }, + fof: { + name: 'Fistful of Frags', + release_year: 2014, + options: { + port: 27015, + protocol: 'valve' + } + }, + foundry: { + name: 'FOUNDRY', + release_year: 2024, + options: { + port: 27015, + protocol: 'valve' + } + }, + fortressforever: { + name: 'Fortress Forever', + release_year: 2007, + options: { + port: 27015, + protocol: 'valve' + } + }, + ofcwc: { + name: 'Operation Flashpoint: Cold War Crisis', + release_year: 2001, + options: { + port: 2302, + port_query_offset: 1, + protocol: 'gamespy1' + } + }, + ofr: { + name: 'Operation Flashpoint: Resistance', + release_year: 2002, + options: { + port: 2302, + port_query_offset: 1, + protocol: 'gamespy1' + }, + extra: { + old_id: 'flashpointresistance' + } + }, + ffow: { + name: 'Frontlines: Fuel of War', + release_year: 2008, + options: { + port: 5476, + port_query_offset: 2, + protocol: 'ffow' + } + }, + gta5f: { + name: 'Grand Theft Auto V - FiveM', + release_year: 2013, + options: { + port: 30120, + protocol: 'fivem' + }, + extra: { + old_id: 'fivem', + doc_notes: 'gta5f' + } + }, + gta5r: { + name: 'Grand Theft Auto V - RageMP', + release_year: 2016, + options: { + port: 22005, + protocol: 'ragemp' + }, + extra: { + doc_notes: 'gta5r' + } + }, + gta5am: { + name: 'Grand Theft Auto V - alt:V Multiplayer', + release_year: 2015, + options: { + port: 7788, + protocol: 'altvmp' + }, + extra: { + doc_notes: 'gta5am', + old_id: 'gta5a' + } + }, + garrysmod: { + name: "Garry's Mod", + release_year: 2004, + options: { + port: 27015, + protocol: 'valve' + } + }, + tcgraw: { + name: "Tom Clancy's Ghost Recon Advanced Warfighter", + release_year: 2006, + options: { + port_query: 15250, + protocol: 'gamespy2' + }, + extra: { + old_id: 'graw' + } + }, + tcgraw2: { + name: "Tom Clancy's Ghost Recon Advanced Warfighter 2", + release_year: 2007, + options: { + port_query: 16250, + protocol: 'gamespy2' + }, + extra: { + old_id: 'graw2' + } + }, + gck: { + name: 'Giants: Citizen Kabuto', + release_year: 2000, + options: { + port_query: 8911, + protocol: 'gamespy1' + }, + extra: { + old_id: 'giantscitizenkabuto' + } + }, + globaloperations: { + name: 'Global Operations', + release_year: 2002, + options: { + port_query: 28672, + protocol: 'gamespy1' + } + }, + geneshift: { + name: 'Geneshift', + release_year: 2017, + options: { + port: 11235, + protocol: 'geneshift' + } + }, + goldeneyesource: { + name: 'GoldenEye: Source', + release_year: 2010, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'ges' + } + }, + gus: { + name: 'Gore: Ultimate Soldier', + release_year: 2002, + options: { + port: 27777, + port_query_offset: 1, + protocol: 'gamespy1' + }, + extra: { + old_id: 'gore' + } + }, + groundbreach: { + name: 'Ground Breach', + release_year: 2018, + options: { + port: 27015, + protocol: 'valve' + } + }, + gunmanchronicles: { + name: 'Gunman Chronicles', + release_year: 2000, + options: { + port: 27015, + protocol: 'valve' + } + }, + hld: { + name: 'Half-Life Deathmatch', + release_year: 1998, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'hldm' + } + }, + hlds: { + name: 'Half-Life Deathmatch: Source', + release_year: 2005, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'hldms' + } + }, + hlof: { + name: 'Half-Life: Opposing Force', + release_year: 1999, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'hlopfor' + } + }, + hl2d: { + name: 'Half-Life 2: Deathmatch', + release_year: 2004, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'hl2dm' + } + }, + halo: { + name: 'Halo', + release_year: 2003, + options: { + port: 2302, + protocol: 'gamespy2' + } + }, + halo2: { + name: 'Halo 2', + release_year: 2007, + options: { + port: 2302, + protocol: 'gamespy2' + } + }, + hawakening: { + name: 'Hawakening', + release_year: 2024, + options: { + port: 7777, + port_query: 27015, + protocol: 'hawakening' + }, + extra: { + doc_notes: 'hawakening' + } + }, + heretic2: { + name: 'Heretic II', + release_year: 1998, + options: { + port: 27900, + port_query_offset: 1, + protocol: 'gamespy1' + } + }, + hexen2: { + name: 'Hexen II', + release_year: 1997, + options: { + port: 26900, + port_query_offset: 50, + protocol: 'hexen2' + } + }, + thehidden: { + name: 'The Hidden', + release_year: 2005, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'hidden' + } + }, + hll: { + name: 'Hell Let Loose', + release_year: 2019, + options: { + port: 27015, + protocol: 'valve' + } + }, + hiddendangerous2: { + name: 'Hidden & Dangerous 2', + release_year: 2003, + options: { + port: 11001, + port_query_offset: 3, + protocol: 'gamespy1' + }, + extra: { + old_id: 'had2' + } + }, + homefront: { + name: 'Homefront', + release_year: 2011, + options: { + port: 27015, + protocol: 'valve' + } + }, + homeworld2: { + name: 'Homeworld 2', + release_year: 2003, + options: { + port_query: 6500, + protocol: 'gamespy1' + } + }, + hurtworld: { + name: 'Hurtworld', + release_year: 2015, + options: { + port: 12871, + port_query: 12881, + protocol: 'valve' + } + }, + i2cs: { + name: 'IGI 2: Covert Strike', + release_year: 2003, + options: { + port_query: 26001, + protocol: 'gamespy1' + }, + extra: { + old_id: 'igi2' + } + }, + i2s: { + name: 'IL-2 Sturmovik', + release_year: 2001, + options: { + port_query: 21000, + protocol: 'gamespy1' + }, + extra: { + old_id: 'il2' + } + }, + icarus: { + name: 'Icarus', + release_year: 2021, + options: { + port: 27015, + protocol: 'valve' + } + }, + insurgency: { + name: 'Insurgency', + release_year: 2014, + options: { + port: 27015, + protocol: 'valve' + } + }, + imic: { + name: 'Insurgency: Modern Infantry Combat', + release_year: 2007, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'insurgencymic' + } + }, + insurgencysandstorm: { + name: 'Insurgency: Sandstorm', + release_year: 2018, + options: { + port: 27015, + port_query_offset: 1, + protocol: 'valve' + } + }, + ironstorm: { + name: 'Iron Storm', + release_year: 2002, + options: { + port_query: 3505, + protocol: 'gamespy1' + } + }, + theisle: { + name: 'The Isle', + release_year: 2015, + options: { + port: 7707, + port_query_offset: 1, + protocol: 'valve' + }, + extra: { + old_id: 'isle' + } + }, + tie: { + name: 'The Isle Evrima', + options: { + port: 7777, + protocol: 'theisleevrima' + }, + release_year: 2020 + }, + jb0n: { + name: 'James Bond 007: Nightfire', + release_year: 2002, + options: { + port_query: 6550, + protocol: 'gamespy1' + }, + extra: { + old_id: 'jamesbondnightfire' + } + }, + jc2m: { + name: 'Just Cause 2 - Multiplayer', + release_year: 2010, + options: { + port: 7777, + protocol: 'jc2mp' + }, + extra: { + old_id: 'jc2mp' + } + }, + jc3m: { + name: 'Just Cause 3 - Multiplayer', + release_year: 2017, + options: { + port: 4200, + port_query_offset: 1, + protocol: 'valve' + }, + extra: { + old_id: 'jc3mp' + } + }, + killingfloor: { + name: 'Killing Floor', + release_year: 2009, + options: { + port: 7707, + port_query_offset: 1, + protocol: 'unreal2' + } + }, + killingfloor2: { + name: 'Killing Floor 2', + release_year: 2016, + options: { + port: 7777, + port_query: 27015, + protocol: 'valve' + } + }, + kloc: { + name: 'Kingpin: Life of Crime', + release_year: 1999, + options: { + port: 31510, + port_query_offset: -10, + protocol: 'gamespy1' + }, + extra: { + old_id: 'kingpin' + } + }, + kpctnc: { + name: 'Kiss: Psycho Circus: The Nightmare Child', + release_year: 2000, + options: { + port: 7777, + port_query_offset: 1, + protocol: 'gamespy1' + }, + extra: { + old_id: 'kisspc' + } + }, + kspd: { + name: 'Kerbal Space Program - DMP', + release_year: 2015, + options: { + port: 6702, + port_query_offset: 1, + protocol: 'kspdmp' + }, + extra: { + old_id: 'kspdmp' + } + }, + kreedzclimbing: { + name: 'Kreedz Climbing', + release_year: 2017, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'kzmod' + } + }, + l4d: { + name: 'Left 4 Dead', + release_year: 2008, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'left4dead' + } + }, + l4d2: { + name: 'Left 4 Dead 2', + release_year: 2009, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'left4dead2' + } + }, + m2m: { + name: 'Mafia II - Multiplayer', + release_year: 2010, + options: { + port: 27016, + port_query_offset: 1, + protocol: 'mafia2mp' + }, + extra: { + old_id: 'm2mp' + } + }, + m2o: { + name: 'Mafia II - Online', + release_year: 2010, + options: { + port: 27015, + port_query_offset: 1, + protocol: 'mafia2online' + } + }, + medievalengineers: { + name: 'Medieval Engineers', + release_year: 2015, + options: { + port: 27015, + protocol: 'valve' + } + }, + moe: { + name: 'Myth of Empires', + release_year: 2024, + options: { + port_query: 12888, + protocol: 'valve' + } + }, + mohaa: { + name: 'Medal of Honor: Allied Assault', + release_year: 2002, + options: { + port: 12203, + port_query_offset: 97, + protocol: 'gamespy1' + } + }, + mohaas: { + name: 'Medal of Honor: Allied Assault Spearhead', + release_year: 2002, + options: { + port: 12203, + port_query_offset: 97, + protocol: 'gamespy1' + }, + extra: { + old_id: 'mohsh' + } + }, + mohaab: { + name: 'Medal of Honor: Allied Assault Breakthrough', + release_year: 2003, + options: { + port: 12203, + port_query_offset: 97, + protocol: 'gamespy1' + }, + extra: { + old_id: 'mohbt' + } + }, + mohpa: { + name: 'Medal of Honor: Pacific Assault', + release_year: 2004, + options: { + port: 13203, + port_query_offset: 97, + protocol: 'gamespy1' + } + }, + moha: { + name: 'Medal of Honor: Airborne', + release_year: 2007, + options: { + port: 12203, + port_query_offset: 97, + protocol: 'gamespy1' + }, + extra: { + old_id: 'mohab' + } + }, + moh: { + name: 'Medal of Honor', + release_year: 2010, + options: { + port: 7673, + port_query: 48888, + protocol: 'battlefield' + }, + extra: { + old_id: 'moh2010' + } + }, + mohw: { + name: 'Medal of Honor: Warfighter', + release_year: 2012, + options: { + port: 25200, + port_query_offset: 22000, + protocol: 'battlefield' + }, + extra: { + old_id: 'mohwf' + } + }, + minecraft: { + name: 'Minecraft', + release_year: 2009, + options: { + port: 25565, + protocol: 'minecraft' + }, + extra: { + doc_notes: 'minecraft' + } + }, + minetest: { + name: 'Minetest', + release_year: 2010, + options: { + port: 30000, + protocol: 'minetest' + } + }, + mbe: { + name: 'Minecraft: Bedrock Edition', + release_year: 2011, + options: { + port: 19132, + protocol: 'minecraft' + }, + extra: { + old_id: 'minecraftbe' + } + }, + mnc: { + name: 'Monday Night Combat', + release_year: 2011, + options: { + port: 7777, + port_query: 27016, + protocol: 'valve' + } + }, + mordhau: { + name: 'Mordhau', + release_year: 2019, + options: { + port: 7777, + port_query: 27015, + protocol: 'valve' + } + }, + gtavcmta: { + name: 'Grand Theft Auto: Vice City - Multi Theft Auto', + release_year: 2002, + options: { + port: 22003, + port_query_offset: 123, + protocol: 'ase' + }, + extra: { + old_id: 'mtavc' + } + }, + gtasamta: { + name: 'Grand Theft Auto: San Andreas - Multi Theft Auto', + release_year: 2004, + options: { + port: 22003, + port_query_offset: 123, + protocol: 'ase' + }, + extra: { + old_id: 'mtasa' + } + }, + mgm: { + name: 'Mumble - GT Murmur', + release_year: 2005, + options: { + port: 64738, + port_query: 27800, + protocol: 'mumble' + }, + extra: { + doc_notes: 'mumble' + } + }, + mumble: { + name: 'Mumble', + release_year: 2005, + options: { + port: 64738, + protocol: 'mumbleping' + }, + extra: { + doc_notes: 'mumble' + } + }, + mutantfactions: { + name: 'Mutant Factions', + release_year: 2009, + options: { + port: 11235, + protocol: 'geneshift' + } + }, + nascarthunder2004: { + name: 'NASCAR Thunder 2004', + release_year: 2003, + options: { + port_query: 13333, + protocol: 'gamespy2' + } + }, + netpanzer: { + name: 'netPanzer', + release_year: 2002, + options: { + port: 3030, + protocol: 'gamespy1' + } + }, + nmrih: { + name: 'No More Room in Hell', + release_year: 2011, + options: { + port: 27015, + protocol: 'valve' + } + }, + naturalselection: { + name: 'Natural Selection', + release_year: 2002, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'ns' + } + }, + naturalselection2: { + name: 'Natural Selection 2', + release_year: 2012, + options: { + port_query_offset: 1, + protocol: 'valve' + }, + extra: { + old_id: 'ns2' + } + }, + nfshp2: { + name: 'Need for Speed: Hot Pursuit 2', + release_year: 2002, + options: { + port_query: 61220, + protocol: 'gamespy1' + } + }, + nab: { + name: 'Nerf Arena Blast', + release_year: 1999, + options: { + port: 4444, + port_query_offset: 1, + protocol: 'gamespy1' + } + }, + neverwinternights: { + name: 'Neverwinter Nights', + release_year: 2002, + options: { + port_query: 5121, + protocol: 'gamespy2' + }, + extra: { + old_id: 'nwn' + } + }, + neverwinternights2: { + name: 'Neverwinter Nights 2', + release_year: 2006, + options: { + port: 5121, + port_query: 6500, + protocol: 'gamespy2' + }, + extra: { + old_id: 'nwn2' + } + }, + nexuiz: { + name: 'Nexuiz', + release_year: 2005, + options: { + port_query: 26000, + protocol: 'quake3' + } + }, + nitrofamily: { + name: 'Nitro Family', + release_year: 2004, + options: { + port_query: 25601, + protocol: 'gamespy1' + } + }, + tonolf: { + name: 'The Operative: No One Lives Forever', + release_year: 2000, + options: { + port_query: 27888, + protocol: 'gamespy1' + }, + extra: { + old_id: 'nolf' + } + }, + nla: { + name: 'Nova-Life: Amboise', + release_year: 2020, + options: { + port_query: 27015, + protocol: 'valve' + } + }, + nolf2asihw: { + name: "No One Lives Forever 2: A Spy in H.A.R.M.'s Way", + release_year: 2002, + options: { + port_query: 27890, + protocol: 'gamespy1' + }, + extra: { + old_id: 'nolf2' + } + }, + nucleardawn: { + name: 'Nuclear Dawn', + release_year: 2011, + options: { + port: 27015, + protocol: 'valve' + } + }, + ohd: { + name: 'Operation: Harsh Doorstop', + release_year: 2023, + options: { + port: 7777, + port_query: 27005, + protocol: 'valve' + } + }, + onset: { + name: 'Onset', + release_year: 2019, + options: { + port: 7777, + port_query_offset: -1, + protocol: 'valve' + } + }, + openarena: { + name: 'OpenArena', + release_year: 2005, + options: { + port_query: 27960, + protocol: 'quake3' + } + }, + openttd: { + name: 'OpenTTD', + release_year: 2004, + options: { + port: 3979, + protocol: 'openttd' + } + }, + painkiller: { + name: 'Painkiller', + release_year: 2004, + options: { + port: 3455, + port_query_offset: 123, + protocol: 'ase' + } + }, + palworld: { + name: 'Palworld', + release_year: 2024, + options: { + port: 8212, + protocol: 'palworld' + }, + extra: { + doc_notes: 'palworld' + } + }, + pvak2: { + name: 'Pirates, Vikings, and Knights II', + release_year: 2007, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'pvkii' + } + }, + pixark: { + name: 'PixARK', + release_year: 2018, + options: { + port: 7777, + port_query: 27015, + protocol: 'valve' + } + }, + postscriptum: { + name: 'Post Scriptum', + release_year: 2018, + options: { + port: 10037, + protocol: 'valve' + }, + extra: { + old_id: 'ps' + } + }, + postal2: { + name: 'Postal 2', + release_year: 2003, + options: { + port: 7777, + port_query_offset: 1, + protocol: 'gamespy1' + } + }, + prey: { + name: 'Prey', + release_year: 2017, + options: { + port: 27719, + protocol: 'doom3' + } + }, + pce: { + name: 'Primal Carnage: Extinction', + release_year: 2015, + options: { + port: 7777, + port_query: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'primalcarnage' + } + }, + projectcars: { + name: 'Project Cars', + release_year: 2015, + options: { + port: 27015, + port_query_offset: 1, + protocol: 'valve' + }, + extra: { + old_id: 'pc' + } + }, + projectcars2: { + name: 'Project Cars 2', + release_year: 2017, + options: { + port: 27015, + port_query_offset: 1, + protocol: 'valve' + }, + extra: { + old_id: 'pc2' + } + }, + prb2: { + name: 'Project Reality: Battlefield 2', + release_year: 2005, + options: { + port: 16567, + port_query: 29900, + protocol: 'gamespy3' + }, + extra: { + old_id: 'prbf2' + } + }, + projectzomboid: { + name: 'Project Zomboid', + release_year: 2013, + options: { + port: 16261, + protocol: 'valve' + }, + extra: { + old_id: 'przomboid' + } + }, + quake: { + name: 'Quake', + release_year: 1996, + options: { + port: 27500, + protocol: 'quake1' + }, + extra: { + old_id: 'quake1' + } + }, + quake2: { + name: 'Quake 2', + release_year: 1997, + options: { + port: 27910, + protocol: 'quake2' + } + }, + q3a: { + name: 'Quake 3: Arena', + release_year: 1999, + options: { + port: 27960, + protocol: 'quake3' + }, + extra: { + old_id: 'quake3' + } + }, + quake4: { + name: 'Quake 4', + release_year: 2005, + options: { + port: 28004, + protocol: 'doom3' + } + }, + quakelive: { + name: 'Quake Live', + release_year: 2010, + options: { + port: 27960, + protocol: 'valve' + } + }, + rdkf: { + name: 'Rag Doll Kung Fu', + release_year: 2005, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'ragdollkungfu' + } + }, + rainbowsix: { + name: 'Rainbow Six', + release_year: 1998, + options: { + port_query: 2348, + protocol: 'gamespy1' + }, + extra: { + old_id: 'r6' + } + }, + rs2rs: { + name: 'Rainbow Six 2: Rogue Spear', + release_year: 1999, + options: { + port_query: 2346, + protocol: 'gamespy1' + }, + extra: { + old_id: 'r6roguespear' + } + }, + rs3rs: { + name: 'Rainbow Six 3: Raven Shield', + release_year: 2003, + options: { + port: 7777, + port_query_offset: 1000, + protocol: 'gamespy1' + }, + extra: { + old_id: 'r6ravenshield' + } + }, + rallisportchallenge: { + name: 'RalliSport Challenge', + release_year: 2002, + options: { + port_query: 17500, + protocol: 'gamespy1' + } + }, + rallymasters: { + name: 'Rally Masters', + release_year: 2000, + options: { + port_query: 16666, + protocol: 'gamespy1' + } + }, + redorchestra: { + name: 'Red Orchestra', + release_year: 2018, + options: { + port: 7758, + port_query_offset: 1, + protocol: 'unreal2' + } + }, + roo4145: { + name: 'Red Orchestra: Ostfront 41-45', + release_year: 2006, + options: { + port: 7757, + port_query_offset: 10, + protocol: 'gamespy1' + }, + extra: { + old_id: 'redorchestraost' + } + }, + redorchestra2: { + name: 'Red Orchestra 2', + release_year: 2011, + options: { + port: 7777, + port_query: 27015, + protocol: 'valve' + } + }, + redline: { + name: 'Redline', + release_year: 2010, + options: { + port_query: 25252, + protocol: 'gamespy1' + } + }, + renegade10: { + name: 'Renegade X', + release_year: 2014, + options: { + protocol: 'renegadex' + } + }, + renown: { + name: 'Renown', + release_year: 2025, + options: { + protocol: 'renown' + } + }, + rdr2r: { + name: 'Red Dead Redemption 2 - RedM', + release_year: 2018, + options: { + port: 30120, + protocol: 'fivem' + }, + extra: { + old_id: 'redm' + } + }, + rtcw: { + name: 'Return to Castle Wolfenstein', + release_year: 2001, + options: { + port_query: 27960, + protocol: 'quake3' + } + }, + rfactor: { + name: 'rFactor', + release_year: 2005, + options: { + port: 34397, + port_query_offset: -100, + protocol: 'rfactor' + } + }, + rfactor2: { + name: 'rFactor 2', + release_year: 2013, + options: { + port_query: 64299, + protocol: 'valve' + } + }, + ricochet: { + name: 'Ricochet', + release_year: 2005, + options: { + port: 27015, + protocol: 'valve' + } + }, + ron: { + name: 'Rise of Nations', + release_year: 2003, + options: { + port_query: 6501, + protocol: 'gamespy1' + }, + extra: { + old_id: 'riseofnations' + } + }, + risingworld: { + name: 'Rising World', + release_year: 2014, + options: { + port: 4255, + port_query_offset: -1, + protocol: 'valve' + } + }, + ror2: { + name: 'Risk of Rain 2', + release_year: 2020, + options: { + port: 27015, + port_query_offset: 1, + protocol: 'valve' + } + }, + rs2v: { + name: 'Rising Storm 2: Vietnam', + release_year: 2017, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'rs2' + } + }, + rune: { + name: 'Rune', + release_year: 2000, + options: { + port: 7777, + port_query_offset: 1, + protocol: 'gamespy1' + } + }, + rust: { + name: 'Rust', + release_year: 2013, + options: { + port: 28015, + protocol: 'valve' + } + }, + gtasam: { + name: 'Grand Theft Auto: San Andreas Multiplayer', + release_year: 2006, + options: { + port: 7777, + protocol: 'samp' + }, + extra: { + old_id: 'samp' + } + }, + gtasao: { + name: 'Grand Theft Auto: San Andreas OpenMP', + release_year: 2019, + options: { + port: 7777, + protocol: 'gtasao' + }, + extra: { + old_id: 'saomp' + } + }, + s2ats: { + name: 'Savage 2: A Tortured Soul', + release_year: 2008, + options: { + port_query: 11235, + protocol: 'savage2' + }, + extra: { + old_id: 'savage2' + } + }, + sdtd: { + name: '7 Days to Die', + release_year: 2013, + options: { + port: 26900, + port_query_offset: 1, + protocol: 'sdtd' + }, + extra: { + old_id: '7d2d', + doc_notes: 'sdtd' + } + }, + satisfactory: { + name: 'Satisfactory', + release_year: 2019, + options: { + port: 7777, + protocol: 'satisfactory' + }, + extra: { + doc_notes: 'satisfactory' + } + }, + spaceengineers: { + name: 'Space Engineers', + release_year: 2019, + options: { + port: 27015, + protocol: 'valve' + } + }, + serioussam: { + name: 'Serious Sam', + release_year: 2001, + options: { + port: 25600, + port_query_offset: 1, + protocol: 'gamespy1' + }, + extra: { + old_id: 'ss' + } + }, + serioussam2: { + name: 'Serious Sam 2', + release_year: 2005, + options: { + port: 25600, + protocol: 'gamespy2' + }, + extra: { + old_id: 'ss2' + } + }, + shatteredhorizon: { + name: 'Shattered Horizon', + release_year: 2009, + options: { + port: 27015, + protocol: 'valve' + } + }, + sstse: { + name: 'Serious Sam: The Second Encounter', + release_year: 2002, + options: { + port: 25600, + port_query_offset: 1, + protocol: 'gamespy1' + } + }, + theship: { + name: 'The Ship', + release_year: 2006, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'ship' + } + }, + shogo: { + name: 'Shogo', + release_year: 1998, + options: { + port_query: 27888, + protocol: 'gamespy1' + } + }, + shootmania: { + name: 'Shootmania', + release_year: 2013, + options: { + port: 2350, + port_query: 5000, + protocol: 'nadeo' + }, + extra: { + doc_notes: 'nadeo' + } + }, + sin: { + name: 'SiN', + release_year: 1998, + options: { + port_query: 22450, + protocol: 'gamespy1' + } + }, + sinepisodes: { + name: 'SiN Episodes', + release_year: 2006, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'sinep' + } + }, + soldat: { + name: 'Soldat', + release_year: 2002, + options: { + port: 23073, + port_query_offset: 10, + protocol: 'soldat' + }, + extra: { + doc_notes: 'soldat' + } + }, + sof: { + name: 'Soldier of Fortune', + release_year: 2000, + options: { + port: 28910, + port_query_offset: 1, + protocol: 'gamespy1' + } + }, + sof2: { + name: 'Soldier of Fortune 2', + release_year: 2002, + options: { + port_query: 20100, + protocol: 'quake3' + } + }, + sotf: { + name: 'Sons Of The Forest', + release_year: 2023, + options: { + port: 8766, + port_query: 27016, + protocol: 'valve' + }, + extra: { + old_id: 'sonsoftheforest' + } + }, + soulmask: { + name: 'Soulmask', + release_year: 2024, + options: { + port: 8777, + port_query: 27015, + protocol: 'valve' + } + }, + ssl: { + name: 'SCP: Secret Labratory', + release_year: 2020, + options: { + protocol: 'scpsl' + }, + extra: { + doc_notes: 'ssl' + } + }, + stalker: { + name: 'S.T.A.L.K.E.R.', + release_year: 2007, + options: { + port: 5445, + port_query_offset: 2, + protocol: 'gamespy3' + } + }, + stn: { + name: 'Survive the Nights', + release_year: 2017, + options: { + port: 7950, + port_query_offset: 1, + protocol: 'valve' + } + }, + stbc: { + name: 'Star Trek: Bridge Commander', + release_year: 2002, + options: { + port_query: 22101, + protocol: 'gamespy1' + } + }, + stvef: { + name: 'Star Trek: Voyager - Elite Force', + release_year: 2000, + options: { + port_query: 27960, + protocol: 'quake3' + } + }, + stvef2: { + name: 'Star Trek: Voyager - Elite Force 2', + release_year: 2003, + options: { + port_query: 29253, + protocol: 'quake3' + } + }, + squad: { + name: 'Squad', + release_year: 2020, + options: { + port: 7787, + port_query: 27165, + protocol: 'valve' + } + }, + swb: { + name: 'Star Wars: Battlefront', + release_year: 2004, + options: { + port_query: 3658, + protocol: 'gamespy2' + }, + extra: { + old_id: 'swbf' + } + }, + swb2: { + name: 'Star Wars: Battlefront 2', + release_year: 2005, + options: { + port_query: 3658, + protocol: 'gamespy2' + }, + extra: { + old_id: 'swbf2' + } + }, + swjkja: { + name: 'Star Wars Jedi Knight: Jedi Academy', + release_year: 2003, + options: { + port_query: 29070, + protocol: 'quake3' + }, + extra: { + old_id: 'swjk' + } + }, + swjk2jo: { + name: 'Star Wars Jedi Knight II: Jedi Outcast', + release_year: 2002, + options: { + port_query: 28070, + protocol: 'quake3' + }, + extra: { + old_id: 'swjk2' + } + }, + swrc: { + name: 'Star Wars: Republic Commando', + release_year: 2005, + options: { + port: 7777, + port_query: 11138, + protocol: 'gamespy2' + } + }, + starbound: { + name: 'Starbound', + release_year: 2016, + options: { + port: 21025, + protocol: 'valve' + } + }, + starmade: { + name: 'StarMade', + release_year: 2012, + options: { + port: 4242, + protocol: 'starmade' + } + }, + starsiege: { + name: 'Starsiege', + release_year: 2009, + options: { + port: 29001, + protocol: 'starsiege' + } + }, + suicidesurvival: { + name: 'Suicide Survival', + release_year: 2008, + options: { + port: 27015, + protocol: 'valve' + } + }, + swat4: { + name: 'SWAT 4', + release_year: 2005, + options: { + port: 10480, + port_query_offset: 2, + protocol: 'gamespy2' + } + }, + svencoop: { + name: 'Sven Coop', + release_year: 1999, + options: { + port: 27015, + protocol: 'valve' + } + }, + synergy: { + name: 'Synergy', + release_year: 2005, + options: { + port: 27015, + protocol: 'valve' + } + }, + tacticalops: { + name: 'Tactical Ops', + release_year: 1999, + options: { + port: 7777, + port_query_offset: 1, + protocol: 'gamespy1' + } + }, + toh: { + name: 'Take On Helicopters', + release_year: 2011, + options: { + port: 2302, + port_query_offset: 1, + protocol: 'gamespy1' + }, + extra: { + old_id: 'takeonhelicopters' + } + }, + teamfactor: { + name: 'Team Factor', + release_year: 2002, + options: { + port_query: 57778, + protocol: 'gamespy1' + } + }, + tfc: { + name: 'Team Fortress Classic', + release_year: 1999, + options: { + port: 27015, + protocol: 'valve' + } + }, + teamfortress2: { + name: 'Team Fortress 2', + release_year: 2007, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'tf2' + } + }, + teamspeak2: { + name: 'Teamspeak 2', + release_year: 2001, + options: { + port: 8767, + protocol: 'teamspeak2' + } + }, + teamspeak3: { + name: 'Teamspeak 3', + release_year: 2011, + options: { + port: 9987, + protocol: 'teamspeak3' + }, + extra: { + doc_notes: 'teamspeak3' + } + }, + terminus: { + name: 'Terminus', + release_year: 2000, + options: { + port_query: 12286, + protocol: 'gamespy1' + } + }, + terrariatshock: { + name: 'Terraria - TShock', + release_year: 2011, + options: { + port: 7777, + port_query_offset: 101, + protocol: 'terraria' + }, + extra: { + old_id: 'terraria', + doc_notes: 'terraria' + } + }, + theforest: { + name: 'The Forest', + release_year: 2014, + options: { + port: 27015, + port_query_offset: 1, + protocol: 'valve' + }, + extra: { + old_id: 'forrest' + } + }, + thefront: { + name: 'The Front', + release_year: 2023, + options: { + port_query: 27015, + protocol: 'valve' + } + }, + thps3: { + name: "Tony Hawk's Pro Skater 3", + release_year: 2001, + options: { + port_query: 6500, + protocol: 'gamespy1' + } + }, + thps4: { + name: "Tony Hawk's Pro Skater 4", + release_year: 2002, + options: { + port_query: 6500, + protocol: 'gamespy1' + } + }, + thu2: { + name: "Tony Hawk's Underground 2", + release_year: 2004, + options: { + port_query: 5153, + protocol: 'gamespy1' + } + }, + towerunite: { + name: 'Tower Unite', + release_year: 2016, + options: { + port: 27015, + protocol: 'valve' + } + }, + trackmania2: { + name: 'Trackmania 2', + release_year: 2011, + options: { + port: 2350, + port_query: 5000, + protocol: 'nadeo' + }, + extra: { + doc_notes: 'nadeo' + } + }, + trackmaniaforever: { + name: 'Trackmania Forever', + release_year: 2008, + options: { + port: 2350, + port_query: 5000, + protocol: 'nadeo' + }, + extra: { + doc_notes: 'nadeo' + } + }, + tremulous: { + name: 'Tremulous', + release_year: 2006, + options: { + port_query: 30720, + protocol: 'quake3' + } + }, + t1s: { + name: 'Tribes 1: Starsiege', + release_year: 1998, + options: { + port: 28001, + protocol: 'tribes1' + }, + extra: { + old_id: 'tribes1' + } + }, + tribesvengeance: { + name: 'Tribes: Vengeance', + release_year: 2004, + options: { + port: 7777, + port_query_offset: 1, + protocol: 'gamespy2' + } + }, + tron20: { + name: 'Tron 2.0', + release_year: 2003, + options: { + port_query: 27888, + protocol: 'gamespy2' + } + }, + thespecialists: { + name: 'The Specialists', + release_year: 2002, + options: { + port: 27015, + protocol: 'valve' + } + }, + toxikk: { + name: 'TOXIKK', + release_year: 2016, + options: { + port: 7777, + port_query: 27015, + protocol: 'toxikk' + } + }, + turok2: { + name: 'Turok 2', + release_year: 1998, + options: { + port_query: 12880, + protocol: 'gamespy1' + } + }, + u2tax: { + name: 'Unreal 2: The Awakening - XMP', + release_year: 2003, + options: { + port: 7777, + port_query_offset: 1, + protocol: 'unreal2' + } + }, + universalcombat: { + name: 'Universal Combat', + release_year: 2004, + options: { + port: 1135, + port_query_offset: 123, + protocol: 'ase' + } + }, + unreal: { + name: 'Unreal', + release_year: 1998, + options: { + port: 7777, + port_query_offset: 1, + protocol: 'gamespy1' + } + }, + unturned: { + name: 'unturned', + release_year: 2014, + options: { + port: 27015, + port_query_offset: 1, + protocol: 'valve' + } + }, + unrealtournament: { + name: 'Unreal Tournament', + release_year: 1993, + options: { + port: 7777, + port_query_offset: 1, + protocol: 'gamespy1' + }, + extra: { + old_id: 'ut' + } + }, + unrealtournament2003: { + name: 'Unreal Tournament 2003', + release_year: 2003, + options: { + port: 7757, + port_query_offset: 1, + protocol: 'unreal2' + }, + extra: { + old_id: 'ut2003' + } + }, + unrealtournament2004: { + name: 'Unreal Tournament 2004', + release_year: 2004, + options: { + port: 7777, + port_query_offset: 1, + protocol: 'unreal2' + }, + extra: { + old_id: 'ut2004' + } + }, + unrealtournament3: { + name: 'Unreal Tournament 3', + release_year: 2007, + options: { + port: 7777, + port_query_offset: -1277, + protocol: 'ut3' + }, + extra: { + old_id: 'ut3' + } + }, + urbanterror: { + name: 'Urban Terror', + release_year: 2000, + options: { + port_query: 27960, + protocol: 'quake3' + } + }, + v8sc: { + name: 'V8 Supercar Challenge', + release_year: 2002, + options: { + port_query: 16700, + protocol: 'gamespy1' + }, + extra: { + old_id: 'v8supercar' + } + }, + valheim: { + name: 'Valheim', + release_year: 2021, + options: { + port: 2456, + port_query_offset: 1, + protocol: 'valve' + }, + extra: { + doc_notes: 'valheim' + } + }, + vcm: { + name: 'Vice City Multiplayer', + release_year: 2015, + options: { + port: 8192, + protocol: 'vcmp' + }, + extra: { + old_id: 'vcmp' + } + }, + ventrilo: { + name: 'Ventrilo', + release_year: 2002, + options: { + port: 3784, + protocol: 'ventrilo' + } + }, + vietcong: { + name: 'Vietcong', + release_year: 2003, + options: { + port: 5425, + port_query: 15425, + protocol: 'gamespy1' + } + }, + vietcong2: { + name: 'Vietcong 2', + release_year: 2005, + options: { + port: 5001, + port_query: 19967, + protocol: 'gamespy2' + } + }, + vrising: { + name: 'V Rising', + release_year: 2022, + options: { + port: 27015, + port_query_offset: [1, 15], + protocol: 'valve' + } + }, + vampireslayer: { + name: 'Vampire Slayer', + release_year: 2000, + options: { + port: 27015, + protocol: 'valve' + }, + extra: { + old_id: 'vs' + } + }, + vintagestory: { + name: 'Vintage Story', + release_year: 2016, + options: { + port: 42420, + protocol: 'vintagestory' + } + }, + warsow: { + name: 'Warsow', + release_year: 2012, + options: { + port: 44400, + protocol: 'warsow' + } + }, + warfork: { + name: 'Warfork', + release_year: 2019, + options: { + port_query: 44400, + protocol: 'warsow' + } + }, + wot: { + name: 'Wheel of Time', + release_year: 1999, + options: { + port: 7777, + port_query_offset: 1, + protocol: 'gamespy1' + }, + extra: { + old_id: 'wheeloftime' + } + }, + wolfenstein: { + name: 'Wolfenstein', + release_year: 2009, + options: { + port: 27666, + protocol: 'doom3' + }, + extra: { + old_id: 'wolfenstein2009' + } + }, + wet: { + name: 'Wolfenstein: Enemy Territory', + release_year: 2003, + options: { + port_query: 27960, + protocol: 'quake3' + }, + extra: { + old_id: 'wolfensteinet' + } + }, + wurmunlimited: { + name: 'Wurm Unlimited', + release_year: 2006, + options: { + port: 3724, + port_query: 27016, + protocol: 'valve' + }, + extra: { + old_id: 'wurm' + } + }, + xonotic: { + name: 'Xonotic', + release_year: 2011, + options: { + port: 26000, + protocol: 'xonotic' + } + }, + wop: { + name: 'World Of Padman', + release_year: 2007, + options: { + port: 26000, + protocol: 'quake3' + } + }, + xpandrally: { + name: 'Xpand Rally', + release_year: 2004, + options: { + port: 28015, + port_query_offset: 123, + protocol: 'ase' + } + }, + zombiemaster: { + name: 'Zombie Master', + release_year: 2007, + options: { + port: 27015, + protocol: 'valve' + } + }, + zps: { + name: 'Zombie Panic: Source', + release_year: 2007, + options: { + port: 27015, + protocol: 'valve' + } + } +} \ No newline at end of file diff --git a/client/src/Validation/validation.js b/client/src/Validation/validation.js index 9c6699d4b..6282bc12b 100644 --- a/client/src/Validation/validation.js +++ b/client/src/Validation/validation.js @@ -205,6 +205,7 @@ const monitorValidation = joi.object({ expectedValue: joi.string().allow(null, ""), jsonPath: joi.string().allow(null, ""), matchMethod: joi.string().allow(null, ""), + gameId: joi.string().allow(null, "") }); const imageValidation = joi.object({ diff --git a/client/src/locales/ar.json b/client/src/locales/ar.json index 7188c3711..33f245fd5 100644 --- a/client/src/locales/ar.json +++ b/client/src/locales/ar.json @@ -260,6 +260,7 @@ "notifyEmails": "", "seperateEmails": "", "checkFrequency": "", + "chooseGame": "", "matchMethod": "", "expectedValue": "", "deleteDialogTitle": "", @@ -437,7 +438,8 @@ "http": "", "ping": "", "docker": "", - "port": "" + "port": "", + "game": "" }, "common": { "appName": "", diff --git a/client/src/locales/cs.json b/client/src/locales/cs.json index 00c513203..1fb579270 100644 --- a/client/src/locales/cs.json +++ b/client/src/locales/cs.json @@ -260,6 +260,7 @@ "notifyEmails": "", "seperateEmails": "", "checkFrequency": "", + "chooseGame": "", "matchMethod": "", "expectedValue": "", "deleteDialogTitle": "", @@ -437,7 +438,8 @@ "http": "", "ping": "", "docker": "", - "port": "" + "port": "", + "game": "" }, "common": { "appName": "Checkmate", diff --git a/client/src/locales/de.json b/client/src/locales/de.json index aa416bd95..4ac77ef29 100644 --- a/client/src/locales/de.json +++ b/client/src/locales/de.json @@ -260,6 +260,7 @@ "notifyEmails": "Benachrichtige auch per E-Mail an mehrere Adressen (kommt bald)", "seperateEmails": "Du kannst mehrere E-Mails mit einem Komma trennen", "checkFrequency": "Überprüfungsfrequenz", + "chooseGame": "", "matchMethod": "", "expectedValue": "Erwarteter Wert", "deleteDialogTitle": "Möchtest du diesen Monitor wirklich löschen?", @@ -437,7 +438,8 @@ "http": "", "ping": "", "docker": "", - "port": "" + "port": "", + "game": "" }, "common": { "appName": "Checkmate", diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 97aa761d1..a9a81b773 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -216,6 +216,7 @@ "cancel": "Cancel", "checkFormError": "Please check the form for errors.", "checkFrequency": "Check frequency", + "chooseGame": "Choose game", "checkHooks": { "failureResolveAll": "Failed to resolve all incidents.", "failureResolveMonitor": "Failed to resolve monitor incidents.", @@ -627,6 +628,11 @@ "label": "URL to monitor", "namePlaceholder": "Localhost:5173", "placeholder": "localhost" + }, + "game": { + "label": "URL to monitor", + "namePlaceholder": "localhost:5173", + "placeholder": "localhost" } }, "ms": "ms", @@ -748,6 +754,8 @@ "pingMonitoringDescription": "Check whether your server is available or not.", "portMonitoring": "Port monitoring", "portMonitoringDescription": "Check whether your port is open or not.", + "gameServerMonitoring": "Game Server Monitoring", + "gameServerMonitoringDescription": "Check whether your game server is running or not", "portToMonitor": "Port to monitor", "publicLink": "Public link", "publicURL": "Public URL", @@ -1008,7 +1016,8 @@ "docker": "Enter the Docker ID of your container. Docker IDs must be the full 64 char Docker ID. You can run docker inspect to get the full container ID.", "http": "Enter the URL or IP to monitor (e.g., https://example.com/ or 192.168.1.100) and add a clear display name that appears on the dashboard.", "ping": "Enter the IP address or hostname to ping (e.g., 192.168.1.100 or example.com) and add a clear display name that appears on the dashboard.", - "port": "Enter the URL or IP of the server, the port number and a clear display name that appears on the dashboard." + "port": "Enter the URL or IP of the server, the port number and a clear display name that appears on the dashboard.", + "game": "Enter the IP address or hostname and the port number to ping (e.g., 192.168.1.100 or example.com) and choose game type." }, "uptimeMonitor": { "fallback": { diff --git a/client/src/locales/es.json b/client/src/locales/es.json index 6ecebadf5..1e2ee3b6c 100644 --- a/client/src/locales/es.json +++ b/client/src/locales/es.json @@ -260,6 +260,7 @@ "notifyEmails": "", "seperateEmails": "", "checkFrequency": "", + "chooseGame": "", "matchMethod": "", "expectedValue": "", "deleteDialogTitle": "", @@ -437,7 +438,8 @@ "http": "", "ping": "", "docker": "", - "port": "" + "port": "", + "game": "" }, "common": { "appName": "", diff --git a/client/src/locales/fi.json b/client/src/locales/fi.json index 2198e2798..cce87a89f 100644 --- a/client/src/locales/fi.json +++ b/client/src/locales/fi.json @@ -260,6 +260,7 @@ "notifyEmails": "", "seperateEmails": "", "checkFrequency": "", + "chooseGame": "", "matchMethod": "", "expectedValue": "", "deleteDialogTitle": "", @@ -437,7 +438,8 @@ "http": "", "ping": "", "docker": "", - "port": "" + "port": "", + "game": "" }, "common": { "appName": "", diff --git a/client/src/locales/fr.json b/client/src/locales/fr.json index c6e263c4d..52d5d8c7e 100644 --- a/client/src/locales/fr.json +++ b/client/src/locales/fr.json @@ -264,6 +264,7 @@ "notifyEmails": "Envoyer également une notification par email à plusieurs adresses (bientôt).", "seperateEmails": "Vous pouvez ajouter plusieurs adresses emails en les séparant par une virgule", "checkFrequency": "Vérifier la fréquence", + "chooseGame": "", "matchMethod": "Méthode de rapprochement", "expectedValue": "Valeur attendue", "deleteDialogTitle": "Voulez-vous vraiment supprimer ce moniteur ?", @@ -441,7 +442,8 @@ "http": "Entrez l'URL ou l'IP du moniteur (par exemple https://exemple.fr ou 192.168.1.100) et ajoutez un nom familier qui apparaîtra sur le tableau de bord.", "ping": "Entrez l'adresse IP ou le nom d'hôte à tester (par exemple, 192.168.1.100 ou exemple.fr) et ajoutez un nom familier qui apparaîtra sur le tableau de bord.", "docker": "Entrer l'ID Docker du container. Les identifiants Docker doivent être les 64 caractères de l'ID Docker. Vous pouvez utiliser la commande docker inspect pour avoir l'ID complet.", - "port": "Entrez l'URL ou l'adresse IP du serveur, le numéro de port et un nom d'affichage familier qui apparaîtra sur le tableau de bord." + "port": "Entrez l'URL ou l'adresse IP du serveur, le numéro de port et un nom d'affichage familier qui apparaîtra sur le tableau de bord.", + "game": "" }, "common": { "appName": "Checkmate", diff --git a/client/src/locales/ja.json b/client/src/locales/ja.json index 782fdd9e0..80245d64b 100644 --- a/client/src/locales/ja.json +++ b/client/src/locales/ja.json @@ -264,6 +264,7 @@ "notifyEmails": "複数のアドレスにメールでも通知(近日公開)", "seperateEmails": "複数のメールはカンマで区切ることができます", "checkFrequency": "チェック頻度", + "chooseGame": "", "matchMethod": "マッチ方法", "expectedValue": "期待値", "deleteDialogTitle": "本当にこのモニターを削除しますか?", @@ -441,7 +442,8 @@ "http": "監視するURLまたはIPを入力(例: https://example.com/ または 192.168.1.100)し、ダッシュボードに表示される明確な表示名を追加。", "ping": "pingするIPアドレスまたはホスト名を入力(例: 192.168.1.100 または example.com)し、ダッシュボードに表示される明確な表示名を追加。", "docker": "コンテナのDocker IDを入力。Docker IDは64文字のフルDocker IDである必要があります。docker inspect を実行してフルコンテナIDを取得できます。", - "port": "サーバーのURLまたはIP、ポート番号、ダッシュボードに表示される明確な表示名を入力。" + "port": "サーバーのURLまたはIP、ポート番号、ダッシュボードに表示される明確な表示名を入力。", + "game": "" }, "common": { "appName": "Checkmate", diff --git a/client/src/locales/pt-BR.json b/client/src/locales/pt-BR.json index 2315a723d..4b4bd04a9 100644 --- a/client/src/locales/pt-BR.json +++ b/client/src/locales/pt-BR.json @@ -260,6 +260,7 @@ "notifyEmails": "Também notificar por e-mail para vários endereços (em breve)", "seperateEmails": "Você pode separar vários e-mails com uma vírgula", "checkFrequency": "Verifique a frequência", + "chooseGame": "", "matchMethod": "Método de correspondência", "expectedValue": "Valor esperado", "deleteDialogTitle": "Você realmente deseja excluir este monitor?", @@ -437,7 +438,8 @@ "http": "Insira a URL ou IP para monitorar (ex. https://exemplo.com.br/ ou 192.168.1.100) e adicione uma descrição que aparecerá na dashboard.", "ping": "Insira o endereço IP ou nome de domínio para ping (ex. 192.168.1.100 ou exemplo.com.br) e adicione uma descrição que aparecerá na dashboard.", "docker": "Insira o Docker Id do seu container. Docker Ids devem ser todos os 64 caracteres. Você pode executar docker inspect para descobrir o Id completo.", - "port": "Insira a URL ou o IP do servidor, aporta e uma descrição que aparecerá na dashboard." + "port": "Insira a URL ou o IP do servidor, aporta e uma descrição que aparecerá na dashboard.", + "game": "" }, "common": { "appName": "Checkmate", diff --git a/client/src/locales/ru.json b/client/src/locales/ru.json index 814e94da1..25e1038aa 100644 --- a/client/src/locales/ru.json +++ b/client/src/locales/ru.json @@ -260,6 +260,7 @@ "notifyEmails": "Также уведомлять по электронной почте на несколько адресов (скоро)", "seperateEmails": "Вы можете разделить несколько адресов электронной почты запятой.", "checkFrequency": "Проверить частоту", + "chooseGame": "", "matchMethod": "Метод сопоставления", "expectedValue": "Ожидаемое значение", "deleteDialogTitle": "Вы действительно хотите удалить этот монитор?", @@ -437,7 +438,8 @@ "http": "", "ping": "", "docker": "", - "port": "" + "port": "", + "game": "" }, "common": { "appName": "", diff --git a/client/src/locales/tr.json b/client/src/locales/tr.json index e9e2d4e29..9e20242fb 100644 --- a/client/src/locales/tr.json +++ b/client/src/locales/tr.json @@ -260,6 +260,7 @@ "notifyEmails": "Ayrıca birden fazla eposta adresine bildirim gönderebilirsiniz (yakında geliyor)", "seperateEmails": "Birden fazla epostayı virgülle ayırabilirsiniz", "checkFrequency": "Frekansı denetle", + "chooseGame": "Oyun seç", "matchMethod": "Eşleşme yöntemi", "expectedValue": "Beklenen değer", "deleteDialogTitle": "Gerçekten bu monitörü silmek istiyor musunuz?", @@ -437,7 +438,8 @@ "http": "", "ping": "", "docker": "", - "port": "" + "port": "", + "game": "" }, "common": { "appName": "Checkmate", diff --git a/client/src/locales/uk.json b/client/src/locales/uk.json index b3b8fc4ce..22b96487c 100644 --- a/client/src/locales/uk.json +++ b/client/src/locales/uk.json @@ -260,6 +260,7 @@ "notifyEmails": "", "seperateEmails": "", "checkFrequency": "", + "chooseGame": "", "matchMethod": "", "expectedValue": "", "deleteDialogTitle": "", @@ -435,7 +436,8 @@ "http": "", "ping": "", "docker": "", - "port": "" + "port": "", + "game": "" }, "common": { "appName": "", diff --git a/client/src/locales/vi.json b/client/src/locales/vi.json index b3b8fc4ce..22b96487c 100644 --- a/client/src/locales/vi.json +++ b/client/src/locales/vi.json @@ -260,6 +260,7 @@ "notifyEmails": "", "seperateEmails": "", "checkFrequency": "", + "chooseGame": "", "matchMethod": "", "expectedValue": "", "deleteDialogTitle": "", @@ -435,7 +436,8 @@ "http": "", "ping": "", "docker": "", - "port": "" + "port": "", + "game": "" }, "common": { "appName": "", diff --git a/client/src/locales/zh-TW.json b/client/src/locales/zh-TW.json index 56f1841d4..90a1aa30c 100644 --- a/client/src/locales/zh-TW.json +++ b/client/src/locales/zh-TW.json @@ -260,6 +260,7 @@ "notifyEmails": "", "seperateEmails": "", "checkFrequency": "", + "chooseGame": "", "matchMethod": "", "expectedValue": "", "deleteDialogTitle": "", @@ -437,7 +438,8 @@ "http": "", "ping": "", "docker": "", - "port": "" + "port": "", + "game": "" }, "common": { "appName": "", diff --git a/server/package-lock.json b/server/package-lock.json index a41a50518..b29fb0642 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -19,6 +19,7 @@ "dotenv": "^16.4.5", "express": "^4.19.2", "express-rate-limit": "8.0.1", + "gamedig": "^5.3.1", "handlebars": "^4.7.8", "helmet": "^8.0.0", "ioredis": "^5.4.2", @@ -1031,6 +1032,18 @@ "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", "license": "BSD-3-Clause" }, + "node_modules/@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -1080,15 +1093,16 @@ "dev": true, "license": "(Unlicense OR Apache-2.0)" }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "license": "ISC", - "optional": true, - "peer": true, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.1" + }, "engines": { - "node": ">=10.13.0" + "node": ">=14.16" } }, "node_modules/@types/estree": { @@ -1098,6 +1112,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -1249,6 +1269,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -1328,6 +1354,39 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/barse": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/barse/-/barse-0.4.3.tgz", + "integrity": "sha512-UEpvriJqAn8zuVinYICuKoPttZy3XxXEoqX/V2uYAL4zzJRuNzCK3+20nAu3YUIa2U7G53kf90wfBIp9/A+Odw==", + "license": "MIT", + "dependencies": { + "readable-stream": "~1.0.2" + } + }, + "node_modules/barse/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "license": "MIT" + }, + "node_modules/barse/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/barse/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "license": "MIT" + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -1619,6 +1678,33 @@ } } }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -2263,21 +2349,6 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, "node_modules/css-what": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", @@ -2474,6 +2545,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/deep-eql": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", @@ -2491,6 +2589,15 @@ "dev": true, "license": "MIT" }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2707,6 +2814,14 @@ "integrity": "sha512-k4McmnB2091YIsdCgkS0fMVMPOJgxl93ltFzaryXqwip1AaxeDqKCGLxkXODDA5Ab/D+tV5EL5+aTx76RvLRxw==", "license": "ISC" }, + "node_modules/emitter-component": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/emitter-component/-/emitter-component-1.1.2.tgz", + "integrity": "sha512-QdXO3nXOzZB4pAjM0n6ZE+R9/+kPpECA/XSELIcc54NeYVnBqIk+4DFiBgK+8QbV3mdvTG6nedl7dTYgO+5wDw==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -3114,6 +3229,13 @@ "node": ">= 0.6" } }, + "node_modules/event-to-promise": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/event-to-promise/-/event-to-promise-0.7.0.tgz", + "integrity": "sha512-VOBBfyaADfe378ZzG0tgkzmsvzUyeU5arehrFzNRt5yaASUDshgctTwSrPI17ocAwR3+YftsxRClHF+GBKFByQ==", + "deprecated": "Use promise-toolbox/fromEvent instead", + "license": "MIT" + }, "node_modules/express": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", @@ -3220,6 +3342,24 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-xml-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.3.tgz", + "integrity": "sha512-OdCYfRqfpuLUFonTNjvd30rCBZUneHpSQkCqfaeWQ9qrKcl6XlWeDBNVwGb+INAIxRshuN2jF+BE0L6gbBO2mw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fecha": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", @@ -3413,6 +3553,15 @@ "node": ">= 6" } }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "license": "MIT", + "engines": { + "node": ">= 14.17" + } + }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -3472,6 +3621,41 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gamedig": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/gamedig/-/gamedig-5.3.1.tgz", + "integrity": "sha512-RM/eCR8bAKEX+5dA9sxg4D6oQjS5t0MPsIxGquerBLSIu0f1hAVaDGC58Lp1srYwF6A1C2wYY2p9PzOrPTKf4Q==", + "license": "MIT", + "dependencies": { + "fast-xml-parser": "5.2.3", + "gbxremote": "0.2.1", + "got": "13.0.0", + "iconv-lite": "0.6.3", + "long": "5.3.2", + "minimist": "1.2.8", + "seek-bzip": "2.0.0", + "telnet-client": "2.2.5", + "varint": "6.0.0" + }, + "bin": { + "gamedig": "bin/gamedig.js" + }, + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/gamedig/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/gaxios": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", @@ -3488,6 +3672,21 @@ "node": ">=14" } }, + "node_modules/gbxremote": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/gbxremote/-/gbxremote-0.2.1.tgz", + "integrity": "sha512-SMehu6Y6ndq2Qgp9VxAb8Np3f+UUD+RWoW2SAMaxzGS96rWXyr4T1GGkecO0HHtxeH1m7pEh4FJWB8a/6aM2XQ==", + "dependencies": { + "any-promise": "^1.1.0", + "barse": "~0.4.2", + "event-to-promise": "^0.7.0", + "string-to-stream": "^1.0.1", + "xmlrpc": "^1.3.1" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -3534,6 +3733,18 @@ "node": ">= 0.4" } }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -3616,6 +3827,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/got": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", + "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, "node_modules/handlebars": { "version": "4.7.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", @@ -3777,6 +4013,12 @@ "entities": "^4.4.0" } }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause" + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -3793,6 +4035,19 @@ "node": ">= 0.8" } }, + "node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -4174,7 +4429,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, "license": "MIT" }, "node_modules/json-parse-even-better-errors": { @@ -4336,7 +4590,6 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -4536,6 +4789,18 @@ "dev": true, "license": "MIT" }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -4587,14 +4852,6 @@ "node": ">= 0.4" } }, - "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "license": "CC0-1.0", - "optional": true, - "peer": true - }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -4676,6 +4933,18 @@ "node": ">= 0.6" } }, + "node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -5498,6 +5767,12 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "license": "MIT" }, + "node_modules/net": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/net/-/net-1.0.2.tgz", + "integrity": "sha512-kbhcj2SVVR4caaVnGLJKmlk2+f+oLkjqdKeQlmUtz6nGzOpbcobwVIeSURNgraV/v3tlmGIX82OcPCl0K6RbHQ==", + "license": "MIT" + }, "node_modules/nise": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz", @@ -5681,6 +5956,18 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-url": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.2.tgz", + "integrity": "sha512-Ee/R3SyN4BuynXcnTaekmaVdbDAEiNrHqjQIA37mHU8G9pf7aaAD4ZX3XjBLo6rsdcxA/gtkcNYZLt30ACgynw==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -5777,6 +6064,15 @@ "node": ">= 0.8.0" } }, + "node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -6733,6 +7029,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/rambda": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.5.0.tgz", @@ -6830,6 +7138,12 @@ "node": ">=0.10.0" } }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "license": "MIT" + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -6839,6 +7153,21 @@ "node": ">=4" } }, + "node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "license": "MIT", + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -6880,6 +7209,28 @@ "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "license": "ISC" }, + "node_modules/seek-bzip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-2.0.0.tgz", + "integrity": "sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg==", + "license": "MIT", + "dependencies": { + "commander": "^6.0.0" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, + "node_modules/seek-bzip/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -7267,6 +7618,15 @@ "node": ">= 0.8" } }, + "node_modules/stream": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stream/-/stream-0.0.2.tgz", + "integrity": "sha512-gCq3NDI2P35B2n6t76YJuOp7d6cN/C7Rt0577l91wllh0sY9ZBuw9KaSGqH/b0hzn3CWWJbpbW0W0WvQ1H/Q7g==", + "license": "MIT", + "dependencies": { + "emitter-component": "^1.1.1" + } + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -7284,6 +7644,46 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-to-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string-to-stream/-/string-to-stream-1.1.1.tgz", + "integrity": "sha512-QySF2+3Rwq0SdO3s7BAp4x+c3qsClpPQ6abAmb0DGViiSBAkT5kL6JT2iyzEVP+T1SmzHrQD1TwlP9QAHCc+Sw==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.1.0" + } + }, + "node_modules/string-to-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/string-to-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/string-to-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -7393,6 +7793,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/stylehacks": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.6.tgz", @@ -7516,44 +7928,6 @@ "node": ">=8" } }, - "node_modules/svgo": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", - "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^5.1.0", - "css-tree": "^2.3.1", - "css-what": "^6.1.0", - "csso": "^5.0.5", - "picocolors": "^1.0.0" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/svgo" - } - }, - "node_modules/svgo/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 10" - } - }, "node_modules/swagger-ui-dist": { "version": "5.27.0", "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.27.0.tgz", @@ -7606,6 +7980,20 @@ "node": ">=6" } }, + "node_modules/telnet-client": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/telnet-client/-/telnet-client-2.2.5.tgz", + "integrity": "sha512-X5xEkmKHgpCpngnH7QnkFX87UyBErauHsjzlCGVp87MbhnmHoaAeacuALGfqovHh3MXAfrpPs+g30PgyBpy8Jw==", + "license": "MIT", + "dependencies": { + "net": "^1.0.2", + "stream": "^0.0.2" + }, + "funding": { + "type": "paypal", + "url": "https://paypal.me/kozjak" + } + }, "node_modules/test-exclude": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", @@ -7917,6 +8305,12 @@ "node": ">=10" } }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", + "license": "MIT" + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -8283,6 +8677,35 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/xmlbuilder": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", + "integrity": "sha512-eKRAFz04jghooy8muekqzo8uCSVNeyRedbuJrp0fovbLIi7wlsYtdUn3vBAAPq2Y3/0xMz2WMEUQ8yhVVO9Stw==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xmlrpc": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/xmlrpc/-/xmlrpc-1.3.2.tgz", + "integrity": "sha512-jQf5gbrP6wvzN71fgkcPPkF4bF/Wyovd7Xdff8d6/ihxYmgETQYSuTc+Hl+tsh/jmgPLro/Aro48LMFlIyEKKQ==", + "license": "MIT", + "dependencies": { + "sax": "1.2.x", + "xmlbuilder": "8.2.x" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.0.0" + } + }, + "node_modules/xmlrpc/node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "license": "ISC" + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/server/package.json b/server/package.json index 4768fb45f..3e52d6983 100755 --- a/server/package.json +++ b/server/package.json @@ -26,6 +26,7 @@ "dotenv": "^16.4.5", "express": "^4.19.2", "express-rate-limit": "8.0.1", + "gamedig": "^5.3.1", "handlebars": "^4.7.8", "helmet": "^8.0.0", "ioredis": "^5.4.2", diff --git a/server/src/db/models/Monitor.js b/server/src/db/models/Monitor.js index 266cebeef..1ee467999 100755 --- a/server/src/db/models/Monitor.js +++ b/server/src/db/models/Monitor.js @@ -33,7 +33,7 @@ const MonitorSchema = mongoose.Schema( type: { type: String, required: true, - enum: ["http", "ping", "pagespeed", "hardware", "docker", "port"], + enum: ["http", "ping", "pagespeed", "hardware", "docker", "port", "game"], }, ignoreTlsErrors: { type: Boolean, @@ -115,6 +115,9 @@ const MonitorSchema = mongoose.Schema( return this.alertThreshold; }, }, + gameId: { + type: String, + }, }, { timestamps: true, diff --git a/server/src/db/mongo/modules/checkModule.js b/server/src/db/mongo/modules/checkModule.js index f806b5bf5..a606a593b 100755 --- a/server/src/db/mongo/modules/checkModule.js +++ b/server/src/db/mongo/modules/checkModule.js @@ -86,6 +86,7 @@ class CheckModule { port: this.Check, pagespeed: this.PageSpeedCheck, hardware: this.HardwareCheck, + game: this.Check, }; const Model = checkModels[type]; diff --git a/server/src/db/mongo/modules/monitorModule.js b/server/src/db/mongo/modules/monitorModule.js index 76f055f94..c4a896030 100755 --- a/server/src/db/mongo/modules/monitorModule.js +++ b/server/src/db/mongo/modules/monitorModule.js @@ -310,7 +310,7 @@ class MonitorModule { checks: this.processChecksForDisplay(this.NormalizeData, checksForDateRange, numToDisplay, normalize), }; - if (monitor.type === "http" || monitor.type === "ping" || monitor.type === "docker" || monitor.type === "port") { + if (monitor.type === "http" || monitor.type === "ping" || monitor.type === "docker" || monitor.type === "port" || monitor.type === "game") { // HTTP/PING Specific stats monitorStats.periodAvgResponseTime = this.getAverageResponseTime(checksForDateRange); monitorStats.periodUptime = this.getUptimePercentage(checksForDateRange); diff --git a/server/src/db/mongo/modules/monitorModuleQueries.js b/server/src/db/mongo/modules/monitorModuleQueries.js index d96e128fa..d5fea1931 100755 --- a/server/src/db/mongo/modules/monitorModuleQueries.js +++ b/server/src/db/mongo/modules/monitorModuleQueries.js @@ -819,7 +819,7 @@ const buildGetMonitorsByTeamIdPipeline = (req) => { $switch: { branches: [ { - case: { $in: ["$type", ["http", "ping", "docker", "port"]] }, + case: { $in: ["$type", ["http", "ping", "docker", "port", "game"]] }, then: "$standardchecks", }, { diff --git a/server/src/service/business/monitorService.js b/server/src/service/business/monitorService.js index c52b5cadf..c6984a327 100644 --- a/server/src/service/business/monitorService.js +++ b/server/src/service/business/monitorService.js @@ -71,7 +71,7 @@ class MonitorService { }; createMonitor = async ({ teamId, userId, body }) => { - const monitor = await this.db.createMonitor({ + const monitor = await this.db.monitorModule.createMonitor({ body, teamId, userId, @@ -122,7 +122,7 @@ class MonitorService { await createMonitorsBodyValidation.validateAsync(enrichedData); - const monitors = await this.db.createBulkMonitors(enrichedData); + const monitors = await this.db.monitorModule.createBulkMonitors(enrichedData); await Promise.all( monitors.map(async (monitor) => { @@ -141,14 +141,14 @@ class MonitorService { deleteMonitor = async ({ teamId, monitorId }) => { await this.verifyTeamAccess({ teamId, monitorId }); - const monitor = await this.db.deleteMonitor({ teamId, monitorId }); + const monitor = await this.db.monitorModule.deleteMonitor({ teamId, monitorId }); await this.jobQueue.deleteJob(monitor); await this.db.statusPageModule.deleteStatusPagesByMonitorId(monitor._id); return monitor; }; deleteAllMonitors = async ({ teamId }) => { - const { monitors, deletedCount } = await this.db.deleteAllMonitors(teamId); + const { monitors, deletedCount } = await this.db.monitorModule.deleteAllMonitors(teamId); await Promise.all( monitors.map(async (monitor) => { try { @@ -171,19 +171,19 @@ class MonitorService { editMonitor = async ({ teamId, monitorId, body }) => { await this.verifyTeamAccess({ teamId, monitorId }); - const editedMonitor = await this.db.editMonitor({ monitorId, body }); + const editedMonitor = await this.db.monitorModule.editMonitor({ monitorId, body }); await this.jobQueue.updateJob(editedMonitor); }; pauseMonitor = async ({ teamId, monitorId }) => { await this.verifyTeamAccess({ teamId, monitorId }); - const monitor = await this.db.pauseMonitor({ monitorId }); + const monitor = await this.db.monitorModule.pauseMonitor({ monitorId }); monitor.isActive === true ? await this.jobQueue.resumeJob(monitor._id, monitor) : await this.jobQueue.pauseJob(monitor); return monitor; }; addDemoMonitors = async ({ userId, teamId }) => { - const demoMonitors = await this.db.addDemoMonitors(userId, teamId); + const demoMonitors = await this.db.monitorModule.addDemoMonitors(userId, teamId); await Promise.all(demoMonitors.map((monitor) => this.jobQueue.addJob(monitor._id, monitor))); return demoMonitors; }; diff --git a/server/src/service/infrastructure/bufferService.js b/server/src/service/infrastructure/bufferService.js index 46da6bef3..dc86597b2 100755 --- a/server/src/service/infrastructure/bufferService.js +++ b/server/src/service/infrastructure/bufferService.js @@ -6,6 +6,7 @@ const TYPE_MAP = { docker: "checks", pagespeed: "pagespeedChecks", hardware: "hardwareChecks", + game: "checks", }; class BufferService { diff --git a/server/src/service/infrastructure/networkService.js b/server/src/service/infrastructure/networkService.js index 6a2e3c8a7..ca3c69bab 100755 --- a/server/src/service/infrastructure/networkService.js +++ b/server/src/service/infrastructure/networkService.js @@ -1,5 +1,6 @@ import jmespath from "jmespath"; import https from "https"; +import { GameDig } from 'gamedig'; const SERVICE_NAME = "NetworkService"; const UPROCK_ENDPOINT = "https://api.uprock.com/checkmate/push"; @@ -23,6 +24,7 @@ class NetworkService { this.TYPE_HARDWARE = "hardware"; this.TYPE_DOCKER = "docker"; this.TYPE_PORT = "port"; + this.TYPE_GAME = "game"; this.SERVICE_NAME = SERVICE_NAME; this.NETWORK_ERROR = 5000; this.PING_ERROR = 5001; @@ -470,6 +472,57 @@ class NetworkService { } } + /** + * Requests the status of a game monitor. + * + * @param {Object} monitor - The monitor object to request the status for. + * @returns {Promise} The response from the game status request. + * @throws {Error} Throws an error if the request fails or if the monitor is not configured correctly. + * @property {string} monitorId - The ID of the monitor. + * @property {string} type - The type of the monitor (should be "game"). + * @property {number} responseTime - The time taken for the request. + * @property {Object|null} payload - The game state response or null if the request failed. + * @property {boolean} status - Indicates if the request was successful (true) or not (false). + * @property {number} code - The status code of the request (200 for success, NETWORK_ERROR for failure). + * @property {string} message - A message indicating the result of the request. + */ + async requestGame(monitor) { + try { + const { url, port, gameId } = monitor; + + const gameResponse = { + code: 200, + status: true, + message: "Success", + monitorId: monitor._id, + type: "game", + }; + + const state = await GameDig.query({ + type: gameId, + host: url, + port: port, + }).catch((error) => { + return false; + }); + + if (!state) { + gameResponse.status = false; + gameResponse.code = this.NETWORK_ERROR; + gameResponse.message = "No response"; + return gameResponse; + } + + gameResponse.responseTime = state.ping; + gameResponse.payload = state; + return gameResponse; + } catch (error) { + error.service = this.SERVICE_NAME; + error.method = "requestPing"; + throw error; + } + } + /** * Gets the status of a job based on its type and returns the appropriate response. * @@ -494,6 +547,8 @@ class NetworkService { return await this.requestDocker(monitor); case this.TYPE_PORT: return await this.requestPort(monitor); + case this.TYPE_GAME: + return await this.requestGame(monitor); default: return this.handleUnsupportedType(type); } diff --git a/server/src/validation/joi.js b/server/src/validation/joi.js index f2c15205b..ee1c9bb4c 100755 --- a/server/src/validation/joi.js +++ b/server/src/validation/joi.js @@ -124,8 +124,8 @@ const getMonitorsByTeamIdQueryValidation = joi.object({ type: joi .alternatives() .try( - joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port"), - joi.array().items(joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port")) + joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port", "game"), + joi.array().items(joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port", "game")) ), page: joi.number(), rowsPerPage: joi.number(), @@ -171,6 +171,7 @@ const createMonitorBodyValidation = joi.object({ jsonPath: joi.string().allow(""), expectedValue: joi.string().allow(""), matchMethod: joi.string(), + gameId: joi.string() }); const createMonitorsBodyValidation = joi.array().items( @@ -197,6 +198,7 @@ const editMonitorBodyValidation = joi.object({ usage_disk: joi.number(), usage_temperature: joi.number(), }), + gameId: joi.string() }); const pauseMonitorParamValidation = joi.object({ @@ -294,7 +296,7 @@ const getChecksParamValidation = joi.object({ }); const getChecksQueryValidation = joi.object({ - type: joi.string().valid("http", "ping", "pagespeed", "hardware", "docker", "port"), + type: joi.string().valid("http", "ping", "pagespeed", "hardware", "docker", "port", "game"), sortOrder: joi.string().valid("asc", "desc"), limit: joi.number(), dateRange: joi.string().valid("recent", "hour", "day", "week", "month", "all"), From b53650a50f3a45284789add6aa4b947962659a2e Mon Sep 17 00:00:00 2001 From: Burak Arslan Date: Sat, 2 Aug 2025 02:25:58 +0300 Subject: [PATCH 195/259] Run npm run format --- client/src/Hooks/monitorHooks.js | 4 +- client/src/Pages/Uptime/Create/index.jsx | 34 +- client/src/Utils/games.js | 6898 ++++++++--------- client/src/Validation/validation.js | 2 +- .../service/infrastructure/networkService.js | 4 +- server/src/validation/joi.js | 4 +- 6 files changed, 3475 insertions(+), 3471 deletions(-) diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index 4a19f2fa7..8b2a054e6 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -357,7 +357,9 @@ const useUpdateMonitor = () => { expectedValue: monitor.expectedValue, ignoreTlsErrors: monitor.ignoreTlsErrors, jsonPath: monitor.jsonPath, - ...((monitor.type === "port" || monitor.type === "game") && { port: monitor.port }), + ...((monitor.type === "port" || monitor.type === "game") && { + port: monitor.port, + }), ...(monitor.type === "hardware" && { thresholds: monitor.thresholds, secret: monitor.secret, diff --git a/client/src/Pages/Uptime/Create/index.jsx b/client/src/Pages/Uptime/Create/index.jsx index 3a1080421..5ab4de847 100644 --- a/client/src/Pages/Uptime/Create/index.jsx +++ b/client/src/Pages/Uptime/Create/index.jsx @@ -43,7 +43,7 @@ import { useFetchMonitorById, } from "../../../Hooks/monitorHooks"; -import { GAMES } from '../../../Utils/games'; +import { GAMES } from "../../../Utils/games"; /** * Parses a URL string and returns a URL object. @@ -115,10 +115,10 @@ const UptimeCreate = ({ isClone = false }) => { { _id: 4, name: t("time.fourMinutes") }, { _id: 5, name: t("time.fiveMinutes") }, ]; - + const GAMELIST = Object.entries(GAMES).map(([key, value]) => ({ _id: key, - name: value.name + name: value.name, })); const CRUMBS = [ @@ -165,7 +165,7 @@ const UptimeCreate = ({ isClone = false }) => { label: t("monitorType.game.label"), placeholder: t("monitorType.game.placeholder"), namePlaceholder: t("monitorType.game.namePlaceholder"), - } + }, }; // Handlers @@ -182,7 +182,8 @@ const UptimeCreate = ({ isClone = false }) => { : monitor.url, name: monitor.name || monitor.url.substring(0, 50), type: monitor.type, - port: (monitor.type === "port" || monitor.type === "game") ? monitor.port : undefined, + port: + monitor.type === "port" || monitor.type === "game" ? monitor.port : undefined, interval: monitor.interval, matchMethod: monitor.matchMethod, expectedValue: monitor.expectedValue, @@ -202,7 +203,8 @@ const UptimeCreate = ({ isClone = false }) => { interval: monitor.interval, teamId: monitor.teamId, userId: monitor.userId, - port: (monitor.type === "port" || monitor.type === "game") ? monitor.port : undefined, + port: + monitor.type === "port" || monitor.type === "game" ? monitor.port : undefined, ignoreTlsErrors: monitor.ignoreTlsErrors, gameId: monitor.gameId || undefined, }; @@ -573,16 +575,16 @@ const UptimeCreate = ({ isClone = false }) => { helperText={errors["port"]} hidden={monitor.type !== "port" && monitor.type !== "game"} /> - { - monitor.type === "game" && - ( + )} Date: Sat, 2 Aug 2025 12:34:31 +0530 Subject: [PATCH 196/259] Removed teh required true from network db. --- server/src/db/models/HardwareCheck.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/db/models/HardwareCheck.js b/server/src/db/models/HardwareCheck.js index f29df2dbc..3e65e2df4 100755 --- a/server/src/db/models/HardwareCheck.js +++ b/server/src/db/models/HardwareCheck.js @@ -41,7 +41,7 @@ const captureSchema = mongoose.Schema({ }); const networkInterfaceSchema = mongoose.Schema({ - name: { type: String, required: true }, + name: { type: String}, bytes_sent: { type: Number, default: 0 }, bytes_recv: { type: Number, default: 0 }, packets_sent: { type: Number, default: 0 }, @@ -87,6 +87,7 @@ const HardwareCheckSchema = mongoose.Schema( net: { type: [networkInterfaceSchema], default: () => [], + required: false, }, }, { timestamps: true } From 950c5fdc92a567046c3fd75c8d56fd57846c4aa6 Mon Sep 17 00:00:00 2001 From: owiaseimdad Date: Sat, 2 Aug 2025 12:42:35 +0530 Subject: [PATCH 197/259] Formating done. --- server/src/db/models/HardwareCheck.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/db/models/HardwareCheck.js b/server/src/db/models/HardwareCheck.js index 3e65e2df4..5b7d56213 100755 --- a/server/src/db/models/HardwareCheck.js +++ b/server/src/db/models/HardwareCheck.js @@ -41,7 +41,7 @@ const captureSchema = mongoose.Schema({ }); const networkInterfaceSchema = mongoose.Schema({ - name: { type: String}, + name: { type: String }, bytes_sent: { type: Number, default: 0 }, bytes_recv: { type: Number, default: 0 }, packets_sent: { type: Number, default: 0 }, From 91b4c976e5500badd4e08c7ef38edf9e9dad1ccb Mon Sep 17 00:00:00 2001 From: Jesulayomy Date: Sat, 2 Aug 2025 03:53:11 -0400 Subject: [PATCH 198/259] [Infra]: Refactor Create Component, Added Pause and Remove tool bar --- .../src/Pages/Infrastructure/Create/index.jsx | 334 +++++++++++++----- 1 file changed, 243 insertions(+), 91 deletions(-) diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx index cde9315aa..50609b588 100644 --- a/client/src/Pages/Infrastructure/Create/index.jsx +++ b/client/src/Pages/Infrastructure/Create/index.jsx @@ -1,78 +1,51 @@ -// React, Redux, Router -import { useTheme } from "@emotion/react"; -import { useParams } from "react-router-dom"; -import { useState, useEffect } from "react"; -import { useSelector } from "react-redux"; -// Utility and Network -import { infrastructureMonitorValidation } from "../../../Validation/validation"; -import { useFetchHardwareMonitorById } from "../../../Hooks/monitorHooks"; -import { capitalizeFirstLetter } from "../../../Utils/stringUtils"; -import { useTranslation } from "react-i18next"; -import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications"; -import NotificationsConfig from "../../../Components/NotificationConfig"; -import { - useUpdateMonitor, - useCreateMonitor, - useFetchGlobalSettings, -} from "../../../Hooks/monitorHooks"; - -// MUI -import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material"; - //Components import Breadcrumbs from "../../../Components/Breadcrumbs"; -import Link from "../../../Components/Link"; import ConfigBox from "../../../Components/ConfigBox"; +import Dialog from "../../../Components/Dialog"; +import FieldWrapper from "../../../Components/Inputs/FieldWrapper"; +import Link from "../../../Components/Link"; +import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline"; +import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded"; +import PulseDot from "../../../Components/Animated/PulseDot"; +import Select from "../../../Components/Inputs/Select"; import TextInput from "../../../Components/Inputs/TextInput"; +import { Box, Stack, Tooltip, Typography, Button, ButtonGroup } from "@mui/material"; +import { CustomThreshold } from "./Components/CustomThreshold"; import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments"; import { createToast } from "../../../Utils/toastUtils"; -import Select from "../../../Components/Inputs/Select"; -import { CustomThreshold } from "./Components/CustomThreshold"; -import FieldWrapper from "../../../Components/Inputs/FieldWrapper"; - -const SELECT_VALUES = [ - { _id: 0.25, name: "15 seconds" }, - { _id: 0.5, name: "30 seconds" }, - { _id: 1, name: "1 minute" }, - { _id: 2, name: "2 minutes" }, - { _id: 5, name: "5 minutes" }, - { _id: 10, name: "10 minutes" }, -]; - -const METRICS = ["cpu", "memory", "disk", "temperature"]; -const METRIC_PREFIX = "usage_"; -const MS_PER_MINUTE = 60000; - -const hasAlertError = (errors) => { - return Object.keys(errors).filter((k) => k.startsWith(METRIC_PREFIX)).length > 0; -}; - -const getAlertError = (errors) => { - return Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX)) - ? errors[Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX))] - : null; -}; +// Utils +import NotificationsConfig from "../../../Components/NotificationConfig"; +import { capitalizeFirstLetter } from "../../../Utils/stringUtils"; +import { infrastructureMonitorValidation } from "../../../Validation/validation"; +import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications"; +import { useMonitorUtils } from "../../../Hooks/useMonitorUtils"; +import { useParams } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { useState, useEffect } from "react"; +import { useTheme } from "@emotion/react"; +import { useTranslation } from "react-i18next"; +import { + useCreateMonitor, + useDeleteMonitor, + useFetchGlobalSettings, + useFetchHardwareMonitorById, + usePauseMonitor, + useUpdateMonitor, +} from "../../../Hooks/monitorHooks"; const CreateInfrastructureMonitor = () => { - const theme = useTheme(); const { user } = useSelector((state) => state.auth); const { monitorId } = useParams(); - const { t } = useTranslation(); - - // Determine if we are creating or editing const isCreate = typeof monitorId === "undefined"; - // Fetch monitor details if editing - const [monitor, isLoading, networkError] = useFetchHardwareMonitorById({ monitorId }); - const [notifications, notificationsAreLoading, notificationsError] = - useGetNotificationsByTeamId(); - const [updateMonitor, isUpdating] = useUpdateMonitor(); - const [createMonitor, isCreating] = useCreateMonitor(); - const [globalSettings, globalSettingsLoading] = useFetchGlobalSettings(); + const theme = useTheme(); + const { t } = useTranslation(); // State const [errors, setErrors] = useState({}); const [https, setHttps] = useState(false); + const [isOpen, setIsOpen] = useState(false); + const [updateTrigger, setUpdateTrigger] = useState(false); const [infrastructureMonitor, setInfrastructureMonitor] = useState({ url: "", name: "", @@ -90,8 +63,51 @@ const CreateInfrastructureMonitor = () => { secret: "", }); - // Populate form fields if editing + // Fetch monitor details if editing + const { statusColor, pagespeedStatusMsg, determineState } = useMonitorUtils(); + const [monitor, isLoading] = useFetchHardwareMonitorById({ + monitorId, + updateTrigger, + }); + const [createMonitor, isCreating] = useCreateMonitor(); + const [deleteMonitor, isDeleting] = useDeleteMonitor(); + const [globalSettings, globalSettingsLoading] = useFetchGlobalSettings(); + const [notifications, notificationsAreLoading] = useGetNotificationsByTeamId(); + const [pauseMonitor, isPausing] = usePauseMonitor(); + const [updateMonitor, isUpdating] = useUpdateMonitor(); + const FREQUENCIES = [ + { _id: 0.25, name: t("time.fifteenSeconds") }, + { _id: 0.5, name: t("time.thirtySeconds") }, + { _id: 1, name: t("time.oneMinute") }, + { _id: 2, name: t("time.twoMinutes") }, + { _id: 5, name: t("time.fiveMinutes") }, + { _id: 10, name: t("time.tenMinutes") }, + ]; + const CRUMBS = [ + { name: "Infrastructure monitors", path: "/infrastructure" }, + ...(isCreate + ? [{ name: "Create", path: "/infrastructure/create" }] + : [ + { name: "Details", path: `/infrastructure/${monitorId}` }, + { name: "Configure", path: `/infrastructure/configure/${monitorId}` }, + ]), + ]; + const METRICS = ["cpu", "memory", "disk", "temperature"]; + const METRIC_PREFIX = "usage_"; + const MS_PER_MINUTE = 60000; + + const hasAlertError = (errors) => { + return Object.keys(errors).filter((k) => k.startsWith(METRIC_PREFIX)).length > 0; + }; + + const getAlertError = (errors) => { + return Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX)) + ? errors[Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX))] + : null; + }; + + // Populate form fields if editing useEffect(() => { if (isCreate) { if (globalSettingsLoading) return; @@ -231,6 +247,10 @@ const CreateInfrastructureMonitor = () => { : await updateMonitor({ monitor: form, redirect: "/infrastructure" }); }; + const triggerUpdate = () => { + setUpdateTrigger(!updateTrigger); + }; + const onChange = (event) => { const { value, name } = event.target; setInfrastructureMonitor({ @@ -257,19 +277,26 @@ const CreateInfrastructureMonitor = () => { }); }; + const handlePause = async () => { + await pauseMonitor({ monitorId, triggerUpdate }); + }; + + const handleRemove = async (event) => { + event.preventDefault(); + await deleteMonitor({ monitor, redirect: "/infrastructure" }); + }; + + const isBusy = + isLoading || + isUpdating || + isCreating || + isDeleting || + isPausing || + notificationsAreLoading; + return ( - + { gap={theme.spacing(12)} mt={theme.spacing(6)} > - - - {t(isCreate ? "infrastructureCreateYour" : "infrastructureEditYour")}{" "} - - - {t("monitor")} - - + + + + {!isCreate ? infrastructureMonitor.name : t("createYour") + " "} + + {isCreate ? ( + + {t("monitor")} + + ) : ( + <> + )} + + {!isCreate && ( + + + + + + + + {infrastructureMonitor.url?.replace(/^https?:\/\//, "") || "..."} + + + {t("editing")} + + + )} + + {!isCreate && ( + + + + + )} + { label="Check frequency" value={infrastructureMonitor.interval || 15} onChange={onChange} - items={SELECT_VALUES} + items={FREQUENCIES} /> @@ -474,12 +615,23 @@ const CreateInfrastructureMonitor = () => { type="submit" variant="contained" color="accent" - loading={isLoading || isUpdating || isCreating || notificationsAreLoading} + loading={isBusy} > {t(isCreate ? "infrastructureCreateMonitor" : "infrastructureEditMonitor")} + {!isCreate && ( + setIsOpen(false)} + confirmationButtonLabel={t("delete")} + onConfirm={handleRemove} + /> + )} ); }; From 2fc0eb9460b65db9594b56ec2d669a984afc4c3c Mon Sep 17 00:00:00 2001 From: Jesulayomy Date: Sat, 2 Aug 2025 03:53:54 -0400 Subject: [PATCH 199/259] [Infra]: Added translations for some time blocks --- client/src/locales/en.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 605a50fa0..230637474 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -980,6 +980,7 @@ "testLocale": "testLocale", "testNotificationsDisabled": "There are no notifications setup for this monitor. You need to add one by clicking 'Configure' button", "time": { + "fifteenSeconds": "15 seconds", "fiveMinutes": "5 minutes", "fourMinutes": "4 minutes", "oneDay": "1 day", @@ -987,6 +988,7 @@ "oneMinute": "1 minute", "oneWeek": "1 week", "tenMinutes": "10 minutes", + "thirtySeconds": "30 seconds", "threeMinutes": "3 minutes", "twentyMinutes": "20 minutes", "twoMinutes": "2 minutes" From aa35e9211bbb135af0178b1046f3a55c48582552 Mon Sep 17 00:00:00 2001 From: Jesulayomy Date: Sat, 2 Aug 2025 04:13:29 -0400 Subject: [PATCH 200/259] [Infra]: Removed duplicate call to .find method on the errors object --- client/src/Pages/Infrastructure/Create/index.jsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx index 50609b588..ae95cce33 100644 --- a/client/src/Pages/Infrastructure/Create/index.jsx +++ b/client/src/Pages/Infrastructure/Create/index.jsx @@ -102,9 +102,8 @@ const CreateInfrastructureMonitor = () => { }; const getAlertError = (errors) => { - return Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX)) - ? errors[Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX))] - : null; + const errorKey = Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX)); + return errorKey ? errors[errorKey] : null; }; // Populate form fields if editing From 7b963ca87777b16031f7a3c7e265b19afda80d65 Mon Sep 17 00:00:00 2001 From: owiaseimdad Date: Sun, 3 Aug 2025 00:30:04 +0530 Subject: [PATCH 201/259] Network tab first implementation --- .../Components/NetworkStats/NetworkCharts.jsx | 59 +++++++++++ .../NetworkStats/NetworkStatBoxes.jsx | 82 ++++++++++++++ .../Details/Components/NetworkStats/index.jsx | 100 ++++++++++++++++++ .../Components/NetworkStats/skeleton.jsx | 56 ++++++++++ .../Pages/Infrastructure/Details/index.jsx | 69 ++++++++---- 5 files changed, 347 insertions(+), 19 deletions(-) create mode 100644 client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx create mode 100644 client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx create mode 100644 client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx create mode 100644 client/src/Pages/Infrastructure/Details/Components/NetworkStats/skeleton.jsx diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx new file mode 100644 index 000000000..497fa1410 --- /dev/null +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx @@ -0,0 +1,59 @@ +// NetworkCharts.jsx +import { Grid, Card, CardContent, Typography } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; +import AreaChart from "../../../../../Components/Charts/AreaChart"; +import { TzTick } from '../../../../../Components/Charts/Utils/chartUtils'; + +const BytesTick = ({ x, y, payload }) => { + const value = payload.value; + const label = + value >= 1024 ** 3 + ? `${(value / 1024 ** 3).toFixed(2)} GB` + : value >= 1024 ** 2 + ? `${(value / 1024 ** 2).toFixed(2)} MB` + : `${(value / 1024).toFixed(2)} KB`; + + return {label}; +}; + +const NetworkCharts = ({ eth0Data, dateRange }) => { + const theme = useTheme(); + const textColor = theme.palette.primary.contrastTextTertiary; + + const charts = [ + { title: "eth0 Bytes/sec", key: "bytesPerSec", color: theme.palette.info.main, yTick: }, + { title: "eth0 Packets/sec", key: "packetsPerSec", color: theme.palette.success.main }, + { title: "eth0 Errors", key: "errors", color: theme.palette.error.main }, + { title: "eth0 Drops", key: "drops", color: theme.palette.warning.main } + ]; + + return ( + + {charts.map((chart) => ( + + + + + {chart.title} + + } + strokeColor={chart.color} + gradient + gradientStartColor={chart.color} + gradientEndColor="#fff" + height={200} + /> + + + + ))} + + ); +}; + +export default NetworkCharts; \ No newline at end of file diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx new file mode 100644 index 000000000..abe67fa8e --- /dev/null +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx @@ -0,0 +1,82 @@ +// NetworkStatBoxes.jsx +import DataUsageIcon from "@mui/icons-material/DataUsage"; +import NetworkCheckIcon from "@mui/icons-material/NetworkCheck"; +import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline"; +import StatusBoxes from "../../../../../Components/StatusBoxes"; +import StatBox from "../../../../../Components/StatBox"; +import { Typography } from "@mui/material"; + +const INTERFACE_LABELS = { + en0: "Ethernet/Wi-Fi (Primary)", + wlan0: "Wi-Fi (Secondary)", +}; + +function formatBytes(bytes) { + if (bytes === 0 || bytes == null) return "0 B"; + const k = 1024; + const sizes = ["B", "KB", "MB", "GB", "TB"]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`; +} + +// Format numbers with commas +function formatNumber(num) { + return num != null ? num.toLocaleString() : "0"; +} + +const NetworkStatBoxes = ({ shouldRender, net }) => { + const filtered = net?.filter( + (iface) => iface.name === "en0" || iface.name === "wlan0" + ) || []; + + if (!net?.length) { + return No network stats available.; + } + + return ( + + {filtered.map((iface) => ( + <> + + + + + + + + ))} + + ); +}; + +export default NetworkStatBoxes; diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx new file mode 100644 index 000000000..ecb62e769 --- /dev/null +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx @@ -0,0 +1,100 @@ +import NetworkStatBoxes from "./NetworkStatBoxes"; +import NetworkCharts from "./NetworkCharts"; +import MonitorTimeFrameHeader from "../../../../../Components/MonitorTimeFrameHeader"; + +function filterByDateRange(data, dateRange) { + if (!Array.isArray(data)) return []; + const now = Date.now(); + let cutoff; + switch (dateRange) { + case "recent": + cutoff = now - 2 * 60 * 60 * 1000; // last 2 hours + break; + case "day": + cutoff = now - 24 * 60 * 60 * 1000; // last 24 hours + break; + case "week": + cutoff = now - 7 * 24 * 60 * 60 * 1000; // last 7 days + break; + case "month": + cutoff = now - 30 * 24 * 60 * 60 * 1000; // last 30 days + break; + default: + cutoff = 0; + } + return data.filter((d) => new Date(d.time).getTime() >= cutoff); +} + +const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => { + const eth0Data = getEth0TimeSeries(checks); + const xAxisFormatter = getXAxisFormatter(checks); + const filteredEth0Data = filterByDateRange(eth0Data, dateRange); + + return ( + <> + + + + + ); +}; + +export default Network; + +/* ---------- Helper functions ---------- */ +function getEth0TimeSeries(checks) { + const sorted = [...(checks || [])].sort((a, b) => new Date(a._id) - new Date(b._id)); + const series = []; + let prev = null; + + for (const check of sorted) { + const eth = (check.net || []).find((iface) => iface.name === "en0"); + if (!eth) { + prev = check; + continue; + } + if (prev) { + const prevEth = (prev.net || []).find((iface) => iface.name === "en0"); + const t1 = new Date(check._id); + const t0 = new Date(prev._id); + if (!prevEth || isNaN(t1) || isNaN(t0)) { + prev = check; + continue; + } + const dt = (t1 - t0) / 1000; + if (dt > 0) { + series.push({ + time: check._id, + bytesPerSec: (eth.bytesSent - prevEth.bytesSent) / dt, + packetsPerSec: (eth.packetsSent - prevEth.packetsSent) / dt, + errors: (eth.errIn ?? 0) + (eth.errOut ?? 0), + drops: (eth.dropIn ?? 0) + (eth.dropOut ?? 0), + }); + } + } + prev = check; + } + + return series; +} + +function getXAxisFormatter(checks) { + if (!checks || checks.length === 0) return (val) => val; + const sorted = [...checks].sort((a, b) => new Date(a._id) - new Date(b._id)); + const first = new Date(sorted[0]._id); + const last = new Date(sorted[sorted.length - 1]._id); + const diffDays = (last - first) / (1000 * 60 * 60 * 24); + + return diffDays > 2 + ? (val) => new Date(val).toLocaleDateString(undefined, { month: "short", day: "numeric" }) + : (val) => + new Date(val).toLocaleTimeString(undefined, { + hour: "2-digit", + minute: "2-digit", + hour12: false, + }); +} diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/skeleton.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/skeleton.jsx new file mode 100644 index 000000000..52fcfad87 --- /dev/null +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/skeleton.jsx @@ -0,0 +1,56 @@ +import { + Card, + CardContent, + Skeleton, + Table, + TableHead, + TableRow, + TableCell, + TableBody, +} from "@mui/material"; + +const SkeletonLayout = () => { + return ( + + + + + + + Name + Bytes Sent + Bytes Received + Packets Sent + Packets Received + Errors In + Errors Out + Drops In + Drops Out + + + + {Array.from({ length: 5 }).map((_, idx) => ( + + {Array.from({ length: 9 }).map((__, colIdx) => ( + + + + ))} + + ))} + +
+
+
+ ); +}; + +export default SkeletonLayout; diff --git a/client/src/Pages/Infrastructure/Details/index.jsx b/client/src/Pages/Infrastructure/Details/index.jsx index 30840851f..78b78fa78 100644 --- a/client/src/Pages/Infrastructure/Details/index.jsx +++ b/client/src/Pages/Infrastructure/Details/index.jsx @@ -1,5 +1,5 @@ // Components -import { Stack, Typography } from "@mui/material"; +import { Stack, Typography, Tab } from "@mui/material"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import MonitorDetailsControlHeader from "../../../Components/MonitorDetailsControlHeader"; import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader"; @@ -7,6 +7,9 @@ import StatusBoxes from "./Components/StatusBoxes"; import GaugeBoxes from "./Components/GaugeBoxes"; import AreaChartBoxes from "./Components/AreaChartBoxes"; import GenericFallback from "../../../Components/GenericFallback"; +import NetworkStats from "./Components/NetworkStats"; +import CustomTabList from "../../../Components/Tab"; +import TabContext from "@mui/lab/TabContext"; // Utils import { useTheme } from "@emotion/react"; @@ -22,11 +25,11 @@ const BREADCRUMBS = [ { name: "details", path: "" }, ]; const InfrastructureDetails = () => { - // Redux state // Local state const [dateRange, setDateRange] = useState("recent"); const [trigger, setTrigger] = useState(false); + const [tab, setTab] = useState("details"); // Utils const theme = useTheme(); @@ -87,23 +90,51 @@ const InfrastructureDetails = () => { monitor={monitor} triggerUpdate={triggerUpdate} /> - - - - + + setTab(v)} + > + + + + {tab === "details" && ( + <> + + + + + + )} + {tab === "network" && ( + + )} + ); }; From 10c5463892617e1a8254ec36c2f084dc0f09c429 Mon Sep 17 00:00:00 2001 From: owiaseimdad Date: Sun, 3 Aug 2025 01:26:13 +0530 Subject: [PATCH 202/259] Fixed the tool tip. --- .../Components/Charts/Utils/chartUtils.jsx | 5 +- .../Components/NetworkStats/NetworkCharts.jsx | 80 +++++++-- .../NetworkStats/NetworkStatBoxes.jsx | 118 ++++++------- .../Details/Components/NetworkStats/index.jsx | 163 +++++++++--------- 4 files changed, 214 insertions(+), 152 deletions(-) diff --git a/client/src/Components/Charts/Utils/chartUtils.jsx b/client/src/Components/Charts/Utils/chartUtils.jsx index 414e1ff9c..bbf81ae38 100644 --- a/client/src/Components/Charts/Utils/chartUtils.jsx +++ b/client/src/Components/Charts/Utils/chartUtils.jsx @@ -112,6 +112,7 @@ export const InfrastructureTooltip = ({ yLabel, dotColor, dateRange, + formatter = getFormattedPercentage, }) => { const uiTimezone = useSelector((state) => state.ui.timezone); const theme = useTheme(); @@ -166,8 +167,8 @@ export const InfrastructureTooltip = ({ sx={{ opacity: 0.8 }} > {yIdx >= 0 - ? `${yLabel} ${getFormattedPercentage(payload[0].payload[hardwareType][yIdx][metric])}` - : `${yLabel} ${getFormattedPercentage(payload[0].payload[yKey])}`} + ? `${yLabel} ${formatter(payload[0].payload[hardwareType][yIdx][metric])}` + : `${yLabel} ${formatter(payload[0].payload[yKey])}`} diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx index 497fa1410..9ef548b2e 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx @@ -2,7 +2,7 @@ import { Grid, Card, CardContent, Typography } from "@mui/material"; import { useTheme } from "@mui/material/styles"; import AreaChart from "../../../../../Components/Charts/AreaChart"; -import { TzTick } from '../../../../../Components/Charts/Utils/chartUtils'; +import { TzTick, InfrastructureTooltip } from '../../../../../Components/Charts/Utils/chartUtils'; const BytesTick = ({ x, y, payload }) => { const value = payload.value; @@ -16,37 +16,89 @@ const BytesTick = ({ x, y, payload }) => { return {label}; }; +const getFormattedNetworkMetric = (value) => { + if (typeof value !== "number" || isNaN(value)) return "0"; + if (value >= 1024 ** 3) return `${(value / 1024 ** 3).toFixed(1)} GB/s`; + if (value >= 1024 ** 2) return `${(value / 1024 ** 2).toFixed(1)} MB/s`; + if (value >= 1024) return `${(value / 1024).toFixed(1)} KB/s`; + return `${Math.round(value)} B/s`; +}; + const NetworkCharts = ({ eth0Data, dateRange }) => { const theme = useTheme(); const textColor = theme.palette.primary.contrastTextTertiary; const charts = [ - { title: "eth0 Bytes/sec", key: "bytesPerSec", color: theme.palette.info.main, yTick: }, - { title: "eth0 Packets/sec", key: "packetsPerSec", color: theme.palette.success.main }, - { title: "eth0 Errors", key: "errors", color: theme.palette.error.main }, - { title: "eth0 Drops", key: "drops", color: theme.palette.warning.main } + { title: "Bytes per second", key: "bytesPerSec", color: theme.palette.info.main, yTick: }, + { title: "Packets per second", key: "packetsPerSec", color: theme.palette.success.main }, + { title: "Errors", key: "errors", color: theme.palette.error.main }, + { title: "Drops", key: "drops", color: theme.palette.warning.main } ]; + const formatYAxis = (key, value) => { + if (key === "bytesPerSec") { + // Format as MB/s or GB/s if large + if (value >= 1024 ** 3) return `${(value / 1024 ** 3).toFixed(1)} GB/s`; + if (value >= 1024 ** 2) return `${(value / 1024 ** 2).toFixed(1)} MB/s`; + if (value >= 1024) return `${(value / 1024).toFixed(1)} KB/s`; + return `${Math.round(value)} B/s`; + } + return Math.round(value).toLocaleString(); + }; + + const CustomTick = ({ x, y, payload, chartKey }) => { + // Ensure value is always rounded for display + let value = payload.value; + if (typeof value === 'number') { + value = Math.round(value); + } + return ( + + {formatYAxis(chartKey, value)} + + ); + }; + + const chartConfigs = charts.map((chart) => ({ + data: eth0Data, + dataKeys: [chart.key], + heading: chart.title, + strokeColor: chart.color, + gradientStartColor: chart.color, + yTick: chart.yTick || , + xTick: , + toolTip: ( + + ), + })); + return ( - {charts.map((chart) => ( - + {chartConfigs.map((config, idx) => ( + - {chart.title} + {config.heading} } - strokeColor={chart.color} + yTick={config.yTick} + xTick={config.xTick} + strokeColor={config.strokeColor} gradient - gradientStartColor={chart.color} + gradientStartColor={config.gradientStartColor} gradientEndColor="#fff" height={200} + customTooltip={config.toolTip} /> diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx index abe67fa8e..5253742a6 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx @@ -7,76 +7,78 @@ import StatBox from "../../../../../Components/StatBox"; import { Typography } from "@mui/material"; const INTERFACE_LABELS = { - en0: "Ethernet/Wi-Fi (Primary)", - wlan0: "Wi-Fi (Secondary)", + en0: "Ethernet/Wi-Fi (Primary)", + wlan0: "Wi-Fi (Secondary)", }; function formatBytes(bytes) { - if (bytes === 0 || bytes == null) return "0 B"; - const k = 1024; - const sizes = ["B", "KB", "MB", "GB", "TB"]; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`; + if (bytes === 0 || bytes == null) return "0 B"; + const k = 1024; + const sizes = ["B", "KB", "MB", "GB", "TB"]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`; } // Format numbers with commas function formatNumber(num) { - return num != null ? num.toLocaleString() : "0"; + return num != null ? num.toLocaleString() : "0"; } const NetworkStatBoxes = ({ shouldRender, net }) => { - const filtered = net?.filter( - (iface) => iface.name === "en0" || iface.name === "wlan0" - ) || []; + const filtered = + net?.filter((iface) => iface.name === "en0" || iface.name === "wlan0") || []; - if (!net?.length) { - return No network stats available.; - } + if (!net?.length) { + return No network stats available.; + } - return ( - - {filtered.map((iface) => ( - <> - - - - - - - - ))} - - ); + return ( + + {filtered.map((iface) => ( + <> + + + + + + + + ))} + + ); }; export default NetworkStatBoxes; diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx index ecb62e769..60825be0f 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx @@ -3,98 +3,105 @@ import NetworkCharts from "./NetworkCharts"; import MonitorTimeFrameHeader from "../../../../../Components/MonitorTimeFrameHeader"; function filterByDateRange(data, dateRange) { - if (!Array.isArray(data)) return []; - const now = Date.now(); - let cutoff; - switch (dateRange) { - case "recent": - cutoff = now - 2 * 60 * 60 * 1000; // last 2 hours - break; - case "day": - cutoff = now - 24 * 60 * 60 * 1000; // last 24 hours - break; - case "week": - cutoff = now - 7 * 24 * 60 * 60 * 1000; // last 7 days - break; - case "month": - cutoff = now - 30 * 24 * 60 * 60 * 1000; // last 30 days - break; - default: - cutoff = 0; - } - return data.filter((d) => new Date(d.time).getTime() >= cutoff); + if (!Array.isArray(data)) return []; + const now = Date.now(); + let cutoff; + switch (dateRange) { + case "recent": + cutoff = now - 2 * 60 * 60 * 1000; // last 2 hours + break; + case "day": + cutoff = now - 24 * 60 * 60 * 1000; // last 24 hours + break; + case "week": + cutoff = now - 7 * 24 * 60 * 60 * 1000; // last 7 days + break; + case "month": + cutoff = now - 30 * 24 * 60 * 60 * 1000; // last 30 days + break; + default: + cutoff = 0; + } + return data.filter((d) => new Date(d.time).getTime() >= cutoff); } const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => { - const eth0Data = getEth0TimeSeries(checks); - const xAxisFormatter = getXAxisFormatter(checks); - const filteredEth0Data = filterByDateRange(eth0Data, dateRange); + const eth0Data = getEth0TimeSeries(checks); + const xAxisFormatter = getXAxisFormatter(checks); + const filteredEth0Data = filterByDateRange(eth0Data, dateRange); - return ( - <> - - - - - ); + return ( + <> + + + + + ); }; export default Network; /* ---------- Helper functions ---------- */ function getEth0TimeSeries(checks) { - const sorted = [...(checks || [])].sort((a, b) => new Date(a._id) - new Date(b._id)); - const series = []; - let prev = null; + const sorted = [...(checks || [])].sort((a, b) => new Date(a._id) - new Date(b._id)); + const series = []; + let prev = null; - for (const check of sorted) { - const eth = (check.net || []).find((iface) => iface.name === "en0"); - if (!eth) { - prev = check; - continue; - } - if (prev) { - const prevEth = (prev.net || []).find((iface) => iface.name === "en0"); - const t1 = new Date(check._id); - const t0 = new Date(prev._id); - if (!prevEth || isNaN(t1) || isNaN(t0)) { - prev = check; - continue; - } - const dt = (t1 - t0) / 1000; - if (dt > 0) { - series.push({ - time: check._id, - bytesPerSec: (eth.bytesSent - prevEth.bytesSent) / dt, - packetsPerSec: (eth.packetsSent - prevEth.packetsSent) / dt, - errors: (eth.errIn ?? 0) + (eth.errOut ?? 0), - drops: (eth.dropIn ?? 0) + (eth.dropOut ?? 0), - }); - } - } - prev = check; - } + for (const check of sorted) { + const eth = (check.net || []).find((iface) => iface.name === "en0"); + if (!eth) { + prev = check; + continue; + } + if (prev) { + const prevEth = (prev.net || []).find((iface) => iface.name === "en0"); + const t1 = new Date(check._id); + const t0 = new Date(prev._id); + if (!prevEth || isNaN(t1) || isNaN(t0)) { + prev = check; + continue; + } + const dt = (t1 - t0) / 1000; + if (dt > 0) { + series.push({ + time: check._id, + bytesPerSec: (eth.bytesSent - prevEth.bytesSent) / dt, + packetsPerSec: (eth.packetsSent - prevEth.packetsSent) / dt, + errors: (eth.errIn ?? 0) + (eth.errOut ?? 0), + drops: (eth.dropIn ?? 0) + (eth.dropOut ?? 0), + }); + } + } + prev = check; + } - return series; + return series; } function getXAxisFormatter(checks) { - if (!checks || checks.length === 0) return (val) => val; - const sorted = [...checks].sort((a, b) => new Date(a._id) - new Date(b._id)); - const first = new Date(sorted[0]._id); - const last = new Date(sorted[sorted.length - 1]._id); - const diffDays = (last - first) / (1000 * 60 * 60 * 24); + if (!checks || checks.length === 0) return (val) => val; + const sorted = [...checks].sort((a, b) => new Date(a._id) - new Date(b._id)); + const first = new Date(sorted[0]._id); + const last = new Date(sorted[sorted.length - 1]._id); + const diffDays = (last - first) / (1000 * 60 * 60 * 24); - return diffDays > 2 - ? (val) => new Date(val).toLocaleDateString(undefined, { month: "short", day: "numeric" }) - : (val) => - new Date(val).toLocaleTimeString(undefined, { - hour: "2-digit", - minute: "2-digit", - hour12: false, - }); + return diffDays > 2 + ? (val) => + new Date(val).toLocaleDateString(undefined, { month: "short", day: "numeric" }) + : (val) => + new Date(val).toLocaleTimeString(undefined, { + hour: "2-digit", + minute: "2-digit", + hour12: false, + }); } From 281cbbc30fcd409fdb31f9855f79522559ca4438 Mon Sep 17 00:00:00 2001 From: Burak Arslan Date: Sun, 3 Aug 2025 13:19:55 +0300 Subject: [PATCH 203/259] feat: add game server monitoring support - Added a new `/games` route in `monitorRoute.js` to fetch all supported games. - Implemented `getAllGames` method in `monitorController.js` using the `gamedig` library. - Introduced `useFetchMonitorGames` hook in `monitorHooks.js` to fetch game data in the frontend. - Updated `UptimeCreate` page to support game server monitoring: - Added a new monitor type `"game"`. - Dynamically fetches and displays game options using the new `/games` API. - Removed hardcoded `GAMES` object and replaced it with dynamic fetching from the backend. - Updated `NetworkService.js` with a new `getMonitorGames` method for API calls. This update introduces game server monitoring functionality, allowing users to monitor game servers dynamically. --- client/src/Hooks/monitorHooks.js | 23 + client/src/Pages/Uptime/Create/index.jsx | 20 +- client/src/Utils/NetworkService.js | 14 + client/src/Utils/games.js | 3450 ------------------- server/src/controllers/monitorController.js | 12 + server/src/routes/monitorRoute.js | 3 + 6 files changed, 67 insertions(+), 3455 deletions(-) delete mode 100644 client/src/Utils/games.js diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index 8b2a054e6..970b5d97f 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -202,6 +202,25 @@ const useFetchStatsByMonitorId = ({ return [monitor, audits, isLoading, networkError]; }; +const useFetchMonitorGames = ({ setGames, updateTrigger }) => { + const [isLoading, setIsLoading] = useState(true); + useEffect(() => { + const fetchGames = async () => { + try { + setIsLoading(true); + const res = await networkService.getMonitorGames(); + setGames(res.data.data); + } catch (error) { + createToast({ body: error.message }); + } finally { + setIsLoading(false); + } + }; + fetchGames(); + }, [setGames, updateTrigger]); + return [isLoading]; +}; + const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => { const [isLoading, setIsLoading] = useState(true); useEffect(() => { @@ -360,6 +379,9 @@ const useUpdateMonitor = () => { ...((monitor.type === "port" || monitor.type === "game") && { port: monitor.port, }), + ...(monitor.type == "game" && { + gameId: monitor.gameId, + }), ...(monitor.type === "hardware" && { thresholds: monitor.thresholds, secret: monitor.secret, @@ -532,4 +554,5 @@ export { useDeleteMonitorStats, useCreateBulkMonitors, useExportMonitors, + useFetchMonitorGames, }; diff --git a/client/src/Pages/Uptime/Create/index.jsx b/client/src/Pages/Uptime/Create/index.jsx index 5ab4de847..1eea0c783 100644 --- a/client/src/Pages/Uptime/Create/index.jsx +++ b/client/src/Pages/Uptime/Create/index.jsx @@ -41,10 +41,9 @@ import { useUpdateMonitor, usePauseMonitor, useFetchMonitorById, + useFetchMonitorGames, } from "../../../Hooks/monitorHooks"; -import { GAMES } from "../../../Utils/games"; - /** * Parses a URL string and returns a URL object. * @@ -83,6 +82,7 @@ const UptimeCreate = ({ isClone = false }) => { const [isOpen, setIsOpen] = useState(false); const [useAdvancedMatching, setUseAdvancedMatching] = useState(false); const [updateTrigger, setUpdateTrigger] = useState(false); + const [games, setGames] = useState({}); const triggerUpdate = () => { setUpdateTrigger(!updateTrigger); }; @@ -91,12 +91,22 @@ const UptimeCreate = ({ isClone = false }) => { const [notifications, notificationsAreLoading, notificationsError] = useGetNotificationsByTeamId(); const { determineState, statusColor } = useMonitorUtils(); - // Network - const [isLoading] = useFetchMonitorById({ + // Fetch monitor details + const [isFetchingMonitor] = useFetchMonitorById({ monitorId, setMonitor, updateTrigger: true, }); + + // Fetch games + const [isFetchingGames] = useFetchMonitorGames({ + setGames, + triggerUpdate: true, + }); + + // Combine the loading states + const isLoading = isFetchingMonitor || isFetchingGames; + const [createMonitor, isCreating] = useCreateMonitor(); const [pauseMonitor, isPausing] = usePauseMonitor({}); const [deleteMonitor, isDeleting] = useDeleteMonitor(); @@ -116,7 +126,7 @@ const UptimeCreate = ({ isClone = false }) => { { _id: 5, name: t("time.fiveMinutes") }, ]; - const GAMELIST = Object.entries(GAMES).map(([key, value]) => ({ + const GAMELIST = Object.entries(games).map(([key, value]) => ({ _id: key, name: value.name, })); diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js index 59e2a0a21..dce0faaf5 100644 --- a/client/src/Utils/NetworkService.js +++ b/client/src/Utils/NetworkService.js @@ -74,6 +74,20 @@ class NetworkService { } } + /** + * Fetch the games associated with a monitor + * + * @async + * @returns {Promise} The response from the axios GET request. + */ + async getMonitorGames() { + return this.axiosInstance.get(`/monitors/games`, { + headers: { + "Content-Type": "application/json", + }, + }); + } + /** * * ************************************ diff --git a/client/src/Utils/games.js b/client/src/Utils/games.js deleted file mode 100644 index ce6dc9f42..000000000 --- a/client/src/Utils/games.js +++ /dev/null @@ -1,3450 +0,0 @@ -export const GAMES = { - abioticfactor: { - name: "Abiotic Factor", - release_year: 2024, - options: { - port: 27015, - protocol: "valve", - }, - }, - actionsource: { - name: "Action: Source", - release_year: 2019, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "as", - }, - }, - ahl: { - name: "Action Half-Life", - release_year: 2009, - options: { - port: 27015, - protocol: "valve", - }, - }, - aoc: { - name: "Age of Chivalry", - release_year: 2007, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "ageofchivalry", - }, - }, - aoe2: { - name: "Age of Empires 2", - release_year: 2009, - options: { - port_query: 27224, - protocol: "ase", - }, - }, - alienarena: { - name: "Alien Arena", - release_year: 2004, - options: { - port_query: 27910, - protocol: "quake2", - }, - }, - alienswarm: { - name: "Alien Swarm", - release_year: 2004, - options: { - port: 27015, - protocol: "valve", - }, - }, - ase: { - name: "Ark: Survival Evolved", - release_year: 2017, - options: { - port: 7777, - port_query: 27015, - protocol: "valve", - }, - extra: { - old_id: "arkse", - }, - }, - asa: { - name: "Ark: Survival Ascended", - release_year: 2023, - options: { - port: 7777, - protocol: "asa", - }, - }, - assettocorsa: { - name: "Assetto Corsa", - release_year: 2014, - options: { - port: 9610, - protocol: "assettocorsa", - }, - }, - atlas: { - name: "Atlas", - release_year: 2018, - options: { - port: 5761, - port_query_offset: 51800, - protocol: "valve", - }, - }, - avorion: { - name: "Avorion", - release_year: 2020, - options: { - port: 27000, - port_query_offset: 20, - protocol: "valve", - }, - }, - avp2: { - name: "Aliens versus Predator 2", - release_year: 2001, - options: { - port: 27888, - protocol: "gamespy1", - }, - }, - avp2010: { - name: "Aliens vs. Predator 2010", - release_year: 2010, - options: { - port: 27015, - protocol: "valve", - }, - }, - americasarmy: { - name: "America's Army", - release_year: 2002, - options: { - port: 1716, - port_query_offset: 1, - protocol: "gamespy2", - }, - }, - americasarmy2: { - name: "America's Army 2", - release_year: 2003, - options: { - port: 1716, - port_query_offset: 1, - protocol: "gamespy2", - }, - }, - americasarmy3: { - name: "America's Army 3", - release_year: 2009, - options: { - port: 8777, - port_query: 27020, - protocol: "valve", - }, - }, - aapg: { - name: "America's Army: Proving Grounds", - release_year: 2015, - options: { - port: 8777, - port_query: 27020, - protocol: "valve", - }, - extra: { - old_id: "americasarmypg", - }, - }, - asr08: { - name: "Arca Sim Racing '08", - release_year: 2008, - options: { - port: 34397, - port_query_offset: -100, - protocol: "rfactor", - }, - extra: { - old_id: "arcasimracing", - }, - }, - aaa: { - name: "ARMA: Armed Assault", - release_year: 2006, - options: { - port: 2302, - protocol: "gamespy2", - }, - extra: { - old_id: "arma", - }, - }, - arma2: { - name: "ARMA 2", - release_year: 2009, - options: { - port: 2302, - port_query_offset: 1, - protocol: "valve", - }, - }, - a2oa: { - name: "ARMA 2: Operation Arrowhead", - release_year: 2010, - options: { - port: 2302, - port_query_offset: 1, - protocol: "valve", - }, - extra: { - old_id: "arma2oa", - }, - }, - acwa: { - name: "ARMA: Cold War Assault", - release_year: 2011, - options: { - port: 2302, - port_query_offset: 1, - protocol: "gamespy1", - }, - extra: { - old_id: "armacwa", - }, - }, - armaresistance: { - name: "ARMA: Resistance", - release_year: 2011, - options: { - port: 2302, - port_query_offset: 1, - protocol: "gamespy1", - }, - extra: { - old_id: "armar", - }, - }, - arma3: { - name: "ARMA 3", - release_year: 2013, - options: { - port: 2302, - port_query_offset: 1, - protocol: "valve", - }, - }, - armareforger: { - name: "ARMA: Reforger", - release_year: 2022, - options: { - port: 2001, - port_query: 17777, - protocol: "valve", - }, - extra: { - old_id: "armare", - doc_notes: "armareforger", - }, - }, - armagetronadvanced: { - name: "Armagetron Advanced", - release_year: 2001, - options: { - port: 4534, - protocol: "armagetron", - }, - extra: { - old_id: "armagetron", - }, - }, - baldursgate: { - name: "Baldur's Gate", - release_year: 1998, - options: { - port: 6073, - port_query: 1470, - protocol: "gamespy1", - }, - }, - ballisticoverkill: { - name: "Ballistic Overkill", - release_year: 2017, - options: { - port: 27015, - port_query_offset: 1, - protocol: "valve", - }, - }, - barotrauma: { - name: "Barotrauma", - release_year: 2019, - options: { - port: 27015, - port_query_offset: 1, - protocol: "valve", - }, - }, - battalion1944: { - name: "Battalion 1944", - release_year: 2018, - options: { - port: 7777, - port_query_offset: 3, - protocol: "valve", - }, - extra: { - old_id: "bat1944", - }, - }, - beammp: { - name: "BeamMP (2021)", - options: { - port: 30814, - protocol: "beammp", - }, - }, - battlefield1942: { - name: "Battlefield 1942", - release_year: 2002, - options: { - port: 14567, - port_query: 23000, - protocol: "gamespy1", - }, - extra: { - old_id: "bf1942", - }, - }, - battlefieldvietnam: { - name: "Battlefield Vietnam", - release_year: 2004, - options: { - port: 15567, - port_query: 23000, - protocol: "gamespy2", - }, - extra: { - old_id: "bfv", - }, - }, - battlefield2: { - name: "Battlefield 2", - release_year: 2005, - options: { - port: 16567, - port_query: 29900, - protocol: "gamespy3", - }, - extra: { - old_id: "bf2", - }, - }, - battlefield2142: { - name: "Battlefield 2142", - release_year: 2006, - options: { - port: 16567, - port_query: 29900, - protocol: "gamespy3", - }, - extra: { - old_id: "bf2142", - }, - }, - bbc2: { - name: "Battlefield: Bad Company 2", - release_year: 2010, - options: { - port: 19567, - port_query: 48888, - protocol: "battlefield", - }, - extra: { - old_id: "bfbc2", - }, - }, - battlefield3: { - name: "Battlefield 3", - release_year: 2011, - options: { - port: 25200, - port_query_offset: 22000, - protocol: "battlefield", - }, - extra: { - old_id: "bf3", - }, - }, - battlefield4: { - name: "Battlefield 4", - release_year: 2013, - options: { - port: 25200, - port_query_offset: 22000, - protocol: "battlefield", - }, - extra: { - old_id: "bf4", - }, - }, - battlefieldhardline: { - name: "Battlefield Hardline", - release_year: 2015, - options: { - port: 25200, - port_query_offset: 22000, - protocol: "battlefield", - }, - extra: { - old_id: "bfh", - }, - }, - blackmesa: { - name: "Black Mesa", - release_year: 2020, - options: { - port: 27015, - protocol: "valve", - }, - }, - brainbread2: { - name: "BrainBread 2", - release_year: 2022, - options: { - port: 27015, - protocol: "valve", - }, - }, - brainbread: { - name: "BrainBread", - release_year: 2007, - options: { - port: 27015, - protocol: "valve", - }, - }, - breach: { - name: "Breach", - release_year: 2011, - options: { - port: 27016, - protocol: "valve", - }, - }, - breed: { - name: "Breed", - release_year: 2004, - options: { - port: 7649, - protocol: "gamespy2", - }, - }, - brink: { - name: "Brink", - release_year: 2011, - options: { - port_query_offset: 1, - protocol: "valve", - }, - }, - brokeprotocol: { - name: "BROKE PROTOCOL", - release_year: 2024, - options: { - protocol: "brokeprotocol", - }, - extra: { - doc_notes: "brokeprotocol", - }, - }, - basedefense: { - name: "Base Defense", - release_year: 2017, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "bd", - }, - }, - bladesymphony: { - name: "Blade Symphony", - release_year: 2014, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "bs", - }, - }, - bas: { - name: "Build and Shoot", - release_year: 2012, - options: { - port: 32887, - port_query_offset: -1, - protocol: "buildandshoot", - }, - extra: { - old_id: "buildandshoot", - doc_notes: "aosc-buildandshoot", - }, - }, - aosc: { - name: "Ace of Spades Classic", - release_year: 2012, - options: { - port: 32887, - port_query_offset: -1, - protocol: "buildandshoot", - }, - extra: { - doc_notes: "aosc-buildandshoot", - }, - }, - cod: { - name: "Call of Duty", - release_year: 2003, - options: { - port: 28960, - protocol: "quake3", - }, - }, - coduo: { - name: "Call of Duty: United Offensive", - release_year: 2004, - options: { - port: 28960, - protocol: "quake3", - }, - }, - cod2: { - name: "Call of Duty 2", - release_year: 2005, - options: { - port: 28960, - protocol: "quake3", - }, - }, - cod3: { - name: "Call of Duty 3", - release_year: 2006, - options: { - port: 28960, - protocol: "quake3", - }, - }, - cod4mw: { - name: "Call of Duty 4: Modern Warfare", - release_year: 2007, - options: { - port: 28960, - protocol: "quake3", - }, - extra: { - old_id: "cod4", - }, - }, - codbo3: { - name: "Call of Duty: Black Ops 3", - release_year: 2015, - options: { - port: 27017, - protocol: "valve", - }, - }, - codwaw: { - name: "Call of Duty: World at War", - release_year: 2008, - options: { - port: 28960, - protocol: "quake3", - }, - }, - codmw2: { - name: "Call of Duty: Modern Warfare 2", - release_year: 2009, - options: { - port: 28960, - protocol: "quake3", - }, - }, - codmw3: { - name: "Call of Duty: Modern Warfare 3", - release_year: 2011, - options: { - port_query_offset: 2, - protocol: "valve", - }, - }, - coj: { - name: "Call of Juarez", - release_year: 2006, - options: { - port_query: 26000, - protocol: "ase", - }, - extra: { - old_id: "callofjuarez", - }, - }, - chaser: { - name: "Chaser", - release_year: 2003, - options: { - port: 3000, - port_query_offset: 123, - protocol: "ase", - }, - }, - cmw: { - name: "Chivalry: Medieval Warfare", - release_year: 2012, - options: { - port: 7777, - port_query_offset: 2, - protocol: "valve", - }, - extra: { - old_id: "chivalry", - }, - }, - chrome: { - name: "Chrome", - release_year: 2003, - options: { - port: 27015, - port_query_offset: 123, - protocol: "ase", - }, - }, - codenamecure: { - name: "Codename CURE", - release_year: 2017, - options: { - port: 27015, - protocol: "valve", - }, - }, - codenameeagle: { - name: "Codename Eagle", - release_year: 2000, - options: { - port_query: 4711, - protocol: "gamespy1", - }, - }, - colonysurvival: { - name: "Colony Survival", - release_year: 2017, - options: { - port: 27004, - protocol: "valve", - }, - }, - c3db: { - name: "Commandos 3: Destination Berlin", - release_year: 2003, - options: { - port_query: 6500, - protocol: "gamespy1", - }, - extra: { - old_id: "commandos3", - }, - }, - cacr: { - name: "Command and Conquer: Renegade", - release_year: 2002, - options: { - port: 4848, - port_query: 25300, - protocol: "gamespy1", - }, - extra: { - old_id: "cacrenegade", - }, - }, - conanexiles: { - name: "Conan Exiles", - release_year: 2018, - options: { - port: 7777, - port_query: 27015, - protocol: "valve", - }, - extra: { - doc_notes: "conanexiles", - }, - }, - contagion: { - name: "Contagion", - release_year: 2011, - options: { - port: 27015, - protocol: "valve", - }, - }, - contractjack: { - name: "Contract J.A.C.K.", - release_year: 2003, - options: { - port_query: 27888, - protocol: "gamespy1", - }, - extra: { - old_id: "contactjack", - }, - }, - corekeeper: { - name: "Core Keeper", - release_year: 2022, - options: { - port: 1234, - port_query_offset: 1, - protocol: "valve", - }, - }, - counterstrike15: { - name: "Counter-Strike 1.5", - release_year: 2002, - options: { - port: 27015, - protocol: "goldsrc", - }, - extra: { - old_id: "cs15", - }, - }, - counterstrike16: { - name: "Counter-Strike 1.6", - release_year: 2003, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "cs16", - }, - }, - c2d: { - name: "CS2D", - release_year: 2004, - options: { - port: 36963, - protocol: "cs2d", - }, - extra: { - old_id: "cs2d", - }, - }, - cscz: { - name: "Counter-Strike: Condition Zero", - release_year: 2004, - options: { - port: 27015, - protocol: "valve", - }, - }, - css: { - name: "Counter-Strike: Source", - release_year: 2004, - options: { - port: 27015, - protocol: "valve", - }, - }, - csgo: { - name: "Counter-Strike: Global Offensive", - release_year: 2012, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - doc_notes: "csgo", - }, - }, - counterstrike2: { - name: "Counter-Strike 2", - release_year: 2023, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "cs2", - doc_notes: "cs2", - }, - }, - creativerse: { - name: "Creativerse", - release_year: 2017, - options: { - port: 26900, - port_query_offset: 1, - protocol: "valve", - }, - }, - crce: { - name: "Cross Racing Championship Extreme", - release_year: 2005, - options: { - port: 12321, - port_query_offset: 123, - protocol: "ase", - }, - extra: { - old_id: "crossracing", - }, - }, - crysis: { - name: "Crysis", - release_year: 2007, - options: { - port: 64087, - protocol: "gamespy3", - }, - }, - crysiswars: { - name: "Crysis Wars", - release_year: 2008, - options: { - port: 64100, - protocol: "gamespy3", - }, - }, - crysis2: { - name: "Crysis 2", - release_year: 2011, - options: { - port: 64000, - protocol: "gamespy3", - }, - }, - dab: { - name: "Double Action: Boogaloo", - release_year: 2014, - options: { - port: 27015, - protocol: "valve", - }, - }, - daikatana: { - name: "Daikatana", - release_year: 2000, - options: { - port: 27982, - port_query_offset: 10, - protocol: "quake2", - }, - }, - dmomam: { - name: "Dark Messiah of Might and Magic", - release_year: 2006, - options: { - port: 27015, - protocol: "valve", - }, - }, - dhe4445: { - name: "Darkest Hour: Europe '44-'45", - release_year: 2008, - options: { - port: 7757, - port_query_offset: 1, - protocol: "unreal2", - }, - extra: { - old_id: "darkesthour", - }, - }, - dayofdragons: { - name: "Day of Dragons", - release_year: 2019, - options: { - port: 7777, - port_query: 27015, - protocol: "valve", - }, - }, - dow: { - name: "Days of War", - release_year: 2017, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "daysofwar", - }, - }, - dayz: { - name: "DayZ", - release_year: 2018, - options: { - port: 2302, - port_query_offset: 24714, - protocol: "dayz", - }, - extra: { - doc_notes: "dayz", - }, - }, - dayzmod: { - name: "DayZ Mod", - release_year: 2013, - options: { - port: 2302, - port_query_offset: 1, - protocol: "valve", - }, - }, - ddpt: { - name: "Deadly Dozen: Pacific Theater", - release_year: 2002, - options: { - port_query: 25300, - protocol: "gamespy1", - }, - extra: { - old_id: "deadlydozenpt", - }, - }, - deerhunter2005: { - name: "Deer Hunter 2005", - release_year: 2004, - options: { - port: 23459, - port_query: 34567, - protocol: "gamespy2", - }, - extra: { - old_id: "dh2005", - }, - }, - descent3: { - name: "Descent 3", - release_year: 1999, - options: { - port: 2092, - port_query: 20142, - protocol: "gamespy1", - }, - }, - deusex: { - name: "Deus Ex", - release_year: 2000, - options: { - port: 7791, - port_query_offset: 1, - protocol: "gamespy1", - }, - }, - devastation: { - name: "Devastation", - release_year: 2003, - options: { - port: 7777, - port_query_offset: 1, - protocol: "unreal2", - }, - }, - ddd: { - name: "Dino D-Day", - release_year: 2011, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "dinodday", - }, - }, - dtr2: { - name: "Dirt Track Racing 2", - release_year: 2002, - options: { - port: 32240, - port_query_offset: -100, - protocol: "gamespy1", - }, - extra: { - old_id: "dirttrackracing2", - }, - }, - discord: { - name: "Discord", - release_year: 2015, - options: { - protocol: "discord", - }, - extra: { - doc_notes: "discord", - }, - }, - deathmatchclassic: { - name: "Deathmatch Classic", - release_year: 2001, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "dmc", - }, - }, - dal: { - name: "Dark and Light", - release_year: 2017, - options: { - port: 7777, - port_query: 27015, - protocol: "valve", - }, - extra: { - old_id: "dnl", - }, - }, - dnf2001: { - name: "Duke Nukem Forever 2001", - release_year: 2022, - options: { - port: 7777, - port_query_offset: 1, - protocol: "gamespy1", - }, - }, - dod: { - name: "Day of Defeat", - release_year: 2003, - options: { - port: 27015, - protocol: "valve", - }, - }, - dods: { - name: "Day of Defeat: Source", - release_year: 2005, - options: { - port: 27015, - protocol: "valve", - }, - }, - doi: { - name: "Day of Infamy", - release_year: 2017, - options: { - port: 27015, - protocol: "valve", - }, - }, - doom3: { - name: "Doom 3", - release_year: 2004, - options: { - port: 27666, - protocol: "doom3", - }, - }, - dota2: { - name: "Dota 2", - release_year: 2013, - options: { - port: 27015, - protocol: "valve", - }, - }, - dootf: { - name: "Drakan: Order of the Flame", - release_year: 1999, - options: { - port: 27045, - port_query_offset: 1, - protocol: "gamespy1", - }, - extra: { - old_id: "drakan", - }, - }, - dst: { - name: "Don't Starve Together", - release_year: 2016, - options: { - port: 10999, - port_query: 27016, - protocol: "valve", - }, - }, - dystopia: { - name: "Dystopia", - release_year: 2005, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "dys", - }, - }, - eco: { - name: "Eco", - release_year: 2018, - options: { - port: 3000, - port_query_offset: 1, - protocol: "eco", - }, - }, - eldewrito: { - name: "Halo Online - ElDewrito", - options: { - port: 11775, - protocol: "eldewrito", - }, - }, - empiresmod: { - name: "Empires Mod", - release_year: 2008, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "em", - }, - }, - egs: { - name: "Empyrion - Galactic Survival", - release_year: 2015, - options: { - port: 30000, - port_query_offset: 1, - protocol: "valve", - }, - extra: { - old_id: "empyrion", - }, - }, - enshrouded: { - name: "enshrouded", - release_year: 2024, - options: { - port: 15636, - port_query: 15637, - protocol: "valve", - }, - }, - etqw: { - name: "Enemy Territory: Quake Wars", - release_year: 2007, - options: { - port: 3074, - port_query: 27733, - protocol: "doom3", - }, - }, - ets2: { - name: "Euro Truck Simulator 2", - release_year: 2012, - options: { - port: 27015, - port_query_offset: 1, - protocol: "valve", - }, - }, - exfil: { - name: "Exfil", - release_year: 2024, - options: { - port: 7777, - port_query: 27015, - protocol: "valve", - }, - }, - fear: { - name: "F.E.A.R.", - release_year: 2005, - options: { - port_query: 27888, - protocol: "gamespy2", - }, - }, - formulaone2002: { - name: "Formula One 2002", - release_year: 2002, - options: { - port_query: 3297, - protocol: "gamespy1", - }, - extra: { - old_id: "f12002", - }, - }, - f1c9902: { - name: "F1 Challenge '99-'02", - release_year: 2002, - options: { - port_query: 34397, - protocol: "gamespy1", - }, - }, - factorio: { - name: "Factorio", - release_year: 2016, - options: { - port_query: 34197, - protocol: "factorio", - }, - }, - farcry: { - name: "Far Cry", - release_year: 2004, - options: { - port: 49001, - port_query_offset: 123, - protocol: "ase", - }, - }, - farcry2: { - name: "Far Cry 2", - release_year: 2008, - options: { - port_query: 14001, - protocol: "ase", - }, - }, - farmingsimulator19: { - name: "Farming Simulator 19", - release_year: 2018, - options: { - port: 8080, - protocol: "farmingsimulator", - }, - }, - farmingsimulator22: { - name: "Farming Simulator 22", - release_year: 2021, - options: { - port: 8080, - protocol: "farmingsimulator", - }, - }, - farmingsimulator25: { - name: "Farming Simulator 25", - release_year: 2024, - options: { - port: 8080, - protocol: "farmingsimulator", - }, - }, - fof: { - name: "Fistful of Frags", - release_year: 2014, - options: { - port: 27015, - protocol: "valve", - }, - }, - foundry: { - name: "FOUNDRY", - release_year: 2024, - options: { - port: 27015, - protocol: "valve", - }, - }, - fortressforever: { - name: "Fortress Forever", - release_year: 2007, - options: { - port: 27015, - protocol: "valve", - }, - }, - ofcwc: { - name: "Operation Flashpoint: Cold War Crisis", - release_year: 2001, - options: { - port: 2302, - port_query_offset: 1, - protocol: "gamespy1", - }, - }, - ofr: { - name: "Operation Flashpoint: Resistance", - release_year: 2002, - options: { - port: 2302, - port_query_offset: 1, - protocol: "gamespy1", - }, - extra: { - old_id: "flashpointresistance", - }, - }, - ffow: { - name: "Frontlines: Fuel of War", - release_year: 2008, - options: { - port: 5476, - port_query_offset: 2, - protocol: "ffow", - }, - }, - gta5f: { - name: "Grand Theft Auto V - FiveM", - release_year: 2013, - options: { - port: 30120, - protocol: "fivem", - }, - extra: { - old_id: "fivem", - doc_notes: "gta5f", - }, - }, - gta5r: { - name: "Grand Theft Auto V - RageMP", - release_year: 2016, - options: { - port: 22005, - protocol: "ragemp", - }, - extra: { - doc_notes: "gta5r", - }, - }, - gta5am: { - name: "Grand Theft Auto V - alt:V Multiplayer", - release_year: 2015, - options: { - port: 7788, - protocol: "altvmp", - }, - extra: { - doc_notes: "gta5am", - old_id: "gta5a", - }, - }, - garrysmod: { - name: "Garry's Mod", - release_year: 2004, - options: { - port: 27015, - protocol: "valve", - }, - }, - tcgraw: { - name: "Tom Clancy's Ghost Recon Advanced Warfighter", - release_year: 2006, - options: { - port_query: 15250, - protocol: "gamespy2", - }, - extra: { - old_id: "graw", - }, - }, - tcgraw2: { - name: "Tom Clancy's Ghost Recon Advanced Warfighter 2", - release_year: 2007, - options: { - port_query: 16250, - protocol: "gamespy2", - }, - extra: { - old_id: "graw2", - }, - }, - gck: { - name: "Giants: Citizen Kabuto", - release_year: 2000, - options: { - port_query: 8911, - protocol: "gamespy1", - }, - extra: { - old_id: "giantscitizenkabuto", - }, - }, - globaloperations: { - name: "Global Operations", - release_year: 2002, - options: { - port_query: 28672, - protocol: "gamespy1", - }, - }, - geneshift: { - name: "Geneshift", - release_year: 2017, - options: { - port: 11235, - protocol: "geneshift", - }, - }, - goldeneyesource: { - name: "GoldenEye: Source", - release_year: 2010, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "ges", - }, - }, - gus: { - name: "Gore: Ultimate Soldier", - release_year: 2002, - options: { - port: 27777, - port_query_offset: 1, - protocol: "gamespy1", - }, - extra: { - old_id: "gore", - }, - }, - groundbreach: { - name: "Ground Breach", - release_year: 2018, - options: { - port: 27015, - protocol: "valve", - }, - }, - gunmanchronicles: { - name: "Gunman Chronicles", - release_year: 2000, - options: { - port: 27015, - protocol: "valve", - }, - }, - hld: { - name: "Half-Life Deathmatch", - release_year: 1998, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "hldm", - }, - }, - hlds: { - name: "Half-Life Deathmatch: Source", - release_year: 2005, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "hldms", - }, - }, - hlof: { - name: "Half-Life: Opposing Force", - release_year: 1999, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "hlopfor", - }, - }, - hl2d: { - name: "Half-Life 2: Deathmatch", - release_year: 2004, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "hl2dm", - }, - }, - halo: { - name: "Halo", - release_year: 2003, - options: { - port: 2302, - protocol: "gamespy2", - }, - }, - halo2: { - name: "Halo 2", - release_year: 2007, - options: { - port: 2302, - protocol: "gamespy2", - }, - }, - hawakening: { - name: "Hawakening", - release_year: 2024, - options: { - port: 7777, - port_query: 27015, - protocol: "hawakening", - }, - extra: { - doc_notes: "hawakening", - }, - }, - heretic2: { - name: "Heretic II", - release_year: 1998, - options: { - port: 27900, - port_query_offset: 1, - protocol: "gamespy1", - }, - }, - hexen2: { - name: "Hexen II", - release_year: 1997, - options: { - port: 26900, - port_query_offset: 50, - protocol: "hexen2", - }, - }, - thehidden: { - name: "The Hidden", - release_year: 2005, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "hidden", - }, - }, - hll: { - name: "Hell Let Loose", - release_year: 2019, - options: { - port: 27015, - protocol: "valve", - }, - }, - hiddendangerous2: { - name: "Hidden & Dangerous 2", - release_year: 2003, - options: { - port: 11001, - port_query_offset: 3, - protocol: "gamespy1", - }, - extra: { - old_id: "had2", - }, - }, - homefront: { - name: "Homefront", - release_year: 2011, - options: { - port: 27015, - protocol: "valve", - }, - }, - homeworld2: { - name: "Homeworld 2", - release_year: 2003, - options: { - port_query: 6500, - protocol: "gamespy1", - }, - }, - hurtworld: { - name: "Hurtworld", - release_year: 2015, - options: { - port: 12871, - port_query: 12881, - protocol: "valve", - }, - }, - i2cs: { - name: "IGI 2: Covert Strike", - release_year: 2003, - options: { - port_query: 26001, - protocol: "gamespy1", - }, - extra: { - old_id: "igi2", - }, - }, - i2s: { - name: "IL-2 Sturmovik", - release_year: 2001, - options: { - port_query: 21000, - protocol: "gamespy1", - }, - extra: { - old_id: "il2", - }, - }, - icarus: { - name: "Icarus", - release_year: 2021, - options: { - port: 27015, - protocol: "valve", - }, - }, - insurgency: { - name: "Insurgency", - release_year: 2014, - options: { - port: 27015, - protocol: "valve", - }, - }, - imic: { - name: "Insurgency: Modern Infantry Combat", - release_year: 2007, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "insurgencymic", - }, - }, - insurgencysandstorm: { - name: "Insurgency: Sandstorm", - release_year: 2018, - options: { - port: 27015, - port_query_offset: 1, - protocol: "valve", - }, - }, - ironstorm: { - name: "Iron Storm", - release_year: 2002, - options: { - port_query: 3505, - protocol: "gamespy1", - }, - }, - theisle: { - name: "The Isle", - release_year: 2015, - options: { - port: 7707, - port_query_offset: 1, - protocol: "valve", - }, - extra: { - old_id: "isle", - }, - }, - tie: { - name: "The Isle Evrima", - options: { - port: 7777, - protocol: "theisleevrima", - }, - release_year: 2020, - }, - jb0n: { - name: "James Bond 007: Nightfire", - release_year: 2002, - options: { - port_query: 6550, - protocol: "gamespy1", - }, - extra: { - old_id: "jamesbondnightfire", - }, - }, - jc2m: { - name: "Just Cause 2 - Multiplayer", - release_year: 2010, - options: { - port: 7777, - protocol: "jc2mp", - }, - extra: { - old_id: "jc2mp", - }, - }, - jc3m: { - name: "Just Cause 3 - Multiplayer", - release_year: 2017, - options: { - port: 4200, - port_query_offset: 1, - protocol: "valve", - }, - extra: { - old_id: "jc3mp", - }, - }, - killingfloor: { - name: "Killing Floor", - release_year: 2009, - options: { - port: 7707, - port_query_offset: 1, - protocol: "unreal2", - }, - }, - killingfloor2: { - name: "Killing Floor 2", - release_year: 2016, - options: { - port: 7777, - port_query: 27015, - protocol: "valve", - }, - }, - kloc: { - name: "Kingpin: Life of Crime", - release_year: 1999, - options: { - port: 31510, - port_query_offset: -10, - protocol: "gamespy1", - }, - extra: { - old_id: "kingpin", - }, - }, - kpctnc: { - name: "Kiss: Psycho Circus: The Nightmare Child", - release_year: 2000, - options: { - port: 7777, - port_query_offset: 1, - protocol: "gamespy1", - }, - extra: { - old_id: "kisspc", - }, - }, - kspd: { - name: "Kerbal Space Program - DMP", - release_year: 2015, - options: { - port: 6702, - port_query_offset: 1, - protocol: "kspdmp", - }, - extra: { - old_id: "kspdmp", - }, - }, - kreedzclimbing: { - name: "Kreedz Climbing", - release_year: 2017, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "kzmod", - }, - }, - l4d: { - name: "Left 4 Dead", - release_year: 2008, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "left4dead", - }, - }, - l4d2: { - name: "Left 4 Dead 2", - release_year: 2009, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "left4dead2", - }, - }, - m2m: { - name: "Mafia II - Multiplayer", - release_year: 2010, - options: { - port: 27016, - port_query_offset: 1, - protocol: "mafia2mp", - }, - extra: { - old_id: "m2mp", - }, - }, - m2o: { - name: "Mafia II - Online", - release_year: 2010, - options: { - port: 27015, - port_query_offset: 1, - protocol: "mafia2online", - }, - }, - medievalengineers: { - name: "Medieval Engineers", - release_year: 2015, - options: { - port: 27015, - protocol: "valve", - }, - }, - moe: { - name: "Myth of Empires", - release_year: 2024, - options: { - port_query: 12888, - protocol: "valve", - }, - }, - mohaa: { - name: "Medal of Honor: Allied Assault", - release_year: 2002, - options: { - port: 12203, - port_query_offset: 97, - protocol: "gamespy1", - }, - }, - mohaas: { - name: "Medal of Honor: Allied Assault Spearhead", - release_year: 2002, - options: { - port: 12203, - port_query_offset: 97, - protocol: "gamespy1", - }, - extra: { - old_id: "mohsh", - }, - }, - mohaab: { - name: "Medal of Honor: Allied Assault Breakthrough", - release_year: 2003, - options: { - port: 12203, - port_query_offset: 97, - protocol: "gamespy1", - }, - extra: { - old_id: "mohbt", - }, - }, - mohpa: { - name: "Medal of Honor: Pacific Assault", - release_year: 2004, - options: { - port: 13203, - port_query_offset: 97, - protocol: "gamespy1", - }, - }, - moha: { - name: "Medal of Honor: Airborne", - release_year: 2007, - options: { - port: 12203, - port_query_offset: 97, - protocol: "gamespy1", - }, - extra: { - old_id: "mohab", - }, - }, - moh: { - name: "Medal of Honor", - release_year: 2010, - options: { - port: 7673, - port_query: 48888, - protocol: "battlefield", - }, - extra: { - old_id: "moh2010", - }, - }, - mohw: { - name: "Medal of Honor: Warfighter", - release_year: 2012, - options: { - port: 25200, - port_query_offset: 22000, - protocol: "battlefield", - }, - extra: { - old_id: "mohwf", - }, - }, - minecraft: { - name: "Minecraft", - release_year: 2009, - options: { - port: 25565, - protocol: "minecraft", - }, - extra: { - doc_notes: "minecraft", - }, - }, - minetest: { - name: "Minetest", - release_year: 2010, - options: { - port: 30000, - protocol: "minetest", - }, - }, - mbe: { - name: "Minecraft: Bedrock Edition", - release_year: 2011, - options: { - port: 19132, - protocol: "minecraft", - }, - extra: { - old_id: "minecraftbe", - }, - }, - mnc: { - name: "Monday Night Combat", - release_year: 2011, - options: { - port: 7777, - port_query: 27016, - protocol: "valve", - }, - }, - mordhau: { - name: "Mordhau", - release_year: 2019, - options: { - port: 7777, - port_query: 27015, - protocol: "valve", - }, - }, - gtavcmta: { - name: "Grand Theft Auto: Vice City - Multi Theft Auto", - release_year: 2002, - options: { - port: 22003, - port_query_offset: 123, - protocol: "ase", - }, - extra: { - old_id: "mtavc", - }, - }, - gtasamta: { - name: "Grand Theft Auto: San Andreas - Multi Theft Auto", - release_year: 2004, - options: { - port: 22003, - port_query_offset: 123, - protocol: "ase", - }, - extra: { - old_id: "mtasa", - }, - }, - mgm: { - name: "Mumble - GT Murmur", - release_year: 2005, - options: { - port: 64738, - port_query: 27800, - protocol: "mumble", - }, - extra: { - doc_notes: "mumble", - }, - }, - mumble: { - name: "Mumble", - release_year: 2005, - options: { - port: 64738, - protocol: "mumbleping", - }, - extra: { - doc_notes: "mumble", - }, - }, - mutantfactions: { - name: "Mutant Factions", - release_year: 2009, - options: { - port: 11235, - protocol: "geneshift", - }, - }, - nascarthunder2004: { - name: "NASCAR Thunder 2004", - release_year: 2003, - options: { - port_query: 13333, - protocol: "gamespy2", - }, - }, - netpanzer: { - name: "netPanzer", - release_year: 2002, - options: { - port: 3030, - protocol: "gamespy1", - }, - }, - nmrih: { - name: "No More Room in Hell", - release_year: 2011, - options: { - port: 27015, - protocol: "valve", - }, - }, - naturalselection: { - name: "Natural Selection", - release_year: 2002, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "ns", - }, - }, - naturalselection2: { - name: "Natural Selection 2", - release_year: 2012, - options: { - port_query_offset: 1, - protocol: "valve", - }, - extra: { - old_id: "ns2", - }, - }, - nfshp2: { - name: "Need for Speed: Hot Pursuit 2", - release_year: 2002, - options: { - port_query: 61220, - protocol: "gamespy1", - }, - }, - nab: { - name: "Nerf Arena Blast", - release_year: 1999, - options: { - port: 4444, - port_query_offset: 1, - protocol: "gamespy1", - }, - }, - neverwinternights: { - name: "Neverwinter Nights", - release_year: 2002, - options: { - port_query: 5121, - protocol: "gamespy2", - }, - extra: { - old_id: "nwn", - }, - }, - neverwinternights2: { - name: "Neverwinter Nights 2", - release_year: 2006, - options: { - port: 5121, - port_query: 6500, - protocol: "gamespy2", - }, - extra: { - old_id: "nwn2", - }, - }, - nexuiz: { - name: "Nexuiz", - release_year: 2005, - options: { - port_query: 26000, - protocol: "quake3", - }, - }, - nitrofamily: { - name: "Nitro Family", - release_year: 2004, - options: { - port_query: 25601, - protocol: "gamespy1", - }, - }, - tonolf: { - name: "The Operative: No One Lives Forever", - release_year: 2000, - options: { - port_query: 27888, - protocol: "gamespy1", - }, - extra: { - old_id: "nolf", - }, - }, - nla: { - name: "Nova-Life: Amboise", - release_year: 2020, - options: { - port_query: 27015, - protocol: "valve", - }, - }, - nolf2asihw: { - name: "No One Lives Forever 2: A Spy in H.A.R.M.'s Way", - release_year: 2002, - options: { - port_query: 27890, - protocol: "gamespy1", - }, - extra: { - old_id: "nolf2", - }, - }, - nucleardawn: { - name: "Nuclear Dawn", - release_year: 2011, - options: { - port: 27015, - protocol: "valve", - }, - }, - ohd: { - name: "Operation: Harsh Doorstop", - release_year: 2023, - options: { - port: 7777, - port_query: 27005, - protocol: "valve", - }, - }, - onset: { - name: "Onset", - release_year: 2019, - options: { - port: 7777, - port_query_offset: -1, - protocol: "valve", - }, - }, - openarena: { - name: "OpenArena", - release_year: 2005, - options: { - port_query: 27960, - protocol: "quake3", - }, - }, - openttd: { - name: "OpenTTD", - release_year: 2004, - options: { - port: 3979, - protocol: "openttd", - }, - }, - painkiller: { - name: "Painkiller", - release_year: 2004, - options: { - port: 3455, - port_query_offset: 123, - protocol: "ase", - }, - }, - palworld: { - name: "Palworld", - release_year: 2024, - options: { - port: 8212, - protocol: "palworld", - }, - extra: { - doc_notes: "palworld", - }, - }, - pvak2: { - name: "Pirates, Vikings, and Knights II", - release_year: 2007, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "pvkii", - }, - }, - pixark: { - name: "PixARK", - release_year: 2018, - options: { - port: 7777, - port_query: 27015, - protocol: "valve", - }, - }, - postscriptum: { - name: "Post Scriptum", - release_year: 2018, - options: { - port: 10037, - protocol: "valve", - }, - extra: { - old_id: "ps", - }, - }, - postal2: { - name: "Postal 2", - release_year: 2003, - options: { - port: 7777, - port_query_offset: 1, - protocol: "gamespy1", - }, - }, - prey: { - name: "Prey", - release_year: 2017, - options: { - port: 27719, - protocol: "doom3", - }, - }, - pce: { - name: "Primal Carnage: Extinction", - release_year: 2015, - options: { - port: 7777, - port_query: 27015, - protocol: "valve", - }, - extra: { - old_id: "primalcarnage", - }, - }, - projectcars: { - name: "Project Cars", - release_year: 2015, - options: { - port: 27015, - port_query_offset: 1, - protocol: "valve", - }, - extra: { - old_id: "pc", - }, - }, - projectcars2: { - name: "Project Cars 2", - release_year: 2017, - options: { - port: 27015, - port_query_offset: 1, - protocol: "valve", - }, - extra: { - old_id: "pc2", - }, - }, - prb2: { - name: "Project Reality: Battlefield 2", - release_year: 2005, - options: { - port: 16567, - port_query: 29900, - protocol: "gamespy3", - }, - extra: { - old_id: "prbf2", - }, - }, - projectzomboid: { - name: "Project Zomboid", - release_year: 2013, - options: { - port: 16261, - protocol: "valve", - }, - extra: { - old_id: "przomboid", - }, - }, - quake: { - name: "Quake", - release_year: 1996, - options: { - port: 27500, - protocol: "quake1", - }, - extra: { - old_id: "quake1", - }, - }, - quake2: { - name: "Quake 2", - release_year: 1997, - options: { - port: 27910, - protocol: "quake2", - }, - }, - q3a: { - name: "Quake 3: Arena", - release_year: 1999, - options: { - port: 27960, - protocol: "quake3", - }, - extra: { - old_id: "quake3", - }, - }, - quake4: { - name: "Quake 4", - release_year: 2005, - options: { - port: 28004, - protocol: "doom3", - }, - }, - quakelive: { - name: "Quake Live", - release_year: 2010, - options: { - port: 27960, - protocol: "valve", - }, - }, - rdkf: { - name: "Rag Doll Kung Fu", - release_year: 2005, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "ragdollkungfu", - }, - }, - rainbowsix: { - name: "Rainbow Six", - release_year: 1998, - options: { - port_query: 2348, - protocol: "gamespy1", - }, - extra: { - old_id: "r6", - }, - }, - rs2rs: { - name: "Rainbow Six 2: Rogue Spear", - release_year: 1999, - options: { - port_query: 2346, - protocol: "gamespy1", - }, - extra: { - old_id: "r6roguespear", - }, - }, - rs3rs: { - name: "Rainbow Six 3: Raven Shield", - release_year: 2003, - options: { - port: 7777, - port_query_offset: 1000, - protocol: "gamespy1", - }, - extra: { - old_id: "r6ravenshield", - }, - }, - rallisportchallenge: { - name: "RalliSport Challenge", - release_year: 2002, - options: { - port_query: 17500, - protocol: "gamespy1", - }, - }, - rallymasters: { - name: "Rally Masters", - release_year: 2000, - options: { - port_query: 16666, - protocol: "gamespy1", - }, - }, - redorchestra: { - name: "Red Orchestra", - release_year: 2018, - options: { - port: 7758, - port_query_offset: 1, - protocol: "unreal2", - }, - }, - roo4145: { - name: "Red Orchestra: Ostfront 41-45", - release_year: 2006, - options: { - port: 7757, - port_query_offset: 10, - protocol: "gamespy1", - }, - extra: { - old_id: "redorchestraost", - }, - }, - redorchestra2: { - name: "Red Orchestra 2", - release_year: 2011, - options: { - port: 7777, - port_query: 27015, - protocol: "valve", - }, - }, - redline: { - name: "Redline", - release_year: 2010, - options: { - port_query: 25252, - protocol: "gamespy1", - }, - }, - renegade10: { - name: "Renegade X", - release_year: 2014, - options: { - protocol: "renegadex", - }, - }, - renown: { - name: "Renown", - release_year: 2025, - options: { - protocol: "renown", - }, - }, - rdr2r: { - name: "Red Dead Redemption 2 - RedM", - release_year: 2018, - options: { - port: 30120, - protocol: "fivem", - }, - extra: { - old_id: "redm", - }, - }, - rtcw: { - name: "Return to Castle Wolfenstein", - release_year: 2001, - options: { - port_query: 27960, - protocol: "quake3", - }, - }, - rfactor: { - name: "rFactor", - release_year: 2005, - options: { - port: 34397, - port_query_offset: -100, - protocol: "rfactor", - }, - }, - rfactor2: { - name: "rFactor 2", - release_year: 2013, - options: { - port_query: 64299, - protocol: "valve", - }, - }, - ricochet: { - name: "Ricochet", - release_year: 2005, - options: { - port: 27015, - protocol: "valve", - }, - }, - ron: { - name: "Rise of Nations", - release_year: 2003, - options: { - port_query: 6501, - protocol: "gamespy1", - }, - extra: { - old_id: "riseofnations", - }, - }, - risingworld: { - name: "Rising World", - release_year: 2014, - options: { - port: 4255, - port_query_offset: -1, - protocol: "valve", - }, - }, - ror2: { - name: "Risk of Rain 2", - release_year: 2020, - options: { - port: 27015, - port_query_offset: 1, - protocol: "valve", - }, - }, - rs2v: { - name: "Rising Storm 2: Vietnam", - release_year: 2017, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "rs2", - }, - }, - rune: { - name: "Rune", - release_year: 2000, - options: { - port: 7777, - port_query_offset: 1, - protocol: "gamespy1", - }, - }, - rust: { - name: "Rust", - release_year: 2013, - options: { - port: 28015, - protocol: "valve", - }, - }, - gtasam: { - name: "Grand Theft Auto: San Andreas Multiplayer", - release_year: 2006, - options: { - port: 7777, - protocol: "samp", - }, - extra: { - old_id: "samp", - }, - }, - gtasao: { - name: "Grand Theft Auto: San Andreas OpenMP", - release_year: 2019, - options: { - port: 7777, - protocol: "gtasao", - }, - extra: { - old_id: "saomp", - }, - }, - s2ats: { - name: "Savage 2: A Tortured Soul", - release_year: 2008, - options: { - port_query: 11235, - protocol: "savage2", - }, - extra: { - old_id: "savage2", - }, - }, - sdtd: { - name: "7 Days to Die", - release_year: 2013, - options: { - port: 26900, - port_query_offset: 1, - protocol: "sdtd", - }, - extra: { - old_id: "7d2d", - doc_notes: "sdtd", - }, - }, - satisfactory: { - name: "Satisfactory", - release_year: 2019, - options: { - port: 7777, - protocol: "satisfactory", - }, - extra: { - doc_notes: "satisfactory", - }, - }, - spaceengineers: { - name: "Space Engineers", - release_year: 2019, - options: { - port: 27015, - protocol: "valve", - }, - }, - serioussam: { - name: "Serious Sam", - release_year: 2001, - options: { - port: 25600, - port_query_offset: 1, - protocol: "gamespy1", - }, - extra: { - old_id: "ss", - }, - }, - serioussam2: { - name: "Serious Sam 2", - release_year: 2005, - options: { - port: 25600, - protocol: "gamespy2", - }, - extra: { - old_id: "ss2", - }, - }, - shatteredhorizon: { - name: "Shattered Horizon", - release_year: 2009, - options: { - port: 27015, - protocol: "valve", - }, - }, - sstse: { - name: "Serious Sam: The Second Encounter", - release_year: 2002, - options: { - port: 25600, - port_query_offset: 1, - protocol: "gamespy1", - }, - }, - theship: { - name: "The Ship", - release_year: 2006, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "ship", - }, - }, - shogo: { - name: "Shogo", - release_year: 1998, - options: { - port_query: 27888, - protocol: "gamespy1", - }, - }, - shootmania: { - name: "Shootmania", - release_year: 2013, - options: { - port: 2350, - port_query: 5000, - protocol: "nadeo", - }, - extra: { - doc_notes: "nadeo", - }, - }, - sin: { - name: "SiN", - release_year: 1998, - options: { - port_query: 22450, - protocol: "gamespy1", - }, - }, - sinepisodes: { - name: "SiN Episodes", - release_year: 2006, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "sinep", - }, - }, - soldat: { - name: "Soldat", - release_year: 2002, - options: { - port: 23073, - port_query_offset: 10, - protocol: "soldat", - }, - extra: { - doc_notes: "soldat", - }, - }, - sof: { - name: "Soldier of Fortune", - release_year: 2000, - options: { - port: 28910, - port_query_offset: 1, - protocol: "gamespy1", - }, - }, - sof2: { - name: "Soldier of Fortune 2", - release_year: 2002, - options: { - port_query: 20100, - protocol: "quake3", - }, - }, - sotf: { - name: "Sons Of The Forest", - release_year: 2023, - options: { - port: 8766, - port_query: 27016, - protocol: "valve", - }, - extra: { - old_id: "sonsoftheforest", - }, - }, - soulmask: { - name: "Soulmask", - release_year: 2024, - options: { - port: 8777, - port_query: 27015, - protocol: "valve", - }, - }, - ssl: { - name: "SCP: Secret Labratory", - release_year: 2020, - options: { - protocol: "scpsl", - }, - extra: { - doc_notes: "ssl", - }, - }, - stalker: { - name: "S.T.A.L.K.E.R.", - release_year: 2007, - options: { - port: 5445, - port_query_offset: 2, - protocol: "gamespy3", - }, - }, - stn: { - name: "Survive the Nights", - release_year: 2017, - options: { - port: 7950, - port_query_offset: 1, - protocol: "valve", - }, - }, - stbc: { - name: "Star Trek: Bridge Commander", - release_year: 2002, - options: { - port_query: 22101, - protocol: "gamespy1", - }, - }, - stvef: { - name: "Star Trek: Voyager - Elite Force", - release_year: 2000, - options: { - port_query: 27960, - protocol: "quake3", - }, - }, - stvef2: { - name: "Star Trek: Voyager - Elite Force 2", - release_year: 2003, - options: { - port_query: 29253, - protocol: "quake3", - }, - }, - squad: { - name: "Squad", - release_year: 2020, - options: { - port: 7787, - port_query: 27165, - protocol: "valve", - }, - }, - swb: { - name: "Star Wars: Battlefront", - release_year: 2004, - options: { - port_query: 3658, - protocol: "gamespy2", - }, - extra: { - old_id: "swbf", - }, - }, - swb2: { - name: "Star Wars: Battlefront 2", - release_year: 2005, - options: { - port_query: 3658, - protocol: "gamespy2", - }, - extra: { - old_id: "swbf2", - }, - }, - swjkja: { - name: "Star Wars Jedi Knight: Jedi Academy", - release_year: 2003, - options: { - port_query: 29070, - protocol: "quake3", - }, - extra: { - old_id: "swjk", - }, - }, - swjk2jo: { - name: "Star Wars Jedi Knight II: Jedi Outcast", - release_year: 2002, - options: { - port_query: 28070, - protocol: "quake3", - }, - extra: { - old_id: "swjk2", - }, - }, - swrc: { - name: "Star Wars: Republic Commando", - release_year: 2005, - options: { - port: 7777, - port_query: 11138, - protocol: "gamespy2", - }, - }, - starbound: { - name: "Starbound", - release_year: 2016, - options: { - port: 21025, - protocol: "valve", - }, - }, - starmade: { - name: "StarMade", - release_year: 2012, - options: { - port: 4242, - protocol: "starmade", - }, - }, - starsiege: { - name: "Starsiege", - release_year: 2009, - options: { - port: 29001, - protocol: "starsiege", - }, - }, - suicidesurvival: { - name: "Suicide Survival", - release_year: 2008, - options: { - port: 27015, - protocol: "valve", - }, - }, - swat4: { - name: "SWAT 4", - release_year: 2005, - options: { - port: 10480, - port_query_offset: 2, - protocol: "gamespy2", - }, - }, - svencoop: { - name: "Sven Coop", - release_year: 1999, - options: { - port: 27015, - protocol: "valve", - }, - }, - synergy: { - name: "Synergy", - release_year: 2005, - options: { - port: 27015, - protocol: "valve", - }, - }, - tacticalops: { - name: "Tactical Ops", - release_year: 1999, - options: { - port: 7777, - port_query_offset: 1, - protocol: "gamespy1", - }, - }, - toh: { - name: "Take On Helicopters", - release_year: 2011, - options: { - port: 2302, - port_query_offset: 1, - protocol: "gamespy1", - }, - extra: { - old_id: "takeonhelicopters", - }, - }, - teamfactor: { - name: "Team Factor", - release_year: 2002, - options: { - port_query: 57778, - protocol: "gamespy1", - }, - }, - tfc: { - name: "Team Fortress Classic", - release_year: 1999, - options: { - port: 27015, - protocol: "valve", - }, - }, - teamfortress2: { - name: "Team Fortress 2", - release_year: 2007, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "tf2", - }, - }, - teamspeak2: { - name: "Teamspeak 2", - release_year: 2001, - options: { - port: 8767, - protocol: "teamspeak2", - }, - }, - teamspeak3: { - name: "Teamspeak 3", - release_year: 2011, - options: { - port: 9987, - protocol: "teamspeak3", - }, - extra: { - doc_notes: "teamspeak3", - }, - }, - terminus: { - name: "Terminus", - release_year: 2000, - options: { - port_query: 12286, - protocol: "gamespy1", - }, - }, - terrariatshock: { - name: "Terraria - TShock", - release_year: 2011, - options: { - port: 7777, - port_query_offset: 101, - protocol: "terraria", - }, - extra: { - old_id: "terraria", - doc_notes: "terraria", - }, - }, - theforest: { - name: "The Forest", - release_year: 2014, - options: { - port: 27015, - port_query_offset: 1, - protocol: "valve", - }, - extra: { - old_id: "forrest", - }, - }, - thefront: { - name: "The Front", - release_year: 2023, - options: { - port_query: 27015, - protocol: "valve", - }, - }, - thps3: { - name: "Tony Hawk's Pro Skater 3", - release_year: 2001, - options: { - port_query: 6500, - protocol: "gamespy1", - }, - }, - thps4: { - name: "Tony Hawk's Pro Skater 4", - release_year: 2002, - options: { - port_query: 6500, - protocol: "gamespy1", - }, - }, - thu2: { - name: "Tony Hawk's Underground 2", - release_year: 2004, - options: { - port_query: 5153, - protocol: "gamespy1", - }, - }, - towerunite: { - name: "Tower Unite", - release_year: 2016, - options: { - port: 27015, - protocol: "valve", - }, - }, - trackmania2: { - name: "Trackmania 2", - release_year: 2011, - options: { - port: 2350, - port_query: 5000, - protocol: "nadeo", - }, - extra: { - doc_notes: "nadeo", - }, - }, - trackmaniaforever: { - name: "Trackmania Forever", - release_year: 2008, - options: { - port: 2350, - port_query: 5000, - protocol: "nadeo", - }, - extra: { - doc_notes: "nadeo", - }, - }, - tremulous: { - name: "Tremulous", - release_year: 2006, - options: { - port_query: 30720, - protocol: "quake3", - }, - }, - t1s: { - name: "Tribes 1: Starsiege", - release_year: 1998, - options: { - port: 28001, - protocol: "tribes1", - }, - extra: { - old_id: "tribes1", - }, - }, - tribesvengeance: { - name: "Tribes: Vengeance", - release_year: 2004, - options: { - port: 7777, - port_query_offset: 1, - protocol: "gamespy2", - }, - }, - tron20: { - name: "Tron 2.0", - release_year: 2003, - options: { - port_query: 27888, - protocol: "gamespy2", - }, - }, - thespecialists: { - name: "The Specialists", - release_year: 2002, - options: { - port: 27015, - protocol: "valve", - }, - }, - toxikk: { - name: "TOXIKK", - release_year: 2016, - options: { - port: 7777, - port_query: 27015, - protocol: "toxikk", - }, - }, - turok2: { - name: "Turok 2", - release_year: 1998, - options: { - port_query: 12880, - protocol: "gamespy1", - }, - }, - u2tax: { - name: "Unreal 2: The Awakening - XMP", - release_year: 2003, - options: { - port: 7777, - port_query_offset: 1, - protocol: "unreal2", - }, - }, - universalcombat: { - name: "Universal Combat", - release_year: 2004, - options: { - port: 1135, - port_query_offset: 123, - protocol: "ase", - }, - }, - unreal: { - name: "Unreal", - release_year: 1998, - options: { - port: 7777, - port_query_offset: 1, - protocol: "gamespy1", - }, - }, - unturned: { - name: "unturned", - release_year: 2014, - options: { - port: 27015, - port_query_offset: 1, - protocol: "valve", - }, - }, - unrealtournament: { - name: "Unreal Tournament", - release_year: 1993, - options: { - port: 7777, - port_query_offset: 1, - protocol: "gamespy1", - }, - extra: { - old_id: "ut", - }, - }, - unrealtournament2003: { - name: "Unreal Tournament 2003", - release_year: 2003, - options: { - port: 7757, - port_query_offset: 1, - protocol: "unreal2", - }, - extra: { - old_id: "ut2003", - }, - }, - unrealtournament2004: { - name: "Unreal Tournament 2004", - release_year: 2004, - options: { - port: 7777, - port_query_offset: 1, - protocol: "unreal2", - }, - extra: { - old_id: "ut2004", - }, - }, - unrealtournament3: { - name: "Unreal Tournament 3", - release_year: 2007, - options: { - port: 7777, - port_query_offset: -1277, - protocol: "ut3", - }, - extra: { - old_id: "ut3", - }, - }, - urbanterror: { - name: "Urban Terror", - release_year: 2000, - options: { - port_query: 27960, - protocol: "quake3", - }, - }, - v8sc: { - name: "V8 Supercar Challenge", - release_year: 2002, - options: { - port_query: 16700, - protocol: "gamespy1", - }, - extra: { - old_id: "v8supercar", - }, - }, - valheim: { - name: "Valheim", - release_year: 2021, - options: { - port: 2456, - port_query_offset: 1, - protocol: "valve", - }, - extra: { - doc_notes: "valheim", - }, - }, - vcm: { - name: "Vice City Multiplayer", - release_year: 2015, - options: { - port: 8192, - protocol: "vcmp", - }, - extra: { - old_id: "vcmp", - }, - }, - ventrilo: { - name: "Ventrilo", - release_year: 2002, - options: { - port: 3784, - protocol: "ventrilo", - }, - }, - vietcong: { - name: "Vietcong", - release_year: 2003, - options: { - port: 5425, - port_query: 15425, - protocol: "gamespy1", - }, - }, - vietcong2: { - name: "Vietcong 2", - release_year: 2005, - options: { - port: 5001, - port_query: 19967, - protocol: "gamespy2", - }, - }, - vrising: { - name: "V Rising", - release_year: 2022, - options: { - port: 27015, - port_query_offset: [1, 15], - protocol: "valve", - }, - }, - vampireslayer: { - name: "Vampire Slayer", - release_year: 2000, - options: { - port: 27015, - protocol: "valve", - }, - extra: { - old_id: "vs", - }, - }, - vintagestory: { - name: "Vintage Story", - release_year: 2016, - options: { - port: 42420, - protocol: "vintagestory", - }, - }, - warsow: { - name: "Warsow", - release_year: 2012, - options: { - port: 44400, - protocol: "warsow", - }, - }, - warfork: { - name: "Warfork", - release_year: 2019, - options: { - port_query: 44400, - protocol: "warsow", - }, - }, - wot: { - name: "Wheel of Time", - release_year: 1999, - options: { - port: 7777, - port_query_offset: 1, - protocol: "gamespy1", - }, - extra: { - old_id: "wheeloftime", - }, - }, - wolfenstein: { - name: "Wolfenstein", - release_year: 2009, - options: { - port: 27666, - protocol: "doom3", - }, - extra: { - old_id: "wolfenstein2009", - }, - }, - wet: { - name: "Wolfenstein: Enemy Territory", - release_year: 2003, - options: { - port_query: 27960, - protocol: "quake3", - }, - extra: { - old_id: "wolfensteinet", - }, - }, - wurmunlimited: { - name: "Wurm Unlimited", - release_year: 2006, - options: { - port: 3724, - port_query: 27016, - protocol: "valve", - }, - extra: { - old_id: "wurm", - }, - }, - xonotic: { - name: "Xonotic", - release_year: 2011, - options: { - port: 26000, - protocol: "xonotic", - }, - }, - wop: { - name: "World Of Padman", - release_year: 2007, - options: { - port: 26000, - protocol: "quake3", - }, - }, - xpandrally: { - name: "Xpand Rally", - release_year: 2004, - options: { - port: 28015, - port_query_offset: 123, - protocol: "ase", - }, - }, - zombiemaster: { - name: "Zombie Master", - release_year: 2007, - options: { - port: 27015, - protocol: "valve", - }, - }, - zps: { - name: "Zombie Panic: Source", - release_year: 2007, - options: { - port: 27015, - protocol: "valve", - }, - }, -}; diff --git a/server/src/controllers/monitorController.js b/server/src/controllers/monitorController.js index a4f391087..4197b8d73 100755 --- a/server/src/controllers/monitorController.js +++ b/server/src/controllers/monitorController.js @@ -15,6 +15,7 @@ import { import sslChecker from "ssl-checker"; import { fetchMonitorCertificate } from "./controllerUtils.js"; import BaseController from "./baseController.js"; +import { games } from "gamedig"; const SERVICE_NAME = "monitorController"; class MonitorController extends BaseController { @@ -441,6 +442,17 @@ class MonitorController extends BaseController { SERVICE_NAME, "exportMonitorsToCSV" ); + + getAllGames = this.asyncHandler( + async (req, res) => { + return res.success({ + msg: "OK", + data: games, + }); + }, + SERVICE_NAME, + "getAllGames" + ); } export default MonitorController; diff --git a/server/src/routes/monitorRoute.js b/server/src/routes/monitorRoute.js index 0124c209d..b31790991 100755 --- a/server/src/routes/monitorRoute.js +++ b/server/src/routes/monitorRoute.js @@ -25,6 +25,9 @@ class MonitorRoutes { // Hardware routes this.router.get("/hardware/details/:monitorId", this.monitorController.getHardwareDetailsById); + // Game routes + this.router.get("/games", this.monitorController.getAllGames); + // General monitor routes this.router.post("/pause/:monitorId", isAllowed(["admin", "superadmin"]), this.monitorController.pauseMonitor); this.router.get("/stats/:monitorId", this.monitorController.getMonitorStatsById); From 45352b62e1cd9ed7c0c70353b9b08ddde9bcd51b Mon Sep 17 00:00:00 2001 From: mohadeseh safari Date: Sun, 3 Aug 2025 18:51:12 -0400 Subject: [PATCH 204/259] feat(status-page): add customCSS field to StatusPage model --- server/src/db/models/StatusPage.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/src/db/models/StatusPage.js b/server/src/db/models/StatusPage.js index 92cbab1cd..621749fb8 100755 --- a/server/src/db/models/StatusPage.js +++ b/server/src/db/models/StatusPage.js @@ -74,6 +74,10 @@ const StatusPageSchema = mongoose.Schema( type: Boolean, default: false, }, + customCSS: { + type: String, + default: "", + }, }, { timestamps: true } ); From 86a0b0b60ca8e06e757f2ef1aabc76cc63759f3b Mon Sep 17 00:00:00 2001 From: vineet-channe Date: Mon, 4 Aug 2025 18:43:37 +0530 Subject: [PATCH 205/259] fix: update diagnostic page design to match infrastructure page - Remove 'System diagnostics' title and divider line - Add infrastructure-style status boxes with dynamic status - Update gauge components to use BaseContainer styling - Match infrastructure page layout and spacing Resolves #2725 --- .../Diagnostics/components/gauges/index.jsx | 164 +++++++++++------- client/src/Pages/Logs/Diagnostics/index.jsx | 83 +++++---- 2 files changed, 155 insertions(+), 92 deletions(-) diff --git a/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx b/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx index e9f235112..538c157e3 100644 --- a/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx +++ b/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx @@ -1,34 +1,83 @@ import Stack from "@mui/material/Stack"; -import Gauge from "../../../../../Components/Charts/CustomGauge"; +import CustomGauge from "../../../../../Components/Charts/CustomGauge"; import Typography from "@mui/material/Typography"; // Utils import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; -import { getPercentage } from "../../utils/utils"; +import { getPercentage, formatBytes } from "../../utils/utils"; import { useTranslation } from "react-i18next"; +import { Box } from "@mui/material"; -const GaugeBox = ({ title, subtitle, children }) => { - const theme = useTheme(); - return ( - - {children} - - {title} - {subtitle} - +const BaseContainer = ({children}) => { + const theme = useTheme() + return( + + {children} + ); }; -GaugeBox.propTypes = { - title: PropTypes.string.isRequired, - subtitle: PropTypes.string.isRequired, - children: PropTypes.node.isRequired, +const InfrastructureStyleGauge = ({ value, heading, metricOne, valueOne, metricTwo, valueTwo }) => { + const theme = useTheme(); + const valueStyle = { + borderRadius: theme.spacing(2), + backgroundColor: theme.palette.tertiary.main, + width: "40%", + mb: theme.spacing(2), + mt: theme.spacing(2), + pr: theme.spacing(2), + textAlign: "right", + }; + + return( + + + + + + {heading} + + + + + {metricOne} + {valueOne} + + + {metricTwo} + {valueTwo} + + + + + ); }; const Gauges = ({ diagnostics, isLoading }) => { @@ -53,50 +102,41 @@ const Gauges = ({ diagnostics, isLoading }) => { return ( - - - - - - - - - - - - + + + + 80 ? "High" : diagnostics?.cpuUsage?.usagePercentage > 50 ? "Medium" : "Low"} + /> ); }; diff --git a/client/src/Pages/Logs/Diagnostics/index.jsx b/client/src/Pages/Logs/Diagnostics/index.jsx index 7c5d1f5a9..61e64811b 100644 --- a/client/src/Pages/Logs/Diagnostics/index.jsx +++ b/client/src/Pages/Logs/Diagnostics/index.jsx @@ -1,14 +1,14 @@ import Stack from "@mui/material/Stack"; import Box from "@mui/material/Box"; -import Typography from "@mui/material/Typography"; import Gauges from "./components/gauges"; -import Stats from "./components/stats"; -import Divider from "@mui/material/Divider"; import Button from "@mui/material/Button"; - +import StatBox from "../../../Components/StatBox"; +import StatusBoxes from "../../../Components/StatusBoxes"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import { useFetchDiagnostics } from "../../../Hooks/logHooks"; +import { getHumanReadableDuration } from "../../../Utils/timeUtils"; +import { formatBytes, getPercentage } from "./utils/utils"; const Diagnostics = () => { // Local state @@ -19,34 +19,57 @@ const Diagnostics = () => { const [diagnostics, fetchDiagnostics, isLoading, error] = useFetchDiagnostics(); // Setup return ( - + + + + + + + + + + - {t("diagnosticsPage.diagnosticDescription")} + - - - - - - - - ); }; From de34710c90ce797447d5e2b8ac82d36feb8b63b7 Mon Sep 17 00:00:00 2001 From: vineet-channe Date: Tue, 5 Aug 2025 17:35:40 +0530 Subject: [PATCH 206/259] fix: address review feedback - Replace hardcoded button text with translation - Fix status box translation to avoid showing removed title - Add conditional rendering for optional gauge metrics --- .../Diagnostics/components/gauges/index.jsx | 136 ++++++++++-------- client/src/Pages/Logs/Diagnostics/index.jsx | 22 +-- 2 files changed, 84 insertions(+), 74 deletions(-) diff --git a/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx b/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx index 538c157e3..5bb227b7c 100644 --- a/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx +++ b/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx @@ -14,10 +14,9 @@ const BaseContainer = ({children}) => { return( @@ -28,15 +27,28 @@ const BaseContainer = ({children}) => { const InfrastructureStyleGauge = ({ value, heading, metricOne, valueOne, metricTwo, valueTwo }) => { const theme = useTheme(); - const valueStyle = { - borderRadius: theme.spacing(2), - backgroundColor: theme.palette.tertiary.main, - width: "40%", - mb: theme.spacing(2), - mt: theme.spacing(2), - pr: theme.spacing(2), - textAlign: "right", - }; + + const MetricRow = ({ label, value }) => ( + + {label} + + {value} + + + ); return( @@ -47,7 +59,6 @@ const InfrastructureStyleGauge = ({ value, heading, metricOne, valueOne, metricT flexDirection: "column", alignItems: "center", width: "100%", - backgroundColor: theme.palette.gradient?.color1 || "transparent" }} > @@ -55,25 +66,11 @@ const InfrastructureStyleGauge = ({ value, heading, metricOne, valueOne, metricT {heading} - - - {metricOne} - {valueOne} - - - {metricTwo} - {valueTwo} - + + + {metricTwo && valueTwo && ( + + )} @@ -105,38 +102,38 @@ const Gauges = ({ diagnostics, isLoading }) => { spacing={theme.spacing(8)} flexWrap="wrap" > - - - - 80 ? "High" : diagnostics?.cpuUsage?.usagePercentage > 50 ? "Medium" : "Low"} - /> + + + + ); }; @@ -146,4 +143,17 @@ Gauges.propTypes = { isLoading: PropTypes.bool, }; +InfrastructureStyleGauge.propTypes = { + value: PropTypes.number, + heading: PropTypes.string, + metricOne: PropTypes.string, + valueOne: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), + metricTwo: PropTypes.string, + valueTwo: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), +}; + +BaseContainer.propTypes = { + children: PropTypes.node.isRequired, +}; + export default Gauges; diff --git a/client/src/Pages/Logs/Diagnostics/index.jsx b/client/src/Pages/Logs/Diagnostics/index.jsx index 61e64811b..72ee47f48 100644 --- a/client/src/Pages/Logs/Diagnostics/index.jsx +++ b/client/src/Pages/Logs/Diagnostics/index.jsx @@ -20,39 +20,39 @@ const Diagnostics = () => { // Setup return ( - + @@ -67,7 +67,7 @@ const Diagnostics = () => { onClick={fetchDiagnostics} loading={isLoading} > - Fetch Diagnostics + {t("queuePage.refreshButton")} From d4e83818272d8a5080a10b45bc8f1c76b4ac9817 Mon Sep 17 00:00:00 2001 From: Owaise Date: Tue, 5 Aug 2025 19:43:35 +0530 Subject: [PATCH 207/259] Renamed them as average values in the table. --- server/src/db/mongo/modules/monitorModuleQueries.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/src/db/mongo/modules/monitorModuleQueries.js b/server/src/db/mongo/modules/monitorModuleQueries.js index df15593e2..f7283c8e9 100755 --- a/server/src/db/mongo/modules/monitorModuleQueries.js +++ b/server/src/db/mongo/modules/monitorModuleQueries.js @@ -393,7 +393,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { 0, ], }, - bytesSent: { + avgBytesSent: { $avg: { $map: { input: "$net", @@ -404,7 +404,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - bytesRecv: { + avgBytesRecv: { $avg: { $map: { input: "$net", @@ -415,7 +415,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - packetsSent: { + avgPacketsSent: { $avg: { $map: { input: "$net", @@ -426,7 +426,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - packetsRecv: { + avgPacketsRecv: { $avg: { $map: { input: "$net", @@ -437,7 +437,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - errIn: { + avgErrIn: { $avg: { $map: { input: "$net", @@ -448,7 +448,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - errOut: { + avgErrOut: { $avg: { $map: { input: "$net", From e42b4b28081760fef67cd8638bb0e62e1c8d41ad Mon Sep 17 00:00:00 2001 From: Owaise Date: Tue, 5 Aug 2025 20:46:42 +0530 Subject: [PATCH 208/259] fixed the minor changes for FE. --- .../Components/Charts/Utils/chartUtils.jsx | 2 +- .../Components/NetworkStats/NetworkCharts.jsx | 219 ++++++++++-------- .../NetworkStats/NetworkStatBoxes.jsx | 64 ++--- .../Details/Components/NetworkStats/index.jsx | 132 +++++++---- 4 files changed, 234 insertions(+), 183 deletions(-) diff --git a/client/src/Components/Charts/Utils/chartUtils.jsx b/client/src/Components/Charts/Utils/chartUtils.jsx index bbf81ae38..af5f27c66 100644 --- a/client/src/Components/Charts/Utils/chartUtils.jsx +++ b/client/src/Components/Charts/Utils/chartUtils.jsx @@ -88,7 +88,7 @@ PercentTick.propTypes = { */ const getFormattedPercentage = (value) => { if (typeof value !== "number") return value; - return `${(value * 100).toFixed(2)}.%`; + return `${(value * 100).toFixed(2)}%`; }; /** diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx index 9ef548b2e..343f7681b 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx @@ -1,111 +1,130 @@ // NetworkCharts.jsx -import { Grid, Card, CardContent, Typography } from "@mui/material"; -import { useTheme } from "@mui/material/styles"; -import AreaChart from "../../../../../Components/Charts/AreaChart"; -import { TzTick, InfrastructureTooltip } from '../../../../../Components/Charts/Utils/chartUtils'; +import PropTypes from "prop-types"; +import { Stack } from "@mui/material"; +import InfraAreaChart from "../../../../../Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart"; -const BytesTick = ({ x, y, payload }) => { - const value = payload.value; - const label = - value >= 1024 ** 3 - ? `${(value / 1024 ** 3).toFixed(2)} GB` - : value >= 1024 ** 2 - ? `${(value / 1024 ** 2).toFixed(2)} MB` - : `${(value / 1024).toFixed(2)} KB`; - - return {label}; -}; +// Utils +import { + TzTick, + InfrastructureTooltip, +} from "../../../../../Components/Charts/Utils/chartUtils"; +import { useTheme } from "@emotion/react"; const getFormattedNetworkMetric = (value) => { - if (typeof value !== "number" || isNaN(value)) return "0"; - if (value >= 1024 ** 3) return `${(value / 1024 ** 3).toFixed(1)} GB/s`; - if (value >= 1024 ** 2) return `${(value / 1024 ** 2).toFixed(1)} MB/s`; - if (value >= 1024) return `${(value / 1024).toFixed(1)} KB/s`; - return `${Math.round(value)} B/s`; + if (typeof value !== "number" || isNaN(value)) return "0"; + if (value >= 1024 ** 3) return `${(value / 1024 ** 3).toFixed(1)} GB/s`; + if (value >= 1024 ** 2) return `${(value / 1024 ** 2).toFixed(1)} MB/s`; + if (value >= 1024) return `${(value / 1024).toFixed(1)} KB/s`; + return `${Math.round(value)} B/s`; }; const NetworkCharts = ({ eth0Data, dateRange }) => { - const theme = useTheme(); - const textColor = theme.palette.primary.contrastTextTertiary; + const theme = useTheme(); - const charts = [ - { title: "Bytes per second", key: "bytesPerSec", color: theme.palette.info.main, yTick: }, - { title: "Packets per second", key: "packetsPerSec", color: theme.palette.success.main }, - { title: "Errors", key: "errors", color: theme.palette.error.main }, - { title: "Drops", key: "drops", color: theme.palette.warning.main } - ]; + const configs = [ + { + type: "network-bytes", + data: eth0Data, + dataKeys: ["bytesPerSec"], + heading: "Bytes per second", + strokeColor: theme.palette.info.main, + gradientStartColor: theme.palette.info.main, + yLabel: "Bytes per second", + xTick: , + toolTip: ( + + ), + }, + { + type: "network-packets", + data: eth0Data, + dataKeys: ["packetsPerSec"], + heading: "Packets per second", + strokeColor: theme.palette.success.main, + gradientStartColor: theme.palette.success.main, + yLabel: "Packets per second", + xTick: , + toolTip: ( + Math.round(value).toLocaleString()} + /> + ), + }, + { + type: "network-errors", + data: eth0Data, + dataKeys: ["errors"], + heading: "Errors", + strokeColor: theme.palette.error.main, + gradientStartColor: theme.palette.error.main, + yLabel: "Errors", + xTick: , + toolTip: ( + Math.round(value).toLocaleString()} + /> + ), + }, + { + type: "network-drops", + data: eth0Data, + dataKeys: ["drops"], + heading: "Drops", + strokeColor: theme.palette.warning.main, + gradientStartColor: theme.palette.warning.main, + yLabel: "Drops", + xTick: , + toolTip: ( + Math.round(value).toLocaleString()} + /> + ), + }, + ]; - const formatYAxis = (key, value) => { - if (key === "bytesPerSec") { - // Format as MB/s or GB/s if large - if (value >= 1024 ** 3) return `${(value / 1024 ** 3).toFixed(1)} GB/s`; - if (value >= 1024 ** 2) return `${(value / 1024 ** 2).toFixed(1)} MB/s`; - if (value >= 1024) return `${(value / 1024).toFixed(1)} KB/s`; - return `${Math.round(value)} B/s`; - } - return Math.round(value).toLocaleString(); - }; - - const CustomTick = ({ x, y, payload, chartKey }) => { - // Ensure value is always rounded for display - let value = payload.value; - if (typeof value === 'number') { - value = Math.round(value); - } - return ( - - {formatYAxis(chartKey, value)} - - ); - }; - - const chartConfigs = charts.map((chart) => ({ - data: eth0Data, - dataKeys: [chart.key], - heading: chart.title, - strokeColor: chart.color, - gradientStartColor: chart.color, - yTick: chart.yTick || , - xTick: , - toolTip: ( - - ), - })); - - return ( - - {chartConfigs.map((config, idx) => ( - - - - - {config.heading} - - - - - - ))} - - ); + return ( + *": { + flexBasis: `calc(50% - ${theme.spacing(8)})`, + maxWidth: `calc(50% - ${theme.spacing(8)})`, + }, + }} + > + {configs.map((config) => ( + + ))} + + ); }; -export default NetworkCharts; \ No newline at end of file +NetworkCharts.propTypes = { + eth0Data: PropTypes.array.isRequired, + dateRange: PropTypes.string.isRequired, +}; + +export default NetworkCharts; diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx index 5253742a6..b2f2f7841 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx @@ -1,16 +1,9 @@ // NetworkStatBoxes.jsx -import DataUsageIcon from "@mui/icons-material/DataUsage"; -import NetworkCheckIcon from "@mui/icons-material/NetworkCheck"; -import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline"; +import PropTypes from "prop-types"; import StatusBoxes from "../../../../../Components/StatusBoxes"; import StatBox from "../../../../../Components/StatBox"; import { Typography } from "@mui/material"; -const INTERFACE_LABELS = { - en0: "Ethernet/Wi-Fi (Primary)", - wlan0: "Wi-Fi (Secondary)", -}; - function formatBytes(bytes) { if (bytes === 0 || bytes == null) return "0 B"; const k = 1024; @@ -37,48 +30,57 @@ const NetworkStatBoxes = ({ shouldRender, net }) => { shouldRender={shouldRender} flexWrap="wrap" > - {filtered.map((iface) => ( - <> + {filtered + .map((iface) => [ + />, + />, + />, + />, + />, - - ))} + />, + ]) + .flat()} ); }; +NetworkStatBoxes.propTypes = { + shouldRender: PropTypes.bool.isRequired, + net: PropTypes.arrayOf( + PropTypes.shape({ + name: PropTypes.string.isRequired, + bytes_sent: PropTypes.number, + bytes_recv: PropTypes.number, + packets_sent: PropTypes.number, + packets_recv: PropTypes.number, + err_in: PropTypes.number, + err_out: PropTypes.number, + }) + ), +}; + export default NetworkStatBoxes; diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx index 60825be0f..6b8ccb85a 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx @@ -1,34 +1,10 @@ +import PropTypes from "prop-types"; import NetworkStatBoxes from "./NetworkStatBoxes"; import NetworkCharts from "./NetworkCharts"; import MonitorTimeFrameHeader from "../../../../../Components/MonitorTimeFrameHeader"; -function filterByDateRange(data, dateRange) { - if (!Array.isArray(data)) return []; - const now = Date.now(); - let cutoff; - switch (dateRange) { - case "recent": - cutoff = now - 2 * 60 * 60 * 1000; // last 2 hours - break; - case "day": - cutoff = now - 24 * 60 * 60 * 1000; // last 24 hours - break; - case "week": - cutoff = now - 7 * 24 * 60 * 60 * 1000; // last 7 days - break; - case "month": - cutoff = now - 30 * 24 * 60 * 60 * 1000; // last 30 days - break; - default: - cutoff = 0; - } - return data.filter((d) => new Date(d.time).getTime() >= cutoff); -} - const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => { const eth0Data = getEth0TimeSeries(checks); - const xAxisFormatter = getXAxisFormatter(checks); - const filteredEth0Data = filterByDateRange(eth0Data, dateRange); return ( <> @@ -42,66 +18,120 @@ const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => { setDateRange={setDateRange} /> ); }; +Network.propTypes = { + net: PropTypes.array, + checks: PropTypes.array, + isLoading: PropTypes.bool.isRequired, + dateRange: PropTypes.string.isRequired, + setDateRange: PropTypes.func.isRequired, +}; + export default Network; /* ---------- Helper functions ---------- */ function getEth0TimeSeries(checks) { + console.log(`[NetworkStats] Processing ${checks?.length || 0} checks`); + if (checks && checks.length > 0) { + console.log("[NetworkStats] First check _id:", checks[0]._id); + console.log("[NetworkStats] Last check _id:", checks[checks.length - 1]._id); + console.log( + "[NetworkStats] Sample check structure:", + JSON.stringify(checks[0], null, 2) + ); + } + const sorted = [...(checks || [])].sort((a, b) => new Date(a._id) - new Date(b._id)); const series = []; let prev = null; for (const check of sorted) { + console.log(`[NetworkStats] Processing check: ${check._id}`); const eth = (check.net || []).find((iface) => iface.name === "en0"); if (!eth) { + console.log("[NetworkStats] No en0 interface found in check:", check._id); prev = check; continue; } + + console.log(`[NetworkStats] Found en0 interface in check ${check._id}:`, eth); + if (prev) { + console.log(`[NetworkStats] Have previous check: ${prev._id}`); const prevEth = (prev.net || []).find((iface) => iface.name === "en0"); const t1 = new Date(check._id); const t0 = new Date(prev._id); + console.log(`[NetworkStats] Time difference: ${t1 - t0}ms`); + if (!prevEth || isNaN(t1) || isNaN(t0)) { + console.log("[NetworkStats] Skipping - invalid prev data or time"); prev = check; continue; } + const dt = (t1 - t0) / 1000; + console.log(`[NetworkStats] Delta time: ${dt}s`); + if (dt > 0) { - series.push({ - time: check._id, - bytesPerSec: (eth.bytesSent - prevEth.bytesSent) / dt, - packetsPerSec: (eth.packetsSent - prevEth.packetsSent) / dt, - errors: (eth.errIn ?? 0) + (eth.errOut ?? 0), - drops: (eth.dropIn ?? 0) + (eth.dropOut ?? 0), + const bytesField = eth.avgBytesSent; + const prevBytesField = prevEth.avgBytesSent; + + console.log(`[NetworkStats] Bytes comparison:`, { + current: bytesField, + previous: prevBytesField, + diff: bytesField - prevBytesField, }); + + if (bytesField !== undefined && prevBytesField !== undefined) { + const dataPoint = { + _id: check._id, // Use _id instead of time to match AreaChartBoxes + bytesPerSec: (bytesField - prevBytesField) / dt, + packetsPerSec: (eth.avgPacketsSent - prevEth.avgPacketsSent) / dt, + errors: (eth.avgErrIn ?? 0) + (eth.avgErrOut ?? 0), + drops: 0, // Skip drops for now since we don't have avgDropIn/Out + }; + console.log(`[NetworkStats] Adding data point:`, dataPoint); + series.push(dataPoint); + } else { + console.warn("[NetworkStats] Missing bytes fields:", { eth, prevEth }); + } + } else { + console.log("[NetworkStats] Skipping - zero or negative time delta"); } + } else { + console.log("[NetworkStats] No previous check yet, setting as prev"); } prev = check; } + console.log(`[NetworkStats] Generated ${series.length} time series data points`); + + // If we only have one check, create a single data point with absolute values + if (series.length === 0 && sorted.length === 1) { + const check = sorted[0]; + const eth = (check.net || []).find((iface) => iface.name === "en0"); + if (eth) { + console.log( + "[NetworkStats] Only one data point available, showing absolute values" + ); + series.push({ + _id: check._id, // Use _id instead of time to match AreaChartBoxes + bytesPerSec: eth.avgBytesSent || 0, // Show absolute value instead of rate + packetsPerSec: eth.avgPacketsSent || 0, // Show absolute value instead of rate + errors: (eth.avgErrIn ?? 0) + (eth.avgErrOut ?? 0), + drops: 0, + }); + } + } + + if (series.length > 0) { + console.log("[NetworkStats] Sample series data:", series[0]); + } return series; } - -function getXAxisFormatter(checks) { - if (!checks || checks.length === 0) return (val) => val; - const sorted = [...checks].sort((a, b) => new Date(a._id) - new Date(b._id)); - const first = new Date(sorted[0]._id); - const last = new Date(sorted[sorted.length - 1]._id); - const diffDays = (last - first) / (1000 * 60 * 60 * 24); - - return diffDays > 2 - ? (val) => - new Date(val).toLocaleDateString(undefined, { month: "short", day: "numeric" }) - : (val) => - new Date(val).toLocaleTimeString(undefined, { - hour: "2-digit", - minute: "2-digit", - hour12: false, - }); -} From 765a1a24ed97825184a53a6c673ee006b5dff77e Mon Sep 17 00:00:00 2001 From: Owaise Date: Tue, 5 Aug 2025 21:04:15 +0530 Subject: [PATCH 209/259] Added en.json based values and console removal. --- .../Components/NetworkStats/NetworkCharts.jsx | 26 +++++----- .../NetworkStats/NetworkStatBoxes.jsx | 16 +++--- .../Details/Components/NetworkStats/index.jsx | 49 ++----------------- .../Pages/Infrastructure/Details/index.jsx | 5 +- client/src/locales/en.json | 13 +++++ 5 files changed, 43 insertions(+), 66 deletions(-) diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx index 343f7681b..7aad494ab 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx @@ -9,6 +9,7 @@ import { InfrastructureTooltip, } from "../../../../../Components/Charts/Utils/chartUtils"; import { useTheme } from "@emotion/react"; +import { useTranslation } from "react-i18next"; const getFormattedNetworkMetric = (value) => { if (typeof value !== "number" || isNaN(value)) return "0"; @@ -20,22 +21,23 @@ const getFormattedNetworkMetric = (value) => { const NetworkCharts = ({ eth0Data, dateRange }) => { const theme = useTheme(); + const { t } = useTranslation(); const configs = [ { type: "network-bytes", data: eth0Data, dataKeys: ["bytesPerSec"], - heading: "Bytes per second", + heading: t("bytesPerSecond"), strokeColor: theme.palette.info.main, gradientStartColor: theme.palette.info.main, - yLabel: "Bytes per second", + yLabel: t("bytesPerSecond"), xTick: , toolTip: ( @@ -45,16 +47,16 @@ const NetworkCharts = ({ eth0Data, dateRange }) => { type: "network-packets", data: eth0Data, dataKeys: ["packetsPerSec"], - heading: "Packets per second", + heading: t("packetsPerSecond"), strokeColor: theme.palette.success.main, gradientStartColor: theme.palette.success.main, - yLabel: "Packets per second", + yLabel: t("packetsPerSecond"), xTick: , toolTip: ( Math.round(value).toLocaleString()} /> @@ -64,16 +66,16 @@ const NetworkCharts = ({ eth0Data, dateRange }) => { type: "network-errors", data: eth0Data, dataKeys: ["errors"], - heading: "Errors", + heading: t("errors"), strokeColor: theme.palette.error.main, gradientStartColor: theme.palette.error.main, - yLabel: "Errors", + yLabel: t("errors"), xTick: , toolTip: ( Math.round(value).toLocaleString()} /> @@ -83,16 +85,16 @@ const NetworkCharts = ({ eth0Data, dateRange }) => { type: "network-drops", data: eth0Data, dataKeys: ["drops"], - heading: "Drops", + heading: t("drops"), strokeColor: theme.palette.warning.main, gradientStartColor: theme.palette.warning.main, - yLabel: "Drops", + yLabel: t("drops"), xTick: , toolTip: ( Math.round(value).toLocaleString()} /> diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx index b2f2f7841..3125e86a9 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx @@ -3,6 +3,7 @@ import PropTypes from "prop-types"; import StatusBoxes from "../../../../../Components/StatusBoxes"; import StatBox from "../../../../../Components/StatBox"; import { Typography } from "@mui/material"; +import { useTranslation } from "react-i18next"; function formatBytes(bytes) { if (bytes === 0 || bytes == null) return "0 B"; @@ -18,11 +19,12 @@ function formatNumber(num) { } const NetworkStatBoxes = ({ shouldRender, net }) => { + const { t } = useTranslation(); const filtered = net?.filter((iface) => iface.name === "en0" || iface.name === "wlan0") || []; if (!net?.length) { - return No network stats available.; + return {t("noNetworkStatsAvailable")}; } return ( @@ -34,32 +36,32 @@ const NetworkStatBoxes = ({ shouldRender, net }) => { .map((iface) => [ , , , , , , ]) diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx index 6b8ccb85a..03e0c019d 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx @@ -37,101 +37,62 @@ export default Network; /* ---------- Helper functions ---------- */ function getEth0TimeSeries(checks) { - console.log(`[NetworkStats] Processing ${checks?.length || 0} checks`); - if (checks && checks.length > 0) { - console.log("[NetworkStats] First check _id:", checks[0]._id); - console.log("[NetworkStats] Last check _id:", checks[checks.length - 1]._id); - console.log( - "[NetworkStats] Sample check structure:", - JSON.stringify(checks[0], null, 2) - ); - } - const sorted = [...(checks || [])].sort((a, b) => new Date(a._id) - new Date(b._id)); const series = []; let prev = null; for (const check of sorted) { - console.log(`[NetworkStats] Processing check: ${check._id}`); const eth = (check.net || []).find((iface) => iface.name === "en0"); if (!eth) { - console.log("[NetworkStats] No en0 interface found in check:", check._id); prev = check; continue; } - console.log(`[NetworkStats] Found en0 interface in check ${check._id}:`, eth); - if (prev) { - console.log(`[NetworkStats] Have previous check: ${prev._id}`); const prevEth = (prev.net || []).find((iface) => iface.name === "en0"); const t1 = new Date(check._id); const t0 = new Date(prev._id); - console.log(`[NetworkStats] Time difference: ${t1 - t0}ms`); if (!prevEth || isNaN(t1) || isNaN(t0)) { - console.log("[NetworkStats] Skipping - invalid prev data or time"); prev = check; continue; } const dt = (t1 - t0) / 1000; - console.log(`[NetworkStats] Delta time: ${dt}s`); if (dt > 0) { const bytesField = eth.avgBytesSent; const prevBytesField = prevEth.avgBytesSent; - console.log(`[NetworkStats] Bytes comparison:`, { - current: bytesField, - previous: prevBytesField, - diff: bytesField - prevBytesField, - }); - if (bytesField !== undefined && prevBytesField !== undefined) { const dataPoint = { - _id: check._id, // Use _id instead of time to match AreaChartBoxes + _id: check._id, bytesPerSec: (bytesField - prevBytesField) / dt, packetsPerSec: (eth.avgPacketsSent - prevEth.avgPacketsSent) / dt, errors: (eth.avgErrIn ?? 0) + (eth.avgErrOut ?? 0), - drops: 0, // Skip drops for now since we don't have avgDropIn/Out + drops: 0, }; - console.log(`[NetworkStats] Adding data point:`, dataPoint); series.push(dataPoint); - } else { - console.warn("[NetworkStats] Missing bytes fields:", { eth, prevEth }); } - } else { - console.log("[NetworkStats] Skipping - zero or negative time delta"); } - } else { - console.log("[NetworkStats] No previous check yet, setting as prev"); } prev = check; } - console.log(`[NetworkStats] Generated ${series.length} time series data points`); - // If we only have one check, create a single data point with absolute values if (series.length === 0 && sorted.length === 1) { const check = sorted[0]; const eth = (check.net || []).find((iface) => iface.name === "en0"); if (eth) { - console.log( - "[NetworkStats] Only one data point available, showing absolute values" - ); series.push({ - _id: check._id, // Use _id instead of time to match AreaChartBoxes - bytesPerSec: eth.avgBytesSent || 0, // Show absolute value instead of rate - packetsPerSec: eth.avgPacketsSent || 0, // Show absolute value instead of rate + _id: check._id, + bytesPerSec: eth.avgBytesSent || 0, + packetsPerSec: eth.avgPacketsSent || 0, errors: (eth.avgErrIn ?? 0) + (eth.avgErrOut ?? 0), drops: 0, }); } } - if (series.length > 0) { - console.log("[NetworkStats] Sample series data:", series[0]); - } return series; } diff --git a/client/src/Pages/Infrastructure/Details/index.jsx b/client/src/Pages/Infrastructure/Details/index.jsx index 78b78fa78..72f384101 100644 --- a/client/src/Pages/Infrastructure/Details/index.jsx +++ b/client/src/Pages/Infrastructure/Details/index.jsx @@ -25,7 +25,6 @@ const BREADCRUMBS = [ { name: "details", path: "" }, ]; const InfrastructureDetails = () => { - // Local state const [dateRange, setDateRange] = useState("recent"); const [trigger, setTrigger] = useState(false); @@ -96,11 +95,11 @@ const InfrastructureDetails = () => { onChange={(e, v) => setTab(v)} > diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 230637474..bf949ea45 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -201,6 +201,9 @@ "avgCpuTemperature": "Average CPU Temperature", "bar": "Bar", "basicInformation": "Basic Information", + "bytesPerSecond": "Bytes per second", + "bytesReceived": "Bytes Received", + "bytesSent": "Bytes Sent", "bulkImport": { "fallbackPage": "Import a file to upload a list of servers in bulk", "invalidFileType": "Invalid file type", @@ -332,6 +335,11 @@ }, "disk": "Disk", "diskUsage": "Disk Usage", + "drops": "Drops", + "errors": "Errors", + "errorsIn": "Errors In", + "errorsOut": "Errors Out", + "details": "Details", "displayName": "Display name", "distributedRightCategoryTitle": "Monitor", "distributedStatusHeaderText": "Real-time, real-device coverage", @@ -632,7 +640,9 @@ }, "ms": "ms", "navControls": "Controls", + "network": "Network", "nextWindow": "Next window", + "noNetworkStatsAvailable": "No network stats available.", "notFoundButton": "Go to the main dashboard", "notificationConfig": { "description": "Select the notifications channels you want to use", @@ -744,6 +754,9 @@ "passwordRequirements": "New password must contain at least 8 characters and must have at least one uppercase letter, one lowercase letter, one number and one special character.", "saving": "Saving..." }, + "packetsPerSecond": "Packets per second", + "packetsReceived": "Packets Received", + "packetsSent": "Packets Sent", "pause": "Pause", "pingMonitoring": "Ping monitoring", "pingMonitoringDescription": "Check whether your server is available or not.", From 4c2c452b91ae7c34e900d3ed4c9371c1e976138b Mon Sep 17 00:00:00 2001 From: Burak Arslan Date: Wed, 6 Aug 2025 00:53:35 +0300 Subject: [PATCH 210/259] Improve validation and error handling on form field --- client/src/Components/Inputs/Select/index.jsx | 3 +++ client/src/Pages/Uptime/Create/index.jsx | 10 +++++++++- client/src/Validation/validation.js | 15 +++++++++++---- server/src/config/services.js | 2 ++ server/src/controllers/monitorController.js | 3 +-- server/src/routes/monitorRoute.js | 4 +--- server/src/service/business/monitorService.js | 7 ++++++- .../src/service/infrastructure/networkService.js | 2 +- server/src/validation/joi.js | 2 +- 9 files changed, 35 insertions(+), 13 deletions(-) diff --git a/client/src/Components/Inputs/Select/index.jsx b/client/src/Components/Inputs/Select/index.jsx index a438b6bb2..70e6bfca5 100644 --- a/client/src/Components/Inputs/Select/index.jsx +++ b/client/src/Components/Inputs/Select/index.jsx @@ -50,6 +50,7 @@ const Select = ({ onChange, onBlur, sx, + error = false, name = "", labelControlSpacing = 6, maxWidth, @@ -93,6 +94,7 @@ const Select = ({ onChange={onChange} onBlur={onBlur} displayEmpty + error={error} name={name} inputProps={{ id: id }} IconComponent={KeyboardArrowDownIcon} @@ -172,6 +174,7 @@ Select.propTypes = { label: PropTypes.string, placeholder: PropTypes.string, isHidden: PropTypes.bool, + error: PropTypes.bool, value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]) .isRequired, items: PropTypes.arrayOf( diff --git a/client/src/Pages/Uptime/Create/index.jsx b/client/src/Pages/Uptime/Create/index.jsx index 1eea0c783..f821ea9e4 100644 --- a/client/src/Pages/Uptime/Create/index.jsx +++ b/client/src/Pages/Uptime/Create/index.jsx @@ -270,6 +270,11 @@ const UptimeCreate = ({ isClone = false }) => { setMonitor((prev) => ({ ...prev, [name]: value })); + if (name === "type") { + setErrors({}); + return; + } + const { error } = monitorValidation.validate( { type: monitor.type, [name]: value }, { abortEarly: false } @@ -277,7 +282,9 @@ const UptimeCreate = ({ isClone = false }) => { setErrors((prev) => ({ ...prev, - ...(error ? { [name]: error.details[0].message } : { [name]: undefined }), + ...(error && error.details[0].path[0] === name + ? { [name]: error.details[0].message } + : { [name]: undefined }), })); }; @@ -593,6 +600,7 @@ const UptimeCreate = ({ isClone = false }) => { placeholder={t("chooseGame")} onChange={onChange} items={GAMELIST} + error={errors["gameId"] ? true : false} /> )} { return res.success({ msg: "OK", - data: games, + data: this.monitorService.getAllGames(), }); }, SERVICE_NAME, diff --git a/server/src/routes/monitorRoute.js b/server/src/routes/monitorRoute.js index b31790991..043d361d1 100755 --- a/server/src/routes/monitorRoute.js +++ b/server/src/routes/monitorRoute.js @@ -25,9 +25,6 @@ class MonitorRoutes { // Hardware routes this.router.get("/hardware/details/:monitorId", this.monitorController.getHardwareDetailsById); - // Game routes - this.router.get("/games", this.monitorController.getAllGames); - // General monitor routes this.router.post("/pause/:monitorId", isAllowed(["admin", "superadmin"]), this.monitorController.pauseMonitor); this.router.get("/stats/:monitorId", this.monitorController.getMonitorStatsById); @@ -47,6 +44,7 @@ class MonitorRoutes { this.router.get("/export", isAllowed(["admin", "superadmin"]), this.monitorController.exportMonitorsToCSV); this.router.post("/bulk", isAllowed(["admin", "superadmin"]), upload.single("csvFile"), this.monitorController.createBulkMonitors); this.router.post("/test-email", isAllowed(["admin", "superadmin"]), this.monitorController.sendTestEmail); + this.router.get("/games", this.monitorController.getAllGames); // Individual monitor CRUD routes this.router.get("/:monitorId", this.monitorController.getMonitorById); diff --git a/server/src/service/business/monitorService.js b/server/src/service/business/monitorService.js index 2dd9e9b4f..637870b71 100644 --- a/server/src/service/business/monitorService.js +++ b/server/src/service/business/monitorService.js @@ -4,7 +4,7 @@ const SERVICE_NAME = "MonitorService"; class MonitorService { static SERVICE_NAME = SERVICE_NAME; - constructor({ db, settingsService, jobQueue, stringService, emailService, papaparse, logger, errorService }) { + constructor({ db, settingsService, jobQueue, stringService, emailService, papaparse, logger, errorService, games }) { this.db = db; this.settingsService = settingsService; this.jobQueue = jobQueue; @@ -13,6 +13,7 @@ class MonitorService { this.papaparse = papaparse; this.logger = logger; this.errorService = errorService; + this.games = games; } get serviceName() { @@ -262,6 +263,10 @@ class MonitorService { const csv = this.papaparse.unparse(csvData); return csv; }; + + getAllGames = () => { + return this.games; + }; } export default MonitorService; diff --git a/server/src/service/infrastructure/networkService.js b/server/src/service/infrastructure/networkService.js index bad4f5899..4a3b79ed3 100755 --- a/server/src/service/infrastructure/networkService.js +++ b/server/src/service/infrastructure/networkService.js @@ -503,7 +503,7 @@ class NetworkService { host: url, port: port, }).catch((error) => { - return false; + null; }); if (!state) { diff --git a/server/src/validation/joi.js b/server/src/validation/joi.js index d3fa25fb5..31f8e97c0 100755 --- a/server/src/validation/joi.js +++ b/server/src/validation/joi.js @@ -171,7 +171,7 @@ const createMonitorBodyValidation = joi.object({ jsonPath: joi.string().allow(""), expectedValue: joi.string().allow(""), matchMethod: joi.string(), - gameId: joi.string(), + gameId: joi.string().allow(""), }); const createMonitorsBodyValidation = joi.array().items( From 786dd427bc4f314f89cb59de8396075701245c54 Mon Sep 17 00:00:00 2001 From: karenvicent Date: Wed, 6 Aug 2025 17:17:44 -0400 Subject: [PATCH 211/259] feat: Update Register page to match new Login design for consistent UX --- client/src/Components/LanguageSelector.jsx | 18 +- client/src/Pages/Auth/Login/index.jsx | 194 +++++----------- client/src/Pages/Auth/Register/index.jsx | 217 ++++++++---------- .../Pages/Auth/components/AuthPageWrapper.jsx | 107 +++++++++ .../Pages/Auth/components/PasswordTooltip.jsx | 116 ++++++++++ client/src/locales/en.json | 5 +- 6 files changed, 393 insertions(+), 264 deletions(-) create mode 100644 client/src/Pages/Auth/components/AuthPageWrapper.jsx create mode 100644 client/src/Pages/Auth/components/PasswordTooltip.jsx diff --git a/client/src/Components/LanguageSelector.jsx b/client/src/Components/LanguageSelector.jsx index 56a9a898d..9b46204e3 100644 --- a/client/src/Components/LanguageSelector.jsx +++ b/client/src/Components/LanguageSelector.jsx @@ -30,7 +30,17 @@ const LanguageSelector = () => { value={language} onChange={handleChange} size="small" - sx={{ minWidth: 80 }} + sx={{ + minWidth: 80, + "& .MuiSelect-select": { + display: "flex", + alignItems: "center", + justifyContent: "center", + }, + "& .MuiSelect-icon": { + alignSelf: "center", + }, + }} > {languages.map((lang) => { let parsedLang = lang === "en" ? "gb" : lang; @@ -47,11 +57,17 @@ const LanguageSelector = () => { { [name]: error?.details?.[0]?.message || "", })); }; - const onSubmit = async (e) => { e.preventDefault(); const toSubmit = { ...form }; const { error } = loginCredentials.validate(toSubmit, { abortEarly: false }); - if (error) { const formErrors = {}; for (const err of error.details) { @@ -64,7 +58,6 @@ const Login = () => { setErrors(formErrors); return; } - const action = await dispatch(login(form)); if (action.payload.success) { navigate("/uptime"); @@ -89,150 +82,69 @@ const Login = () => { } } }; - return ( - - - - - - - - - - - - - - - - {t("auth.login.welcome")} - - {t("auth.login.heading")} - - - - } - /> - - - - } /> + - + + + ); }; diff --git a/client/src/Pages/Auth/Register/index.jsx b/client/src/Pages/Auth/Register/index.jsx index 97b27e5cf..2b45d2d15 100644 --- a/client/src/Pages/Auth/Register/index.jsx +++ b/client/src/Pages/Auth/Register/index.jsx @@ -1,10 +1,10 @@ // Components import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import AuthHeader from "../components/AuthHeader"; import TextInput from "../../../Components/Inputs/TextInput"; -import Check from "../../../Components/Check/Check"; import Button from "@mui/material/Button"; +import Box from "@mui/material/Box"; +import PasswordTooltip from "../components/PasswordTooltip"; // Utils import { useTheme } from "@emotion/react"; @@ -16,6 +16,7 @@ import { useParams } from "react-router-dom"; import { networkService } from "../../../main"; import { newOrChangedCredentials } from "../../../Validation/validation"; import { register } from "../../../Features/Auth/authSlice"; +import AuthPageWrapper from "../components/AuthPageWrapper"; import { createToast } from "../../../Utils/toastUtils"; import PropTypes from "prop-types"; @@ -195,59 +196,38 @@ const Register = ({ superAdminExists }) => { }; return ( - - - {t("auth.registration.heading.user")} - + + {superAdminExists + ? t("auth.registration.description.user") + : t("auth.registration.description.superAdmin")} + - - {superAdminExists - ? t("auth.registration.heading.user") - : t("auth.registration.heading.superAdmin")} - - - {superAdminExists - ? t("auth.registration.description.user") - : t("auth.registration.description.superAdmin")} - - (e.target.value = e.target.value.toLowerCase())} - onChange={onChange} - error={errors.email ? true : false} - helperText={errors.email ? t(errors.email) : ""} // Localization keys are in validation.js - /> { { error={errors.lastName ? true : false} helperText={errors.lastName ? t(errors.lastName) : ""} // Localization keys are in validation.js /> - - - - - - - - - - - + (e.target.value = e.target.value.toLowerCase())} + onChange={onChange} + error={errors.email ? true : false} + helperText={errors.email ? t(errors.email) : ""} // Localization keys are in validation.js + /> + + + + + + + - + ); }; diff --git a/client/src/Pages/Auth/components/AuthPageWrapper.jsx b/client/src/Pages/Auth/components/AuthPageWrapper.jsx new file mode 100644 index 000000000..b83be19c5 --- /dev/null +++ b/client/src/Pages/Auth/components/AuthPageWrapper.jsx @@ -0,0 +1,107 @@ +import Background from "../../../assets/Images/background-grid.svg?react"; +import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import AuthHeader from "../components/AuthHeader"; +import { useTheme } from "@mui/material/styles"; +import Logo from "../../../assets/icons/checkmate-icon.svg?react"; +import PropTypes from "prop-types"; + +const AuthPageWrapper = ({ children, heading, welcome }) => { + const theme = useTheme(); + return ( + + + + + + + + + + + + + + + + {welcome} + + {heading} + + {children} + + + ); +}; + +export default AuthPageWrapper; + +AuthPageWrapper.propTypes = { + children: PropTypes.node, + heading: PropTypes.node, + welcome: PropTypes.node, +}; diff --git a/client/src/Pages/Auth/components/PasswordTooltip.jsx b/client/src/Pages/Auth/components/PasswordTooltip.jsx new file mode 100644 index 000000000..93a74403a --- /dev/null +++ b/client/src/Pages/Auth/components/PasswordTooltip.jsx @@ -0,0 +1,116 @@ +import Check from "../../../Components/Check/Check"; +import Stack from "@mui/material/Stack"; +import { Tooltip, useTheme } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import PropTypes from "prop-types"; + +const PasswordTooltip = ({ feedback, form, children }) => { + const theme = useTheme(); + const { t } = useTranslation(); + const hasPassword = form.password.length > 0; + const hasInvalidFeedback = Object.values(feedback).some( + (status) => status !== "success" + ); + const showPasswordTooltip = hasPassword && hasInvalidFeedback; + return ( + + + + + + + + + } + slotProps={{ + tooltip: { + sx: { + backgroundColor: theme.palette.tertiary.background, + border: `0.5px solid ${theme.palette.primary.lowContrast}90`, + borderRadius: theme.spacing(4), + color: theme.palette.primary.contrastText, + width: "auto", + maxWidth: { xs: "25vw", md: "none" }, + whiteSpace: { xs: "normal", md: "nowrap" }, + paddingTop: theme.spacing(8), + px: theme.spacing(8), + }, + }, + arrow: { + sx: { + color: theme.palette.tertiary.background, + }, + }, + }} + > + {children} + + ); +}; + +PasswordTooltip.propTypes = { + feedback: PropTypes.shape({ + length: PropTypes.string.isRequired, + special: PropTypes.string, + number: PropTypes.string, + uppercase: PropTypes.string, + lowercase: PropTypes.string, + confirm: PropTypes.string, + }), + form: PropTypes.shape({ + password: PropTypes.string.isRequired, + }), + children: PropTypes.node, +}; + +export default PasswordTooltip; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 605a50fa0..21c74773a 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -87,7 +87,7 @@ "highlighted": "one lower character" }, "match": { - "beginning": "Confirm password and password", + "beginning": "Passwords", "highlighted": "must match" }, "number": { @@ -195,7 +195,8 @@ "termsAndPolicies": "By creating an account, you agree to our Terms of Service and Privacy Policy.", "toasts": { "success": "Welcome! Your account was created successfully." - } + }, + "welcome": "Welcome to Checkmate!" } }, "avgCpuTemperature": "Average CPU Temperature", From d04b8fddb015b108c2fadcd3cb70f2b81fca1dca Mon Sep 17 00:00:00 2001 From: Br0wnHammer Date: Thu, 7 Aug 2025 18:04:58 +0530 Subject: [PATCH 212/259] Fix: Appname Color --- client/src/Components/Sidebar/components/logo.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/Components/Sidebar/components/logo.jsx b/client/src/Components/Sidebar/components/logo.jsx index 7a59f406d..3021b7284 100644 --- a/client/src/Components/Sidebar/components/logo.jsx +++ b/client/src/Components/Sidebar/components/logo.jsx @@ -49,9 +49,9 @@ const Logo = ({ collapsed }) => { {t("common.appName")} From d451f725cde36399c6c8c01d999d97f904dd6c89 Mon Sep 17 00:00:00 2001 From: Br0wnHammer Date: Thu, 7 Aug 2025 18:21:50 +0530 Subject: [PATCH 213/259] Redundant sx prop --- client/src/Components/Sidebar/components/logo.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/Components/Sidebar/components/logo.jsx b/client/src/Components/Sidebar/components/logo.jsx index 3021b7284..a0c0ff84e 100644 --- a/client/src/Components/Sidebar/components/logo.jsx +++ b/client/src/Components/Sidebar/components/logo.jsx @@ -51,7 +51,7 @@ const Logo = ({ collapsed }) => { mt={theme.spacing(2)} color={theme.palette.primary.contrastText} fontSize={"var(--env-var-font-size-medium-plus)"} - sx={{ opacity: 1, fontWeight: 500 }} + sx={{ fontWeight: 500 }} > {t("common.appName")} From 424df24bf39833c8f741789e6286009d4e3212fc Mon Sep 17 00:00:00 2001 From: vineet-channe Date: Thu, 7 Aug 2025 20:35:24 +0530 Subject: [PATCH 214/259] feat: Add yellow warning range (50-80%) for infrastructure monitors Fixes #2615 --- client/src/Components/Charts/CustomGauge/index.jsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/client/src/Components/Charts/CustomGauge/index.jsx b/client/src/Components/Charts/CustomGauge/index.jsx index d9718d72c..132cb4237 100644 --- a/client/src/Components/Charts/CustomGauge/index.jsx +++ b/client/src/Components/Charts/CustomGauge/index.jsx @@ -64,10 +64,14 @@ const CustomGauge = ({ const progressWithinRange = Math.max(MINIMUM_VALUE, Math.min(progress, MAXIMUM_VALUE)); - const fillColor = - progressWithinRange > threshold - ? theme.palette.error.lowContrast - : theme.palette.accent.main; + let fillColor; + if (progressWithinRange < 50) { + fillColor = theme.palette.success.main; + } else if (progressWithinRange < 80) { + fillColor = theme.palette.warning.lowContrast; + } else { + fillColor = theme.palette.error.lowContrast; + } if (isLoading) { return ( From dc8b0aed9f7c5424d34449a53f02fdd5b6d08d3f Mon Sep 17 00:00:00 2001 From: vineet-channe Date: Thu, 7 Aug 2025 20:46:15 +0530 Subject: [PATCH 215/259] chore: format CustomGauge with Prettier --- client/src/Components/Charts/CustomGauge/index.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/Components/Charts/CustomGauge/index.jsx b/client/src/Components/Charts/CustomGauge/index.jsx index 132cb4237..a79cba093 100644 --- a/client/src/Components/Charts/CustomGauge/index.jsx +++ b/client/src/Components/Charts/CustomGauge/index.jsx @@ -66,11 +66,11 @@ const CustomGauge = ({ let fillColor; if (progressWithinRange < 50) { - fillColor = theme.palette.success.main; + fillColor = theme.palette.success.main; } else if (progressWithinRange < 80) { - fillColor = theme.palette.warning.lowContrast; + fillColor = theme.palette.warning.lowContrast; } else { - fillColor = theme.palette.error.lowContrast; + fillColor = theme.palette.error.lowContrast; } if (isLoading) { From 029b8fac373ecdb773ba948f405eab971ab4da56 Mon Sep 17 00:00:00 2001 From: Br0wnHammer Date: Fri, 8 Aug 2025 01:07:49 +0530 Subject: [PATCH 216/259] Feat: Remove Theme Toggle Button --- client/src/Components/Sidebar/components/authFooter.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/Components/Sidebar/components/authFooter.jsx b/client/src/Components/Sidebar/components/authFooter.jsx index 0e6937913..03c8aef4d 100644 --- a/client/src/Components/Sidebar/components/authFooter.jsx +++ b/client/src/Components/Sidebar/components/authFooter.jsx @@ -156,7 +156,6 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => { direction="row" columnGap={theme.spacing(2)} > - Date: Fri, 8 Aug 2025 01:11:27 +0530 Subject: [PATCH 217/259] Remove Redundant Stack --- .../Sidebar/components/authFooter.jsx | 54 +++++++++---------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/client/src/Components/Sidebar/components/authFooter.jsx b/client/src/Components/Sidebar/components/authFooter.jsx index 03c8aef4d..d4d317908 100644 --- a/client/src/Components/Sidebar/components/authFooter.jsx +++ b/client/src/Components/Sidebar/components/authFooter.jsx @@ -136,7 +136,6 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => { display: "block", whiteSpace: "nowrap", overflow: "hidden", - textOverflow: "ellipsis", }} > {authState.user?.firstName} {authState.user?.lastName} @@ -152,37 +151,32 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => { {getRoleDisplayText(authState.user, t)} - - - openPopup(event)} - > - - - - + "& svg": { + width: "22px", + height: "22px", + }, + "& svg path": { + /* Vertical three dots */ + stroke: theme.palette.primary.contrastTextTertiary, + }, + }} + onClick={(event) => openPopup(event)} + > + + + Date: Fri, 8 Aug 2025 01:31:20 +0530 Subject: [PATCH 218/259] Tooltip Position --- client/src/Components/Sidebar/components/authFooter.jsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/src/Components/Sidebar/components/authFooter.jsx b/client/src/Components/Sidebar/components/authFooter.jsx index d4d317908..286713dbd 100644 --- a/client/src/Components/Sidebar/components/authFooter.jsx +++ b/client/src/Components/Sidebar/components/authFooter.jsx @@ -136,6 +136,7 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => { display: "block", whiteSpace: "nowrap", overflow: "hidden", + textOverflow: "ellipsis", }} > {authState.user?.firstName} {authState.user?.lastName} @@ -157,11 +158,9 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => { > Date: Fri, 8 Aug 2025 02:06:44 +0530 Subject: [PATCH 219/259] Remove unused Imports --- client/src/Components/Sidebar/components/authFooter.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/Components/Sidebar/components/authFooter.jsx b/client/src/Components/Sidebar/components/authFooter.jsx index 286713dbd..e94aeddee 100644 --- a/client/src/Components/Sidebar/components/authFooter.jsx +++ b/client/src/Components/Sidebar/components/authFooter.jsx @@ -4,7 +4,6 @@ import Typography from "@mui/material/Typography"; import Tooltip from "@mui/material/Tooltip"; import IconButton from "@mui/material/IconButton"; import Avatar from "../../Avatar"; -import ThemeSwitch from "../../ThemeSwitch"; import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; import Divider from "@mui/material/Divider"; From 10ad3397a3ce12277fa47eb3e4a7cb53e9514179 Mon Sep 17 00:00:00 2001 From: vineet-channe Date: Fri, 8 Aug 2025 03:03:23 +0530 Subject: [PATCH 220/259] docs(CustomGauge): remove threshold prop from documentation and usage example - Removed all references to the unused prop from the JSDoc and usage example in the CustomGauge component. - Ensured PropTypes and documentation accurately --- client/src/Components/Charts/CustomGauge/index.jsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/client/src/Components/Charts/CustomGauge/index.jsx b/client/src/Components/Charts/CustomGauge/index.jsx index a79cba093..614b3774c 100644 --- a/client/src/Components/Charts/CustomGauge/index.jsx +++ b/client/src/Components/Charts/CustomGauge/index.jsx @@ -17,7 +17,6 @@ const MAXIMUM_VALUE = 100; * @param {number} [props.progress=0] - Progress percentage (0-100) * @param {number} [props.radius=60] - Radius of the gauge circle * @param {number} [props.strokeWidth=15] - Width of the gauge stroke - * @param {number} [props.threshold=50] - Threshold for color change * @param {number} [props.precision=1] - Precision of the progress percentage * @param {string} [props.unit="%"] - Unit of progress * @@ -26,7 +25,6 @@ const MAXIMUM_VALUE = 100; * progress={75} * radius={50} * strokeWidth={10} - * threshold={50} * /> * * @returns {React.ReactElement} Rendered CustomGauge component @@ -36,7 +34,6 @@ const CustomGauge = ({ progress = 0, radius = 70, strokeWidth = 15, - threshold = 50, precision = 1, unit = "%", }) => { @@ -146,7 +143,6 @@ CustomGauge.propTypes = { progress: PropTypes.number, radius: PropTypes.number, strokeWidth: PropTypes.number, - threshold: PropTypes.number, precision: PropTypes.number, unit: PropTypes.string, }; From c6d56b30e431f30489507baeeebdf2590955832f Mon Sep 17 00:00:00 2001 From: karenvicent Date: Thu, 7 Aug 2025 18:53:05 -0400 Subject: [PATCH 221/259] fix gaps in select components --- client/src/Components/Inputs/Search/index.jsx | 6 +++--- client/src/Components/Inputs/Select/index.jsx | 2 +- client/src/Pages/Settings/SettingsTimeZone.jsx | 2 +- .../Pages/StatusPage/Create/Components/Tabs/Settings.jsx | 2 +- client/src/Utils/Theme/globalTheme.js | 5 +++-- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/client/src/Components/Inputs/Search/index.jsx b/client/src/Components/Inputs/Search/index.jsx index c3008886c..7202cc2eb 100644 --- a/client/src/Components/Inputs/Search/index.jsx +++ b/client/src/Components/Inputs/Search/index.jsx @@ -4,7 +4,6 @@ import { ListItem, Autocomplete, TextField, - Stack, Typography, Checkbox, } from "@mui/material"; @@ -33,7 +32,6 @@ const SearchAdornment = () => { const theme = useTheme(); return ( diff --git a/client/src/Components/Inputs/Select/index.jsx b/client/src/Components/Inputs/Select/index.jsx index a438b6bb2..a132b5d77 100644 --- a/client/src/Components/Inputs/Select/index.jsx +++ b/client/src/Components/Inputs/Select/index.jsx @@ -112,7 +112,7 @@ const Select = ({ fill: theme.palette.primary.contrastTextTertiary, }, "& .MuiSelect-select": { - padding: "0 13px", + padding: "0", minHeight: "34px", display: "flex", alignItems: "center", diff --git a/client/src/Pages/Settings/SettingsTimeZone.jsx b/client/src/Pages/Settings/SettingsTimeZone.jsx index be255ab52..ee5cf48de 100644 --- a/client/src/Pages/Settings/SettingsTimeZone.jsx +++ b/client/src/Pages/Settings/SettingsTimeZone.jsx @@ -58,7 +58,7 @@ const SettingsTimeZone = ({ HEADING_SX, handleChange, timezone }) => { inputValue={rawInput} handleInputChange={(val) => setRawInput(val)} handleChange={handleTimezoneChange} - isAdorned={false} + isAdorned={true} unit="timezone" /> diff --git a/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx index 4c2842b71..dd4345158 100644 --- a/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx +++ b/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx @@ -130,7 +130,7 @@ const TabSettings = ({ inputValue={rawInput} handleInputChange={(newVal) => setRawInput(newVal)} handleChange={handleTimezoneChange} - isAdorned={false} + isAdorned={true} unit="timezone" /> diff --git a/client/src/Utils/Theme/globalTheme.js b/client/src/Utils/Theme/globalTheme.js index 6f2a18e71..ce58153ec 100644 --- a/client/src/Utils/Theme/globalTheme.js +++ b/client/src/Utils/Theme/globalTheme.js @@ -353,7 +353,7 @@ const baseTheme = (palette) => ({ }, "& .MuiInputBase-input.MuiOutlinedInput-input": { - padding: "0 var(--env-var-spacing-1-minus) !important", + padding: `0 ${theme.spacing(5)}`, }, "& .MuiOutlinedInput-root": { @@ -408,6 +408,7 @@ const baseTheme = (palette) => ({ "& .MuiOutlinedInput-root": { paddingTop: 0, paddingBottom: 0, + paddingRight: theme.spacing(5), }, "& fieldset": { borderColor: theme.palette.primary.lowContrast, @@ -569,7 +570,7 @@ const baseTheme = (palette) => ({ "&:hover .MuiOutlinedInput-notchedOutline": { borderColor: theme.palette.primary.lowContrast, }, - padding: "0 13px", + padding: `0 ${theme.spacing(5)}`, minHeight: "34px", display: "flex", alignItems: "center", From b7e7028b6fd200f15bba6433dde83355d55ee926 Mon Sep 17 00:00:00 2001 From: Burak Arslan Date: Fri, 8 Aug 2025 23:59:41 +0300 Subject: [PATCH 222/259] Fix error handling and method assignment in requestGame function --- server/src/service/infrastructure/networkService.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server/src/service/infrastructure/networkService.js b/server/src/service/infrastructure/networkService.js index 4a3b79ed3..8f904bf56 100755 --- a/server/src/service/infrastructure/networkService.js +++ b/server/src/service/infrastructure/networkService.js @@ -503,7 +503,12 @@ class NetworkService { host: url, port: port, }).catch((error) => { - null; + this.logger.warn({ + message: error.message, + service: this.SERVICE_NAME, + method: "requestGame", + details: { url, port, gameId }, + }); }); if (!state) { From f5d0b74ccf67e4412e3bcfdbc5997d034aad543d Mon Sep 17 00:00:00 2001 From: Owaise Date: Sat, 9 Aug 2025 12:14:25 +0530 Subject: [PATCH 223/259] Fixed the processing to be done in backend now. --- .../Components/Charts/Utils/chartUtils.jsx | 43 +++++++++ .../Components/NetworkStats/NetworkCharts.jsx | 3 + .../Details/Components/NetworkStats/index.jsx | 89 ++++--------------- server/src/db/mongo/modules/monitorModule.js | 55 +++++++++++- .../db/mongo/modules/monitorModuleQueries.js | 34 +++++-- 5 files changed, 144 insertions(+), 80 deletions(-) diff --git a/client/src/Components/Charts/Utils/chartUtils.jsx b/client/src/Components/Charts/Utils/chartUtils.jsx index af5f27c66..e2560aa2e 100644 --- a/client/src/Components/Charts/Utils/chartUtils.jsx +++ b/client/src/Components/Charts/Utils/chartUtils.jsx @@ -91,6 +91,49 @@ const getFormattedPercentage = (value) => { return `${(value * 100).toFixed(2)}%`; }; +/** + * Custom tick component for rendering network bytes per second. + * + * @param {Object} props - The properties object. + * @param {number} props.x - The x-coordinate for the tick. + * @param {number} props.y - The y-coordinate for the tick. + * @param {Object} props.payload - The payload object containing tick data. + * @param {number} props.index - The index of the tick. + * @returns {JSX.Element|null} The rendered tick component or null for the first tick. + */ +export const NetworkTick = ({ x, y, payload, index }) => { + const theme = useTheme(); + if (index === 0) return null; + + const formatBytes = (bytes) => { + if (bytes >= 1_000_000_000) return `${(bytes / 1_000_000_000).toFixed(1)} GB/s`; + if (bytes >= 1_000_000) return `${(bytes / 1_000_000).toFixed(1)} MB/s`; + if (bytes >= 1_000) return `${(bytes / 1_000).toFixed(1)} KB/s`; + return `${bytes} B/s`; + }; + + return ( + + {formatBytes(payload?.value)} + + ); +}; + +NetworkTick.propTypes = { + x: PropTypes.number, + y: PropTypes.number, + payload: PropTypes.object, + index: PropTypes.number, +}; + + /** * Custom tooltip component for displaying infrastructure data. * diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx index 7aad494ab..74ddb6bae 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx @@ -7,6 +7,7 @@ import InfraAreaChart from "../../../../../Pages/Infrastructure/Details/Componen import { TzTick, InfrastructureTooltip, + NetworkTick, } from "../../../../../Components/Charts/Utils/chartUtils"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; @@ -23,6 +24,7 @@ const NetworkCharts = ({ eth0Data, dateRange }) => { const theme = useTheme(); const { t } = useTranslation(); + const configs = [ { type: "network-bytes", @@ -33,6 +35,7 @@ const NetworkCharts = ({ eth0Data, dateRange }) => { gradientStartColor: theme.palette.info.main, yLabel: t("bytesPerSecond"), xTick: , + yTick: , toolTip: ( { - const eth0Data = getEth0TimeSeries(checks); + const eth0Data = (checks || []) + .map((check) => { + const en0 = (check.net || []).find((iface) => iface.name === "en0"); + if (!en0) return null; + + return { + _id: check._id, + bytesPerSec: en0.avgBytesRecv, + packetsPerSec: en0.avgPacketsRecv, + errors: (en0.avgErrOut ?? 0), + drops: (en0.avgDropOut ?? 0) + }; + }) + .filter(Boolean); + + console.log(eth0Data); return ( <> - + - + ); }; @@ -34,65 +43,3 @@ Network.propTypes = { }; export default Network; - -/* ---------- Helper functions ---------- */ -function getEth0TimeSeries(checks) { - const sorted = [...(checks || [])].sort((a, b) => new Date(a._id) - new Date(b._id)); - const series = []; - let prev = null; - - for (const check of sorted) { - const eth = (check.net || []).find((iface) => iface.name === "en0"); - if (!eth) { - prev = check; - continue; - } - - if (prev) { - const prevEth = (prev.net || []).find((iface) => iface.name === "en0"); - const t1 = new Date(check._id); - const t0 = new Date(prev._id); - - if (!prevEth || isNaN(t1) || isNaN(t0)) { - prev = check; - continue; - } - - const dt = (t1 - t0) / 1000; - - if (dt > 0) { - const bytesField = eth.avgBytesSent; - const prevBytesField = prevEth.avgBytesSent; - - if (bytesField !== undefined && prevBytesField !== undefined) { - const dataPoint = { - _id: check._id, - bytesPerSec: (bytesField - prevBytesField) / dt, - packetsPerSec: (eth.avgPacketsSent - prevEth.avgPacketsSent) / dt, - errors: (eth.avgErrIn ?? 0) + (eth.avgErrOut ?? 0), - drops: 0, - }; - series.push(dataPoint); - } - } - } - prev = check; - } - - // If we only have one check, create a single data point with absolute values - if (series.length === 0 && sorted.length === 1) { - const check = sorted[0]; - const eth = (check.net || []).find((iface) => iface.name === "en0"); - if (eth) { - series.push({ - _id: check._id, - bytesPerSec: eth.avgBytesSent || 0, - packetsPerSec: eth.avgPacketsSent || 0, - errors: (eth.avgErrIn ?? 0) + (eth.avgErrOut ?? 0), - drops: 0, - }); - } - } - - return series; -} diff --git a/server/src/db/mongo/modules/monitorModule.js b/server/src/db/mongo/modules/monitorModule.js index 76f055f94..4cd28ea9d 100755 --- a/server/src/db/mongo/modules/monitorModule.js +++ b/server/src/db/mongo/modules/monitorModule.js @@ -326,10 +326,53 @@ class MonitorModule { } }; + processNetworkRates = (checks) => { + if (!Array.isArray(checks) || checks.length === 0) return []; + + const sorted = [...checks].sort((a, b) => new Date(a._id) - new Date(b._id)); + const lastSeen = {}; + + for (const check of sorted) { + if (!Array.isArray(check.net)) { + check.net = []; + continue; + } + + const newNet = []; + + for (const iface of check.net) { + const prev = lastSeen[iface.name]; + const t1 = new Date(check._id); + + if (prev) { + const t0 = new Date(prev._id); + const dt = (t1 - t0) / 1000; + + if (dt > 0) { + newNet.push({ + name: iface.name, + avgBytesRecv: (iface.avgBytesRecv - prev.avgBytesRecv), + avgPacketsRecv: (iface.avgPacketsRecv - prev.avgPacketsRecv), + avgErrOut: iface.avgErrOut - prev.avgErrOut, + avgDropOut: iface.avgDropOut, + }); + } + } + + lastSeen[iface.name] = { ...iface, _id: check._id }; + } + + check.net = newNet; + } + + return sorted; + }; + getHardwareDetailsById = async ({ monitorId, dateRange }) => { try { const monitor = await this.Monitor.findById(monitorId); const dates = this.getDateRange(dateRange); + const formatLookup = { recent: "%Y-%m-%dT%H:%M:00Z", day: "%Y-%m-%dT%H:00:00Z", @@ -337,13 +380,19 @@ class MonitorModule { month: "%Y-%m-%dT00:00:00Z", }; const dateString = formatLookup[dateRange]; + const hardwareStats = await this.HardwareCheck.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString)); - const monitorStats = { + const stats = hardwareStats[0]; + if (stats && stats.checks) { + // Replace net with per-second rates + stats.checks = this.processNetworkRates(stats.checks); + } + + return { ...monitor.toObject(), - stats: hardwareStats[0], + stats, }; - return monitorStats; } catch (error) { error.service = SERVICE_NAME; error.method = "getHardwareDetailsById"; diff --git a/server/src/db/mongo/modules/monitorModuleQueries.js b/server/src/db/mongo/modules/monitorModuleQueries.js index df15593e2..79804f183 100755 --- a/server/src/db/mongo/modules/monitorModuleQueries.js +++ b/server/src/db/mongo/modules/monitorModuleQueries.js @@ -393,7 +393,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { 0, ], }, - bytesSent: { + avgBytesSent: { $avg: { $map: { input: "$net", @@ -404,7 +404,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - bytesRecv: { + avgBytesRecv: { $avg: { $map: { input: "$net", @@ -415,7 +415,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - packetsSent: { + avgPacketsSent: { $avg: { $map: { input: "$net", @@ -426,7 +426,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - packetsRecv: { + avgPacketsRecv: { $avg: { $map: { input: "$net", @@ -437,7 +437,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - errIn: { + avgErrIn: { $avg: { $map: { input: "$net", @@ -448,7 +448,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - errOut: { + avgErrOut: { $avg: { $map: { input: "$net", @@ -459,6 +459,28 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, + avgDropIn: { + $avg: { + $map: { + input: "$net", + as: "netArray", + in: { + $arrayElemAt: ["$$netArray.drop_in", "$$netIndex"], + }, + }, + }, + }, + avgDropOut: { + $avg: { + $map: { + input: "$net", + as: "netArray", + in: { + $arrayElemAt: ["$$netArray.drop_out", "$$netIndex"], + }, + }, + }, + }, }, }, }, From 47c231a2141675df72cf68efca55b7340d942a89 Mon Sep 17 00:00:00 2001 From: Owaise Date: Sat, 9 Aug 2025 12:17:25 +0530 Subject: [PATCH 224/259] Formating done. --- .../src/Components/Charts/Utils/chartUtils.jsx | 1 - .../Components/NetworkStats/NetworkCharts.jsx | 1 - .../Details/Components/NetworkStats/index.jsx | 16 +++++++++++----- server/src/db/mongo/modules/monitorModule.js | 4 ++-- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/client/src/Components/Charts/Utils/chartUtils.jsx b/client/src/Components/Charts/Utils/chartUtils.jsx index e2560aa2e..40eb5b539 100644 --- a/client/src/Components/Charts/Utils/chartUtils.jsx +++ b/client/src/Components/Charts/Utils/chartUtils.jsx @@ -133,7 +133,6 @@ NetworkTick.propTypes = { index: PropTypes.number, }; - /** * Custom tooltip component for displaying infrastructure data. * diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx index 74ddb6bae..5dd9feaad 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx @@ -24,7 +24,6 @@ const NetworkCharts = ({ eth0Data, dateRange }) => { const theme = useTheme(); const { t } = useTranslation(); - const configs = [ { type: "network-bytes", diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx index 06cb25d17..f8bdf5e8a 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx @@ -10,11 +10,11 @@ const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => { if (!en0) return null; return { - _id: check._id, + _id: check._id, bytesPerSec: en0.avgBytesRecv, packetsPerSec: en0.avgPacketsRecv, - errors: (en0.avgErrOut ?? 0), - drops: (en0.avgDropOut ?? 0) + errors: en0.avgErrOut ?? 0, + drops: en0.avgDropOut ?? 0, }; }) .filter(Boolean); @@ -23,13 +23,19 @@ const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => { return ( <> - + - + ); }; diff --git a/server/src/db/mongo/modules/monitorModule.js b/server/src/db/mongo/modules/monitorModule.js index 4cd28ea9d..f0b7d190c 100755 --- a/server/src/db/mongo/modules/monitorModule.js +++ b/server/src/db/mongo/modules/monitorModule.js @@ -351,8 +351,8 @@ class MonitorModule { if (dt > 0) { newNet.push({ name: iface.name, - avgBytesRecv: (iface.avgBytesRecv - prev.avgBytesRecv), - avgPacketsRecv: (iface.avgPacketsRecv - prev.avgPacketsRecv), + avgBytesRecv: iface.avgBytesRecv - prev.avgBytesRecv, + avgPacketsRecv: iface.avgPacketsRecv - prev.avgPacketsRecv, avgErrOut: iface.avgErrOut - prev.avgErrOut, avgDropOut: iface.avgDropOut, }); From c5dd16511d1523ebdf0d4a977bd1b5bc289dea62 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 11 Aug 2025 13:55:01 -0700 Subject: [PATCH 225/259] update openapi specs --- server/openapi.json | 4720 +++++++++++++++++++++++++------------------ 1 file changed, 2773 insertions(+), 1947 deletions(-) mode change 100755 => 100644 server/openapi.json diff --git a/server/openapi.json b/server/openapi.json old mode 100755 new mode 100644 index 465bf948a..6cc2e7de0 --- a/server/openapi.json +++ b/server/openapi.json @@ -1,7 +1,7 @@ { "openapi": "3.1.0", "info": { - "title": "Checkmate", + "title": "Checkmate API", "summary": "Checkmate OpenAPI Specifications", "description": "Checkmate is an open source monitoring tool used to track the operational status and performance of servers and websites. It regularly checks whether a server/website is accessible and performs optimally, providing real-time alerts and reports on the monitored services' availability, downtime, and response time.", "contact": { @@ -13,7 +13,7 @@ "name": "AGPLv3", "url": "https://github.com/bluewave-labs/checkmate/tree/HEAD/LICENSE" }, - "version": "1.0" + "version": "2.3" }, "servers": [ { @@ -22,8 +22,8 @@ "variables": { "PORT": { "description": "API Port", - "enum": ["5000"], - "default": "5000" + "enum": ["52345"], + "default": "52345" }, "API_PATH": { "description": "API Base Path", @@ -32,26 +32,10 @@ } } }, - { - "url": "http://localhost/{API_PATH}", - "description": "Distribution Local Development Server", - "variables": { - "API_PATH": { - "description": "API Base Path", - "enum": ["api/v1"], - "default": "api/v1" - } - } - }, { "url": "https://checkmate-demo.bluewavelabs.ca/{API_PATH}", "description": "Checkmate Demo Server", "variables": { - "PORT": { - "description": "API Port", - "enum": ["5000"], - "default": "5000" - }, "API_PATH": { "description": "API Base Path", "enum": ["api/v1"], @@ -63,107 +47,81 @@ "tags": [ { "name": "auth", - "description": "Authentication" + "description": "Authentication and user management" }, { "name": "invite", - "description": "Invite" + "description": "Team invitation management" }, { "name": "monitors", - "description": "Monitors" + "description": "Monitor management (uptime, page speed, hardware)" }, { "name": "checks", - "description": "Checks" + "description": "Monitor check results and history" }, { "name": "maintenance-window", - "description": "Maintenance window" + "description": "Scheduled maintenance windows" }, { "name": "queue", - "description": "Queue" + "description": "Job queue management" }, { "name": "status-page", - "description": "Status Page" + "description": "Public status page management" + }, + { + "name": "settings", + "description": "Application configuration settings" + }, + { + "name": "logs", + "description": "Application logs and diagnostics" + }, + { + "name": "notifications", + "description": "Notification integrations (email, slack, discord, etc.)" + }, + { + "name": "diagnostic", + "description": "System health and performance diagnostics" } ], "paths": { "/auth/register": { "post": { "tags": ["auth"], - "description": "Register a new user", + "summary": "Register new user", + "description": "Register a new user account with profile information", "requestBody": { + "required": true, "content": { "application/json": { "schema": { - "type": "object", - "required": ["firstName", "lastName", "email", "password", "role", "teamId"], - "properties": { - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "email": { - "type": "string", - "format": "email" - }, - "password": { - "type": "string", - "format": "password" - }, - "profileImage": { - "type": "file", - "format": "file" - }, - "role": { - "type": "array", - "enum": [["user"], ["admin"], ["superadmin"], ["Demo"]], - "default": ["superadmin"] - }, - "teamId": { - "type": "string", - "format": "uuid" - } - } + "$ref": "#/components/schemas/RegisterRequest" } } } }, "responses": { "200": { - "description": "OK", + "description": "User registered successfully", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SuccessResponse" + "$ref": "#/components/schemas/AuthResponse" } } } }, "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/ValidationError" }, "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/InternalServerError" } } } @@ -171,57 +129,37 @@ "/auth/login": { "post": { "tags": ["auth"], - "description": "Login with credentials", + "summary": "User login", + "description": "Authenticate user with email and password", "requestBody": { + "required": true, "content": { "application/json": { "schema": { - "type": "object", - "required": ["email", "password"], - "properties": { - "email": { - "type": "string", - "format": "email" - }, - "password": { - "type": "string", - "format": "password" - } - } + "$ref": "#/components/schemas/LoginRequest" } } } }, "responses": { "200": { - "description": "OK", + "description": "Login successful", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SuccessResponse" + "$ref": "#/components/schemas/AuthResponse" } } } }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/ValidationError" }, "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/InternalServerError" } } } @@ -229,23 +167,13 @@ "/auth/refresh": { "post": { "tags": ["auth"], - "description": "Generates a new auth token if the refresh token is valid.", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } - }, - "required": false - }, + "summary": "Refresh access token", + "description": "Generate new access token using refresh token", "parameters": [ { "name": "x-refresh-token", "in": "header", - "description": "Refresh token required to generate a new auth token.", + "description": "Refresh token", "required": true, "schema": { "type": "string" @@ -254,7 +182,7 @@ { "name": "authorization", "in": "header", - "description": "Old access token, used to extract payload).", + "description": "Bearer token (old access token)", "required": true, "schema": { "type": "string" @@ -263,34 +191,20 @@ ], "responses": { "200": { - "description": "New access token generated.", + "description": "Token refreshed successfully", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SuccessResponse" + "$ref": "#/components/schemas/AuthResponse" } } } }, "401": { - "description": "Unauthorized or invalid refresh token.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/Unauthorized" }, "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/InternalServerError" } } } @@ -298,7 +212,1472 @@ "/auth/user/{userId}": { "put": { "tags": ["auth"], - "description": "Change user information", + "summary": "Update user profile", + "description": "Update user information including profile image", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/UserUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/Success" + }, + "422": { + "$ref": "#/components/responses/ValidationError" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "delete": { + "tags": ["auth"], + "summary": "Delete user account", + "description": "Permanently delete user account and associated data", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/Success" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/auth/users": { + "get": { + "tags": ["auth"], + "summary": "Get all users", + "description": "Retrieve all users (admin/superadmin only)", + "responses": { + "200": { + "description": "Users retrieved successfully", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + ] + } + } + } + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/auth/users/superadmin": { + "get": { + "tags": ["auth"], + "summary": "Check superadmin exists", + "description": "Check if a superadmin account exists in the system", + "responses": { + "200": { + "description": "Check completed", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "exists": { + "type": "boolean" + } + } + } + } + } + ] + } + } + } + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/auth/recovery/request": { + "post": { + "tags": ["auth"], + "summary": "Request password reset", + "description": "Send password reset email to user", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["email"], + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "User email address" + } + } + } + } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/Success" + }, + "404": { + "description": "User not found" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/auth/recovery/validate": { + "post": { + "tags": ["auth"], + "summary": "Validate recovery token", + "description": "Validate password reset token", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["recoveryToken"], + "properties": { + "recoveryToken": { + "type": "string", + "description": "Password reset token" + } + } + } + } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/Success" + }, + "400": { + "description": "Invalid or expired token" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/auth/recovery/reset": { + "post": { + "tags": ["auth"], + "summary": "Reset password", + "description": "Reset user password with recovery token", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["recoveryToken", "password"], + "properties": { + "recoveryToken": { + "type": "string", + "description": "Password reset token" + }, + "password": { + "type": "string", + "format": "password", + "description": "New password" + } + } + } + } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/Success" + }, + "400": { + "description": "Invalid token or password requirements not met" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/monitors": { + "get": { + "tags": ["monitors"], + "summary": "Get all monitors", + "description": "Retrieve all monitors for the authenticated user", + "responses": { + "200": { + "description": "Monitors retrieved successfully", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Monitor" + } + } + } + } + ] + } + } + } + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "post": { + "tags": ["monitors"], + "summary": "Create monitor", + "description": "Create a new monitoring endpoint", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateMonitorRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Monitor created successfully", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/Monitor" + } + } + } + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationError" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "delete": { + "tags": ["monitors"], + "summary": "Delete all monitors", + "description": "Delete all monitors (superadmin only)", + "responses": { + "200": { + "$ref": "#/components/responses/Success" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/team": { + "get": { + "tags": ["monitors"], + "summary": "Get monitors by team", + "description": "Get monitors filtered by team with optional parameters", + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Filter by monitor status (up/down)", + "schema": { + "type": "boolean" + } + }, + { + "name": "type", + "in": "query", + "description": "Filter by monitor type", + "schema": { + "type": "string", + "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port"] + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for pagination", + "schema": { + "type": "integer", + "minimum": 1, + "default": 1 + } + }, + { + "name": "rowsPerPage", + "in": "query", + "description": "Number of monitors per page", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 10 + } + }, + { + "name": "filter", + "in": "query", + "description": "Search filter value", + "schema": { + "type": "string" + } + }, + { + "name": "field", + "in": "query", + "description": "Field to filter on", + "schema": { + "type": "string", + "enum": ["name", "url", "description"] + } + } + ], + "responses": { + "200": { + "description": "Team monitors retrieved successfully", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "monitors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Monitor" + } + }, + "totalCount": { + "type": "integer" + } + } + } + } + } + ] + } + } + } + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/team/with-checks": { + "get": { + "tags": ["monitors"], + "summary": "Get monitors with recent checks", + "description": "Get team monitors with their most recent check results", + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "Number of recent checks to include per monitor", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "default": 10 + } + } + ], + "responses": { + "200": { + "description": "Monitors with checks retrieved successfully" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/team/summary": { + "get": { + "tags": ["monitors"], + "summary": "Get monitors summary", + "description": "Get team monitors with summary statistics", + "parameters": [ + { + "name": "type", + "in": "query", + "description": "Filter by monitor type", + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port"] + } + } + } + ], + "responses": { + "200": { + "description": "Monitor summary retrieved successfully" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/{monitorId}": { + "get": { + "tags": ["monitors"], + "summary": "Get monitor by ID", + "description": "Retrieve a specific monitor by its ID", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "description": "Monitor ID", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Monitor retrieved successfully", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/Monitor" + } + } + } + ] + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "put": { + "tags": ["monitors"], + "summary": "Update monitor", + "description": "Update an existing monitor", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateMonitorRequest" + } + } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/Success" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "$ref": "#/components/responses/ValidationError" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "delete": { + "tags": ["monitors"], + "summary": "Delete monitor", + "description": "Delete a specific monitor", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/Success" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/uptime/details/{monitorId}": { + "get": { + "tags": ["monitors"], + "summary": "Get uptime details", + "description": "Get detailed uptime statistics for a monitor", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Uptime details retrieved successfully" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/hardware/details/{monitorId}": { + "get": { + "tags": ["monitors"], + "summary": "Get hardware monitoring details", + "description": "Get hardware performance metrics for a monitor", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Hardware details retrieved successfully" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/pause/{monitorId}": { + "post": { + "tags": ["monitors"], + "summary": "Pause/unpause monitor", + "description": "Toggle monitor active status", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/Success" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/stats/{monitorId}": { + "get": { + "tags": ["monitors"], + "summary": "Get monitor statistics", + "description": "Get comprehensive statistics for a monitor", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Monitor statistics retrieved successfully" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/certificate/{monitorId}": { + "get": { + "tags": ["monitors"], + "summary": "Get SSL certificate info", + "description": "Get SSL certificate information for a monitor", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Certificate information retrieved successfully" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/demo": { + "post": { + "tags": ["monitors"], + "summary": "Add demo monitors", + "description": "Add preconfigured demo monitors for testing", + "responses": { + "200": { + "$ref": "#/components/responses/Success" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/export": { + "get": { + "tags": ["monitors"], + "summary": "Export monitors to CSV", + "description": "Export all monitors to CSV format", + "responses": { + "200": { + "description": "CSV file generated successfully", + "content": { + "text/csv": { + "schema": { + "type": "string" + } + } + } + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/bulk": { + "post": { + "tags": ["monitors"], + "summary": "Bulk import monitors", + "description": "Import monitors from CSV file", + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "csvFile": { + "type": "string", + "format": "binary", + "description": "CSV file containing monitor data" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Monitors imported successfully" + }, + "422": { + "$ref": "#/components/responses/ValidationError" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/test-email": { + "post": { + "tags": ["monitors"], + "summary": "Send test email", + "description": "Send a test email notification", + "responses": { + "200": { + "$ref": "#/components/responses/Success" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/settings": { + "get": { + "tags": ["settings"], + "summary": "Get application settings", + "description": "Retrieve current application configuration", + "responses": { + "200": { + "description": "Settings retrieved successfully", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AppSettings" + } + } + } + ] + } + } + } + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "put": { + "tags": ["settings"], + "summary": "Update application settings", + "description": "Update application configuration (admin/superadmin only)", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AppSettings" + } + } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/Success" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "422": { + "$ref": "#/components/responses/ValidationError" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/settings/test-email": { + "post": { + "tags": ["settings"], + "summary": "Send test email", + "description": "Send test email to verify email configuration", + "responses": { + "200": { + "$ref": "#/components/responses/Success" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/notifications": { + "get": { + "tags": ["notifications"], + "summary": "Get team notifications", + "description": "Get all notification configurations for the team", + "responses": { + "200": { + "description": "Notifications retrieved successfully", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Notification" + } + } + } + } + ] + } + } + } + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "post": { + "tags": ["notifications"], + "summary": "Create notification", + "description": "Create a new notification integration", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateNotificationRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Notification created successfully" + }, + "422": { + "$ref": "#/components/responses/ValidationError" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/notifications/{id}": { + "get": { + "tags": ["notifications"], + "summary": "Get notification by ID", + "description": "Retrieve a specific notification configuration", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Notification retrieved successfully" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "put": { + "tags": ["notifications"], + "summary": "Update notification", + "description": "Update an existing notification configuration", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateNotificationRequest" + } + } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/Success" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "$ref": "#/components/responses/ValidationError" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "delete": { + "tags": ["notifications"], + "summary": "Delete notification", + "description": "Delete a notification configuration", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/Success" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/notifications/test": { + "post": { + "tags": ["notifications"], + "summary": "Test notification", + "description": "Send a test notification to verify configuration", + "responses": { + "200": { + "$ref": "#/components/responses/Success" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/notifications/test/all": { + "post": { + "tags": ["notifications"], + "summary": "Test all notifications", + "description": "Send test notifications to all configured integrations", + "responses": { + "200": { + "$ref": "#/components/responses/Success" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/logs": { + "get": { + "tags": ["logs"], + "summary": "Get application logs", + "description": "Retrieve application logs (admin/superadmin only)", + "parameters": [ + { + "name": "level", + "in": "query", + "description": "Log level filter", + "schema": { + "type": "string", + "enum": ["error", "warn", "info", "debug"] + } + }, + { + "name": "limit", + "in": "query", + "description": "Number of log entries to return", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "default": 100 + } + } + ], + "responses": { + "200": { + "description": "Logs retrieved successfully" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/diagnostic/system": { + "get": { + "tags": ["diagnostic"], + "summary": "Get system diagnostics", + "description": "Get system health and performance metrics (admin/superadmin only)", + "responses": { + "200": { + "description": "System diagnostics retrieved successfully", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/SystemDiagnostics" + } + } + } + ] + } + } + } + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/auth/user": { + "put": { + "tags": ["auth"], + "summary": "Update current user profile", + "description": "Update authenticated user's profile information", + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/UserUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/Success" + }, + "422": { + "$ref": "#/components/responses/ValidationError" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "delete": { + "tags": ["auth"], + "summary": "Delete current user account", + "description": "Delete authenticated user's account and associated data", + "responses": { + "200": { + "$ref": "#/components/responses/Success" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/auth/users/{userId}": { + "get": { + "tags": ["auth"], + "summary": "Get user by ID", + "description": "Get a specific user by ID (superadmin only)", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "User retrieved successfully", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/User" + } + } + } + ] + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "put": { + "tags": ["auth"], + "summary": "Update user by ID", + "description": "Update a specific user by ID (superadmin only)", "parameters": [ { "name": "userId", @@ -321,85 +1700,19 @@ }, "responses": { "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } + "$ref": "#/components/responses/Success" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "403": { + "$ref": "#/components/responses/Forbidden" }, "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/ValidationError" }, "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "delete": { - "tags": ["auth"], - "description": "Delete user", - "parameters": [ - { - "name": "userId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/InternalServerError" } }, "security": [ @@ -409,97 +1722,63 @@ ] } }, - "/auth/users/superadmin": { - "get": { - "tags": ["auth"], - "description": "Checks to see if an admin account exists", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/auth/users": { - "get": { - "tags": ["auth"], - "description": "Get all users", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/auth/recovery/request": { + "/invite": { "post": { - "tags": ["auth"], - "description": "Request a recovery token", + "tags": ["invite"], + "summary": "Create invite token", + "description": "Create a new invitation token", "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["email", "role"], + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "role": { + "type": "array", + "items": { + "type": "string", + "enum": ["user", "admin"] + } + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Invite token created successfully" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "422": { + "$ref": "#/components/responses/ValidationError" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/invite/send": { + "post": { + "tags": ["invite"], + "summary": "Send invitation email", + "description": "Send invitation email to user", + "requestBody": { + "required": true, "content": { "application/json": { "schema": { @@ -517,199 +1796,13 @@ }, "responses": { "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } + "$ref": "#/components/responses/Success" }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "403": { + "$ref": "#/components/responses/Forbidden" }, "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/auth/recovery/validate": { - "post": { - "tags": ["auth"], - "description": "Validate recovery token", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["recoveryToken"], - "properties": { - "recoveryToken": { - "type": "string" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/auth/recovery/reset": { - "post": { - "tags": ["auth"], - "description": "Password reset", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["recoveryToken", "password"], - "properties": { - "recoveryToken": { - "type": "string" - }, - "password": { - "type": "string" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/invite": { - "post": { - "tags": ["invite"], - "description": "Request an invitation", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["email", "role"], - "properties": { - "email": { - "type": "string" - }, - "role": { - "type": "array" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/InternalServerError" } }, "security": [ @@ -722,8 +1815,10 @@ "/invite/verify": { "post": { "tags": ["invite"], - "description": "Request an invitation", + "summary": "Verify invitation token", + "description": "Verify an invitation token", "requestBody": { + "required": true, "content": { "application/json": { "schema": { @@ -740,851 +1835,22 @@ }, "responses": { "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors": { - "get": { - "tags": ["monitors"], - "description": "Get all monitors", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "post": { - "tags": ["monitors"], - "description": "Create a new monitor", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateMonitorBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "delete": { - "tags": ["monitors"], - "description": "Delete all monitors", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/uptime": { - "get": { - "tags": ["monitors"], - "description": "Get all monitors with uptime stats for 1, 7, 30, and 90 days", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/resolution/url": { - "get": { - "tags": ["monitors"], - "description": "Check DNS resolution for a given URL", - "parameters": [ - { - "name": "monitorURL", - "in": "query", - "required": true, - "schema": { - "type": "string", - "example": "https://example.com" - }, - "description": "The URL to check DNS resolution for" - } - ], - "responses": { - "200": { - "description": "URL resolved successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } + "$ref": "#/components/responses/Success" }, "400": { - "description": "DNS resolution failed", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "description": "Invalid or expired token" }, "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/InternalServerError" } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/{monitorId}": { - "get": { - "tags": ["monitors"], - "description": "Get monitor by id", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "put": { - "tags": ["monitors"], - "description": "Update monitor by id", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateMonitorBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "delete": { - "tags": ["monitors"], - "description": "Delete monitor by id", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/stats/{monitorId}": { - "get": { - "tags": ["monitors"], - "description": "Get monitor stats", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/certificate/{monitorId}": { - "get": { - "tags": ["monitors"], - "description": "Get monitor certificate", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/team/summary/{teamId}": { - "get": { - "tags": ["monitors"], - "description": "Get monitors and summary by teamId", - "parameters": [ - { - "name": "teamId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "type", - "in": "query", - "required": false, - "schema": { - "type": "array", - "enum": ["http", "ping", "pagespeed"] - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/team/{teamId}": { - "get": { - "tags": ["monitors"], - "description": "Get monitors by teamId", - "parameters": [ - { - "name": "teamId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "status", - "description": "Status of monitor, true for up, false for down", - "in": "query", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "name": "checkOrder", - "description": "Order of checks", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": ["asc", "desc"] - } - }, - { - "name": "limit", - "description": "Number of checks to return with monitor", - "in": "query", - "required": false, - "schema": { - "type": "integer" - } - }, - { - "name": "type", - "description": "Type of monitor", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": ["http", "ping", "pagespeed"] - } - }, - { - "name": "page", - "in": "query", - "required": false, - "schema": { - "type": "integer" - } - }, - { - "name": "rowsPerPage", - "in": "query", - "required": false, - "schema": { - "type": "integer" - } - }, - { - "name": "filter", - "description": "Value to filter by", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "field", - "description": "Field to filter on", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "order", - "description": "Sort order of results", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": ["http", "ping", "pagespeed"] - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/pause/{monitorId}": { - "post": { - "tags": ["monitors"], - "description": "Pause monitor", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/demo": { - "post": { - "tags": ["monitors"], - "description": "Create a demo monitor", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateMonitorBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] + } } }, "/checks/{monitorId}": { "get": { "tags": ["checks"], - "description": "Get all checks for a monitor", + "summary": "Get checks by monitor", + "description": "Get all checks for a specific monitor", "parameters": [ { "name": "monitorId", @@ -1597,94 +1863,13 @@ ], "responses": { "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } + "description": "Checks retrieved successfully" }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "404": { + "$ref": "#/components/responses/NotFound" }, "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "post": { - "tags": ["checks"], - "description": "Create a new check", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateCheckBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/InternalServerError" } }, "security": [ @@ -1695,7 +1880,8 @@ }, "delete": { "tags": ["checks"], - "description": "Delete all checks for a monitor", + "summary": "Delete checks by monitor", + "description": "Delete all checks for a specific monitor", "parameters": [ { "name": "monitorId", @@ -1708,34 +1894,13 @@ ], "responses": { "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } + "$ref": "#/components/responses/Success" }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "404": { + "$ref": "#/components/responses/NotFound" }, "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/InternalServerError" } }, "security": [ @@ -1745,50 +1910,17 @@ ] } }, - "/checks/team/{teamId}": { + "/checks/team": { "get": { "tags": ["checks"], - "description": "Get all checks for a team", - "parameters": [ - { - "name": "teamId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], + "summary": "Get checks by team", + "description": "Get all checks for team monitors", "responses": { "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "description": "Team checks retrieved successfully" }, "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/InternalServerError" } }, "security": [ @@ -1799,47 +1931,37 @@ }, "delete": { "tags": ["checks"], - "description": "Delete all checks for a team", - "parameters": [ - { - "name": "teamId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], + "summary": "Delete team checks", + "description": "Delete all checks for team (admin/superadmin only)", "responses": { "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } + "$ref": "#/components/responses/Success" }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "403": { + "$ref": "#/components/responses/Forbidden" }, "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/checks/team/summary": { + "get": { + "tags": ["checks"], + "summary": "Get team checks summary", + "description": "Get summary statistics for team checks", + "responses": { + "200": { + "description": "Team checks summary retrieved successfully" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" } }, "security": [ @@ -1852,48 +1974,151 @@ "/checks/team/ttl": { "put": { "tags": ["checks"], - "description": "Update check TTL", + "summary": "Update checks TTL", + "description": "Update time-to-live for checks (admin/superadmin only)", "requestBody": { + "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateCheckTTLBody" + "type": "object", + "required": ["ttl"], + "properties": { + "ttl": { + "type": "integer", + "minimum": 1, + "description": "Time to live in days" + } + } } } } }, "responses": { "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } + "$ref": "#/components/responses/Success" + }, + "403": { + "$ref": "#/components/responses/Forbidden" }, "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/ValidationError" }, "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/checks/check/{checkId}": { + "put": { + "tags": ["checks"], + "summary": "Acknowledge check", + "description": "Acknowledge a specific check", + "parameters": [ + { + "name": "checkId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/Success" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/maintenance-window": { + "post": { + "tags": ["maintenance-window"], + "summary": "Create maintenance window", + "description": "Create a new maintenance window", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["name", "startTime", "endTime"], + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "startTime": { + "type": "string", + "format": "date-time" + }, + "endTime": { + "type": "string", + "format": "date-time" + }, + "monitors": { + "type": "array", + "items": { + "type": "string" + } + } } } } } }, + "responses": { + "201": { + "description": "Maintenance window created successfully" + }, + "422": { + "$ref": "#/components/responses/ValidationError" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/maintenance-window/team": { + "get": { + "tags": ["maintenance-window"], + "summary": "Get team maintenance windows", + "description": "Get all maintenance windows for the team", + "responses": { + "200": { + "description": "Maintenance windows retrieved successfully" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, "security": [ { "bearerAuth": [] @@ -1904,7 +2129,8 @@ "/maintenance-window/monitor/{monitorId}": { "get": { "tags": ["maintenance-window"], - "description": "Get maintenance window for monitor", + "summary": "Get maintenance windows by monitor", + "description": "Get all maintenance windows for a specific monitor", "parameters": [ { "name": "monitorId", @@ -1917,35 +2143,47 @@ ], "responses": { "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } + "description": "Monitor maintenance windows retrieved successfully" }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "404": { + "$ref": "#/components/responses/NotFound" }, "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/maintenance-window/{id}": { + "get": { + "tags": ["maintenance-window"], + "summary": "Get maintenance window by ID", + "description": "Get a specific maintenance window", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" } } + ], + "responses": { + "200": { + "description": "Maintenance window retrieved successfully" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } }, "security": [ { @@ -1953,12 +2191,13 @@ } ] }, - "post": { + "put": { "tags": ["maintenance-window"], - "description": "Create maintenance window for monitor", + "summary": "Update maintenance window", + "description": "Update an existing maintenance window", "parameters": [ { - "name": "monitorId", + "name": "id", "in": "path", "required": true, "schema": { @@ -1967,44 +2206,51 @@ } ], "requestBody": { + "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateMaintenanceWindowBody" + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "startTime": { + "type": "string", + "format": "date-time" + }, + "endTime": { + "type": "string", + "format": "date-time" + }, + "monitors": { + "type": "array", + "items": { + "type": "string" + } + } + } } } } }, "responses": { "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } + "$ref": "#/components/responses/Success" + }, + "404": { + "$ref": "#/components/responses/NotFound" }, "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/ValidationError" }, "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/InternalServerError" } }, "security": [ @@ -2012,15 +2258,14 @@ "bearerAuth": [] } ] - } - }, - "/maintenance-window/user/{userId}": { - "get": { + }, + "delete": { "tags": ["maintenance-window"], - "description": "Get maintenance window for user", + "summary": "Delete maintenance window", + "description": "Delete a specific maintenance window", "parameters": [ { - "name": "userId", + "name": "id", "in": "path", "required": true, "schema": { @@ -2030,34 +2275,13 @@ ], "responses": { "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } + "$ref": "#/components/responses/Success" }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "404": { + "$ref": "#/components/responses/NotFound" }, "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/InternalServerError" } }, "security": [ @@ -2067,81 +2291,238 @@ ] } }, - "/queue/jobs": { + "/queue/health": { "get": { "tags": ["queue"], - "description": "Get all jobs in queue", + "summary": "Check queue health", + "description": "Check the health status of the job queue (admin/superadmin only)", "responses": { "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } + "description": "Queue health status retrieved successfully" }, - "422": { - "description": "Unprocessable Content", + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/queue/all-metrics": { + "get": { + "tags": ["queue"], + "summary": "Get all queue metrics", + "description": "Get comprehensive queue metrics (admin/superadmin only)", + "responses": { + "200": { + "description": "All queue metrics retrieved successfully" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/queue/flush": { + "post": { + "tags": ["queue"], + "summary": "Flush queue", + "description": "Clear all jobs from the queue (admin/superadmin only)", + "responses": { + "200": { + "$ref": "#/components/responses/Success" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/games": { + "get": { + "tags": ["monitors"], + "summary": "Get game server list", + "description": "Get available game servers for monitoring", + "responses": { + "200": { + "description": "Game servers retrieved successfully", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ErrorResponse" + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + } + } + } + ] } } } }, "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/status-page": { + "get": { + "tags": ["status-page"], + "summary": "Get status page", + "description": "Get default status page information", + "responses": { + "200": { + "$ref": "#/components/responses/Success" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + }, + "post": { + "tags": ["status-page"], + "summary": "Create status page", + "description": "Create a new status page with optional logo upload", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "logo": { + "type": "string", + "format": "binary", + "description": "Logo file for the status page" + }, + "title": { + "type": "string", + "description": "Status page title" + }, + "description": { + "type": "string", + "description": "Status page description" + }, + "url": { + "type": "string", + "description": "Custom URL for the status page" + } } } } } }, + "responses": { + "201": { + "$ref": "#/components/responses/Success" + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, "security": [ { "bearerAuth": [] } ] }, - "post": { - "tags": ["queue"], - "description": "Create a new job. Useful for testing scaling workers", + "put": { + "tags": ["status-page"], + "summary": "Update status page", + "description": "Update an existing status page with optional logo upload", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "logo": { + "type": "string", + "format": "binary", + "description": "Logo file for the status page" + }, + "title": { + "type": "string", + "description": "Status page title" + }, + "description": { + "type": "string", + "description": "Status page description" + }, + "url": { + "type": "string", + "description": "Custom URL for the status page" + } + } + } + } + } + }, "responses": { "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } + "$ref": "#/components/responses/Success" }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" }, "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/InternalServerError" } }, "security": [ @@ -2151,83 +2532,20 @@ ] } }, - "/queue/metrics": { + "/status-page/team": { "get": { - "tags": ["queue"], - "description": "Get queue metrics", + "tags": ["status-page"], + "summary": "Get status pages by team", + "description": "Get all status pages for the authenticated user's team", "responses": { "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } + "$ref": "#/components/responses/Success" }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "401": { + "$ref": "#/components/responses/Unauthorized" }, "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/queue/obliterate": { - "post": { - "tags": ["queue"], - "description": "Obliterate job queue", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/InternalServerError" } }, "security": [ @@ -2240,106 +2558,58 @@ "/status-page/{url}": { "get": { "tags": ["status-page"], - "description": "Get a status page by URL", + "summary": "Get status page by URL", + "description": "Get a specific status page by its custom URL", "parameters": [ { "name": "url", "in": "path", "required": true, - "schema": { "type": "string" } + "schema": { + "type": "string" + }, + "description": "Custom URL of the status page" } ], "responses": { "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/SuccessResponse" } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/Success" }, "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/NotFound" }, "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ErrorResponse" } - } - } + "$ref": "#/components/responses/InternalServerError" } } }, - "post": { + "delete": { "tags": ["status-page"], - "description": "Create a status page", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateStatusPageBody" - } - } + "summary": "Delete status page", + "description": "Delete a status page by its custom URL", + "parameters": [ + { + "name": "url", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Custom URL of the status page (supports wildcards)" } - }, + ], "responses": { "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } + "$ref": "#/components/responses/Success" }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "401": { + "$ref": "#/components/responses/Unauthorized" }, - "400": { - "description": "Duplicate URL", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "404": { + "$ref": "#/components/responses/NotFound" }, "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } + "$ref": "#/components/responses/InternalServerError" } }, "security": [ @@ -2355,72 +2625,597 @@ "bearerAuth": { "type": "http", "scheme": "bearer", - "bearerFormat": "JWT" + "bearerFormat": "JWT", + "description": "JWT token obtained from login endpoint" + } + }, + "responses": { + "Success": { + "description": "Operation successful", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "NotFound": { + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "ValidationError": { + "description": "Validation failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "Unauthorized": { + "description": "Authentication required or token invalid", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "Forbidden": { + "description": "Insufficient permissions", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "InternalServerError": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } } }, "schemas": { - "ErrorResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "default": false - }, - "msg": { - "type": "string" - } - } - }, "SuccessResponse": { "type": "object", + "required": ["success", "msg"], "properties": { "success": { "type": "boolean", - "default": true + "example": true }, "msg": { - "type": "string" + "type": "string", + "example": "Operation completed successfully" }, "data": { - "type": "object" + "type": "object", + "description": "Response payload (varies by endpoint)" } } }, - "UserUpdateRequest": { + "ErrorResponse": { "type": "object", - "required": ["firstName", "lastName", "email", "password", "role", "teamId"], + "required": ["success", "msg"], + "properties": { + "success": { + "type": "boolean", + "example": false + }, + "msg": { + "type": "string", + "example": "An error occurred" + }, + "details": { + "type": "object", + "description": "Additional error details" + } + } + }, + "RegisterRequest": { + "type": "object", + "required": ["firstName", "lastName", "email", "password"], "properties": { "firstName": { - "type": "string" + "type": "string", + "minLength": 1, + "maxLength": 50, + "example": "John" }, "lastName": { - "type": "string" + "type": "string", + "minLength": 1, + "maxLength": 50, + "example": "Doe" + }, + "email": { + "type": "string", + "format": "email", + "example": "john@example.com" }, "password": { "type": "string", - "format": "password" - }, - "newPassword": { - "type": "string", - "format": "password" + "minLength": 8, + "format": "password", + "example": "SecurePass123!" }, "profileImage": { - "type": "file", - "format": "file" + "type": "string", + "format": "binary", + "description": "Optional profile image file" }, "role": { "type": "array", - "enum": [["user"], ["admin"], ["superadmin"], ["Demo"]], - "default": ["superadmin"] - }, - "deleteProfileImage": { - "type": "boolean" + "items": { + "type": "string", + "enum": ["user", "admin", "superadmin", "Demo"] + }, + "default": ["user"] } } }, - "CreateMonitorBody": { + "LoginRequest": { + "type": "object", + "required": ["email", "password"], + "properties": { + "email": { + "type": "string", + "format": "email", + "example": "john@example.com" + }, + "password": { + "type": "string", + "format": "password", + "example": "SecurePass123!" + } + } + }, + "AuthResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "token": { + "type": "string", + "description": "JWT access token" + }, + "user": { + "$ref": "#/components/schemas/User" + } + } + } + } + } + ] + }, + "UserUpdateRequest": { + "type": "object", + "properties": { + "firstName": { + "type": "string", + "minLength": 1, + "maxLength": 50 + }, + "lastName": { + "type": "string", + "minLength": 1, + "maxLength": 50 + }, + "password": { + "type": "string", + "format": "password", + "description": "Current password for verification" + }, + "newPassword": { + "type": "string", + "minLength": 8, + "format": "password", + "description": "New password (if changing)" + }, + "profileImage": { + "type": "string", + "format": "binary", + "description": "New profile image file" + }, + "deleteProfileImage": { + "type": "boolean", + "description": "Flag to delete current profile image" + } + } + }, + "User": { + "type": "object", + "properties": { + "_id": { + "type": "string", + "example": "64f123a456b789012c345def" + }, + "firstName": { + "type": "string", + "example": "John" + }, + "lastName": { + "type": "string", + "example": "Doe" + }, + "email": { + "type": "string", + "format": "email", + "example": "john@example.com" + }, + "role": { + "type": "array", + "items": { + "type": "string", + "enum": ["user", "admin", "superadmin", "Demo"] + } + }, + "profileImage": { + "type": "string", + "description": "URL or path to profile image" + }, + "isActive": { + "type": "boolean" + }, + "teamId": { + "type": "string" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + } + }, + "CreateMonitorRequest": { + "type": "object", + "required": ["name", "description", "type", "url", "interval"], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "example": "My Website Monitor" + }, + "description": { + "type": "string", + "maxLength": 500, + "example": "Monitors the main website homepage" + }, + "type": { + "type": "string", + "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port"], + "example": "http" + }, + "url": { + "type": "string", + "format": "uri", + "example": "https://example.com" + }, + "interval": { + "type": "integer", + "minimum": 30, + "maximum": 3600, + "example": 300, + "description": "Check interval in seconds" + }, + "isActive": { + "type": "boolean", + "default": true + }, + "notifications": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of notification IDs to associate with this monitor" + }, + "httpOptions": { + "type": "object", + "properties": { + "method": { + "type": "string", + "enum": ["GET", "POST", "PUT", "DELETE", "HEAD"], + "default": "GET" + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "body": { + "type": "string", + "description": "Request body for POST/PUT requests" + }, + "timeout": { + "type": "integer", + "minimum": 1000, + "maximum": 30000, + "default": 5000, + "description": "Request timeout in milliseconds" + } + } + }, + "assertions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["status-code", "response-time", "body-contains", "header-contains"] + }, + "comparison": { + "type": "string", + "enum": ["equals", "not-equals", "greater-than", "less-than", "contains", "not-contains"] + }, + "value": { + "type": "string" + } + } + } + } + } + }, + "UpdateMonitorRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "interval": { + "type": "integer", + "minimum": 30, + "maximum": 3600 + }, + "isActive": { + "type": "boolean" + }, + "notifications": { + "type": "array", + "items": { + "type": "string" + } + }, + "httpOptions": { + "type": "object", + "properties": { + "method": { + "type": "string", + "enum": ["GET", "POST", "PUT", "DELETE", "HEAD"] + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "body": { + "type": "string" + }, + "timeout": { + "type": "integer", + "minimum": 1000, + "maximum": 30000 + } + } + }, + "assertions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["status-code", "response-time", "body-contains", "header-contains"] + }, + "comparison": { + "type": "string", + "enum": ["equals", "not-equals", "greater-than", "less-than", "contains", "not-contains"] + }, + "value": { + "type": "string" + } + } + } + } + } + }, + "Monitor": { + "type": "object", + "properties": { + "_id": { + "type": "string", + "example": "64f123a456b789012c345def" + }, + "userId": { + "type": "string" + }, + "teamId": { + "type": "string" + }, + "name": { + "type": "string", + "example": "My Website Monitor" + }, + "description": { + "type": "string", + "example": "Monitors the main website homepage" + }, + "type": { + "type": "string", + "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port"] + }, + "url": { + "type": "string", + "format": "uri", + "example": "https://example.com" + }, + "interval": { + "type": "integer", + "example": 300 + }, + "isActive": { + "type": "boolean", + "example": true + }, + "status": { + "type": "boolean", + "description": "Current monitor status (up/down)" + }, + "lastChecked": { + "type": "string", + "format": "date-time", + "description": "Timestamp of last check" + }, + "notifications": { + "type": "array", + "items": { + "type": "string" + } + }, + "httpOptions": { + "type": "object", + "properties": { + "method": { + "type": "string" + }, + "headers": { + "type": "object" + }, + "body": { + "type": "string" + }, + "timeout": { + "type": "integer" + } + } + }, + "assertions": { + "type": "array", + "items": { + "type": "object" + } + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + } + }, + "CreateNotificationRequest": { + "type": "object", + "required": ["type", "name"], + "properties": { + "type": { + "type": "string", + "enum": ["email", "webhook", "slack", "discord", "telegram", "zapier"], + "example": "email" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "example": "Admin Email Notifications" + }, + "config": { + "type": "object", + "description": "Configuration specific to notification type", + "oneOf": [ + { + "title": "Email Configuration", + "properties": { + "to": { + "type": "array", + "items": { + "type": "string", + "format": "email" + } + } + } + }, + { + "title": "Webhook Configuration", + "properties": { + "url": { + "type": "string", + "format": "uri" + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + { + "title": "Slack Configuration", + "properties": { + "webhookUrl": { + "type": "string", + "format": "uri" + }, + "channel": { + "type": "string" + } + } + }, + { + "title": "Discord Configuration", + "properties": { + "webhookUrl": { + "type": "string", + "format": "uri" + } + } + } + ] + }, + "isActive": { + "type": "boolean", + "default": true + } + } + }, + "Notification": { "type": "object", - "required": ["userId", "teamId", "name", "description", "type", "url"], "properties": { "_id": { "type": "string" @@ -2431,122 +3226,153 @@ "teamId": { "type": "string" }, + "type": { + "type": "string", + "enum": ["email", "webhook", "slack", "discord", "telegram", "zapier"] + }, "name": { "type": "string" }, - "description": { - "type": "string" - }, - "type": { - "type": "string", - "enum": ["http", "ping", "pagespeed"] - }, - "url": { - "type": "string" + "config": { + "type": "object", + "description": "Type-specific configuration" }, "isActive": { "type": "boolean" }, - "interval": { - "type": "integer" + "createdAt": { + "type": "string", + "format": "date-time" }, - "notifications": { - "type": "array", - "items": { - "type": "string" + "updatedAt": { + "type": "string", + "format": "date-time" + } + } + }, + "AppSettings": { + "type": "object", + "properties": { + "appName": { + "type": "string", + "example": "Checkmate" + }, + "appUrl": { + "type": "string", + "format": "uri", + "example": "https://checkmate.example.com" + }, + "logLevel": { + "type": "string", + "enum": ["error", "warn", "info", "debug"], + "default": "info" + }, + "emailConfig": { + "type": "object", + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "secure": { + "type": "boolean" + }, + "user": { + "type": "string" + }, + "pass": { + "type": "string", + "format": "password" + }, + "from": { + "type": "string", + "format": "email" + } } + }, + "webhookRetries": { + "type": "integer", + "minimum": 0, + "maximum": 10, + "default": 3 + }, + "checksRetention": { + "type": "integer", + "minimum": 1, + "maximum": 365, + "default": 90, + "description": "Days to retain check results" } } }, - "UpdateMonitorBody": { + "SystemDiagnostics": { "type": "object", "properties": { - "name": { - "type": "string" + "uptime": { + "type": "number", + "description": "System uptime in seconds" }, - "description": { - "type": "string" - }, - "interval": { - "type": "integer" - }, - "notifications": { - "type": "array", - "items": { - "type": "string" + "memory": { + "type": "object", + "properties": { + "total": { + "type": "number", + "description": "Total memory in bytes" + }, + "used": { + "type": "number", + "description": "Used memory in bytes" + }, + "free": { + "type": "number", + "description": "Free memory in bytes" + } } - } - } - }, - "CreateCheckBody": { - "type": "object", - "required": ["monitorId", "status", "responseTime", "statusCode", "message"], - "properties": { - "monitorId": { - "type": "string" }, - "status": { - "type": "boolean" + "cpu": { + "type": "object", + "properties": { + "usage": { + "type": "number", + "description": "CPU usage percentage" + }, + "cores": { + "type": "integer", + "description": "Number of CPU cores" + } + } }, - "responseTime": { - "type": "integer" + "database": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": ["connected", "disconnected", "error"] + }, + "responseTime": { + "type": "number", + "description": "Database response time in milliseconds" + } + } }, - "statusCode": { - "type": "integer" - }, - "message": { - "type": "string" - } - } - }, - "UpdateCheckTTLBody": { - "type": "object", - "required": ["ttl"], - "properties": { - "ttl": { - "type": "integer" - } - } - }, - "CreateMaintenanceWindowBody": { - "type": "object", - "required": ["userId", "active", "oneTime", "start", "end"], - "properties": { - "userId": { - "type": "string" - }, - "active": { - "type": "boolean" - }, - "oneTime": { - "type": "boolean" - }, - "start": { - "type": "string", - "format": "date-time" - }, - "end": { - "type": "string", - "format": "date-time" - }, - "expiry": { - "type": "string", - "format": "date-time" - } - } - }, - "CreateStatusPageBody": { - "type": "object", - "required": ["companyName", "url", "timezone", "color", "theme", "monitors"], - "properties": { - "companyName": { "type": "string" }, - "url": { "type": "string" }, - "timezone": { "type": "string" }, - "color": { "type": "string" }, - "theme": { "type": "string" }, - "monitors": { - "type": "array", - "items": { "type": "string" } + "queue": { + "type": "object", + "properties": { + "active": { + "type": "integer" + }, + "waiting": { + "type": "integer" + }, + "completed": { + "type": "integer" + }, + "failed": { + "type": "integer" + } + } } } } From 2fba22d0f68a96510f47121cbf245177b126214c Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Mon, 11 Aug 2025 17:23:14 -0400 Subject: [PATCH 226/259] feat: add input sanitization infrastructure Adds foundational utilities for XSS prevention without applying them yet. This prepares the codebase for comprehensive input sanitization in a follow-up PR. Changes: - Added isomorphic-dompurify and jsdom dependencies - Created sanitization utilities in src/utils/sanitization.js - Includes recursive object sanitization functions - Provides Express middleware for body and query sanitization Infrastructure only - no behavioral changes yet. Files added: - src/utils/sanitization.js (sanitization utilities) - package.json (new dependencies) Next steps: - Apply sanitization middleware in follow-up PR - Enable XSS protection across application --- server/package-lock.json | 364 +++++++++++++++++++++++++++++++ server/package.json | 2 + server/src/utils/sanitization.js | 85 ++++++++ 3 files changed, 451 insertions(+) create mode 100644 server/src/utils/sanitization.js diff --git a/server/package-lock.json b/server/package-lock.json index b29fb0642..fc820beca 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -23,8 +23,10 @@ "handlebars": "^4.7.8", "helmet": "^8.0.0", "ioredis": "^5.4.2", + "isomorphic-dompurify": "^2.26.0", "jmespath": "^0.16.0", "joi": "^17.13.1", + "jsdom": "^26.1.0", "jsonwebtoken": "9.0.2", "mailersend": "^2.2.0", "mjml": "^5.0.0-alpha.4", @@ -53,6 +55,19 @@ "sinon": "19.0.2" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -110,6 +125,116 @@ "node": ">=0.1.90" } }, + "node_modules/@csstools/color-helpers": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", + "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", + "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.0.2", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@dabh/diagnostics": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", @@ -1147,6 +1272,13 @@ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "license": "MIT" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/webidl-conversions": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", @@ -2482,6 +2614,19 @@ "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", "license": "CC0-1.0" }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -2491,6 +2636,19 @@ "node": ">= 12" } }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/date.js": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/date.js/-/date.js-0.3.3.tgz", @@ -2545,6 +2703,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -2747,6 +2911,15 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", + "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/domutils": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", @@ -3941,6 +4114,18 @@ "node": ">=18.0.0" } }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -4035,6 +4220,19 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/http2-wrapper": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", @@ -4266,6 +4464,12 @@ "node": ">=8" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "license": "MIT" + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -4303,6 +4507,19 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/isomorphic-dompurify": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/isomorphic-dompurify/-/isomorphic-dompurify-2.26.0.tgz", + "integrity": "sha512-nZmoK4wKdzPs5USq4JHBiimjdKSVAOm2T1KyDoadtMPNXYHxiENd19ou4iU/V4juFM6LVgYQnpxCYmxqNP4Obw==", + "license": "MIT", + "dependencies": { + "dompurify": "^3.2.6", + "jsdom": "^26.1.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/isomorphic-unfetch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-4.0.2.tgz", @@ -4425,6 +4642,45 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -5986,6 +6242,12 @@ "integrity": "sha512-pv/ue2Odr7IfYOO0byC1KgBI10wo5YDauLhxY6/saNzAdAs0r1SotGCPzzCLNPL0xtrAwWRialLu23AAu9xO1g==", "license": "MIT" }, + "node_modules/nwsapi": { + "version": "2.2.21", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.21.tgz", + "integrity": "sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==", + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -7168,6 +7430,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "license": "MIT" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -7209,6 +7477,18 @@ "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "license": "ISC" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/seek-bzip": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-2.0.0.tgz", @@ -7952,6 +8232,12 @@ "express": ">=4.0.0 || >=5.0.0-beta" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "license": "MIT" + }, "node_modules/tar-fs": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", @@ -8041,6 +8327,24 @@ "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "license": "MIT" }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -8072,6 +8376,18 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/tr46": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", @@ -8320,6 +8636,18 @@ "node": ">= 0.8" } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/web-resource-inliner": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-7.0.0.tgz", @@ -8677,6 +9005,36 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/xmlbuilder": { "version": "8.2.2", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", @@ -8686,6 +9044,12 @@ "node": ">=4.0" } }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, "node_modules/xmlrpc": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/xmlrpc/-/xmlrpc-1.3.2.tgz", diff --git a/server/package.json b/server/package.json index 3e52d6983..9403748e4 100755 --- a/server/package.json +++ b/server/package.json @@ -30,8 +30,10 @@ "handlebars": "^4.7.8", "helmet": "^8.0.0", "ioredis": "^5.4.2", + "isomorphic-dompurify": "^2.26.0", "jmespath": "^0.16.0", "joi": "^17.13.1", + "jsdom": "^26.1.0", "jsonwebtoken": "9.0.2", "mailersend": "^2.2.0", "mjml": "^5.0.0-alpha.4", diff --git a/server/src/utils/sanitization.js b/server/src/utils/sanitization.js new file mode 100644 index 000000000..a42d20658 --- /dev/null +++ b/server/src/utils/sanitization.js @@ -0,0 +1,85 @@ +import { JSDOM } from "jsdom"; +import DOMPurify from "isomorphic-dompurify"; + +// Initialize DOMPurify with jsdom +const window = new JSDOM("").window; +const purify = DOMPurify(window); + +/** + * Sanitizes user input to prevent XSS attacks + * @param {string} input - The input string to sanitize + * @param {Object} options - Sanitization options + * @returns {string} The sanitized string + */ +export const sanitizeInput = (input, options = {}) => { + if (typeof input !== "string") { + return input; + } + + // Default configuration - remove all HTML tags and attributes + const defaultConfig = { + ALLOWED_TAGS: [], + ALLOWED_ATTR: [], + KEEP_CONTENT: true, + ...options, + }; + + return purify.sanitize(input, defaultConfig); +}; + +/** + * Sanitizes an object recursively + * @param {Object} obj - The object to sanitize + * @param {Object} options - Sanitization options + * @returns {Object} The sanitized object + */ +export const sanitizeObject = (obj, options = {}) => { + if (typeof obj !== "object" || obj === null) { + return obj; + } + + if (Array.isArray(obj)) { + return obj.map((item) => sanitizeObject(item, options)); + } + + const sanitized = {}; + for (const [key, value] of Object.entries(obj)) { + if (typeof value === "string") { + sanitized[key] = sanitizeInput(value, options); + } else if (typeof value === "object" && value !== null) { + sanitized[key] = sanitizeObject(value, options); + } else { + sanitized[key] = value; + } + } + + return sanitized; +}; + +/** + * Express middleware for sanitizing request body + * @param {Object} options - Sanitization options + * @returns {Function} Express middleware function + */ +export const sanitizeBody = (options = {}) => { + return (req, res, next) => { + if (req.body && typeof req.body === "object") { + req.body = sanitizeObject(req.body, options); + } + next(); + }; +}; + +/** + * Express middleware for sanitizing query parameters + * @param {Object} options - Sanitization options + * @returns {Function} Express middleware function + */ +export const sanitizeQuery = (options = {}) => { + return (req, res, next) => { + if (req.query && typeof req.query === "object") { + req.query = sanitizeObject(req.query, options); + } + next(); + }; +}; \ No newline at end of file From 7968ed8a193fca6796a6b5b498ba0f719ce9db9f Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Mon, 11 Aug 2025 17:26:28 -0400 Subject: [PATCH 227/259] fix: apply prettier formatting to sanitization.js Fixed formatting issues identified by CI/CD system --- server/src/utils/sanitization.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/utils/sanitization.js b/server/src/utils/sanitization.js index a42d20658..a89325de1 100644 --- a/server/src/utils/sanitization.js +++ b/server/src/utils/sanitization.js @@ -82,4 +82,4 @@ export const sanitizeQuery = (options = {}) => { } next(); }; -}; \ No newline at end of file +}; From 8b322a0ec0297ff971f262ca68306cad6d5cde31 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Mon, 11 Aug 2025 17:33:05 -0400 Subject: [PATCH 228/259] feat: apply input sanitization to prevent XSS attacks Enables comprehensive XSS protection by applying sanitization middleware to all incoming requests. Uses the sanitization utilities added in previous PR. Changes: - Added sanitizeBody() and sanitizeQuery() middleware after express.json() - Enhanced Content Security Policy headers for additional XSS protection - Added 'object-src: none' and 'base-uri: self' directives - All user inputs now automatically sanitized before processing Security improvements: - Prevents stored XSS attacks through HTML sanitization - Recursive sanitization handles nested objects and arrays - Maintains backward compatibility with existing functionality - Strengthened CSP headers block unsafe content injection Files changed: src/app.js (9 lines added) Risk level: Medium (global middleware application) --- server/src/app.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server/src/app.js b/server/src/app.js index 6ba1d0d05..034ff9d29 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -9,6 +9,7 @@ import swaggerUi from "swagger-ui-express"; import { handleErrors } from "./middleware/handleErrors.js"; import { setupRoutes } from "./config/routes.js"; import { generalApiLimiter } from "./middleware/rateLimiter.js"; +import { sanitizeBody, sanitizeQuery } from "./utils/sanitization.js"; export const createApp = ({ services, controllers, envSettings, frontendPath, openApiSpec }) => { const allowedOrigin = envSettings.clientHost; @@ -30,6 +31,11 @@ export const createApp = ({ services, controllers, envSettings, frontendPath, op }) ); app.use(express.json()); + + // Apply input sanitization middleware + app.use(sanitizeBody()); + app.use(sanitizeQuery()); + app.use( helmet({ hsts: false, @@ -37,6 +43,9 @@ export const createApp = ({ services, controllers, envSettings, frontendPath, op useDefaults: true, directives: { upgradeInsecureRequests: null, + "script-src": ["'self'", "'unsafe-inline'", "'unsafe-eval'"], + "object-src": ["'none'"], + "base-uri": ["'self'"], }, }, }) From 88b68a9df08793635b59d026b1bbfda496e16ee8 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Mon, 11 Aug 2025 17:39:08 -0400 Subject: [PATCH 229/259] fix: apply prettier formatting to app.js Fixed formatting issues to pass CI/CD checks --- server/src/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/app.js b/server/src/app.js index 034ff9d29..556b306fd 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -31,11 +31,11 @@ export const createApp = ({ services, controllers, envSettings, frontendPath, op }) ); app.use(express.json()); - + // Apply input sanitization middleware app.use(sanitizeBody()); app.use(sanitizeQuery()); - + app.use( helmet({ hsts: false, From 05945a9a74eb6e3a05b79695d0d76ba0ed69b773 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Mon, 11 Aug 2025 17:47:09 -0400 Subject: [PATCH 230/259] feat: add JWT cookie infrastructure Adds the foundation for secure cookie-based authentication without changing the authentication flow. This prepares the codebase for moving JWT tokens from Redux state to httpOnly cookies in a follow-up PR. Changes: - Added cookie-parser dependency for HTTP cookie handling - Added cookieParser() middleware to Express application - Created cookieHelpers.js utilities for consistent cookie options - Includes getAuthCookieOptions() for setting secure authentication cookies - Includes getClearAuthCookieOptions() for clearing cookies on logout Infrastructure only - no behavioral changes to authentication flow yet. Files added/modified: - package.json (cookie-parser dependency) - src/app.js (cookieParser middleware) - src/utils/cookieHelpers.js (cookie utilities) Next steps: - Follow-up PR will modify JWT verification to check cookies - Enable secure cookie-based authentication - Add logout functionality to clear httpOnly cookies Risk level: LOW (infrastructure only, no authentication changes) --- server/package-lock.json | 23 +++++++++ server/package.json | 1 + server/src/app.js | 2 + server/src/utils/cookieHelpers.js | 26 ++++++++++ server/utils/sanitization.js | 85 +++++++++++++++++++++++++++++++ 5 files changed, 137 insertions(+) create mode 100644 server/src/utils/cookieHelpers.js create mode 100644 server/utils/sanitization.js diff --git a/server/package-lock.json b/server/package-lock.json index b29fb0642..b955e64ab 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -14,6 +14,7 @@ "bcryptjs": "3.0.2", "bullmq": "5.41.2", "compression": "1.8.1", + "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dockerode": "4.0.6", "dotenv": "^16.4.5", @@ -2230,6 +2231,28 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", diff --git a/server/package.json b/server/package.json index 3e52d6983..55f00f364 100755 --- a/server/package.json +++ b/server/package.json @@ -21,6 +21,7 @@ "bcryptjs": "3.0.2", "bullmq": "5.41.2", "compression": "1.8.1", + "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dockerode": "4.0.6", "dotenv": "^16.4.5", diff --git a/server/src/app.js b/server/src/app.js index 6ba1d0d05..d272e7f76 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -4,6 +4,7 @@ import { responseHandler } from "./middleware/responseHandler.js"; import cors from "cors"; import helmet from "helmet"; import compression from "compression"; +import cookieParser from "cookie-parser"; import languageMiddleware from "./middleware/languageMiddleware.js"; import swaggerUi from "swagger-ui-express"; import { handleErrors } from "./middleware/handleErrors.js"; @@ -30,6 +31,7 @@ export const createApp = ({ services, controllers, envSettings, frontendPath, op }) ); app.use(express.json()); + app.use(cookieParser()); app.use( helmet({ hsts: false, diff --git a/server/src/utils/cookieHelpers.js b/server/src/utils/cookieHelpers.js new file mode 100644 index 000000000..7005b66ca --- /dev/null +++ b/server/src/utils/cookieHelpers.js @@ -0,0 +1,26 @@ +/** + * Get standardized cookie options for authentication tokens + * @param {Object} options - Additional cookie options + * @returns {Object} Cookie options object + */ +export const getAuthCookieOptions = (options = {}) => { + return { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + maxAge: 2 * 60 * 60 * 1000, // 2 hours (matches JWT TTL) + ...options, + }; +}; + +/** + * Clear cookie options for authentication tokens + * @returns {Object} Cookie clear options object + */ +export const getClearAuthCookieOptions = () => { + return { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + }; +}; diff --git a/server/utils/sanitization.js b/server/utils/sanitization.js new file mode 100644 index 000000000..a89325de1 --- /dev/null +++ b/server/utils/sanitization.js @@ -0,0 +1,85 @@ +import { JSDOM } from "jsdom"; +import DOMPurify from "isomorphic-dompurify"; + +// Initialize DOMPurify with jsdom +const window = new JSDOM("").window; +const purify = DOMPurify(window); + +/** + * Sanitizes user input to prevent XSS attacks + * @param {string} input - The input string to sanitize + * @param {Object} options - Sanitization options + * @returns {string} The sanitized string + */ +export const sanitizeInput = (input, options = {}) => { + if (typeof input !== "string") { + return input; + } + + // Default configuration - remove all HTML tags and attributes + const defaultConfig = { + ALLOWED_TAGS: [], + ALLOWED_ATTR: [], + KEEP_CONTENT: true, + ...options, + }; + + return purify.sanitize(input, defaultConfig); +}; + +/** + * Sanitizes an object recursively + * @param {Object} obj - The object to sanitize + * @param {Object} options - Sanitization options + * @returns {Object} The sanitized object + */ +export const sanitizeObject = (obj, options = {}) => { + if (typeof obj !== "object" || obj === null) { + return obj; + } + + if (Array.isArray(obj)) { + return obj.map((item) => sanitizeObject(item, options)); + } + + const sanitized = {}; + for (const [key, value] of Object.entries(obj)) { + if (typeof value === "string") { + sanitized[key] = sanitizeInput(value, options); + } else if (typeof value === "object" && value !== null) { + sanitized[key] = sanitizeObject(value, options); + } else { + sanitized[key] = value; + } + } + + return sanitized; +}; + +/** + * Express middleware for sanitizing request body + * @param {Object} options - Sanitization options + * @returns {Function} Express middleware function + */ +export const sanitizeBody = (options = {}) => { + return (req, res, next) => { + if (req.body && typeof req.body === "object") { + req.body = sanitizeObject(req.body, options); + } + next(); + }; +}; + +/** + * Express middleware for sanitizing query parameters + * @param {Object} options - Sanitization options + * @returns {Function} Express middleware function + */ +export const sanitizeQuery = (options = {}) => { + return (req, res, next) => { + if (req.query && typeof req.query === "object") { + req.query = sanitizeObject(req.query, options); + } + next(); + }; +}; From 0bc8142e714561ead67cb35cabfb8e99cca0d365 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 11 Aug 2025 15:02:07 -0700 Subject: [PATCH 231/259] move to middleware --- server/src/app.js | 2 +- server/src/{utils => middleware}/sanitization.js | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename server/src/{utils => middleware}/sanitization.js (100%) diff --git a/server/src/app.js b/server/src/app.js index 556b306fd..dfc7d4542 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -9,7 +9,7 @@ import swaggerUi from "swagger-ui-express"; import { handleErrors } from "./middleware/handleErrors.js"; import { setupRoutes } from "./config/routes.js"; import { generalApiLimiter } from "./middleware/rateLimiter.js"; -import { sanitizeBody, sanitizeQuery } from "./utils/sanitization.js"; +import { sanitizeBody, sanitizeQuery } from "./middleware/sanitization.js"; export const createApp = ({ services, controllers, envSettings, frontendPath, openApiSpec }) => { const allowedOrigin = envSettings.clientHost; diff --git a/server/src/utils/sanitization.js b/server/src/middleware/sanitization.js similarity index 100% rename from server/src/utils/sanitization.js rename to server/src/middleware/sanitization.js From c12f5880e2fcd0ea31e23a7c260698645202c388 Mon Sep 17 00:00:00 2001 From: Alexander Holliday Date: Mon, 11 Aug 2025 15:07:42 -0700 Subject: [PATCH 232/259] Delete server/utils/sanitization.js --- server/utils/sanitization.js | 85 ------------------------------------ 1 file changed, 85 deletions(-) delete mode 100644 server/utils/sanitization.js diff --git a/server/utils/sanitization.js b/server/utils/sanitization.js deleted file mode 100644 index a89325de1..000000000 --- a/server/utils/sanitization.js +++ /dev/null @@ -1,85 +0,0 @@ -import { JSDOM } from "jsdom"; -import DOMPurify from "isomorphic-dompurify"; - -// Initialize DOMPurify with jsdom -const window = new JSDOM("").window; -const purify = DOMPurify(window); - -/** - * Sanitizes user input to prevent XSS attacks - * @param {string} input - The input string to sanitize - * @param {Object} options - Sanitization options - * @returns {string} The sanitized string - */ -export const sanitizeInput = (input, options = {}) => { - if (typeof input !== "string") { - return input; - } - - // Default configuration - remove all HTML tags and attributes - const defaultConfig = { - ALLOWED_TAGS: [], - ALLOWED_ATTR: [], - KEEP_CONTENT: true, - ...options, - }; - - return purify.sanitize(input, defaultConfig); -}; - -/** - * Sanitizes an object recursively - * @param {Object} obj - The object to sanitize - * @param {Object} options - Sanitization options - * @returns {Object} The sanitized object - */ -export const sanitizeObject = (obj, options = {}) => { - if (typeof obj !== "object" || obj === null) { - return obj; - } - - if (Array.isArray(obj)) { - return obj.map((item) => sanitizeObject(item, options)); - } - - const sanitized = {}; - for (const [key, value] of Object.entries(obj)) { - if (typeof value === "string") { - sanitized[key] = sanitizeInput(value, options); - } else if (typeof value === "object" && value !== null) { - sanitized[key] = sanitizeObject(value, options); - } else { - sanitized[key] = value; - } - } - - return sanitized; -}; - -/** - * Express middleware for sanitizing request body - * @param {Object} options - Sanitization options - * @returns {Function} Express middleware function - */ -export const sanitizeBody = (options = {}) => { - return (req, res, next) => { - if (req.body && typeof req.body === "object") { - req.body = sanitizeObject(req.body, options); - } - next(); - }; -}; - -/** - * Express middleware for sanitizing query parameters - * @param {Object} options - Sanitization options - * @returns {Function} Express middleware function - */ -export const sanitizeQuery = (options = {}) => { - return (req, res, next) => { - if (req.query && typeof req.query === "object") { - req.query = sanitizeObject(req.query, options); - } - next(); - }; -}; From ed1b88f1fdcfe65cdf9e314753aeea0ca766206f Mon Sep 17 00:00:00 2001 From: karenvicent Date: Mon, 11 Aug 2025 18:21:23 -0400 Subject: [PATCH 233/259] refactor login page --- client/src/Pages/Auth/Login/index.jsx | 83 ++++--------------- .../src/Pages/Auth/hooks/useLoadingSubmit.jsx | 22 +++++ client/src/Pages/Auth/hooks/useLoginForm.jsx | 19 +++++ .../src/Pages/Auth/hooks/useLoginSubmit.jsx | 41 +++++++++ .../Pages/Auth/hooks/useValidateLoginForm.jsx | 31 +++++++ 5 files changed, 130 insertions(+), 66 deletions(-) create mode 100644 client/src/Pages/Auth/hooks/useLoadingSubmit.jsx create mode 100644 client/src/Pages/Auth/hooks/useLoginForm.jsx create mode 100644 client/src/Pages/Auth/hooks/useLoginSubmit.jsx create mode 100644 client/src/Pages/Auth/hooks/useValidateLoginForm.jsx diff --git a/client/src/Pages/Auth/Login/index.jsx b/client/src/Pages/Auth/Login/index.jsx index d6b281c27..ed9f2e0ff 100644 --- a/client/src/Pages/Auth/Login/index.jsx +++ b/client/src/Pages/Auth/Login/index.jsx @@ -3,84 +3,35 @@ import Stack from "@mui/material/Stack"; import Button from "@mui/material/Button"; import TextInput from "../../../Components/Inputs/TextInput"; import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adornments"; -import { loginCredentials } from "../../../Validation/validation"; import TextLink from "../../../Components/TextLink"; import AuthPageWrapper from "../components/AuthPageWrapper"; // Utils -import { login } from "../../../Features/Auth/authSlice"; -import { useNavigate } from "react-router-dom"; -import { useDispatch } from "react-redux"; -import { useState } from "react"; import { useTheme } from "@mui/material/styles"; import { useTranslation } from "react-i18next"; -import { createToast } from "../../../Utils/toastUtils"; +import useLoginForm from "../hooks/useLoginForm"; +import useValidateLoginForm from "../hooks/useValidateLoginForm"; +import useLoginSubmit from "../hooks/useLoginSubmit"; +import useLoadingSubmit from "../hooks/useLoadingSubmit"; const Login = () => { - // Local state - const [form, setForm] = useState({ - email: "", - password: "", - }); - const [errors, setErrors] = useState({ - email: "", - password: "", - }); - // Hooks const { t } = useTranslation(); const theme = useTheme(); - const dispatch = useDispatch(); - const navigate = useNavigate(); + const [form, onChange] = useLoginForm(); + const [errors, setErrors, validateField, validateForm] = useValidateLoginForm(); + const [handleLoginSubmit] = useLoginSubmit(); + const { submitting, executeSubmit } = useLoadingSubmit(); // Handlers - const onChange = (e) => { - let { name, value } = e.target; - if (name === "email") { - value = value.toLowerCase(); - } - const updatedForm = { ...form, [name]: value }; - const { error } = loginCredentials.validate({ [name]: value }); - setForm(updatedForm); - setErrors((prev) => ({ - ...prev, - [name]: error?.details?.[0]?.message || "", - })); + const handleChange = (e) => { + onChange(e); + validateField(e.target.name, e.target.value); }; const onSubmit = async (e) => { e.preventDefault(); - const toSubmit = { ...form }; - const { error } = loginCredentials.validate(toSubmit, { abortEarly: false }); - if (error) { - const formErrors = {}; - for (const err of error.details) { - formErrors[err.path[0]] = err.message; - } - setErrors(formErrors); - return; - } - const action = await dispatch(login(form)); - if (action.payload.success) { - navigate("/uptime"); - createToast({ - body: t("auth.login.toasts.success"), - }); - } else { - if (action.payload) { - if (action.payload.msg === "Incorrect password") - setErrors({ - password: t("auth.login.errors.password.incorrect"), - }); - // dispatch errors - createToast({ - body: t("auth.login.toasts.incorrectPassword"), - }); - } else { - // unknown errors - createToast({ - body: t("common.toasts.unknownError"), - }); - } - } + const isValid = validateForm(form); + if (!isValid) return; + await executeSubmit(() => handleLoginSubmit(form, setErrors)); }; return ( { placeholder={t("auth.common.inputs.email.placeholder")} autoComplete="email" value={form.email} - onChange={onChange} + onChange={handleChange} error={errors.email ? true : false} helperText={errors.email ? t(errors.email) : ""} // Localization keys are in validation.js /> @@ -120,7 +71,7 @@ const Login = () => { placeholder="••••••••••" autoComplete="current-password" value={form.password} - onChange={onChange} + onChange={handleChange} error={errors.password ? true : false} helperText={errors.password ? t(errors.password) : ""} // Localization keys are in validation.js endAdornment={} @@ -130,6 +81,7 @@ const Login = () => { color="accent" type="submit" sx={{ width: "100%", alignSelf: "center", fontWeight: 700 }} + loading={submitting} > Login @@ -147,5 +99,4 @@ const Login = () => { ); }; - export default Login; diff --git a/client/src/Pages/Auth/hooks/useLoadingSubmit.jsx b/client/src/Pages/Auth/hooks/useLoadingSubmit.jsx new file mode 100644 index 000000000..a779165b6 --- /dev/null +++ b/client/src/Pages/Auth/hooks/useLoadingSubmit.jsx @@ -0,0 +1,22 @@ +// Hook to avoid double submits and manage loading state +import { useState, useCallback } from "react"; + +const useLoadingSubmit = () => { + const [submitting, setSubmitting] = useState(false); + const executeSubmit = useCallback( + async (submitFunction) => { + if (submitting) return; + setSubmitting(true); + try { + return await submitFunction(); + } finally { + setSubmitting(false); + } + }, + [submitting] + ); + + return { submitting, executeSubmit }; +}; + +export default useLoadingSubmit; diff --git a/client/src/Pages/Auth/hooks/useLoginForm.jsx b/client/src/Pages/Auth/hooks/useLoginForm.jsx new file mode 100644 index 000000000..61814d723 --- /dev/null +++ b/client/src/Pages/Auth/hooks/useLoginForm.jsx @@ -0,0 +1,19 @@ +import { useState } from "react"; + +const useLoginForm = () => { + const [form, setForm] = useState({ + email: "", + password: "", + }); + const onChange = (e) => { + let { name, value } = e.target; + if (name === "email") { + value = value.toLowerCase(); + } + const updatedForm = { ...form, [name]: value }; + setForm(updatedForm); + }; + return [form, onChange]; +}; + +export default useLoginForm; diff --git a/client/src/Pages/Auth/hooks/useLoginSubmit.jsx b/client/src/Pages/Auth/hooks/useLoginSubmit.jsx new file mode 100644 index 000000000..e15cd98a0 --- /dev/null +++ b/client/src/Pages/Auth/hooks/useLoginSubmit.jsx @@ -0,0 +1,41 @@ +import { useDispatch } from "react-redux"; +import { login } from "../../../Features/Auth/authSlice"; +import { useNavigate } from "react-router-dom"; +import { createToast } from "../../../Utils/toastUtils"; +import { useTranslation } from "react-i18next"; + +const useLoginSubmit = () => { + const dispatch = useDispatch(); + const navigate = useNavigate(); + const { t } = useTranslation(); + + const handleLoginSubmit = async (form, setErrors) => { + const action = await dispatch(login(form)); + if (action.payload.success) { + navigate("/uptime"); + createToast({ + body: t("auth.login.toasts.success"), + }); + } else { + if (action.payload) { + console.log(action.payload); + if (action.payload.msg === "Incorrect password") + setErrors({ + password: t("auth.login.errors.password.incorrect"), + }); + // dispatch errors + createToast({ + body: t("auth.login.toasts.incorrectPassword"), + }); + } else { + // unknown errors + createToast({ + body: t("common.toasts.unknownError"), + }); + } + } + }; + return [handleLoginSubmit]; +}; + +export default useLoginSubmit; diff --git a/client/src/Pages/Auth/hooks/useValidateLoginForm.jsx b/client/src/Pages/Auth/hooks/useValidateLoginForm.jsx new file mode 100644 index 000000000..f50c777f3 --- /dev/null +++ b/client/src/Pages/Auth/hooks/useValidateLoginForm.jsx @@ -0,0 +1,31 @@ +import { useState } from "react"; +import { loginCredentials } from "../../../Validation/validation"; + +const useValidateLoginForm = () => { + const [errors, setErrors] = useState({ + email: "", + password: "", + }); + const validateField = (name, value) => { + const { error } = loginCredentials.validate({ [name]: value }); + setErrors((prev) => ({ + ...prev, + [name]: error?.details?.[0]?.message || "", + })); + }; + const validateForm = (form) => { + const { error } = loginCredentials.validate(form, { abortEarly: false }); + if (error) { + const formErrors = {}; + for (const err of error.details) { + formErrors[err.path[0]] = err.message; + } + setErrors(formErrors); + return false; + } + return true; + }; + return [errors, setErrors, validateField, validateForm]; +}; + +export default useValidateLoginForm; From 86dc71687d2fae6bbc1ac91f27cea3a6a02a2a3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20=C5=9Ei=C5=9Fmano=C4=9Flu?= Date: Tue, 12 Aug 2025 01:41:45 +0300 Subject: [PATCH 234/259] ci: remove extra 'Close PR' step in check-format workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Şişmanoğlu --- .github/workflows/check-format.yml | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index 76cb300f1..d3f34635c 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -39,24 +39,3 @@ jobs: - name: Check server formatting working-directory: server run: npm run format-check - close-pr-if-needed: - if: always() - runs-on: ubuntu-latest - needs: [format-client, format-server] - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Get PR number - id: pr - run: echo "PR_NUMBER=$(jq -r .pull_request.number "$GITHUB_EVENT_PATH")" >> $GITHUB_ENV - - - name: Close PR using GitHub CLI - if: | - needs.format-client.result == 'failure' || - needs.format-server.result == 'failure' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh pr close "$PR_NUMBER" --delete-branch --comment "❌ Formatting check failed — PR auto-closed. - Please run \`npm run format\` in both `client` and `server` directories and push again." From 781854d85c6faf299e9a05d0c6786c4373047d43 Mon Sep 17 00:00:00 2001 From: karenvicent Date: Mon, 11 Aug 2025 18:42:33 -0400 Subject: [PATCH 235/259] delete console.log --- client/src/Pages/Auth/hooks/useLoginSubmit.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/Pages/Auth/hooks/useLoginSubmit.jsx b/client/src/Pages/Auth/hooks/useLoginSubmit.jsx index e15cd98a0..ed3bbec67 100644 --- a/client/src/Pages/Auth/hooks/useLoginSubmit.jsx +++ b/client/src/Pages/Auth/hooks/useLoginSubmit.jsx @@ -18,7 +18,6 @@ const useLoginSubmit = () => { }); } else { if (action.payload) { - console.log(action.payload); if (action.payload.msg === "Incorrect password") setErrors({ password: t("auth.login.errors.password.incorrect"), From 7b4160ac923c446ca8494de9053e84c008647cd5 Mon Sep 17 00:00:00 2001 From: "Gorkem Cetin (BWL)" <167266851+gorkem-bwl@users.noreply.github.com> Date: Mon, 11 Aug 2025 20:38:53 -0400 Subject: [PATCH 236/259] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2dfcc2ffb..cc1b10e0e 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ If you would like to sponsor an additional feature, [see this page](https://chec ## Contributing -We are [Alex](http://github.com/ajhollid) (team lead), [Vishnu](http://github.com/vishnusn77), [Mohadeseh](http://github.com/mohicody), [Gorkem](http://github.com/gorkem-bwl/), [Owaise](http://github.com/Owaiseimdad), [Aryaman](https://github.com/Br0wnHammer) and [Mert](https://github.com/mertssmnoglu) helping individuals and businesses monitor their infra and servers. +We are [Alex](http://github.com/ajhollid) (team lead), [Mohadeseh](http://github.com/mohicody), [Gorkem](http://github.com/gorkem-bwl/), [Owaise](http://github.com/Owaiseimdad), [Aryaman](https://github.com/Br0wnHammer), [Mert](https://github.com/mertssmnoglu) and [Karen](https://github.com/karenvicent) helping individuals and businesses monitor their infra and servers. We pride ourselves on building strong connections with contributors at every level. Despite being a young project, Checkmate has already earned 7000+ stars and attracted 90+ contributors from around the globe. From c4905082523a7ba100d1e315b23c889095a7d660 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Mon, 11 Aug 2025 23:28:35 -0400 Subject: [PATCH 237/259] fix: reduce animated gif size to 2/3 in empty states Reduces animation size from 100% to 66.67% width for better visual balance --- client/src/Components/Fallback/FallbackBackground.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/src/Components/Fallback/FallbackBackground.jsx b/client/src/Components/Fallback/FallbackBackground.jsx index 8767837dc..632432327 100644 --- a/client/src/Components/Fallback/FallbackBackground.jsx +++ b/client/src/Components/Fallback/FallbackBackground.jsx @@ -19,7 +19,10 @@ const FallbackBackground = () => { zIndex: 1, border: "none", borderRadius: theme.spacing(8), - width: "100%", + width: "66.67%", // 2/3 of original size + maxWidth: "66.67%", + display: "block", + objectFit: "contain", }} /> Date: Tue, 12 Aug 2025 10:05:58 +0530 Subject: [PATCH 238/259] Fixed the code for linus, windows and mac based. Also made changes to pipeline. --- .../Details/Components/NetworkStats/index.jsx | 28 +-- server/src/db/mongo/modules/monitorModule.js | 46 ----- .../db/mongo/modules/monitorModuleQueries.js | 162 +++++++++--------- 3 files changed, 100 insertions(+), 136 deletions(-) diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx index f8bdf5e8a..22a6c3112 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx @@ -3,24 +3,32 @@ import NetworkStatBoxes from "./NetworkStatBoxes"; import NetworkCharts from "./NetworkCharts"; import MonitorTimeFrameHeader from "../../../../../Components/MonitorTimeFrameHeader"; -const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => { - const eth0Data = (checks || []) +const getNetworkInterfaceData = (checks) => { + const interfaceNames = ["eth0", "Ethernet", "en0"]; + + return (checks || []) .map((check) => { - const en0 = (check.net || []).find((iface) => iface.name === "en0"); - if (!en0) return null; + const networkInterface = (check.net || []).find((iface) => + interfaceNames.includes(iface.name) + ); + + if (!networkInterface) { + return null; + } return { _id: check._id, - bytesPerSec: en0.avgBytesRecv, - packetsPerSec: en0.avgPacketsRecv, - errors: en0.avgErrOut ?? 0, - drops: en0.avgDropOut ?? 0, + bytesPerSec: networkInterface.avgBytesRecv, + packetsPerSec: networkInterface.avgPacketsRecv, + errors: networkInterface.avgErrOut ?? 0, + drops: networkInterface.avgDropOut ?? 0, }; }) .filter(Boolean); +}; - console.log(eth0Data); - +const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => { + const eth0Data = getNetworkInterfaceData(checks); return ( <> { - if (!Array.isArray(checks) || checks.length === 0) return []; - - const sorted = [...checks].sort((a, b) => new Date(a._id) - new Date(b._id)); - const lastSeen = {}; - - for (const check of sorted) { - if (!Array.isArray(check.net)) { - check.net = []; - continue; - } - - const newNet = []; - - for (const iface of check.net) { - const prev = lastSeen[iface.name]; - const t1 = new Date(check._id); - - if (prev) { - const t0 = new Date(prev._id); - const dt = (t1 - t0) / 1000; - - if (dt > 0) { - newNet.push({ - name: iface.name, - avgBytesRecv: iface.avgBytesRecv - prev.avgBytesRecv, - avgPacketsRecv: iface.avgPacketsRecv - prev.avgPacketsRecv, - avgErrOut: iface.avgErrOut - prev.avgErrOut, - avgDropOut: iface.avgDropOut, - }); - } - } - - lastSeen[iface.name] = { ...iface, _id: check._id }; - } - - check.net = newNet; - } - - return sorted; - }; - getHardwareDetailsById = async ({ monitorId, dateRange }) => { try { const monitor = await this.Monitor.findById(monitorId); @@ -384,10 +342,6 @@ class MonitorModule { const hardwareStats = await this.HardwareCheck.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString)); const stats = hardwareStats[0]; - if (stats && stats.checks) { - // Replace net with per-second rates - stats.checks = this.processNetworkRates(stats.checks); - } return { ...monitor.toObject(), diff --git a/server/src/db/mongo/modules/monitorModuleQueries.js b/server/src/db/mongo/modules/monitorModuleQueries.js index b3417d05a..c6812b1bc 100755 --- a/server/src/db/mongo/modules/monitorModuleQueries.js +++ b/server/src/db/mongo/modules/monitorModuleQueries.js @@ -378,108 +378,110 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, net: { $map: { - input: { $range: [0, "$$netCount"] }, + input: { + $range: [0, { $size: { $arrayElemAt: ["$net", 0] } }], + }, as: "netIndex", in: { name: { $arrayElemAt: [ { $map: { - input: "$net", - as: "netArray", - in: { $arrayElemAt: ["$$netArray.name", "$$netIndex"] }, + input: { $arrayElemAt: ["$net", 0] }, + as: "iface", + in: "$$iface.name", }, }, - 0, + "$$netIndex", ], }, avgBytesSent: { - $avg: { - $map: { - input: "$net", - as: "netArray", - in: { - $arrayElemAt: ["$$netArray.bytes_sent", "$$netIndex"], - }, + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.bytes_sent", + }, + }, + "$$netIndex", + ], }, - }, + { + $arrayElemAt: [ + { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.bytes_sent" } }, + "$$netIndex", + ], + }, + ], }, avgBytesRecv: { - $avg: { - $map: { - input: "$net", - as: "netArray", - in: { - $arrayElemAt: ["$$netArray.bytes_recv", "$$netIndex"], - }, + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.bytes_recv", + }, + }, + "$$netIndex", + ], }, - }, + { + $arrayElemAt: [ + { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.bytes_recv" } }, + "$$netIndex", + ], + }, + ], }, avgPacketsSent: { - $avg: { - $map: { - input: "$net", - as: "netArray", - in: { - $arrayElemAt: ["$$netArray.packets_sent", "$$netIndex"], - }, + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.packets_sent", + }, + }, + "$$netIndex", + ], }, - }, + { + $arrayElemAt: [ + { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.packets_sent" } }, + "$$netIndex", + ], + }, + ], }, avgPacketsRecv: { - $avg: { - $map: { - input: "$net", - as: "netArray", - in: { - $arrayElemAt: ["$$netArray.packets_recv", "$$netIndex"], - }, + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.packets_recv", + }, + }, + "$$netIndex", + ], }, - }, - }, - avgErrIn: { - $avg: { - $map: { - input: "$net", - as: "netArray", - in: { - $arrayElemAt: ["$$netArray.err_in", "$$netIndex"], - }, + { + $arrayElemAt: [ + { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.packets_recv" } }, + "$$netIndex", + ], }, - }, - }, - avgErrOut: { - $avg: { - $map: { - input: "$net", - as: "netArray", - in: { - $arrayElemAt: ["$$netArray.err_out", "$$netIndex"], - }, - }, - }, - }, - avgDropIn: { - $avg: { - $map: { - input: "$net", - as: "netArray", - in: { - $arrayElemAt: ["$$netArray.drop_in", "$$netIndex"], - }, - }, - }, - }, - avgDropOut: { - $avg: { - $map: { - input: "$net", - as: "netArray", - in: { - $arrayElemAt: ["$$netArray.drop_out", "$$netIndex"], - }, - }, - }, + ], }, }, }, From 136144b8ec51a4c424db38feae4c7eed5bee57c4 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Tue, 12 Aug 2025 11:03:46 -0400 Subject: [PATCH 239/259] fix: use transform scale instead of width for better border radius preservation --- client/src/Components/Fallback/FallbackBackground.jsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/client/src/Components/Fallback/FallbackBackground.jsx b/client/src/Components/Fallback/FallbackBackground.jsx index 632432327..00ecafb4e 100644 --- a/client/src/Components/Fallback/FallbackBackground.jsx +++ b/client/src/Components/Fallback/FallbackBackground.jsx @@ -19,10 +19,8 @@ const FallbackBackground = () => { zIndex: 1, border: "none", borderRadius: theme.spacing(8), - width: "66.67%", // 2/3 of original size - maxWidth: "66.67%", - display: "block", - objectFit: "contain", + width: "100%", + transform: "scale(0.6667)", }} /> Date: Tue, 12 Aug 2025 23:48:47 +0530 Subject: [PATCH 240/259] Added delta instead of avg, and formatted the code to re-use already existing code. --- .../Components/Charts/Utils/chartUtils.jsx | 18 +-- .../Components/NetworkStats/NetworkCharts.jsx | 35 +++-- .../NetworkStats/NetworkStatBoxes.jsx | 19 +-- .../Details/Components/NetworkStats/index.jsx | 31 +++-- .../Details/Hooks/useHardwareUtils.jsx | 25 +++- server/src/db/mongo/modules/monitorModule.js | 11 ++ .../db/mongo/modules/monitorModuleQueries.js | 122 +++++++++++++++++- 7 files changed, 201 insertions(+), 60 deletions(-) diff --git a/client/src/Components/Charts/Utils/chartUtils.jsx b/client/src/Components/Charts/Utils/chartUtils.jsx index 40eb5b539..a7db3ea93 100644 --- a/client/src/Components/Charts/Utils/chartUtils.jsx +++ b/client/src/Components/Charts/Utils/chartUtils.jsx @@ -101,16 +101,17 @@ const getFormattedPercentage = (value) => { * @param {number} props.index - The index of the tick. * @returns {JSX.Element|null} The rendered tick component or null for the first tick. */ -export const NetworkTick = ({ x, y, payload, index }) => { +export const NetworkTick = ({ x, y, payload, index, formatter}) => { const theme = useTheme(); if (index === 0) return null; - const formatBytes = (bytes) => { - if (bytes >= 1_000_000_000) return `${(bytes / 1_000_000_000).toFixed(1)} GB/s`; - if (bytes >= 1_000_000) return `${(bytes / 1_000_000).toFixed(1)} MB/s`; - if (bytes >= 1_000) return `${(bytes / 1_000).toFixed(1)} KB/s`; - return `${bytes} B/s`; - }; + if (formatter === undefined) { + formatter = (value, space=false) => { + if (typeof value !== "number") return value; + // need to add space between value and unit + return `${(value / 1024).toFixed(2)}${space ? " " : ""}Kbps`; + }; + } return ( { fontSize={11} fontWeight={400} > - {formatBytes(payload?.value)} + {formatter(payload?.value, true)} ); }; @@ -131,6 +132,7 @@ NetworkTick.propTypes = { y: PropTypes.number, payload: PropTypes.object, index: PropTypes.number, + formatter: PropTypes.func, }; /** diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx index 5dd9feaad..84325c29e 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx @@ -1,9 +1,7 @@ -// NetworkCharts.jsx import PropTypes from "prop-types"; -import { Stack } from "@mui/material"; +import { Stack, Typography } from "@mui/material"; import InfraAreaChart from "../../../../../Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart"; -// Utils import { TzTick, InfrastructureTooltip, @@ -11,43 +9,42 @@ import { } from "../../../../../Components/Charts/Utils/chartUtils"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; -const getFormattedNetworkMetric = (value) => { - if (typeof value !== "number" || isNaN(value)) return "0"; - if (value >= 1024 ** 3) return `${(value / 1024 ** 3).toFixed(1)} GB/s`; - if (value >= 1024 ** 2) return `${(value / 1024 ** 2).toFixed(1)} MB/s`; - if (value >= 1024) return `${(value / 1024).toFixed(1)} KB/s`; - return `${Math.round(value)} B/s`; -}; - -const NetworkCharts = ({ eth0Data, dateRange }) => { +const NetworkCharts = ({ ethernetData, dateRange }) => { const theme = useTheme(); const { t } = useTranslation(); + const {formatBytesString} = useHardwareUtils(); + + if (!ethernetData?.length) { + return {t("noNetworkStatsAvailable")}; + } + const configs = [ { type: "network-bytes", - data: eth0Data, + data: ethernetData, dataKeys: ["bytesPerSec"], heading: t("bytesPerSecond"), strokeColor: theme.palette.info.main, gradientStartColor: theme.palette.info.main, yLabel: t("bytesPerSecond"), xTick: , - yTick: , + yTick: , toolTip: ( ), }, { type: "network-packets", - data: eth0Data, + data: ethernetData, dataKeys: ["packetsPerSec"], heading: t("packetsPerSecond"), strokeColor: theme.palette.success.main, @@ -66,7 +63,7 @@ const NetworkCharts = ({ eth0Data, dateRange }) => { }, { type: "network-errors", - data: eth0Data, + data: ethernetData, dataKeys: ["errors"], heading: t("errors"), strokeColor: theme.palette.error.main, @@ -85,7 +82,7 @@ const NetworkCharts = ({ eth0Data, dateRange }) => { }, { type: "network-drops", - data: eth0Data, + data: ethernetData, dataKeys: ["drops"], heading: t("drops"), strokeColor: theme.palette.warning.main, @@ -127,7 +124,7 @@ const NetworkCharts = ({ eth0Data, dateRange }) => { }; NetworkCharts.propTypes = { - eth0Data: PropTypes.array.isRequired, + ethernetData: PropTypes.array.isRequired, dateRange: PropTypes.string.isRequired, }; diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx index 3125e86a9..98ba90dae 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx @@ -1,27 +1,19 @@ -// NetworkStatBoxes.jsx import PropTypes from "prop-types"; import StatusBoxes from "../../../../../Components/StatusBoxes"; import StatBox from "../../../../../Components/StatBox"; import { Typography } from "@mui/material"; import { useTranslation } from "react-i18next"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; -function formatBytes(bytes) { - if (bytes === 0 || bytes == null) return "0 B"; - const k = 1024; - const sizes = ["B", "KB", "MB", "GB", "TB"]; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`; -} - -// Format numbers with commas function formatNumber(num) { return num != null ? num.toLocaleString() : "0"; } -const NetworkStatBoxes = ({ shouldRender, net }) => { +const NetworkStatBoxes = ({ shouldRender, net, ifaceName }) => { const { t } = useTranslation(); - const filtered = - net?.filter((iface) => iface.name === "en0" || iface.name === "wlan0") || []; + const { formatBytes } = useHardwareUtils(); + + const filtered = net?.filter((iface) => iface.name === ifaceName) || []; if (!net?.length) { return {t("noNetworkStatsAvailable")}; @@ -83,6 +75,7 @@ NetworkStatBoxes.propTypes = { err_out: PropTypes.number, }) ), + ifaceName: PropTypes.string.isRequired, }; export default NetworkStatBoxes; diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx index 22a6c3112..623720aba 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx @@ -3,37 +3,40 @@ import NetworkStatBoxes from "./NetworkStatBoxes"; import NetworkCharts from "./NetworkCharts"; import MonitorTimeFrameHeader from "../../../../../Components/MonitorTimeFrameHeader"; -const getNetworkInterfaceData = (checks) => { +const getInterfaceName = (net) => { const interfaceNames = ["eth0", "Ethernet", "en0"]; + const found = (net || []).find((iface) => interfaceNames.includes(iface.name)); + return found ? found.name : null; +}; +const getNetworkInterfaceData = (checks, ifaceName) => { return (checks || []) .map((check) => { - const networkInterface = (check.net || []).find((iface) => - interfaceNames.includes(iface.name) + const networkInterface = (check.net || []).find( + (iface) => iface.name === ifaceName ); - - if (!networkInterface) { - return null; - } - + if (!networkInterface) return null; return { _id: check._id, - bytesPerSec: networkInterface.avgBytesRecv, - packetsPerSec: networkInterface.avgPacketsRecv, - errors: networkInterface.avgErrOut ?? 0, - drops: networkInterface.avgDropOut ?? 0, + bytesPerSec: networkInterface.deltaBytesRecv, + packetsPerSec: networkInterface.deltaPacketsRecv, + errors: networkInterface.deltaErrOut ?? 0, + drops: networkInterface.deltaDropOut ?? 0, }; }) .filter(Boolean); }; const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => { - const eth0Data = getNetworkInterfaceData(checks); + const ifaceName = getInterfaceName(net); + const ethernetData = getNetworkInterfaceData(checks, ifaceName); + return ( <> { setDateRange={setDateRange} /> diff --git a/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx b/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx index a2e4c5153..930ca7ba3 100644 --- a/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx +++ b/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx @@ -57,7 +57,7 @@ const useHardwareUtils = () => { if (GB >= 1) { return ( <> - {Number(GB.toFixed(0))} + {Number(GB.toFixed(2))} {space ? " " : ""} {t("gb")} @@ -65,7 +65,7 @@ const useHardwareUtils = () => { } else { return ( <> - {Number(MB.toFixed(0))} + {Number(MB.toFixed(2))} {space ? " " : ""} {t("mb")} @@ -73,6 +73,26 @@ const useHardwareUtils = () => { } }; + const formatBytesString = (bytes, space = false) => { + if ( + bytes === undefined || + bytes === null || + typeof bytes !== "number" || + bytes === 0 + ) { + return `0${space ? " " : ""}${t("gb")}`; + } + + const GB = bytes / (1024 * 1024 * 1024); + const MB = bytes / (1024 * 1024); + + if (GB >= 1) { + return `${Number(GB.toFixed(2))}${space ? " " : ""}${t("gb")}`; + } else { + return `${Number(MB.toFixed(2))}${space ? " " : ""}${t("mb")}`; + } + }; + /** * Converts a decimal value to a percentage * @@ -134,6 +154,7 @@ const useHardwareUtils = () => { decimalToPercentage, buildTemps, getDimensions, + formatBytesString, }; }; diff --git a/server/src/db/mongo/modules/monitorModule.js b/server/src/db/mongo/modules/monitorModule.js index 0e301cdcf..3afe664f6 100755 --- a/server/src/db/mongo/modules/monitorModule.js +++ b/server/src/db/mongo/modules/monitorModule.js @@ -343,6 +343,17 @@ class MonitorModule { const stats = hardwareStats[0]; + if (stats?.net?.length) { + const elapsedSeconds = (new Date(dates.end).getTime() - new Date(dates.start).getTime()) / 1000; + stats.net = stats.net.map((iface) => ({ + ...iface, + bytesSentPerSec: iface.deltaBytesSent / elapsedSeconds, + bytesRecvPerSec: iface.deltaBytesRecv / elapsedSeconds, + packetsSentPerSec: iface.deltaPacketsSent / elapsedSeconds, + packetsRecvPerSec: iface.deltaPacketsRecv / elapsedSeconds, + })); + } + return { ...monitor.toObject(), stats, diff --git a/server/src/db/mongo/modules/monitorModuleQueries.js b/server/src/db/mongo/modules/monitorModuleQueries.js index c6812b1bc..91fb83444 100755 --- a/server/src/db/mongo/modules/monitorModuleQueries.js +++ b/server/src/db/mongo/modules/monitorModuleQueries.js @@ -395,7 +395,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { "$$netIndex", ], }, - avgBytesSent: { + deltaBytesSent: { $subtract: [ { $arrayElemAt: [ @@ -417,7 +417,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, ], }, - avgBytesRecv: { + deltaBytesRecv: { $subtract: [ { $arrayElemAt: [ @@ -439,7 +439,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, ], }, - avgPacketsSent: { + deltaPacketsSent: { $subtract: [ { $arrayElemAt: [ @@ -461,7 +461,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, ], }, - avgPacketsRecv: { + deltaPacketsRecv: { $subtract: [ { $arrayElemAt: [ @@ -483,6 +483,120 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, ], }, + deltaErrIn: { + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.err_in", + }, + }, + "$$netIndex", + ], + }, + { + $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.err_in" } }, "$$netIndex"], + }, + ], + }, + deltaErrOut: { + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.err_out", + }, + }, + "$$netIndex", + ], + }, + { + $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.err_out" } }, "$$netIndex"], + }, + ], + }, + deltaDropIn: { + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.drop_in", + }, + }, + "$$netIndex", + ], + }, + { + $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.drop_in" } }, "$$netIndex"], + }, + ], + }, + deltaDropOut: { + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.drop_out", + }, + }, + "$$netIndex", + ], + }, + { + $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.drop_out" } }, "$$netIndex"], + }, + ], + }, + deltaFifoIn: { + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.fifo_in", + }, + }, + "$$netIndex", + ], + }, + { + $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.fifo_in" } }, "$$netIndex"], + }, + ], + }, + deltaFifoOut: { + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.fifo_out", + }, + }, + "$$netIndex", + ], + }, + { + $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.fifo_out" } }, "$$netIndex"], + }, + ], + }, }, }, }, From f5aefb1f0b573fa4623f0512ae98d1bdc910e71c Mon Sep 17 00:00:00 2001 From: Owaise Date: Tue, 12 Aug 2025 23:49:45 +0530 Subject: [PATCH 241/259] Ran formattor. --- client/src/Components/Charts/Utils/chartUtils.jsx | 4 ++-- .../Details/Components/NetworkStats/NetworkCharts.jsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/Components/Charts/Utils/chartUtils.jsx b/client/src/Components/Charts/Utils/chartUtils.jsx index a7db3ea93..ce15d27cd 100644 --- a/client/src/Components/Charts/Utils/chartUtils.jsx +++ b/client/src/Components/Charts/Utils/chartUtils.jsx @@ -101,12 +101,12 @@ const getFormattedPercentage = (value) => { * @param {number} props.index - The index of the tick. * @returns {JSX.Element|null} The rendered tick component or null for the first tick. */ -export const NetworkTick = ({ x, y, payload, index, formatter}) => { +export const NetworkTick = ({ x, y, payload, index, formatter }) => { const theme = useTheme(); if (index === 0) return null; if (formatter === undefined) { - formatter = (value, space=false) => { + formatter = (value, space = false) => { if (typeof value !== "number") return value; // need to add space between value and unit return `${(value / 1024).toFixed(2)}${space ? " " : ""}Kbps`; diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx index 84325c29e..98eced859 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx @@ -15,7 +15,7 @@ const NetworkCharts = ({ ethernetData, dateRange }) => { const theme = useTheme(); const { t } = useTranslation(); - const {formatBytesString} = useHardwareUtils(); + const { formatBytesString } = useHardwareUtils(); if (!ethernetData?.length) { return {t("noNetworkStatsAvailable")}; @@ -31,7 +31,7 @@ const NetworkCharts = ({ ethernetData, dateRange }) => { gradientStartColor: theme.palette.info.main, yLabel: t("bytesPerSecond"), xTick: , - yTick: , + yTick: , toolTip: ( Date: Tue, 12 Aug 2025 14:30:11 -0400 Subject: [PATCH 242/259] move hooks to login file --- .../src/Pages/Auth/{ => Login}/hooks/useLoadingSubmit.jsx | 0 client/src/Pages/Auth/{ => Login}/hooks/useLoginForm.jsx | 0 .../src/Pages/Auth/{ => Login}/hooks/useLoginSubmit.jsx | 4 ++-- .../Pages/Auth/{ => Login}/hooks/useValidateLoginForm.jsx | 2 +- client/src/Pages/Auth/Login/index.jsx | 8 ++++---- 5 files changed, 7 insertions(+), 7 deletions(-) rename client/src/Pages/Auth/{ => Login}/hooks/useLoadingSubmit.jsx (100%) rename client/src/Pages/Auth/{ => Login}/hooks/useLoginForm.jsx (100%) rename client/src/Pages/Auth/{ => Login}/hooks/useLoginSubmit.jsx (88%) rename client/src/Pages/Auth/{ => Login}/hooks/useValidateLoginForm.jsx (91%) diff --git a/client/src/Pages/Auth/hooks/useLoadingSubmit.jsx b/client/src/Pages/Auth/Login/hooks/useLoadingSubmit.jsx similarity index 100% rename from client/src/Pages/Auth/hooks/useLoadingSubmit.jsx rename to client/src/Pages/Auth/Login/hooks/useLoadingSubmit.jsx diff --git a/client/src/Pages/Auth/hooks/useLoginForm.jsx b/client/src/Pages/Auth/Login/hooks/useLoginForm.jsx similarity index 100% rename from client/src/Pages/Auth/hooks/useLoginForm.jsx rename to client/src/Pages/Auth/Login/hooks/useLoginForm.jsx diff --git a/client/src/Pages/Auth/hooks/useLoginSubmit.jsx b/client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx similarity index 88% rename from client/src/Pages/Auth/hooks/useLoginSubmit.jsx rename to client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx index ed3bbec67..6bc5716ff 100644 --- a/client/src/Pages/Auth/hooks/useLoginSubmit.jsx +++ b/client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx @@ -1,7 +1,7 @@ import { useDispatch } from "react-redux"; -import { login } from "../../../Features/Auth/authSlice"; +import { login } from "../../../../Features/Auth/authSlice"; import { useNavigate } from "react-router-dom"; -import { createToast } from "../../../Utils/toastUtils"; +import { createToast } from "../../../../Utils/toastUtils"; import { useTranslation } from "react-i18next"; const useLoginSubmit = () => { diff --git a/client/src/Pages/Auth/hooks/useValidateLoginForm.jsx b/client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx similarity index 91% rename from client/src/Pages/Auth/hooks/useValidateLoginForm.jsx rename to client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx index f50c777f3..e5d045be8 100644 --- a/client/src/Pages/Auth/hooks/useValidateLoginForm.jsx +++ b/client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { loginCredentials } from "../../../Validation/validation"; +import { loginCredentials } from "../../../../Validation/validation"; const useValidateLoginForm = () => { const [errors, setErrors] = useState({ diff --git a/client/src/Pages/Auth/Login/index.jsx b/client/src/Pages/Auth/Login/index.jsx index ed9f2e0ff..d06ecef6d 100644 --- a/client/src/Pages/Auth/Login/index.jsx +++ b/client/src/Pages/Auth/Login/index.jsx @@ -8,10 +8,10 @@ import AuthPageWrapper from "../components/AuthPageWrapper"; // Utils import { useTheme } from "@mui/material/styles"; import { useTranslation } from "react-i18next"; -import useLoginForm from "../hooks/useLoginForm"; -import useValidateLoginForm from "../hooks/useValidateLoginForm"; -import useLoginSubmit from "../hooks/useLoginSubmit"; -import useLoadingSubmit from "../hooks/useLoadingSubmit"; +import useLoginForm from "./hooks/useLoginForm"; +import useValidateLoginForm from "./hooks/useValidateLoginForm"; +import useLoginSubmit from "./hooks/useLoginSubmit"; +import useLoadingSubmit from "./hooks/useLoadingSubmit"; const Login = () => { // Hooks From 480ba034cebab20aa1ec5c3e170a594cc56b705f Mon Sep 17 00:00:00 2001 From: singh-kanwarpreet Date: Wed, 13 Aug 2025 14:47:09 +0530 Subject: [PATCH 243/259] Made NavBar Still --- client/src/Pages/Logs/index.jsx | 47 ++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/client/src/Pages/Logs/index.jsx b/client/src/Pages/Logs/index.jsx index db421e312..1b2a7411d 100644 --- a/client/src/Pages/Logs/index.jsx +++ b/client/src/Pages/Logs/index.jsx @@ -23,21 +23,42 @@ const Logs = () => { }; const BREADCRUMBS = [{ name: t("logsPage.title"), path: "/logs" }]; + + // Height of the header (Breadcrumbs + Tabs) + const HEADER_HEIGHT = theme.spacing(25); + return ( - - - + - - - - - {value === 0 && } - {value === 1 && } - {value === 2 && } - + + + + + + + + + {/* Main content below fixed header */} + + {value === 0 && } + {value === 1 && } + {value === 2 && } + + ); }; From 7ab5f4ab09e0ebe6f62287023c2b8653eacca8f5 Mon Sep 17 00:00:00 2001 From: singh-kanwarpreet Date: Wed, 13 Aug 2025 22:41:13 +0530 Subject: [PATCH 244/259] Refactored code --- client/src/Pages/Logs/Logs/index.jsx | 20 ++++++++++++++++++-- client/src/Pages/Logs/index.jsx | 24 +++++++++--------------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/client/src/Pages/Logs/Logs/index.jsx b/client/src/Pages/Logs/Logs/index.jsx index b65f9c0fd..a5bc9757e 100644 --- a/client/src/Pages/Logs/Logs/index.jsx +++ b/client/src/Pages/Logs/Logs/index.jsx @@ -50,8 +50,17 @@ const Logs = () => { { _id: "debug", name: t("logsPage.logLevelSelect.values.debug") }, ]; return ( - - + + {t("logsPage.description")} @@ -61,6 +70,13 @@ const Logs = () => { alignItems="center" gap={theme.spacing(4)} mt={theme.spacing(10)} + sx={{ + position: "sticky", + top: 80, + zIndex: 900, + backgroundColor: theme.palette.tertiary.main, + paddingY: theme.spacing(1), + }} > {t("logsPage.logLevelSelect.title")} { - setLogLevel(e.target.value); - }} - /> + + + {t("logsPage.logLevelSelect.title")} + setLogLevel(e.target.value)} - /> - + + + + + {t("logsPage.logLevelSelect.title")} + setSelectedInterface(e.target.value)} + label={t("networkInterface")} + > + {availableInterfaces.map((interfaceName) => ( + + {interfaceName} + + ))} + + + )} + + { } }; - const formatBytesString = (bytes, space = false) => { + const formatBytesPerSecondString = (bytesPerSec, space = false) => { if ( - bytes === undefined || - bytes === null || - typeof bytes !== "number" || - bytes === 0 + bytesPerSec === undefined || + bytesPerSec === null || + typeof bytesPerSec !== "number" || + bytesPerSec === 0 ) { - return `0${space ? " " : ""}${t("gb")}`; + return `0${space ? " " : ""}B/s`; } - const GB = bytes / (1024 * 1024 * 1024); - const MB = bytes / (1024 * 1024); + const GB = bytesPerSec / (1024 * 1024 * 1024); + const MB = bytesPerSec / (1024 * 1024); + const KB = bytesPerSec / 1024; if (GB >= 1) { - return `${Number(GB.toFixed(2))}${space ? " " : ""}${t("gb")}`; + return `${Number(GB.toFixed(1))}${space ? " " : ""}GB/s`; + } else if (MB >= 1) { + return `${Number(MB.toFixed(1))}${space ? " " : ""}MB/s`; + } else if (KB >= 1) { + return `${Number(KB.toFixed(1))}${space ? " " : ""}KB/s`; } else { - return `${Number(MB.toFixed(2))}${space ? " " : ""}${t("mb")}`; + return `${Number(bytesPerSec.toFixed(1))}${space ? " " : ""}B/s`; + } + }; + + const formatPacketsPerSecondString = (packetsPerSec, space = false) => { + if ( + packetsPerSec === undefined || + packetsPerSec === null || + typeof packetsPerSec !== "number" || + packetsPerSec === 0 + ) { + return `0${space ? " " : ""}pps`; + } + + const M = packetsPerSec / (1000 * 1000); + const K = packetsPerSec / 1000; + + if (M >= 1) { + return `${Number(M.toFixed(1))}${space ? " " : ""}Mpps`; + } else if (K >= 1) { + return `${Number(K.toFixed(1))}${space ? " " : ""}Kpps`; + } else { + return `${Math.round(packetsPerSec)}${space ? " " : ""}pps`; } }; @@ -154,7 +181,8 @@ const useHardwareUtils = () => { decimalToPercentage, buildTemps, getDimensions, - formatBytesString, + formatBytesPerSecondString, + formatPacketsPerSecondString, }; }; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 08c60c372..857825c9f 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -205,6 +205,10 @@ "bytesPerSecond": "Bytes per second", "bytesReceived": "Bytes Received", "bytesSent": "Bytes Sent", + "dataReceived": "Data Received", + "dataSent": "Data Sent", + "dataRate": "Data Rate", + "rate": "Rate", "bulkImport": { "fallbackPage": "Import a file to upload a list of servers in bulk", "invalidFileType": "Invalid file type", @@ -341,6 +345,10 @@ "errors": "Errors", "errorsIn": "Errors In", "errorsOut": "Errors Out", + "networkErrors": "Network Errors", + "networkDrops": "Network Drops", + "networkInterface": "Network Interface", + "selectInterface": "Select Interface", "details": "Details", "displayName": "Display name", "distributedRightCategoryTitle": "Monitor", @@ -763,6 +771,7 @@ }, "packetsPerSecond": "Packets per second", "packetsReceived": "Packets Received", + "packetsReceivedRate": "Packets Received Rate", "packetsSent": "Packets Sent", "pause": "Pause", "pingMonitoring": "Ping monitoring", From 22e0507fbdac36eaf165ceaf4c82bb9965005949 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 14 Aug 2025 09:34:30 -0700 Subject: [PATCH 253/259] fix arm dist health check --- docker/dist-arm/docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/dist-arm/docker-compose.yaml b/docker/dist-arm/docker-compose.yaml index 37a5f4636..96bbf8f3f 100644 --- a/docker/dist-arm/docker-compose.yaml +++ b/docker/dist-arm/docker-compose.yaml @@ -23,7 +23,7 @@ services: volumes: - ./mongo/data:/data/db healthcheck: - test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')", "--quiet"] + test: ["CMD", "mongo", "--eval", "db.runCommand({ ping: 1 })", "--quiet"] interval: 5s timeout: 30s start_period: 0s From 0e767d65811d72412069116e16d4d2633257d32f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 14 Aug 2025 09:58:41 -0700 Subject: [PATCH 254/259] remove redis from workflows --- .github/workflows/deploy-images.yml | 12 ------------ .github/workflows/staging-deploy.yml | 11 ----------- 2 files changed, 23 deletions(-) diff --git a/.github/workflows/deploy-images.yml b/.github/workflows/deploy-images.yml index fa39fed85..53d7ab368 100644 --- a/.github/workflows/deploy-images.yml +++ b/.github/workflows/deploy-images.yml @@ -74,18 +74,6 @@ jobs: run: | docker push ghcr.io/bluewave-labs/checkmate-mongo:latest - - name: Build Redis Docker image - run: | - docker build \ - -t ghcr.io/bluewave-labs/checkmate-redis:latest \ - -f ./docker/dist/redis.Dockerfile \ - --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \ - . - - - name: Push Redis Docker image - run: | - docker push ghcr.io/bluewave-labs/checkmate-redis:latest - docker-build-and-push-server-mono-multiarch: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/staging-deploy.yml b/.github/workflows/staging-deploy.yml index ea5477815..27b7e8690 100644 --- a/.github/workflows/staging-deploy.yml +++ b/.github/workflows/staging-deploy.yml @@ -71,17 +71,6 @@ jobs: - name: Push MongoDB Docker image run: docker push ghcr.io/bluewave-labs/checkmate:mongo-staging - - name: Build Redis Docker image - run: | - docker build \ - -t ghcr.io/bluewave-labs/checkmate:redis-staging \ - -f ./docker/staging/redis.Dockerfile \ - --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \ - . - - - name: Push Redis Docker image - run: docker push ghcr.io/bluewave-labs/checkmate:redis-staging - deploy-to-staging: needs: docker-build-and-push-server runs-on: ubuntu-latest From 9ae7314f546ab16788cda422084be4d3c8c31ddc Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 14 Aug 2025 10:00:20 -0700 Subject: [PATCH 255/259] add release workflow --- .github/workflows/deploy-images-on-release.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.github/workflows/deploy-images-on-release.yml b/.github/workflows/deploy-images-on-release.yml index cf06f7840..a9b2dfdb8 100644 --- a/.github/workflows/deploy-images-on-release.yml +++ b/.github/workflows/deploy-images-on-release.yml @@ -79,18 +79,6 @@ jobs: run: | docker push ghcr.io/bluewave-labs/checkmate-mongo:${{ steps.extract_tag.outputs.version }} - - name: Build Redis Docker image - run: | - docker build \ - -t ghcr.io/bluewave-labs/checkmate-redis:${{ steps.extract_tag.outputs.version }} \ - -f ./docker/dist/redis.Dockerfile \ - --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \ - . - - - name: Push Redis Docker image - run: | - docker push ghcr.io/bluewave-labs/checkmate-redis:${{ steps.extract_tag.outputs.version }} - docker-build-and-push-server-mono-multiarch: runs-on: ubuntu-latest steps: From 9e05d024a86e1c5aa46d419c90033ac7811d5ad3 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 14 Aug 2025 10:32:10 -0700 Subject: [PATCH 256/259] remove redis from production workflow --- .github/workflows/production-deploy.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.github/workflows/production-deploy.yml b/.github/workflows/production-deploy.yml index 7764c488b..532197710 100644 --- a/.github/workflows/production-deploy.yml +++ b/.github/workflows/production-deploy.yml @@ -71,17 +71,6 @@ jobs: - name: Push MongoDB Docker image run: docker push ghcr.io/bluewave-labs/checkmate:mongo-demo - - name: Build Redis Docker image - run: | - docker build \ - -t ghcr.io/bluewave-labs/checkmate:redis-demo \ - -f ./docker/prod/redis.Dockerfile \ - --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \ - . - - - name: Push Redis Docker image - run: docker push ghcr.io/bluewave-labs/checkmate:redis-demo - deploy-to-demo: needs: docker-build-and-push-server runs-on: ubuntu-latest From f2c26c0eed91b4e79127821a9e99892238d4c90d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 14 Aug 2025 11:26:46 -0700 Subject: [PATCH 257/259] bump versoin --- client/vite.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/vite.config.js b/client/vite.config.js index 44a5905c5..df686d751 100644 --- a/client/vite.config.js +++ b/client/vite.config.js @@ -5,7 +5,7 @@ import { execSync } from "child_process"; export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), ""); - let version = 2.3; + let version = "3.0-beta"; return { base: "/", From 0241797e7990d5901e48fecf7f3c4b102e06c9db Mon Sep 17 00:00:00 2001 From: "Gorkem Cetin (BWL)" <167266851+gorkem-bwl@users.noreply.github.com> Date: Thu, 14 Aug 2025 15:48:11 -0400 Subject: [PATCH 258/259] Update README.md --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ca55e38ab..048cf0706 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ This repository contains both the frontend and the backend of Checkmate, an open-source, self-hosted monitoring tool for tracking server hardware, uptime, response times, and incidents in real-time with beautiful visualizations. Checkmate regularly checks whether a server/website is accessible and performs optimally, providing real-time alerts and reports on the monitored services' availability, downtime, and response time. -Checkmate also has an agent, called [Capture](https://github.com/bluewave-labs/capture), to retrieve data from remote servers. While Capture is not required to run Checkmate, it provides additional insights about your servers' CPU, RAM, disk, and temperature status. +Checkmate also has an agent, called [Capture](https://github.com/bluewave-labs/capture), to retrieve data from remote servers. While Capture is not required to run Checkmate, it provides additional insights about your servers' CPU, RAM, disk, and temperature status. Capture can run on Linux, Windows, Mac, Raspberry Pi, or any device that can run Go. Checkmate has been stress-tested with 1000+ active monitors without any particular issues or performance bottlenecks. @@ -90,20 +90,22 @@ Feel free to ask questions or share your ideas - we'd love to hear from you! - Completely open source, deployable on your servers or home devices (e.g Raspberry Pi 4 or 5) - Website monitoring - Page speed monitoring -- Infrastructure monitoring (memory, disk usage, CPU performance etc) - requires [Capture](https://github.com/bluewave-labs/capture) agent +- Infrastructure monitoring (memory, disk usage, CPU performance, network etc) - requires [Capture](https://github.com/bluewave-labs/capture) agent - Docker monitoring - Ping monitoring - SSL monitoring - Port monitoring +- Game server monitoring (3.0) - Incidents at a glance - Status pages -- E-mail, Webhooks, Discord, Telegram, Slack notifications +- E-mail, Webhooks, Discord and Slack notifications - Scheduled maintenance - JSON query monitoring - Multi-language support for English, German, Japanese, Portuguese (Brazil), Russian, Turkish, Ukrainian, Vietnamese, Chinese (Traditional, Taiwan) **Short term roadmap:** +- Plugins that will help Checkmate get any information from a remote service (e.g database, etc) - Better notifications - Network monitoring - ..and a few more features @@ -145,7 +147,7 @@ If you would like to sponsor an additional feature, [see this page](https://chec ## Contributing -We are [Alex](http://github.com/ajhollid) (team lead), [Mohadeseh](http://github.com/mohicody), [Gorkem](http://github.com/gorkem-bwl/), [Owaise](http://github.com/Owaiseimdad), [Aryaman](https://github.com/Br0wnHammer), [Mert](https://github.com/mertssmnoglu) and [Karen](https://github.com/karenvicent) helping individuals and businesses monitor their infra and servers. +We are [Alex](http://github.com/ajhollid) (team lead), [Gorkem](http://github.com/gorkem-bwl/), [Owaise](http://github.com/Owaiseimdad), [Aryaman](https://github.com/Br0wnHammer), [Mert](https://github.com/mertssmnoglu) and [Karen](https://github.com/karenvicent) helping individuals and businesses monitor their infra and servers. We pride ourselves on building strong connections with contributors at every level. Despite being a young project, Checkmate has already earned 7000+ stars and attracted 90+ contributors from around the globe. From ae9eb494aec5db1b1c050bf261d711f02b464782 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 14 Aug 2025 12:48:49 -0700 Subject: [PATCH 259/259] add capture to prod and staging docker-compose --- docker/prod/docker-compose.yaml | 11 +++++++++++ docker/staging/docker-compose.yaml | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/docker/prod/docker-compose.yaml b/docker/prod/docker-compose.yaml index 61c6c021d..6e29fd90a 100755 --- a/docker/prod/docker-compose.yaml +++ b/docker/prod/docker-compose.yaml @@ -49,3 +49,14 @@ services: start_period: 0s start_interval: 1s retries: 30 + capture: + image: ghcr.io/bluewave-labs/capture:latest + container_name: capture + restart: unless-stopped + ports: + - "59232:59232" + environment: + - API_SECRET=my_secret + - GIN_MODE=release + volumes: + - /etc/os-release:/etc/os-release:ro diff --git a/docker/staging/docker-compose.yaml b/docker/staging/docker-compose.yaml index 436c6327f..f4ed5ce6f 100755 --- a/docker/staging/docker-compose.yaml +++ b/docker/staging/docker-compose.yaml @@ -49,3 +49,14 @@ services: start_period: 0s start_interval: 1s retries: 30 + capture: + image: ghcr.io/bluewave-labs/capture:latest + container_name: capture + restart: unless-stopped + ports: + - "59232:59232" + environment: + - API_SECRET=my_secret + - GIN_MODE=release + volumes: + - /etc/os-release:/etc/os-release:ro