From 0285302ecc822908952fdcb256dc086972ac663b Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Fri, 9 Jan 2026 10:36:46 +0000 Subject: [PATCH 01/24] bump mongo image version for arm arch to `.30` --- 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 0a5825b3e..9a87aa59b 100644 --- a/docker/dist-arm/docker-compose.yaml +++ b/docker/dist-arm/docker-compose.yaml @@ -15,7 +15,7 @@ services: depends_on: - mongodb mongodb: - image: mongo:4.4.18 + image: mongo:4.4.30 restart: always command: ["mongod", "--quiet", "--bind_ip_all"] ports: From e090d3cfd3e78ebac3a6cc61bb58595330650b63 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 12 Jan 2026 19:32:23 +0000 Subject: [PATCH 02/24] -> timeseries --- server/package-lock.json | 97 ++++ server/package.json | 1 + server/src/app.js | 1 - server/src/config/controllers.js | 47 -- server/src/config/controllers.ts | 35 ++ server/src/config/{routes.js => routes.ts} | 6 +- server/src/config/services.js | 84 +-- server/src/controllers/authController.ts | 233 +++++++++ .../controllers/{v1 => }/checkController.ts | 0 .../controllers/{v1 => }/controllerUtils.ts | 0 .../{v1 => }/diagnosticController.ts | 0 .../{v1 => }/incidentController.ts | 0 .../controllers/{v1 => }/inviteController.ts | 0 .../src/controllers/{v1 => }/logController.ts | 0 .../{v1 => }/maintenanceWindowController.ts | 0 .../controllers/{v1 => }/monitorController.ts | 2 +- .../{v1 => }/notificationController.ts | 0 .../controllers/{v1 => }/queueController.ts | 0 .../{v1 => }/settingsController.ts | 0 .../{v1 => }/statusPageController.ts | 0 .../controllers/v1/announcementsController.js | 77 --- server/src/controllers/v1/authController.js | 478 ------------------ server/src/controllers/v1/baseController.js | 83 --- server/src/db/{v1 => }/MongoDB.js | 0 .../0001_migrateStatusWindowThreshold.ts | 10 + .../0002_convertChecksToTimeSeries.ts | 93 ++++ server/src/db/migration/index.ts | 51 ++ server/src/db/{v1 => }/models/AppSettings.js | 0 server/src/db/models/Check.ts | 214 ++++++++ server/src/db/{v1 => }/models/Incident.js | 0 server/src/db/{v1 => }/models/InviteToken.js | 0 .../db/{v1 => }/models/MaintenanceWindow.js | 0 server/src/db/models/Migration.ts | 13 + server/src/db/{v1 => }/models/Monitor.js | 0 server/src/db/{v1 => }/models/MonitorStats.js | 0 server/src/db/{v1 => }/models/Notification.js | 0 .../src/db/{v1 => }/models/RecoveryToken.js | 0 server/src/db/{v1 => }/models/StatusPage.js | 0 server/src/db/{v1 => }/models/Team.js | 0 server/src/db/{v1 => }/models/User.js | 0 server/src/db/{v1 => }/modules/checkModule.js | 0 .../db/{v1 => }/modules/checkModuleQueries.js | 0 .../src/db/{v1 => }/modules/incidentModule.js | 0 .../src/db/{v1 => }/modules/inviteModule.js | 0 .../modules/maintenanceWindowModule.js | 0 .../src/db/{v1 => }/modules/monitorModule.js | 0 .../{v1 => }/modules/monitorModuleQueries.js | 0 .../db/{v1 => }/modules/notificationModule.js | 0 .../src/db/{v1 => }/modules/recoveryModule.js | 0 .../src/db/{v1 => }/modules/settingsModule.js | 0 .../db/{v1 => }/modules/statusPageModule.js | 0 server/src/db/{v1 => }/modules/userModule.js | 0 server/src/db/{v1 => }/utils/seedDb.js | 0 .../0001_migrateStatusWindowThreshold.ts | 18 - server/src/db/v1/migration/index.ts | 7 - server/src/db/v1/models/Check.js | 178 ------- server/src/index.js | 7 +- server/src/middleware/v1/handleErrors.js | 4 +- server/src/middleware/v1/isAllowed.js | 6 +- server/src/middleware/v1/verifyJWT.js | 6 +- server/src/middleware/v1/verifyOwnership.js | 4 +- .../repositories/checks/IChecksRepository.ts | 0 .../checks/MongoChecksRepistory.ts | 0 .../monitors/IMonitorsRepository.ts | 9 + .../monitors/MongoMonitorsRepository.ts | 5 + server/src/routes/v1/announcementsRoute.js | 33 -- server/src/routes/v1/monitorRoute.js | 2 +- .../service/{v1 => }/business/checkService.js | 0 .../{v1 => }/business/diagnosticService.js | 0 .../{v1 => }/business/incidentService.js | 0 .../{v1 => }/business/inviteService.js | 0 .../business/maintenanceWindowService.js | 0 .../{v1 => }/business/monitorService.js | 2 +- .../service/{v1 => }/business/userService.js | 0 .../SuperSimpleQueue/SuperSimpleQueue.js | 0 .../SuperSimpleQueueHelper.js | 0 .../{v1 => }/infrastructure/bufferService.js | 0 .../{v1 => }/infrastructure/emailService.js | 0 .../{v1 => }/infrastructure/errorService.js | 0 .../{v1 => }/infrastructure/networkService.js | 0 .../notificationProviders/matrix.js | 0 .../infrastructure/notificationService.js | 0 .../infrastructure/notificationUtils.js | 0 .../{v1 => }/infrastructure/statusService.js | 12 +- .../{v1 => }/system/serviceRegistry.js | 0 .../{v1 => }/system/settingsService.js | 0 .../service/{v1 => }/system/stringService.js | 0 .../{v1 => }/system/translationService.js | 0 server/src/types/check.ts | 79 +++ server/src/types/index.ts | 2 + server/src/types/monitor.ts | 29 ++ 91 files changed, 941 insertions(+), 987 deletions(-) delete mode 100644 server/src/config/controllers.js create mode 100644 server/src/config/controllers.ts rename server/src/config/{routes.js => routes.ts} (92%) create mode 100755 server/src/controllers/authController.ts rename server/src/controllers/{v1 => }/checkController.ts (100%) rename server/src/controllers/{v1 => }/controllerUtils.ts (100%) rename server/src/controllers/{v1 => }/diagnosticController.ts (100%) rename server/src/controllers/{v1 => }/incidentController.ts (100%) rename server/src/controllers/{v1 => }/inviteController.ts (100%) rename server/src/controllers/{v1 => }/logController.ts (100%) rename server/src/controllers/{v1 => }/maintenanceWindowController.ts (100%) rename server/src/controllers/{v1 => }/monitorController.ts (99%) rename server/src/controllers/{v1 => }/notificationController.ts (100%) rename server/src/controllers/{v1 => }/queueController.ts (100%) rename server/src/controllers/{v1 => }/settingsController.ts (100%) rename server/src/controllers/{v1 => }/statusPageController.ts (100%) delete mode 100755 server/src/controllers/v1/announcementsController.js delete mode 100755 server/src/controllers/v1/authController.js delete mode 100644 server/src/controllers/v1/baseController.js rename server/src/db/{v1 => }/MongoDB.js (100%) create mode 100644 server/src/db/migration/0001_migrateStatusWindowThreshold.ts create mode 100644 server/src/db/migration/0002_convertChecksToTimeSeries.ts create mode 100644 server/src/db/migration/index.ts rename server/src/db/{v1 => }/models/AppSettings.js (100%) create mode 100755 server/src/db/models/Check.ts rename server/src/db/{v1 => }/models/Incident.js (100%) rename server/src/db/{v1 => }/models/InviteToken.js (100%) rename server/src/db/{v1 => }/models/MaintenanceWindow.js (100%) create mode 100644 server/src/db/models/Migration.ts rename server/src/db/{v1 => }/models/Monitor.js (100%) rename server/src/db/{v1 => }/models/MonitorStats.js (100%) rename server/src/db/{v1 => }/models/Notification.js (100%) rename server/src/db/{v1 => }/models/RecoveryToken.js (100%) rename server/src/db/{v1 => }/models/StatusPage.js (100%) rename server/src/db/{v1 => }/models/Team.js (100%) rename server/src/db/{v1 => }/models/User.js (100%) rename server/src/db/{v1 => }/modules/checkModule.js (100%) rename server/src/db/{v1 => }/modules/checkModuleQueries.js (100%) rename server/src/db/{v1 => }/modules/incidentModule.js (100%) rename server/src/db/{v1 => }/modules/inviteModule.js (100%) rename server/src/db/{v1 => }/modules/maintenanceWindowModule.js (100%) rename server/src/db/{v1 => }/modules/monitorModule.js (100%) rename server/src/db/{v1 => }/modules/monitorModuleQueries.js (100%) rename server/src/db/{v1 => }/modules/notificationModule.js (100%) rename server/src/db/{v1 => }/modules/recoveryModule.js (100%) rename server/src/db/{v1 => }/modules/settingsModule.js (100%) rename server/src/db/{v1 => }/modules/statusPageModule.js (100%) rename server/src/db/{v1 => }/modules/userModule.js (100%) rename server/src/db/{v1 => }/utils/seedDb.js (100%) delete mode 100644 server/src/db/v1/migration/0001_migrateStatusWindowThreshold.ts delete mode 100644 server/src/db/v1/migration/index.ts delete mode 100755 server/src/db/v1/models/Check.js create mode 100644 server/src/repositories/checks/IChecksRepository.ts create mode 100644 server/src/repositories/checks/MongoChecksRepistory.ts create mode 100644 server/src/repositories/monitors/IMonitorsRepository.ts create mode 100644 server/src/repositories/monitors/MongoMonitorsRepository.ts delete mode 100755 server/src/routes/v1/announcementsRoute.js rename server/src/service/{v1 => }/business/checkService.js (100%) rename server/src/service/{v1 => }/business/diagnosticService.js (100%) rename server/src/service/{v1 => }/business/incidentService.js (100%) rename server/src/service/{v1 => }/business/inviteService.js (100%) rename server/src/service/{v1 => }/business/maintenanceWindowService.js (100%) rename server/src/service/{v1 => }/business/monitorService.js (99%) rename server/src/service/{v1 => }/business/userService.js (100%) rename server/src/service/{v1 => }/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js (100%) rename server/src/service/{v1 => }/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js (100%) rename server/src/service/{v1 => }/infrastructure/bufferService.js (100%) rename server/src/service/{v1 => }/infrastructure/emailService.js (100%) rename server/src/service/{v1 => }/infrastructure/errorService.js (100%) rename server/src/service/{v1 => }/infrastructure/networkService.js (100%) rename server/src/service/{v1 => }/infrastructure/notificationProviders/matrix.js (100%) rename server/src/service/{v1 => }/infrastructure/notificationService.js (100%) rename server/src/service/{v1 => }/infrastructure/notificationUtils.js (100%) rename server/src/service/{v1 => }/infrastructure/statusService.js (98%) rename server/src/service/{v1 => }/system/serviceRegistry.js (100%) rename server/src/service/{v1 => }/system/settingsService.js (100%) rename server/src/service/{v1 => }/system/stringService.js (100%) rename server/src/service/{v1 => }/system/translationService.js (100%) create mode 100644 server/src/types/check.ts create mode 100644 server/src/types/index.ts create mode 100644 server/src/types/monitor.ts diff --git a/server/package-lock.json b/server/package-lock.json index e08248989..eaf771a61 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -57,6 +57,7 @@ "esm": "3.2.25", "globals": "^15.14.0", "mocha": "11.1.0", + "nodemon": "^3.1.11", "prettier": "^3.3.3", "sinon": "19.0.2", "tsc-alias": "1.8.16", @@ -6732,6 +6733,13 @@ "node": ">= 4" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -8745,6 +8753,58 @@ "node": ">=6.0.0" } }, + "node_modules/nodemon": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", + "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -9797,6 +9857,13 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, "node_modules/pump": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", @@ -10424,6 +10491,19 @@ "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", "license": "MIT" }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/sinon": { "version": "19.0.2", "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.2.tgz", @@ -11046,6 +11126,16 @@ "node": ">=0.6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "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", @@ -11225,6 +11315,13 @@ "node": ">=0.8.0" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, "node_modules/undici": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz", diff --git a/server/package.json b/server/package.json index 050dd8e66..5610d32a9 100755 --- a/server/package.json +++ b/server/package.json @@ -69,6 +69,7 @@ "esm": "3.2.25", "globals": "^15.14.0", "mocha": "11.1.0", + "nodemon": "^3.1.11", "prettier": "^3.3.3", "sinon": "19.0.2", "tsc-alias": "1.8.16", diff --git a/server/src/app.js b/server/src/app.js index 4578de896..d782d07eb 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -14,7 +14,6 @@ import { sanitizeBody, sanitizeQuery } from "./middleware/v1/sanitization.js"; export const createApp = ({ services, controllers, envSettings, frontendPath, openApiSpec }) => { const allowedOrigin = envSettings.clientHost; - const app = express(); app.use(generalApiLimiter); // Static files diff --git a/server/src/config/controllers.js b/server/src/config/controllers.js deleted file mode 100644 index afd558930..000000000 --- a/server/src/config/controllers.js +++ /dev/null @@ -1,47 +0,0 @@ -import { createCommonDependencies } from "../controllers/v1/baseController.js"; - -// Services - -// V1 Controllers -import MonitorController from "../controllers/v1/monitorController.js"; -import AuthController from "../controllers/v1/authController.js"; -import SettingsController from "../controllers/v1/settingsController.js"; -import CheckController from "../controllers/v1/checkController.js"; -import InviteController from "../controllers/v1/inviteController.js"; -import MaintenanceWindowController from "../controllers/v1/maintenanceWindowController.js"; -import QueueController from "../controllers/v1/queueController.js"; -import LogController from "../controllers/v1/logController.js"; -import StatusPageController from "../controllers/v1/statusPageController.js"; -import NotificationController from "../controllers/v1/notificationController.js"; -import DiagnosticController from "../controllers/v1/diagnosticController.js"; -import IncidentController from "../controllers/v1/incidentController.js"; - -export const initializeControllers = (services) => { - const controllers = {}; - const commonDependencies = createCommonDependencies(services.db, services.errorService, services.logger, services.stringService); - - // V1 - controllers.authController = new AuthController(commonDependencies, { - settingsService: services.settingsService, - emailService: services.emailService, - jobQueue: services.jobQueue, - userService: services.userService, - }); - - controllers.monitorController = new MonitorController(services.monitorService); - - controllers.settingsController = new SettingsController(services.settingsService, services.emailService, services.db); - controllers.checkController = new CheckController(services.checkService); - controllers.inviteController = new InviteController(services.inviteService); - - controllers.maintenanceWindowController = new MaintenanceWindowController(services.maintenanceWindowService); - controllers.queueController = new QueueController(services.jobQueue); - controllers.logController = new LogController(services.logger); - controllers.statusPageController = new StatusPageController(services.db); - controllers.notificationController = new NotificationController(services.notificationService, services.db); - controllers.diagnosticController = new DiagnosticController(services.diagnosticService); - - controllers.incidentController = new IncidentController(services.incidentService); - - return controllers; -}; diff --git a/server/src/config/controllers.ts b/server/src/config/controllers.ts new file mode 100644 index 000000000..9fe834348 --- /dev/null +++ b/server/src/config/controllers.ts @@ -0,0 +1,35 @@ +import MonitorController from "../controllers/monitorController.js"; +import AuthController from "../controllers/authController.js"; +import SettingsController from "../controllers/settingsController.js"; +import CheckController from "../controllers/checkController.js"; +import InviteController from "../controllers/inviteController.js"; +import MaintenanceWindowController from "../controllers/maintenanceWindowController.js"; +import QueueController from "../controllers/queueController.js"; +import LogController from "../controllers/logController.js"; +import StatusPageController from "../controllers/statusPageController.js"; +import NotificationController from "../controllers/notificationController.js"; +import DiagnosticController from "../controllers/diagnosticController.js"; +import IncidentController from "../controllers/incidentController.js"; + +export const initializeControllers = (services: any) => { + const controllers: Record = {}; + + controllers.authController = new AuthController(services.userService); + + controllers.monitorController = new MonitorController(services.monitorService); + + controllers.settingsController = new SettingsController(services.settingsService, services.emailService, services.db); + controllers.checkController = new CheckController(services.checkService); + controllers.inviteController = new InviteController(services.inviteService); + + controllers.maintenanceWindowController = new MaintenanceWindowController(services.maintenanceWindowService); + controllers.queueController = new QueueController(services.jobQueue); + controllers.logController = new LogController(services.logger); + controllers.statusPageController = new StatusPageController(services.db); + controllers.notificationController = new NotificationController(services.notificationService, services.db); + controllers.diagnosticController = new DiagnosticController(services.diagnosticService); + + controllers.incidentController = new IncidentController(services.incidentService); + + return controllers; +}; diff --git a/server/src/config/routes.js b/server/src/config/routes.ts similarity index 92% rename from server/src/config/routes.js rename to server/src/config/routes.ts index 97b2d6a1d..303dd4eb7 100644 --- a/server/src/config/routes.js +++ b/server/src/config/routes.ts @@ -2,7 +2,7 @@ import { verifyJWT } from "../middleware/v1/verifyJWT.js"; import { authApiLimiter } from "../middleware/v1/rateLimiter.js"; import AuthRoutes from "../routes/v1/authRoute.js"; -import InviteRoutes from "../routes/v1//inviteRoute.js"; +import InviteRoutes from "../routes/v1/inviteRoute.js"; import MonitorRoutes from "../routes/v1/monitorRoute.js"; import CheckRoutes from "../routes/v1/checkRoute.js"; import SettingsRoutes from "../routes/v1/settingsRoute.js"; @@ -10,12 +10,12 @@ import MaintenanceWindowRoutes from "../routes/v1/maintenanceWindowRoute.js"; import StatusPageRoutes from "../routes/v1/statusPageRoute.js"; import QueueRoutes from "../routes/v1/queueRoute.js"; import LogRoutes from "../routes/v1/logRoutes.js"; -import DiagnosticRoutes from "../routes/v1//diagnosticRoute.js"; +import DiagnosticRoutes from "../routes/v1/diagnosticRoute.js"; import NotificationRoutes from "../routes/v1/notificationRoute.js"; import IncidentRoutes from "../routes/v1/incidentRoute.js"; -export const setupRoutes = (app, controllers) => { +export const setupRoutes = (app: any, controllers: Record) => { // V1 const authRoutes = new AuthRoutes(controllers.authController); const monitorRoutes = new MonitorRoutes(controllers.monitorController); diff --git a/server/src/config/services.js b/server/src/config/services.js index 9744c647d..28b7c9a86 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -1,23 +1,23 @@ -import ServiceRegistry from "../service/v1/system/serviceRegistry.js"; -import TranslationService from "../service/v1/system/translationService.js"; -import StringService from "../service/v1/system/stringService.js"; -import MongoDB from "../db/v1/MongoDB.js"; -import NetworkService from "../service/v1/infrastructure/networkService.js"; -import EmailService from "../service/v1/infrastructure/emailService.js"; -import BufferService from "../service/v1/infrastructure/bufferService.js"; -import StatusService from "../service/v1/infrastructure/statusService.js"; -import NotificationUtils from "../service/v1/infrastructure/notificationUtils.js"; -import NotificationService from "../service/v1/infrastructure/notificationService.js"; -import ErrorService from "../service/v1/infrastructure/errorService.js"; -import SuperSimpleQueueHelper from "../service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; -import SuperSimpleQueue from "../service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js"; -import UserService from "../service/v1/business/userService.js"; -import CheckService from "../service/v1/business/checkService.js"; -import DiagnosticService from "../service/v1/business/diagnosticService.js"; -import InviteService from "../service/v1/business/inviteService.js"; -import MaintenanceWindowService from "../service/v1/business/maintenanceWindowService.js"; -import MonitorService from "../service/v1/business/monitorService.js"; -import IncidentService from "../service/v1/business/incidentService.js"; +import ServiceRegistry from "../service/system/serviceRegistry.js"; +import TranslationService from "../service/system/translationService.js"; +import StringService from "../service/system/stringService.js"; +import MongoDB from "../db/MongoDB.js"; +import NetworkService from "../service/infrastructure/networkService.js"; +import EmailService from "../service/infrastructure/emailService.js"; +import BufferService from "../service/infrastructure/bufferService.js"; +import StatusService from "../service/infrastructure/statusService.js"; +import NotificationUtils from "../service/infrastructure/notificationUtils.js"; +import NotificationService from "../service/infrastructure/notificationService.js"; +import ErrorService from "../service/infrastructure/errorService.js"; +import SuperSimpleQueueHelper from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; +import SuperSimpleQueue from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js"; +import UserService from "../service/business/userService.js"; +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"; +import IncidentService from "../service/business/incidentService.js"; import papaparse from "papaparse"; import axios from "axios"; import got from "got"; @@ -47,29 +47,29 @@ import { GenerateAvatarImage } from "../utils/imageProcessing.js"; import { ParseBoolean } from "../utils/utils.js"; // Models -import Check from "../db/v1/models/Check.js"; -import Monitor from "../db/v1/models/Monitor.js"; -import User from "../db/v1/models/User.js"; -import InviteToken from "../db/v1/models/InviteToken.js"; -import StatusPage from "../db/v1/models/StatusPage.js"; -import Team from "../db/v1/models/Team.js"; -import MaintenanceWindow from "../db/v1/models/MaintenanceWindow.js"; -import MonitorStats from "../db/v1/models/MonitorStats.js"; -import Notification from "../db/v1/models/Notification.js"; -import RecoveryToken from "../db/v1/models/RecoveryToken.js"; -import AppSettings from "../db/v1/models/AppSettings.js"; -import Incident from "../db/v1/models/Incident.js"; +import Check from "../db/models/Check.js"; +import Monitor from "../db/models/Monitor.js"; +import User from "../db/models/User.js"; +import InviteToken from "../db/models/InviteToken.js"; +import StatusPage from "../db/models/StatusPage.js"; +import Team from "../db/models/Team.js"; +import MaintenanceWindow from "../db/models/MaintenanceWindow.js"; +import MonitorStats from "../db/models/MonitorStats.js"; +import Notification from "../db/models/Notification.js"; +import RecoveryToken from "../db/models/RecoveryToken.js"; +import AppSettings from "../db/models/AppSettings.js"; +import Incident from "../db/models/Incident.js"; -import InviteModule from "../db/v1/modules/inviteModule.js"; -import CheckModule from "../db/v1/modules/checkModule.js"; -import StatusPageModule from "../db/v1/modules/statusPageModule.js"; -import UserModule from "../db/v1/modules/userModule.js"; -import MaintenanceWindowModule from "../db/v1/modules/maintenanceWindowModule.js"; -import MonitorModule from "../db/v1/modules/monitorModule.js"; -import NotificationModule from "../db/v1/modules/notificationModule.js"; -import RecoveryModule from "../db/v1/modules/recoveryModule.js"; -import SettingsModule from "../db/v1/modules/settingsModule.js"; -import IncidentModule from "../db/v1/modules/incidentModule.js"; +import InviteModule from "../db/modules/inviteModule.js"; +import CheckModule from "../db/modules/checkModule.js"; +import StatusPageModule from "../db/modules/statusPageModule.js"; +import UserModule from "../db/modules/userModule.js"; +import MaintenanceWindowModule from "../db/modules/maintenanceWindowModule.js"; +import MonitorModule from "../db/modules/monitorModule.js"; +import NotificationModule from "../db/modules/notificationModule.js"; +import RecoveryModule from "../db/modules/recoveryModule.js"; +import SettingsModule from "../db/modules/settingsModule.js"; +import IncidentModule from "../db/modules/incidentModule.js"; export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); diff --git a/server/src/controllers/authController.ts b/server/src/controllers/authController.ts new file mode 100755 index 000000000..d033aa39d --- /dev/null +++ b/server/src/controllers/authController.ts @@ -0,0 +1,233 @@ +import { Request, Response, NextFunction } from "express"; +import { AppError } from "@/utils/AppError.js"; + +import { + registrationBodyValidation, + loginValidation, + editUserBodyValidation, + recoveryValidation, + recoveryTokenBodyValidation, + newPasswordValidation, + getUserByIdParamValidation, + editUserByIdParamValidation, + editUserByIdBodyValidation, + editSuperadminUserByIdBodyValidation, + editUserPasswordByIdBodyValidation, +} from "@/validation/joi.js"; + +const SERVICE_NAME = "authController"; + +class AuthController { + static SERVICE_NAME = SERVICE_NAME; + + private userService: any; + + constructor(userService: any) { + this.userService = userService; + } + + get serviceName() { + return AuthController.SERVICE_NAME; + } + + registerUser = async (req: Request, res: Response, next: NextFunction) => { + try { + if (req.body?.email) { + req.body.email = req.body.email?.toLowerCase(); + } + await registrationBodyValidation.validateAsync(req.body); + const { user, token } = await this.userService.registerUser(req.body, req.file); + res.status(200).json({ + success: true, + msg: "User registered successfully", + data: { user, token }, + }); + } catch (error) { + next(error); + } + }; + + loginUser = async (req: Request, res: Response, next: NextFunction) => { + try { + if (req.body?.email) { + req.body.email = req.body.email?.toLowerCase(); + } + await loginValidation.validateAsync(req.body); + const { user, token } = await this.userService.loginUser(req.body.email, req.body.password); + + return res.status(200).json({ + success: true, + msg: "User logged in successfully", + data: { + user, + token, + }, + }); + } catch (error) { + next(error); + } + }; + + editUser = async (req: Request, res: Response, next: NextFunction) => { + try { + await editUserBodyValidation.validateAsync(req.body); + + const updatedUser = await this.userService.editUser(req.body, req.file, req.user); + + res.status(200).json({ + success: true, + msg: "User updated successfully", + data: updatedUser, + }); + } catch (error) { + next(error); + } + }; + + checkSuperadminExists = async (req: Request, res: Response, next: NextFunction) => { + try { + const superAdminExists = await this.userService.checkSuperadminExists(); + return res.status(200).json({ + success: true, + msg: "Superadmin existence checked successfully", + data: superAdminExists, + }); + } catch (error) { + next(error); + } + }; + + requestRecovery = async (req: Request, res: Response, next: NextFunction) => { + try { + await recoveryValidation.validateAsync(req.body); + const email = req?.body?.email; + const msgId = await this.userService.requestRecovery(email); + return res.status(200).json({ + success: true, + msg: "Password recovery email sent successfully", + data: msgId, + }); + } catch (error) { + next(error); + } + }; + + validateRecovery = async (req: Request, res: Response, next: NextFunction) => { + try { + await recoveryTokenBodyValidation.validateAsync(req.body); + await this.userService.validateRecovery(req.body.recoveryToken); + return res.status(200).json({ + success: true, + msg: "Recovery token is valid", + }); + } catch (error) { + next(error); + } + }; + + resetPassword = async (req: Request, res: Response, next: NextFunction) => { + try { + await newPasswordValidation.validateAsync(req.body); + const { user, token } = await this.userService.resetPassword(req.body.password, req.body.recoveryToken); + return res.status(200).json({ + success: true, + msg: "Password has been reset successfully", + data: { user, token }, + }); + } catch (error) { + next(error); + } + }; + + deleteUser = async (req: Request, res: Response, next: NextFunction) => { + try { + await this.userService.deleteUser(req.user); + return res.status(200).json({ + success: true, + msg: "User deleted successfully", + }); + } catch (error) { + next(error); + } + }; + + getAllUsers = async (req: Request, res: Response, next: NextFunction) => { + try { + const allUsers = await this.userService.getAllUsers(); + return res.status(200).json({ + success: true, + msg: "Users retrieved successfully", + data: allUsers, + }); + } catch (error) { + next(error); + } + }; + + getUserById = async (req: Request, res: Response, next: NextFunction) => { + try { + await getUserByIdParamValidation.validateAsync(req.params); + const userId = req?.params?.userId; + const roles = req?.user?.role; + + if (!userId) { + throw new Error("No user ID in request"); + } + + if (!roles || roles.length === 0) { + throw new Error("No roles in request"); + } + + const user = await this.userService.getUserById(roles, userId); + + return res.status(200).json({ success: true, msg: "ok", data: user }); + } catch (error) { + next(error); + } + }; + + editUserById = async (req: Request, res: Response, next: NextFunction) => { + try { + const roles = req?.user?.role; + if (!roles.includes("superadmin")) { + throw new AppError({ message: "Unauthorized", status: 403 }); + } + + const userId = req.params.userId; + const user = { ...req.body }; + + await editUserByIdParamValidation.validateAsync(req.params); + // If this is superadmin self edit, allow "superadmin" role + if (userId === req.user._id) { + await editSuperadminUserByIdBodyValidation.validateAsync(req.body); + } else { + await editUserByIdBodyValidation.validateAsync(req.body); + } + + await this.userService.editUserById(userId, user); + return res.status(200).json({ success: true, msg: "ok" }); + } catch (error) { + next(error); + } + }; + + editUserPasswordById = async (req: Request, res: Response, next: NextFunction) => { + try { + const roles = req?.user?.role; + if (!roles.includes("superadmin")) { + throw new AppError({ message: "Unauthorized", status: 403 }); + } + + const userId = req.params.userId; + await editUserByIdParamValidation.validateAsync(req.params); + await editUserPasswordByIdBodyValidation.validateAsync(req.body); + const updatedPassword = req.body.password; + await this.userService.setPasswordByUserId(userId, updatedPassword); + return res.status(200).json({ success: true, msg: "Password reset successfully" }); + } catch (error) { + next(error); + } + }; +} + +export default AuthController; diff --git a/server/src/controllers/v1/checkController.ts b/server/src/controllers/checkController.ts similarity index 100% rename from server/src/controllers/v1/checkController.ts rename to server/src/controllers/checkController.ts diff --git a/server/src/controllers/v1/controllerUtils.ts b/server/src/controllers/controllerUtils.ts similarity index 100% rename from server/src/controllers/v1/controllerUtils.ts rename to server/src/controllers/controllerUtils.ts diff --git a/server/src/controllers/v1/diagnosticController.ts b/server/src/controllers/diagnosticController.ts similarity index 100% rename from server/src/controllers/v1/diagnosticController.ts rename to server/src/controllers/diagnosticController.ts diff --git a/server/src/controllers/v1/incidentController.ts b/server/src/controllers/incidentController.ts similarity index 100% rename from server/src/controllers/v1/incidentController.ts rename to server/src/controllers/incidentController.ts diff --git a/server/src/controllers/v1/inviteController.ts b/server/src/controllers/inviteController.ts similarity index 100% rename from server/src/controllers/v1/inviteController.ts rename to server/src/controllers/inviteController.ts diff --git a/server/src/controllers/v1/logController.ts b/server/src/controllers/logController.ts similarity index 100% rename from server/src/controllers/v1/logController.ts rename to server/src/controllers/logController.ts diff --git a/server/src/controllers/v1/maintenanceWindowController.ts b/server/src/controllers/maintenanceWindowController.ts similarity index 100% rename from server/src/controllers/v1/maintenanceWindowController.ts rename to server/src/controllers/maintenanceWindowController.ts diff --git a/server/src/controllers/v1/monitorController.ts b/server/src/controllers/monitorController.ts similarity index 99% rename from server/src/controllers/v1/monitorController.ts rename to server/src/controllers/monitorController.ts index aac59cd27..425979062 100644 --- a/server/src/controllers/v1/monitorController.ts +++ b/server/src/controllers/monitorController.ts @@ -13,7 +13,7 @@ import { getCertificateParamValidation, getHardwareDetailsByIdParamValidation, getHardwareDetailsByIdQueryValidation, -} from "../../validation/joi.js"; +} from "@/validation/joi.js"; import sslChecker from "ssl-checker"; import { fetchMonitorCertificate } from "./controllerUtils.js"; import { AppError } from "@/utils/AppError.js"; diff --git a/server/src/controllers/v1/notificationController.ts b/server/src/controllers/notificationController.ts similarity index 100% rename from server/src/controllers/v1/notificationController.ts rename to server/src/controllers/notificationController.ts diff --git a/server/src/controllers/v1/queueController.ts b/server/src/controllers/queueController.ts similarity index 100% rename from server/src/controllers/v1/queueController.ts rename to server/src/controllers/queueController.ts diff --git a/server/src/controllers/v1/settingsController.ts b/server/src/controllers/settingsController.ts similarity index 100% rename from server/src/controllers/v1/settingsController.ts rename to server/src/controllers/settingsController.ts diff --git a/server/src/controllers/v1/statusPageController.ts b/server/src/controllers/statusPageController.ts similarity index 100% rename from server/src/controllers/v1/statusPageController.ts rename to server/src/controllers/statusPageController.ts diff --git a/server/src/controllers/v1/announcementsController.js b/server/src/controllers/v1/announcementsController.js deleted file mode 100755 index 0425b44aa..000000000 --- a/server/src/controllers/v1/announcementsController.js +++ /dev/null @@ -1,77 +0,0 @@ -import { createAnnouncementValidation } from "../../validation/joi.js"; -import BaseController from "./baseController.js"; - -const SERVICE_NAME = "announcementController"; - -/** - * Controller for managing announcements in the system. - * This class handles the creation of new announcements. - * - * @class AnnouncementController - */ - -class AnnouncementController extends BaseController { - static SERVICE_NAME = SERVICE_NAME; - constructor(commonDependencies) { - super(commonDependencies); - this.createAnnouncement = this.createAnnouncement.bind(this); - this.getAnnouncement = this.getAnnouncement.bind(this); - } - - get serviceName() { - return AnnouncementController.SERVICE_NAME; - } - - /** - * Handles the creation of a new announcement. - * - * @async - * @param {Object} req - The request object, containing the announcement data in the body. - * @param {Object} res - The response object used to send the result back to the client. - * @param {Function} next - The next middleware function in the stack for error handling. - * - * @returns {Promise} A promise that resolves once the response is sent. - */ - createAnnouncement = asyncHandler( - async (req, res, next) => { - await createAnnouncementValidation.validateAsync(req.body); - const { title, message } = req.body; - const announcementData = { - title: title.trim(), - message: message.trim(), - userId: req.user._id, - }; - - const newAnnouncement = await this.db.createAnnouncement(announcementData); - return res.success({ - msg: this.stringService.createAnnouncement, - data: newAnnouncement, - }); - }, - SERVICE_NAME, - "createAnnouncement" - ); - - /** - * Handles retrieving announcements with pagination. - * - * @async - * @param {Object} res - The response object used to send the result back to the client. - * - `data`: The list of announcements to be sent back to the client. - * - `msg`: A message about the success of the request. - * @param {Function} next - The next middleware function in the stack for error handling. - */ - getAnnouncement = asyncHandler( - async (req, res, next) => { - const allAnnouncements = await this.db.getAnnouncements(); - return res.success({ - msg: this.stringService.getAnnouncement, - data: allAnnouncements, - }); - }, - SERVICE_NAME, - "getAnnouncement" - ); -} - -export default AnnouncementController; diff --git a/server/src/controllers/v1/authController.js b/server/src/controllers/v1/authController.js deleted file mode 100755 index e52de3849..000000000 --- a/server/src/controllers/v1/authController.js +++ /dev/null @@ -1,478 +0,0 @@ -import BaseController from "./baseController.js"; -import { - registrationBodyValidation, - loginValidation, - editUserBodyValidation, - recoveryValidation, - recoveryTokenBodyValidation, - newPasswordValidation, - getUserByIdParamValidation, - editUserByIdParamValidation, - editUserByIdBodyValidation, - editSuperadminUserByIdBodyValidation, - editUserPasswordByIdBodyValidation, -} from "../../validation/joi.js"; - -const SERVICE_NAME = "authController"; -/** - * Authentication Controller - * - * Handles all authentication-related HTTP requests including user registration, - * login, password recovery, and user management operations. - * - * @class AuthController - * @description Manages user authentication and authorization operations - */ -class AuthController extends BaseController { - static SERVICE_NAME = SERVICE_NAME; - /** - * Creates an instance of AuthController. - * - * @param {Object} commonDependencies - Common dependencies injected into the controller - * @param {Object} dependencies - The dependencies required by the controller - * @param {Object} dependencies.settingsService - Service for application settings - * @param {Object} dependencies.emailService - Service for email operations - * @param {Object} dependencies.jobQueue - Service for job queue operations - * @param {Object} dependencies.userService - User business logic service - */ - constructor(commonDependencies, { settingsService, emailService, jobQueue, userService }) { - super(commonDependencies); - this.settingsService = settingsService; - this.emailService = emailService; - this.jobQueue = jobQueue; - this.userService = userService; - } - - get serviceName() { - return AuthController.SERVICE_NAME; - } - - /** - * Registers a new user in the system. - * - * @async - * @function registerUser - * @param {Object} req - Express request object - * @param {Object} req.body - Request body containing user registration data - * @param {string} req.body.firstName - User's first name - * @param {string} req.body.lastName - User's last name - * @param {string} req.body.email - User's email address (will be converted to lowercase) - * @param {string} req.body.password - User's password - * @param {string} [req.body.inviteToken] - Invite token for registration (required if superadmin exists) - * @param {string} [req.body.teamId] - Team ID (auto-assigned if superadmin) - * @param {Array} [req.body.role] - User roles (auto-assigned if superadmin) - * @param {Object} [req.file] - Profile image file uploaded via multer - * @param {Object} res - Express response object - * @returns {Promise} Success response with user data and JWT token - * @throws {Error} 422 - Validation error if request body is invalid - * @throws {Error} 409 - Conflict if user already exists - * @example - * // Register first user (becomes superadmin) - * POST /auth/register - * { - * "firstName": "John", - * "lastName": "Doe", - * "email": "john@example.com", - * "password": "SecurePass123!" - * } - * - * // Register subsequent user (requires invite token) - * POST /auth/register - * { - * "firstName": "Jane", - * "lastName": "Smith", - * "email": "jane@example.com", - * "password": "SecurePass123!", - * "inviteToken": "abc123..." - * } - */ - registerUser = this.asyncHandler( - async (req, res) => { - if (req.body?.email) { - req.body.email = req.body.email?.toLowerCase(); - } - await registrationBodyValidation.validateAsync(req.body); - const { user, token } = await this.userService.registerUser(req.body, req.file); - res.success({ - msg: this.stringService.authCreateUser, - data: { user, token }, - }); - }, - SERVICE_NAME, - "registerUser" - ); - - /** - * Authenticates a user and returns a JWT token. - * - * @async - * @function loginUser - * @param {Object} req - Express request object - * @param {Object} req.body - Request body containing login credentials - * @param {string} req.body.email - User's email address (will be converted to lowercase) - * @param {string} req.body.password - User's password - * @param {Object} res - Express response object - * @returns {Promise} Success response with user data and JWT token - * @throws {Error} 422 - Validation error if request body is invalid - * @throws {Error} 401 - Unauthorized if credentials are incorrect - * @example - * POST /auth/login - * { - * "email": "john@example.com", - * "password": "SecurePass123!" - * } - */ - loginUser = this.asyncHandler( - async (req, res) => { - if (req.body?.email) { - req.body.email = req.body.email?.toLowerCase(); - } - await loginValidation.validateAsync(req.body); - const { user, token } = await this.userService.loginUser(req.body.email, req.body.password); - - return res.success({ - msg: this.stringService.authLoginUser, - data: { - user, - token, - }, - }); - }, - SERVICE_NAME, - "loginUser" - ); - - /** - * Updates the current user's profile information. - * - * @async - * @function editUser - * @param {Object} req - Express request object - * @param {Object} req.body - Request body containing user update data - * @param {string} [req.body.firstName] - Updated first name - * @param {string} [req.body.lastName] - Updated last name - * @param {string} [req.body.password] - Current password (required for password change) - * @param {string} [req.body.newPassword] - New password (required for password change) - * @param {boolean} [req.body.deleteProfileImage] - Flag to delete profile image - * @param {Object} [req.file] - New profile image file - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {Object} res - Express response object - * @returns {Promise} Success response with updated user data - * @throws {Error} 422 - Validation error if request body is invalid - * @throws {Error} 403 - Forbidden if current password is incorrect - * @example - * PUT /auth/user - * { - * "firstName": "John Updated", - * "lastName": "Doe Updated" - * } - * - * // Change password - * PUT /auth/user - * { - * "password": "OldPass123!", - * "newPassword": "NewPass123!" - * } - */ - editUser = this.asyncHandler( - async (req, res) => { - await editUserBodyValidation.validateAsync(req.body); - - const updatedUser = await this.userService.editUser(req.body, req.file, req.user); - - res.success({ - msg: this.stringService.authUpdateUser, - data: updatedUser, - }); - }, - SERVICE_NAME, - "editUser" - ); - - /** - * Checks if a superadmin account exists in the system. - * - * @async - * @function checkSuperadminExists - * @param {Object} req - Express request object - * @param {Object} res - Express response object - * @returns {Promise} Success response with boolean indicating superadmin existence - * @example - * GET /auth/users/superadmin - * // Response: { "data": true } or { "data": false } - */ - checkSuperadminExists = this.asyncHandler( - async (req, res) => { - const superAdminExists = await this.userService.checkSuperadminExists(); - return res.success({ - msg: this.stringService.authAdminExists, - data: superAdminExists, - }); - }, - SERVICE_NAME, - "checkSuperadminExists" - ); - - /** - * Initiates password recovery process by sending a recovery email. - * - * @async - * @function requestRecovery - * @param {Object} req - Express request object - * @param {Object} req.body - Request body containing email - * @param {string} req.body.email - Email address for password recovery - * @param {Object} res - Express response object - * @returns {Promise} Success response with message ID - * @throws {Error} 422 - Validation error if email is invalid - * @throws {Error} 404 - Not found if user doesn't exist - * @example - * POST /auth/recovery/request - * { - * "email": "john@example.com" - * } - */ - requestRecovery = this.asyncHandler( - async (req, res) => { - await recoveryValidation.validateAsync(req.body); - const email = req?.body?.email; - const msgId = await this.userService.requestRecovery(email); - return res.success({ - msg: this.stringService.authCreateRecoveryToken, - data: msgId, - }); - }, - SERVICE_NAME, - "requestRecovery" - ); - - /** - * Validates a password recovery token. - * - * @async - * @function validateRecovery - * @param {Object} req - Express request object - * @param {Object} req.body - Request body containing recovery token - * @param {string} req.body.recoveryToken - Recovery token to validate - * @param {Object} res - Express response object - * @returns {Promise} Success response if token is valid - * @throws {Error} 422 - Validation error if token format is invalid - * @throws {Error} 400 - Bad request if token is invalid or expired - * @example - * POST /auth/recovery/validate - * { - * "recoveryToken": "abc123..." - * } - */ - validateRecovery = this.asyncHandler( - async (req, res) => { - await recoveryTokenBodyValidation.validateAsync(req.body); - await this.userService.validateRecovery(req.body.recoveryToken); - return res.success({ - msg: this.stringService.authVerifyRecoveryToken, - }); - }, - SERVICE_NAME, - "validateRecovery" - ); - - /** - * Resets user password using a valid recovery token. - * - * @async - * @function resetPassword - * @param {Object} req - Express request object - * @param {Object} req.body - Request body containing new password and recovery token - * @param {string} req.body.password - New password - * @param {string} req.body.recoveryToken - Valid recovery token - * @param {Object} res - Express response object - * @returns {Promise} Success response with user data and JWT token - * @throws {Error} 422 - Validation error if password format is invalid - * @throws {Error} 400 - Bad request if token is invalid or expired - * @example - * POST /auth/recovery/reset - * { - * "password": "NewSecurePass123!", - * "recoveryToken": "abc123..." - * } - */ - resetPassword = this.asyncHandler( - async (req, res) => { - await newPasswordValidation.validateAsync(req.body); - const { user, token } = await this.userService.resetPassword(req.body.password, req.body.recoveryToken); - return res.success({ - msg: this.stringService.authResetPassword, - data: { user, token }, - }); - }, - SERVICE_NAME, - "resetPassword" - ); - - /** - * Deletes the current user's account and associated data. - * - * @async - * @function deleteUser - * @param {Object} req - Express request object - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {string} req.user._id - User ID - * @param {string} req.user.email - User email - * @param {string} req.user.teamId - User's team ID - * @param {Array} req.user.role - User roles - * @param {Object} res - Express response object - * @returns {Promise} Success response confirming user deletion - * @throws {Error} 400 - Bad request if user is demo user - * @throws {Error} 404 - Not found if user doesn't exist - * @example - * DELETE /auth/user - * // Requires JWT authentication - */ - deleteUser = this.asyncHandler( - async (req, res) => { - await this.userService.deleteUser(req.user); - return res.success({ - msg: this.stringService.authDeleteUser, - }); - }, - SERVICE_NAME, - "deleteUser" - ); - - /** - * Retrieves all users in the system (admin/superadmin only). - * - * @async - * @function getAllUsers - * @param {Object} req - Express request object - * @param {Object} res - Express response object - * @returns {Promise} Success response with array of users - * @throws {Error} 403 - Forbidden if user doesn't have admin/superadmin role - * @example - * GET /auth/users - * // Requires JWT authentication with admin/superadmin role - */ - getAllUsers = this.asyncHandler( - async (req, res) => { - const allUsers = await this.userService.getAllUsers(); - return res.success({ - msg: this.stringService.authGetAllUsers, - data: allUsers, - }); - }, - SERVICE_NAME, - "getAllUsers" - ); - - /** - * Retrieves a specific user by ID (superadmin only). - * - * @async - * @function getUserById - * @param {Object} req - Express request object - * @param {Object} req.params - URL parameters - * @param {string} req.params.userId - ID of the user to retrieve - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {Array} req.user.role - Current user's roles - * @param {Object} res - Express response object - * @returns {Promise} Success response with user data - * @throws {Error} 422 - Validation error if userId is invalid - * @throws {Error} 403 - Forbidden if user doesn't have superadmin role - * @throws {Error} 404 - Not found if user doesn't exist - * @example - * GET /auth/users/507f1f77bcf86cd799439011 - * // Requires JWT authentication with superadmin role - */ - getUserById = this.asyncHandler( - async (req, res) => { - await getUserByIdParamValidation.validateAsync(req.params); - const userId = req?.params?.userId; - const roles = req?.user?.role; - - if (!userId) { - throw new Error("No user ID in request"); - } - - if (!roles || roles.length === 0) { - throw new Error("No roles in request"); - } - - const user = await this.userService.getUserById(roles, userId); - - return res.success({ msg: "ok", data: user }); - }, - SERVICE_NAME, - "getUserById" - ); - - /** - * Updates a specific user by ID (superadmin only). - * - * @async - * @function editUserById - * @param {Object} req - Express request object - * @param {Object} req.params - URL parameters - * @param {string} req.params.userId - ID of the user to update - * @param {Object} req.body - Request body containing user update data - * @param {string} [req.body.firstName] - Updated first name - * @param {string} [req.body.lastName] - Updated last name - * @param {Array} [req.body.role] - Updated user roles - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {string} req.user._id - Current user's ID - * @param {Array} req.user.role - Current user's roles - * @param {Object} res - Express response object - * @returns {Promise} Success response confirming user update - * @throws {Error} 422 - Validation error if parameters or body are invalid - * @throws {Error} 403 - Forbidden if user doesn't have superadmin role - * @throws {Error} 404 - Not found if user doesn't exist - * @example - * PUT /auth/users/507f1f77bcf86cd799439011 - * { - * "firstName": "Updated Name", - * "role": ["admin"] - * } - * // Requires JWT authentication with superadmin role - */ - editUserById = this.asyncHandler( - async (req, res) => { - const roles = req?.user?.role; - if (!roles.includes("superadmin")) { - throw this.errorService.createError("Unauthorized", 403); - } - - const userId = req.params.userId; - const user = { ...req.body }; - - await editUserByIdParamValidation.validateAsync(req.params); - // If this is superadmin self edit, allow "superadmin" role - if (userId === req.user._id) { - await editSuperadminUserByIdBodyValidation.validateAsync(req.body); - } else { - await editUserByIdBodyValidation.validateAsync(req.body); - } - - await this.userService.editUserById(userId, user); - return res.success({ msg: "ok" }); - }, - SERVICE_NAME, - "editUserById" - ); - editUserPasswordById = this.asyncHandler( - async (req, res) => { - const roles = req?.user?.role; - if (!roles.includes("superadmin")) { - throw this.errorService.createError("Unauthorized", 403); - } - - const userId = req.params.userId; - await editUserByIdParamValidation.validateAsync(req.params); - await editUserPasswordByIdBodyValidation.validateAsync(req.body); - const updatedPassword = req.body.password; - await this.userService.setPasswordByUserId(userId, updatedPassword); - return res.success({ msg: "Password reset successfully" }); - }, - SERVICE_NAME, - "editUserPasswordById" - ); -} - -export default AuthController; diff --git a/server/src/controllers/v1/baseController.js b/server/src/controllers/v1/baseController.js deleted file mode 100644 index e8612cec5..000000000 --- a/server/src/controllers/v1/baseController.js +++ /dev/null @@ -1,83 +0,0 @@ -import { AppError } from "../../service/v1/infrastructure/errorService.js"; - -export const createCommonDependencies = (db, errorService, logger, stringService) => { - return { - db, - errorService, - logger, - stringService, - }; -}; - -class BaseController { - constructor({ db, logger, errorService, ...additionalDependencies }) { - this.db = db; - this.logger = logger; - this.errorService = errorService; - Object.assign(this, additionalDependencies); - - this.asyncHandler = (fn, serviceName, methodName) => { - return async (req, res, next) => { - try { - await fn(req, res, next); - } catch (error) { - // Handle validation errors - if (error.isJoi) { - const validationError = this.errorService.createValidationError(error.message, error.details, serviceName, methodName); - return next(validationError); - } - - if (error.name === "ValidationError") { - const validationError = this.errorService.createValidationError("Database validation failed", error.errors, serviceName, methodName); - return next(validationError); - } - - if (error.name === "CastError") { - const notFoundError = this.errorService.createNotFoundError( - "Invalid resource identifier", - { field: error.path, value: error.value }, - serviceName, - methodName - ); - return next(notFoundError); - } - - if (error.code === "11000") { - const conflictError = this.errorService.createConflictError("Resource already exists", { - originalError: error.message, - code: error.code, - }); - conflictError.service = serviceName; - conflictError.method = methodName; - return next(conflictError); - } - - if (error instanceof AppError) { - error.service = error.service || serviceName; - error.method = error.method || methodName; - return next(error); - } - - if (error.status) { - const appError = this.errorService.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 = this.errorService.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); - } - }; - }; - } -} -export default BaseController; diff --git a/server/src/db/v1/MongoDB.js b/server/src/db/MongoDB.js similarity index 100% rename from server/src/db/v1/MongoDB.js rename to server/src/db/MongoDB.js diff --git a/server/src/db/migration/0001_migrateStatusWindowThreshold.ts b/server/src/db/migration/0001_migrateStatusWindowThreshold.ts new file mode 100644 index 000000000..d9dd12aa1 --- /dev/null +++ b/server/src/db/migration/0001_migrateStatusWindowThreshold.ts @@ -0,0 +1,10 @@ +import Monitor from "@/db/models/Monitor.js"; +async function migrateStatusWindowThreshold() { + const monitors = await Monitor.find({ statusWindowThreshold: { $lt: 1 } }); + for (const monitor of monitors) { + monitor.statusWindowThreshold = monitor.statusWindowThreshold * 100; + await monitor.save(); + } +} + +export { migrateStatusWindowThreshold }; diff --git a/server/src/db/migration/0002_convertChecksToTimeSeries.ts b/server/src/db/migration/0002_convertChecksToTimeSeries.ts new file mode 100644 index 000000000..46e6fbc97 --- /dev/null +++ b/server/src/db/migration/0002_convertChecksToTimeSeries.ts @@ -0,0 +1,93 @@ +import mongoose from "mongoose"; + +const CHECKS_COLLECTION = "checks"; +const BACKUP_COLLECTION = "checks_backup"; +const BATCH_SIZE = 1000; + +const getDb = () => { + const db = mongoose.connection.db; + if (!db) { + throw new Error("Database connection is not initialized"); + } + return db; +}; + +const backupAndDropExistingCollection = async () => { + const db = getDb(); + const collections = await db.listCollections({ name: CHECKS_COLLECTION }).toArray(); + if (collections.length === 0) { + return false; + } + + await db.collection(CHECKS_COLLECTION).aggregate([ + { $match: {} }, + { $out: BACKUP_COLLECTION }, + ]).toArray(); + await db.collection(CHECKS_COLLECTION).drop(); + return true; +}; + +const createTimeSeriesCollection = async () => { + const db = getDb(); + const existing = await db.listCollections({ name: CHECKS_COLLECTION }).toArray(); + if (existing.length === 0) { + await db.createCollection(CHECKS_COLLECTION, { + timeseries: { + timeField: "createdAt", + metaField: "metadata", + granularity: "seconds", + }, + }); + } + + await db.collection(CHECKS_COLLECTION).createIndexes([ + { key: { updatedAt: 1 } }, + { key: { "metadata.monitorId": 1, updatedAt: 1 } }, + { key: { "metadata.monitorId": 1, updatedAt: -1 } }, + { key: { "metadata.teamId": 1, updatedAt: -1 } }, + ]); +}; + +const migrateBackupData = async () => { + const db = getDb(); + const backupExists = await db.listCollections({ name: BACKUP_COLLECTION }).toArray(); + if (backupExists.length === 0) { + return; + } + + const source = db.collection(BACKUP_COLLECTION); + const target = db.collection(CHECKS_COLLECTION); + const cursor = source.find(); + const operations: any[] = []; + + while (await cursor.hasNext()) { + const doc = await cursor.next(); + if (!doc) { + continue; + } + const metadata = { + monitorId: doc.monitorId, + teamId: doc.teamId, + type: doc.type, + }; + + operations.push({ insertOne: { document: { ...doc, metadata } } }); + + if (operations.length >= BATCH_SIZE) { + await target.bulkWrite(operations); + operations.length = 0; + } + } + + if (operations.length) { + await target.bulkWrite(operations); + } +}; + +export const convertChecksToTimeSeries = async () => { + const backedUp = await backupAndDropExistingCollection(); + await createTimeSeriesCollection(); + if (backedUp) { + await migrateBackupData(); + } +}; diff --git a/server/src/db/migration/index.ts b/server/src/db/migration/index.ts new file mode 100644 index 000000000..de2a1dee7 --- /dev/null +++ b/server/src/db/migration/index.ts @@ -0,0 +1,51 @@ +import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshold.js"; +import { convertChecksToTimeSeries } from "./0002_convertChecksToTimeSeries.js"; +import MigrationModel from "../models/Migration.js"; + +type MigrationEntry = { + name: string; + execute: () => Promise; +}; + +const migrations: MigrationEntry[] = [ + { name: "0001_migrateStatusWindowThreshold", execute: migrateStatusWindowThreshold }, + { name: "0002_convertChecksToTimeSeries", execute: convertChecksToTimeSeries }, +]; + +const runMigrations = async (logger?: { info: Function; error: Function }) => { + try { + logger?.info({ message: "Running migrations", service: "Migrations" }); + for (const migration of migrations) { + const exists = await MigrationModel.findOne({ name: migration.name, status: "completed" }); + if (exists) { + logger?.info({ message: `Skipping ${migration.name}`, service: "Migrations" }); + continue; + } + + try { + await migration.execute(); + await MigrationModel.findOneAndUpdate( + { name: migration.name }, + { status: "completed", completedAt: new Date(), error: undefined }, + { upsert: true } + ); + logger?.info({ message: `Completed ${migration.name}`, service: "Migrations" }); + } catch (error) { + const err = error as Error; + await MigrationModel.findOneAndUpdate( + { name: migration.name }, + { status: "failed", error: err?.message }, + { upsert: true } + ); + throw error; + } + } + logger?.info({ message: "Migrations completed", service: "Migrations" }); + } catch (error) { + const err = error as Error; + logger?.error({ message: "Migration failed", service: "Migrations", details: err?.message, stack: err?.stack }); + throw error; + } +}; + +export { runMigrations }; diff --git a/server/src/db/v1/models/AppSettings.js b/server/src/db/models/AppSettings.js similarity index 100% rename from server/src/db/v1/models/AppSettings.js rename to server/src/db/models/AppSettings.js diff --git a/server/src/db/models/Check.ts b/server/src/db/models/Check.ts new file mode 100755 index 000000000..b13aa9a16 --- /dev/null +++ b/server/src/db/models/Check.ts @@ -0,0 +1,214 @@ +import mongoose from "mongoose"; +import { MonitorTypes } from "@/types/monitor.js"; + +const cpuSchema = new mongoose.Schema( + { + physical_core: { type: Number, default: 0 }, + logical_core: { type: Number, default: 0 }, + frequency: { type: Number, default: 0 }, + temperature: { type: [Number], default: [] }, + free_percent: { type: Number, default: 0 }, + usage_percent: { type: Number, default: 0 }, + }, + { _id: false } +); + +const memorySchema = new mongoose.Schema( + { + total_bytes: { type: Number, default: 0 }, + available_bytes: { type: Number, default: 0 }, + used_bytes: { type: Number, default: 0 }, + usage_percent: { type: Number, default: 0 }, + }, + { _id: false } +); + +const diskSchema = new mongoose.Schema( + { + device: { type: String, default: "" }, + mountpoint: { type: String, default: "" }, + read_speed_bytes: { type: Number, default: 0 }, + write_speed_bytes: { type: Number, default: 0 }, + total_bytes: { type: Number, default: 0 }, + free_bytes: { type: Number, default: 0 }, + usage_percent: { type: Number, default: 0 }, + }, + { _id: false } +); + +const hostSchema = new mongoose.Schema( + { + os: { type: String, default: "" }, + platform: { type: String, default: "" }, + kernel_version: { type: String, default: "" }, + }, + { _id: false } +); + +const errorSchema = new mongoose.Schema( + { + metric: { type: [String], default: [] }, + err: { type: String, default: "" }, + }, + { _id: false } +); + +const captureSchema = new mongoose.Schema( + { + version: { type: String, default: "" }, + mode: { type: String, default: "" }, + }, + { _id: false } +); + +const networkInterfaceSchema = new mongoose.Schema( + { + name: { type: String }, + bytes_sent: { type: Number, default: 0 }, + bytes_recv: { type: Number, default: 0 }, + packets_sent: { type: Number, default: 0 }, + packets_recv: { type: Number, default: 0 }, + err_in: { type: Number, default: 0 }, + err_out: { type: Number, default: 0 }, + drop_in: { type: Number, default: 0 }, + drop_out: { type: Number, default: 0 }, + fifo_in: { type: Number, default: 0 }, + fifo_out: { type: Number, default: 0 }, + }, + { _id: false } +); + +const metadataSchema = new mongoose.Schema( + { + monitorId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Monitor", + immutable: true, + index: true, + }, + teamId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Team", + immutable: true, + index: true, + }, + type: { + type: String, + enum: MonitorTypes, + required: true, + index: true, + }, + }, + { _id: false } +); + +const CheckSchema = new mongoose.Schema( + { + metadata: { + type: metadataSchema, + required: true, + }, + status: { + type: Boolean, + index: true, + }, + + responseTime: { + type: Number, + }, + + timings: { + type: Object, + default: {}, + }, + + statusCode: { + type: Number, + index: true, + }, + + message: { + type: String, + }, + + expiry: { + type: Date, + default: Date.now, + }, + + ack: { + type: Boolean, + default: false, + }, + + ackAt: { + type: Date, + }, + + // Hardware fields + cpu: { + type: cpuSchema, + default: () => ({}), + }, + memory: { + type: memorySchema, + default: () => ({}), + }, + disk: { + type: [diskSchema], + default: () => [], + }, + host: { + type: hostSchema, + default: () => ({}), + }, + + errors: { + type: [errorSchema], + default: () => [], + }, + + capture: { + type: captureSchema, + default: () => ({}), + }, + + net: { + type: [networkInterfaceSchema], + default: () => [], + }, + + // PageSpeed fields + accessibility: { + type: Number, + }, + bestPractices: { + type: Number, + }, + seo: { + type: Number, + }, + performance: { + type: Number, + }, + audits: { + type: Object, + }, + }, + { + timestamps: true, + strict: false, + timeseries: { + timeField: "createdAt", + metaField: "metadata", + granularity: "seconds", + }, + } +); + +CheckSchema.index({ updatedAt: 1 }); +CheckSchema.index({ "metadata.monitorId": 1, updatedAt: 1 }); +CheckSchema.index({ "metadata.monitorId": 1, updatedAt: -1 }); +CheckSchema.index({ "metadata.teamId": 1, updatedAt: -1 }); + +export default mongoose.model("Check", CheckSchema); diff --git a/server/src/db/v1/models/Incident.js b/server/src/db/models/Incident.js similarity index 100% rename from server/src/db/v1/models/Incident.js rename to server/src/db/models/Incident.js diff --git a/server/src/db/v1/models/InviteToken.js b/server/src/db/models/InviteToken.js similarity index 100% rename from server/src/db/v1/models/InviteToken.js rename to server/src/db/models/InviteToken.js diff --git a/server/src/db/v1/models/MaintenanceWindow.js b/server/src/db/models/MaintenanceWindow.js similarity index 100% rename from server/src/db/v1/models/MaintenanceWindow.js rename to server/src/db/models/MaintenanceWindow.js diff --git a/server/src/db/models/Migration.ts b/server/src/db/models/Migration.ts new file mode 100644 index 000000000..ba928d910 --- /dev/null +++ b/server/src/db/models/Migration.ts @@ -0,0 +1,13 @@ +import mongoose from "mongoose"; + +const MigrationSchema = new mongoose.Schema( + { + name: { type: String, required: true, unique: true }, + status: { type: String, enum: ["completed", "failed"], default: "completed" }, + completedAt: { type: Date }, + error: { type: String }, + }, + { timestamps: true } +); + +export default mongoose.model("Migration", MigrationSchema); diff --git a/server/src/db/v1/models/Monitor.js b/server/src/db/models/Monitor.js similarity index 100% rename from server/src/db/v1/models/Monitor.js rename to server/src/db/models/Monitor.js diff --git a/server/src/db/v1/models/MonitorStats.js b/server/src/db/models/MonitorStats.js similarity index 100% rename from server/src/db/v1/models/MonitorStats.js rename to server/src/db/models/MonitorStats.js diff --git a/server/src/db/v1/models/Notification.js b/server/src/db/models/Notification.js similarity index 100% rename from server/src/db/v1/models/Notification.js rename to server/src/db/models/Notification.js diff --git a/server/src/db/v1/models/RecoveryToken.js b/server/src/db/models/RecoveryToken.js similarity index 100% rename from server/src/db/v1/models/RecoveryToken.js rename to server/src/db/models/RecoveryToken.js diff --git a/server/src/db/v1/models/StatusPage.js b/server/src/db/models/StatusPage.js similarity index 100% rename from server/src/db/v1/models/StatusPage.js rename to server/src/db/models/StatusPage.js diff --git a/server/src/db/v1/models/Team.js b/server/src/db/models/Team.js similarity index 100% rename from server/src/db/v1/models/Team.js rename to server/src/db/models/Team.js diff --git a/server/src/db/v1/models/User.js b/server/src/db/models/User.js similarity index 100% rename from server/src/db/v1/models/User.js rename to server/src/db/models/User.js diff --git a/server/src/db/v1/modules/checkModule.js b/server/src/db/modules/checkModule.js similarity index 100% rename from server/src/db/v1/modules/checkModule.js rename to server/src/db/modules/checkModule.js diff --git a/server/src/db/v1/modules/checkModuleQueries.js b/server/src/db/modules/checkModuleQueries.js similarity index 100% rename from server/src/db/v1/modules/checkModuleQueries.js rename to server/src/db/modules/checkModuleQueries.js diff --git a/server/src/db/v1/modules/incidentModule.js b/server/src/db/modules/incidentModule.js similarity index 100% rename from server/src/db/v1/modules/incidentModule.js rename to server/src/db/modules/incidentModule.js diff --git a/server/src/db/v1/modules/inviteModule.js b/server/src/db/modules/inviteModule.js similarity index 100% rename from server/src/db/v1/modules/inviteModule.js rename to server/src/db/modules/inviteModule.js diff --git a/server/src/db/v1/modules/maintenanceWindowModule.js b/server/src/db/modules/maintenanceWindowModule.js similarity index 100% rename from server/src/db/v1/modules/maintenanceWindowModule.js rename to server/src/db/modules/maintenanceWindowModule.js diff --git a/server/src/db/v1/modules/monitorModule.js b/server/src/db/modules/monitorModule.js similarity index 100% rename from server/src/db/v1/modules/monitorModule.js rename to server/src/db/modules/monitorModule.js diff --git a/server/src/db/v1/modules/monitorModuleQueries.js b/server/src/db/modules/monitorModuleQueries.js similarity index 100% rename from server/src/db/v1/modules/monitorModuleQueries.js rename to server/src/db/modules/monitorModuleQueries.js diff --git a/server/src/db/v1/modules/notificationModule.js b/server/src/db/modules/notificationModule.js similarity index 100% rename from server/src/db/v1/modules/notificationModule.js rename to server/src/db/modules/notificationModule.js diff --git a/server/src/db/v1/modules/recoveryModule.js b/server/src/db/modules/recoveryModule.js similarity index 100% rename from server/src/db/v1/modules/recoveryModule.js rename to server/src/db/modules/recoveryModule.js diff --git a/server/src/db/v1/modules/settingsModule.js b/server/src/db/modules/settingsModule.js similarity index 100% rename from server/src/db/v1/modules/settingsModule.js rename to server/src/db/modules/settingsModule.js diff --git a/server/src/db/v1/modules/statusPageModule.js b/server/src/db/modules/statusPageModule.js similarity index 100% rename from server/src/db/v1/modules/statusPageModule.js rename to server/src/db/modules/statusPageModule.js diff --git a/server/src/db/v1/modules/userModule.js b/server/src/db/modules/userModule.js similarity index 100% rename from server/src/db/v1/modules/userModule.js rename to server/src/db/modules/userModule.js diff --git a/server/src/db/v1/utils/seedDb.js b/server/src/db/utils/seedDb.js similarity index 100% rename from server/src/db/v1/utils/seedDb.js rename to server/src/db/utils/seedDb.js diff --git a/server/src/db/v1/migration/0001_migrateStatusWindowThreshold.ts b/server/src/db/v1/migration/0001_migrateStatusWindowThreshold.ts deleted file mode 100644 index 55d26cc27..000000000 --- a/server/src/db/v1/migration/0001_migrateStatusWindowThreshold.ts +++ /dev/null @@ -1,18 +0,0 @@ -// import Monitor from "@/db/v1/models/Monitor.js"; -async function migrateStatusWindowThreshold() { - try { - // const monitors = await Monitor.find({ statusWindowThreshold: { $lt: 1 } }); - // for (const monitor of monitors) { - // monitor.statusWindowThreshold = monitor.statusWindowThreshold * 100; - // await monitor.save(); - // console.log(`Migrated monitor ${monitor._id}: statusWindowThreshold set to ${monitor.statusWindowThreshold}`); - // } - // console.log("StatusWindowThreshold migration complete."); - return true; - } catch (err) { - console.error("Migration error:", err); - return false; - } -} - -export { migrateStatusWindowThreshold }; diff --git a/server/src/db/v1/migration/index.ts b/server/src/db/v1/migration/index.ts deleted file mode 100644 index 4bf56895a..000000000 --- a/server/src/db/v1/migration/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshold.js"; - -const runMigrations = async () => { - await migrateStatusWindowThreshold(); -}; - -export { runMigrations }; diff --git a/server/src/db/v1/models/Check.js b/server/src/db/v1/models/Check.js deleted file mode 100755 index 7a5117979..000000000 --- a/server/src/db/v1/models/Check.js +++ /dev/null @@ -1,178 +0,0 @@ -import mongoose from "mongoose"; - -const cpuSchema = mongoose.Schema({ - physical_core: { type: Number, default: 0 }, - logical_core: { type: Number, default: 0 }, - frequency: { type: Number, default: 0 }, - temperature: { type: [Number], default: [] }, - free_percent: { type: Number, default: 0 }, - usage_percent: { type: Number, default: 0 }, -}); - -const memorySchema = mongoose.Schema({ - total_bytes: { type: Number, default: 0 }, - available_bytes: { type: Number, default: 0 }, - used_bytes: { type: Number, default: 0 }, - usage_percent: { type: Number, default: 0 }, -}); - -const diskSchema = mongoose.Schema({ - device: { type: String, default: "" }, - mountpoint: { type: String, default: "" }, - read_speed_bytes: { type: Number, default: 0 }, - write_speed_bytes: { type: Number, default: 0 }, - total_bytes: { type: Number, default: 0 }, - free_bytes: { type: Number, default: 0 }, - usage_percent: { type: Number, default: 0 }, -}); - -const hostSchema = mongoose.Schema({ - os: { type: String, default: "" }, - platform: { type: String, default: "" }, - kernel_version: { type: String, default: "" }, -}); - -const errorSchema = mongoose.Schema({ - metric: { type: [String], default: [] }, - err: { type: String, default: "" }, -}); - -const captureSchema = mongoose.Schema({ - version: { type: String, default: "" }, - mode: { type: String, default: "" }, -}); - -const networkInterfaceSchema = mongoose.Schema({ - name: { type: String }, - bytes_sent: { type: Number, default: 0 }, - bytes_recv: { type: Number, default: 0 }, - packets_sent: { type: Number, default: 0 }, - packets_recv: { type: Number, default: 0 }, - err_in: { type: Number, default: 0 }, - err_out: { type: Number, default: 0 }, - drop_in: { type: Number, default: 0 }, - drop_out: { type: Number, default: 0 }, - fifo_in: { type: Number, default: 0 }, - fifo_out: { type: Number, default: 0 }, -}); - -const CheckSchema = new mongoose.Schema( - { - // Common fields - monitorId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Monitor", - immutable: true, - index: true, - }, - - teamId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Team", - immutable: true, - index: true, - }, - type: { - type: String, - enum: ["http", "ping", "pagespeed", "hardware", "docker", "port", "game"], - required: true, - index: true, - }, - - status: { - type: Boolean, - index: true, - }, - - responseTime: { - type: Number, - }, - - timings: { - type: Object, - default: {}, - }, - - statusCode: { - type: Number, - index: true, - }, - - message: { - type: String, - }, - - expiry: { - type: Date, - default: Date.now, - expires: 60 * 60 * 24 * 30, // 30 days - }, - - ack: { - type: Boolean, - default: false, - }, - - ackAt: { - type: Date, - }, - - // Hardware fields - cpu: { - type: cpuSchema, - default: () => ({}), - }, - memory: { - type: memorySchema, - default: () => ({}), - }, - disk: { - type: [diskSchema], - default: () => [], - }, - host: { - type: hostSchema, - default: () => ({}), - }, - - errors: { - type: [errorSchema], - default: () => [], - }, - - capture: { - type: captureSchema, - default: () => ({}), - }, - - net: { - type: [networkInterfaceSchema], - default: () => [], - }, - - // PageSpeed fields - accessibility: { - type: Number, - }, - bestPractices: { - type: Number, - }, - seo: { - type: Number, - }, - performance: { - type: Number, - }, - audits: { - type: Object, - }, - }, - { timestamps: true } -); - -CheckSchema.index({ updatedAt: 1 }); -CheckSchema.index({ monitorId: 1, updatedAt: 1 }); -CheckSchema.index({ monitorId: 1, updatedAt: -1 }); -CheckSchema.index({ teamId: 1, updatedAt: -1 }); - -export default mongoose.model("Check", CheckSchema); diff --git a/server/src/index.js b/server/src/index.js index e8b756d0e..28dc5e8fd 100755 --- a/server/src/index.js +++ b/server/src/index.js @@ -5,10 +5,11 @@ import { initShutdownListener } from "./shutdown.js"; import { fileURLToPath } from "url"; import path from "path"; import fs from "fs"; +import { runMigrations } from "./db/migration/index.js"; import Logger from "./utils/logger.js"; -import SettingsService from "./service/v1/system/settingsService.js"; -import AppSettings from "./db/v1/models/AppSettings.js"; +import SettingsService from "./service/system/settingsService.js"; +import AppSettings from "./db/models/AppSettings.js"; const SERVICE_NAME = "Server"; let logger; @@ -30,6 +31,8 @@ const startApp = async () => { // Initialize services const services = await initializeServices({ logger, envSettings, settingsService }); + await runMigrations(logger); + // Initialize controllers const controllers = initializeControllers(services); diff --git a/server/src/middleware/v1/handleErrors.js b/server/src/middleware/v1/handleErrors.js index 43671fbe2..0a30b0420 100755 --- a/server/src/middleware/v1/handleErrors.js +++ b/server/src/middleware/v1/handleErrors.js @@ -1,6 +1,6 @@ import { logger } from "../../utils/logger.js"; -import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; -import StringService from "../../service/v1/system/stringService.js"; +import ServiceRegistry from "../../service/system/serviceRegistry.js"; +import StringService from "../../service/system/stringService.js"; const handleErrors = (error, req, res, next) => { const status = error.status || 500; diff --git a/server/src/middleware/v1/isAllowed.js b/server/src/middleware/v1/isAllowed.js index cc240f531..1d0de42b4 100755 --- a/server/src/middleware/v1/isAllowed.js +++ b/server/src/middleware/v1/isAllowed.js @@ -1,9 +1,9 @@ import jwt from "jsonwebtoken"; const TOKEN_PREFIX = "Bearer "; const SERVICE_NAME = "allowedRoles"; -import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; -import StringService from "../../service/v1/system/stringService.js"; -import SettingsService from "../../service/v1/system/settingsService.js"; +import ServiceRegistry from "../../service/system/serviceRegistry.js"; +import StringService from "../../service/system/stringService.js"; +import SettingsService from "../../service/system/settingsService.js"; const isAllowed = (allowedRoles) => { return (req, res, next) => { diff --git a/server/src/middleware/v1/verifyJWT.js b/server/src/middleware/v1/verifyJWT.js index b57391dfd..cf983a6c8 100755 --- a/server/src/middleware/v1/verifyJWT.js +++ b/server/src/middleware/v1/verifyJWT.js @@ -1,7 +1,7 @@ import jwt from "jsonwebtoken"; -import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; -import SettingsService from "../../service/v1/system/settingsService.js"; -import StringService from "../../service/v1/system/stringService.js"; +import ServiceRegistry from "../../service/system/serviceRegistry.js"; +import SettingsService from "../../service/system/settingsService.js"; +import StringService from "../../service/system/stringService.js"; const SERVICE_NAME = "verifyJWT"; const TOKEN_PREFIX = "Bearer "; diff --git a/server/src/middleware/v1/verifyOwnership.js b/server/src/middleware/v1/verifyOwnership.js index 741fb6436..6fcc84f60 100755 --- a/server/src/middleware/v1/verifyOwnership.js +++ b/server/src/middleware/v1/verifyOwnership.js @@ -1,6 +1,6 @@ import { logger } from "../../utils/logger.js"; -import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; -import StringService from "../../service/v1/system/stringService.js"; +import ServiceRegistry from "../../service/system/serviceRegistry.js"; +import StringService from "../../service/system/stringService.js"; import { ObjectId } from "mongodb"; const SERVICE_NAME = "verifyOwnership"; diff --git a/server/src/repositories/checks/IChecksRepository.ts b/server/src/repositories/checks/IChecksRepository.ts new file mode 100644 index 000000000..e69de29bb diff --git a/server/src/repositories/checks/MongoChecksRepistory.ts b/server/src/repositories/checks/MongoChecksRepistory.ts new file mode 100644 index 000000000..e69de29bb diff --git a/server/src/repositories/monitors/IMonitorsRepository.ts b/server/src/repositories/monitors/IMonitorsRepository.ts new file mode 100644 index 000000000..458ee5bad --- /dev/null +++ b/server/src/repositories/monitors/IMonitorsRepository.ts @@ -0,0 +1,9 @@ +import type { Monitor } from "@/types/index.js"; +export interface IMonitorsRepository { + // create + // single fetch + // collection fetch + findAll(): Promise; + // update + // delete +} diff --git a/server/src/repositories/monitors/MongoMonitorsRepository.ts b/server/src/repositories/monitors/MongoMonitorsRepository.ts new file mode 100644 index 000000000..b4bae06c8 --- /dev/null +++ b/server/src/repositories/monitors/MongoMonitorsRepository.ts @@ -0,0 +1,5 @@ +class MongoMonitorsRepository implements IMonitorsRepository { + findAll = async () => { + return null; + }; +} diff --git a/server/src/routes/v1/announcementsRoute.js b/server/src/routes/v1/announcementsRoute.js deleted file mode 100755 index 011dfac4a..000000000 --- a/server/src/routes/v1/announcementsRoute.js +++ /dev/null @@ -1,33 +0,0 @@ -import { Router } from "express"; -import { verifyJWT } from "../middleware/verifyJWT.js"; -import { isAllowed } from "../middleware/isAllowed.js"; - -class AnnouncementRoutes { - constructor(controller) { - this.router = Router(); - this.announcementController = controller; - this.initRoutes(); - } - - initRoutes() { - /** - * @route POST / - * @desc Create a new announcement - * @access Private (Requires JWT verification) - */ - this.router.post("/", verifyJWT, isAllowed(["admin", "superadmin"]), this.announcementController.createAnnouncement); - - /** - * @route GET / - * @desc Get announcements - * @access Public - */ - this.router.get("/", this.announcementController.getAnnouncement); - } - - getRouter() { - return this.router; - } -} - -export default AnnouncementRoutes; diff --git a/server/src/routes/v1/monitorRoute.js b/server/src/routes/v1/monitorRoute.js index fe430d46f..86463b6bb 100755 --- a/server/src/routes/v1/monitorRoute.js +++ b/server/src/routes/v1/monitorRoute.js @@ -1,7 +1,7 @@ import { Router } from "express"; import { isAllowed } from "../../middleware/v1/isAllowed.js"; import multer from "multer"; -import { fetchMonitorCertificate } from "../../controllers/v1/controllerUtils.js"; +import { fetchMonitorCertificate } from "../../controllers/controllerUtils.js"; const upload = multer({ storage: multer.memoryStorage(), // Store file in memory as Buffer }); diff --git a/server/src/service/v1/business/checkService.js b/server/src/service/business/checkService.js similarity index 100% rename from server/src/service/v1/business/checkService.js rename to server/src/service/business/checkService.js diff --git a/server/src/service/v1/business/diagnosticService.js b/server/src/service/business/diagnosticService.js similarity index 100% rename from server/src/service/v1/business/diagnosticService.js rename to server/src/service/business/diagnosticService.js diff --git a/server/src/service/v1/business/incidentService.js b/server/src/service/business/incidentService.js similarity index 100% rename from server/src/service/v1/business/incidentService.js rename to server/src/service/business/incidentService.js diff --git a/server/src/service/v1/business/inviteService.js b/server/src/service/business/inviteService.js similarity index 100% rename from server/src/service/v1/business/inviteService.js rename to server/src/service/business/inviteService.js diff --git a/server/src/service/v1/business/maintenanceWindowService.js b/server/src/service/business/maintenanceWindowService.js similarity index 100% rename from server/src/service/v1/business/maintenanceWindowService.js rename to server/src/service/business/maintenanceWindowService.js diff --git a/server/src/service/v1/business/monitorService.js b/server/src/service/business/monitorService.js similarity index 99% rename from server/src/service/v1/business/monitorService.js rename to server/src/service/business/monitorService.js index 2118e6281..4eae0176f 100644 --- a/server/src/service/v1/business/monitorService.js +++ b/server/src/service/business/monitorService.js @@ -1,4 +1,4 @@ -import { createMonitorsBodyValidation } from "../../../validation/joi.js"; +import { createMonitorsBodyValidation } from "@/validation/joi.js"; const SERVICE_NAME = "MonitorService"; class MonitorService { diff --git a/server/src/service/v1/business/userService.js b/server/src/service/business/userService.js similarity index 100% rename from server/src/service/v1/business/userService.js rename to server/src/service/business/userService.js diff --git a/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js similarity index 100% rename from server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js rename to server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js diff --git a/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js similarity index 100% rename from server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js rename to server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js diff --git a/server/src/service/v1/infrastructure/bufferService.js b/server/src/service/infrastructure/bufferService.js similarity index 100% rename from server/src/service/v1/infrastructure/bufferService.js rename to server/src/service/infrastructure/bufferService.js diff --git a/server/src/service/v1/infrastructure/emailService.js b/server/src/service/infrastructure/emailService.js similarity index 100% rename from server/src/service/v1/infrastructure/emailService.js rename to server/src/service/infrastructure/emailService.js diff --git a/server/src/service/v1/infrastructure/errorService.js b/server/src/service/infrastructure/errorService.js similarity index 100% rename from server/src/service/v1/infrastructure/errorService.js rename to server/src/service/infrastructure/errorService.js diff --git a/server/src/service/v1/infrastructure/networkService.js b/server/src/service/infrastructure/networkService.js similarity index 100% rename from server/src/service/v1/infrastructure/networkService.js rename to server/src/service/infrastructure/networkService.js diff --git a/server/src/service/v1/infrastructure/notificationProviders/matrix.js b/server/src/service/infrastructure/notificationProviders/matrix.js similarity index 100% rename from server/src/service/v1/infrastructure/notificationProviders/matrix.js rename to server/src/service/infrastructure/notificationProviders/matrix.js diff --git a/server/src/service/v1/infrastructure/notificationService.js b/server/src/service/infrastructure/notificationService.js similarity index 100% rename from server/src/service/v1/infrastructure/notificationService.js rename to server/src/service/infrastructure/notificationService.js diff --git a/server/src/service/v1/infrastructure/notificationUtils.js b/server/src/service/infrastructure/notificationUtils.js similarity index 100% rename from server/src/service/v1/infrastructure/notificationUtils.js rename to server/src/service/infrastructure/notificationUtils.js diff --git a/server/src/service/v1/infrastructure/statusService.js b/server/src/service/infrastructure/statusService.js similarity index 98% rename from server/src/service/v1/infrastructure/statusService.js rename to server/src/service/infrastructure/statusService.js index 829b6d850..3c1682818 100755 --- a/server/src/service/v1/infrastructure/statusService.js +++ b/server/src/service/infrastructure/statusService.js @@ -1,5 +1,5 @@ -import MonitorStats from "../../../db/v1/models/MonitorStats.js"; -import Check from "../../../db/v1/models/Check.js"; +import MonitorStats from "../../db/models/MonitorStats.js"; +import Check from "../../db/models/Check.js"; const SERVICE_NAME = "StatusService"; class StatusService { @@ -337,9 +337,11 @@ class StatusService { } = networkResponse; const check = { - monitorId, - teamId, - type, + metadata: { + monitorId, + teamId, + type, + }, status, statusCode: code, responseTime, diff --git a/server/src/service/v1/system/serviceRegistry.js b/server/src/service/system/serviceRegistry.js similarity index 100% rename from server/src/service/v1/system/serviceRegistry.js rename to server/src/service/system/serviceRegistry.js diff --git a/server/src/service/v1/system/settingsService.js b/server/src/service/system/settingsService.js similarity index 100% rename from server/src/service/v1/system/settingsService.js rename to server/src/service/system/settingsService.js diff --git a/server/src/service/v1/system/stringService.js b/server/src/service/system/stringService.js similarity index 100% rename from server/src/service/v1/system/stringService.js rename to server/src/service/system/stringService.js diff --git a/server/src/service/v1/system/translationService.js b/server/src/service/system/translationService.js similarity index 100% rename from server/src/service/v1/system/translationService.js rename to server/src/service/system/translationService.js diff --git a/server/src/types/check.ts b/server/src/types/check.ts new file mode 100644 index 000000000..f2ccc0b77 --- /dev/null +++ b/server/src/types/check.ts @@ -0,0 +1,79 @@ +import type { MonitorType } from "@/types/index.js"; + +export interface CheckTimingPhases { + wait: number; + dns: number; + tcp: number; + tls: number; + request: number; + firstByte: number; + download: number; + total: number; +} + +export interface CheckTimings { + start: number; + socket: number; + lookup: number; + connect: number; + secureConnect: number; + upload: number; + response: number; + end: number; + phases: CheckTimingPhases; +} + +export interface CheckCpuInfo { + physical_core: number; + logical_core: number; + frequency: number; + temperature: number[]; + free_percent: number; + usage_percent: number; +} + +export interface CheckMemoryInfo { + total_bytes: number; + available_bytes: number; + used_bytes: number; + usage_percent: number; +} + +export interface CheckHostInfo { + os: string; + platform: string; + kernel_version: string; +} + +export interface CheckCaptureInfo { + version: string; + mode: string; +} + +export interface CheckDiskInfo {} + +export interface CheckNetInfo {} + +export interface Check { + id: string; + monitorId: string; + teamId: string; + type: MonitorType; + status: boolean; + responseTime: number; + timings: CheckTimings; + statusCode: number; + message: string; + ack: boolean; + expiry: string; + cpu: CheckCpuInfo; + memory: CheckMemoryInfo; + disk: CheckDiskInfo[]; + host: CheckHostInfo; + errors: string[]; + capture: CheckCaptureInfo; + net: CheckNetInfo[]; + __v: number; + createdAt: string; + updatedAt: string; +} diff --git a/server/src/types/index.ts b/server/src/types/index.ts new file mode 100644 index 000000000..92bcb69b0 --- /dev/null +++ b/server/src/types/index.ts @@ -0,0 +1,2 @@ +export * from "@/types/check.js"; +export * from "@/types/monitor.js"; diff --git a/server/src/types/monitor.ts b/server/src/types/monitor.ts new file mode 100644 index 000000000..6a386c2e4 --- /dev/null +++ b/server/src/types/monitor.ts @@ -0,0 +1,29 @@ +export const MonitorTypes = ["http", "ping", "pagespeed", "hardware", "docker", "port", "game"] as const; +export type MonitorType = (typeof MonitorTypes)[number]; + +export interface Monitor { + id: string; + userId: string; + teamId: string; + name: string; + description: string; + statusWindow: boolean[]; + statusWindowSize: number; + statusWindowThreshold: number; + type: MonitorType; + ignoreTlsErrors: boolean; + url: string; + isActive: boolean; + interval: number; + notifications: string[]; + alertThreshold: number; + selectedDisks: string[]; + group: string | null; + cpuAlertThreshold: number; + memoryAlertThreshold: number; + diskAlertThreshold: number; + tempAlertThreshold: number; + createdAt: string; + updatedAt: string; + status: boolean; +} From 7a6b0d5da421ea29acf6c0a2c93d5b78193eee5d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 12 Jan 2026 22:58:46 +0000 Subject: [PATCH 03/24] refactoring --- server/package-lock.json | 11 + server/package.json | 1 + server/src/config/services.js | 14 +- server/src/db/models/Check.ts | 140 +++++++++--- .../src/db/models/{Monitor.js => Monitor.ts} | 72 ++++--- server/src/db/models/index.ts | 5 + server/src/db/modules/checkModule.js | 43 ++-- server/src/db/modules/monitorModule.js | 23 +- server/src/db/modules/monitorModuleQueries.js | 21 +- .../repositories/checks/IChecksRepository.ts | 10 + .../checks/MongoChecksRepistory.ts | 203 ++++++++++++++++++ server/src/repositories/index.ts | 5 + .../monitors/IMonitorsRepository.ts | 17 +- .../monitors/MongoMonitorsRepository.ts | 124 ++++++++++- .../v1/{monitorRoute.js => monitorRoute.ts} | 4 +- server/src/service/business/monitorService.js | 36 +++- .../service/infrastructure/statusService.js | 4 +- server/src/types/check.ts | 57 ++++- server/src/types/monitor.ts | 25 ++- 19 files changed, 699 insertions(+), 116 deletions(-) mode change 100755 => 100644 server/src/db/models/Check.ts rename server/src/db/models/{Monitor.js => Monitor.ts} (70%) mode change 100755 => 100644 create mode 100644 server/src/db/models/index.ts create mode 100644 server/src/repositories/index.ts rename server/src/routes/v1/{monitorRoute.js => monitorRoute.ts} (96%) diff --git a/server/package-lock.json b/server/package-lock.json index eaf771a61..d254da407 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -48,6 +48,7 @@ "@eslint/js": "^9.17.0", "@types/express": "5.0.3", "@types/jsonwebtoken": "9.0.10", + "@types/multer": "^2.0.0", "@types/nodemailer": "7.0.1", "@types/ping": "0.4.4", "c8": "10.1.3", @@ -3301,6 +3302,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/multer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.0.0.tgz", + "integrity": "sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "24.9.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz", diff --git a/server/package.json b/server/package.json index 5610d32a9..3547810ce 100755 --- a/server/package.json +++ b/server/package.json @@ -60,6 +60,7 @@ "@eslint/js": "^9.17.0", "@types/express": "5.0.3", "@types/jsonwebtoken": "9.0.10", + "@types/multer": "^2.0.0", "@types/nodemailer": "7.0.1", "@types/ping": "0.4.4", "c8": "10.1.3", diff --git a/server/src/config/services.js b/server/src/config/services.js index 28b7c9a86..0916752ab 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -47,7 +47,7 @@ import { GenerateAvatarImage } from "../utils/imageProcessing.js"; import { ParseBoolean } from "../utils/utils.js"; // Models -import Check from "../db/models/Check.js"; +import { CheckModel } from "@/db/models/index.js"; import Monitor from "../db/models/Monitor.js"; import User from "../db/models/User.js"; import InviteToken from "../db/models/InviteToken.js"; @@ -71,6 +71,9 @@ import RecoveryModule from "../db/modules/recoveryModule.js"; import SettingsModule from "../db/modules/settingsModule.js"; import IncidentModule from "../db/modules/incidentModule.js"; +// repositories +import { MongoMonitorsRepository, MongoChecksRepository } from "@/repositories/index.js"; + export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); ServiceRegistry.instance = serviceRegistry; @@ -81,7 +84,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService const stringService = new StringService(translationService); // Create DB - const checkModule = new CheckModule({ logger, Check, Monitor, User }); + const checkModule = new CheckModule({ logger, CheckModel, Monitor, User }); const inviteModule = new InviteModule({ InviteToken, crypto, stringService }); const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService }); const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean, stringService }); @@ -89,7 +92,6 @@ export const initializeServices = async ({ logger, envSettings, settingsService const monitorModule = new MonitorModule({ Monitor, MonitorStats, - Check, stringService, fs, path, @@ -120,6 +122,10 @@ export const initializeServices = async ({ logger, envSettings, settingsService await db.connect(); + // Repositories + const monitorsRepository = new MongoMonitorsRepository(); + const checksRepository = new MongoChecksRepository(); + const networkService = new NetworkService({ axios, got, @@ -220,6 +226,8 @@ export const initializeServices = async ({ logger, envSettings, settingsService logger, errorService, games, + monitorsRepository, + checksRepository, }); const services = { diff --git a/server/src/db/models/Check.ts b/server/src/db/models/Check.ts old mode 100755 new mode 100644 index b13aa9a16..b4a31f8f7 --- a/server/src/db/models/Check.ts +++ b/server/src/db/models/Check.ts @@ -1,7 +1,78 @@ -import mongoose from "mongoose"; -import { MonitorTypes } from "@/types/monitor.js"; +import { Schema, model, Types } from "mongoose"; +import { + MonitorTypes, + type MonitorType, +} from "@/types/monitor.js"; +import type { + Check, + CheckAudits, + CheckCaptureInfo, + CheckCpuInfo, + CheckDiskInfo, + CheckErrorInfo, + CheckHostInfo, + CheckMemoryInfo, + CheckMetadata, + CheckNetworkInterfaceInfo, + CheckTimings, + CheckTimingPhases, +} from "@/types/check.js"; -const cpuSchema = new mongoose.Schema( +type CheckMetadataDocument = Omit & { + monitorId: Types.ObjectId; + teamId: Types.ObjectId; + type: MonitorType; +}; + +type CheckDocumentBase = Omit< + Check, + "id" | "metadata" | "expiry" | "ackAt" | "createdAt" | "updatedAt" +> & { + metadata: CheckMetadataDocument; + expiry: Date; + ackAt?: Date | null; + createdAt: Date; + updatedAt: Date; + __v: number; +}; + +interface CheckDocument extends CheckDocumentBase { + _id: Types.ObjectId; +} + +const timingPhasesSchema = new Schema( + { + wait: { type: Number, default: 0 }, + dns: { type: Number, default: 0 }, + tcp: { type: Number, default: 0 }, + tls: { type: Number, default: 0 }, + request: { type: Number, default: 0 }, + firstByte: { type: Number, default: 0 }, + download: { type: Number, default: 0 }, + total: { type: Number, default: 0 }, + }, + { _id: false } +); + +const timingsSchema = new Schema( + { + start: { type: Number, default: 0 }, + socket: { type: Number, default: 0 }, + lookup: { type: Number, default: 0 }, + connect: { type: Number, default: 0 }, + secureConnect: { type: Number, default: 0 }, + upload: { type: Number, default: 0 }, + response: { type: Number, default: 0 }, + end: { type: Number, default: 0 }, + phases: { + type: timingPhasesSchema, + default: () => ({}), + }, + }, + { _id: false } +); + +const cpuSchema = new Schema( { physical_core: { type: Number, default: 0 }, logical_core: { type: Number, default: 0 }, @@ -13,7 +84,7 @@ const cpuSchema = new mongoose.Schema( { _id: false } ); -const memorySchema = new mongoose.Schema( +const memorySchema = new Schema( { total_bytes: { type: Number, default: 0 }, available_bytes: { type: Number, default: 0 }, @@ -23,7 +94,7 @@ const memorySchema = new mongoose.Schema( { _id: false } ); -const diskSchema = new mongoose.Schema( +const diskSchema = new Schema( { device: { type: String, default: "" }, mountpoint: { type: String, default: "" }, @@ -36,7 +107,7 @@ const diskSchema = new mongoose.Schema( { _id: false } ); -const hostSchema = new mongoose.Schema( +const hostSchema = new Schema( { os: { type: String, default: "" }, platform: { type: String, default: "" }, @@ -45,7 +116,7 @@ const hostSchema = new mongoose.Schema( { _id: false } ); -const errorSchema = new mongoose.Schema( +const errorSchema = new Schema( { metric: { type: [String], default: [] }, err: { type: String, default: "" }, @@ -53,7 +124,7 @@ const errorSchema = new mongoose.Schema( { _id: false } ); -const captureSchema = new mongoose.Schema( +const captureSchema = new Schema( { version: { type: String, default: "" }, mode: { type: String, default: "" }, @@ -61,9 +132,9 @@ const captureSchema = new mongoose.Schema( { _id: false } ); -const networkInterfaceSchema = new mongoose.Schema( +const networkInterfaceSchema = new Schema( { - name: { type: String }, + name: { type: String, default: "" }, bytes_sent: { type: Number, default: 0 }, bytes_recv: { type: Number, default: 0 }, packets_sent: { type: Number, default: 0 }, @@ -78,17 +149,30 @@ const networkInterfaceSchema = new mongoose.Schema( { _id: false } ); -const metadataSchema = new mongoose.Schema( +const auditsSchema = new Schema( + { + cls: { type: Number, default: 0 }, + si: { type: Number, default: 0 }, + fcp: { type: Number, default: 0 }, + lcp: { type: Number, default: 0 }, + tbt: { type: Number, default: 0 }, + }, + { _id: false } +); + +const metadataSchema = new Schema( { monitorId: { - type: mongoose.Schema.Types.ObjectId, + type: Schema.Types.ObjectId, ref: "Monitor", + required: true, immutable: true, index: true, }, teamId: { - type: mongoose.Schema.Types.ObjectId, + type: Schema.Types.ObjectId, ref: "Team", + required: true, immutable: true, index: true, }, @@ -102,7 +186,7 @@ const metadataSchema = new mongoose.Schema( { _id: false } ); -const CheckSchema = new mongoose.Schema( +const CheckSchema = new Schema( { metadata: { type: metadataSchema, @@ -112,40 +196,32 @@ const CheckSchema = new mongoose.Schema( type: Boolean, index: true, }, - responseTime: { type: Number, }, - timings: { - type: Object, - default: {}, + type: timingsSchema, + default: undefined, }, - statusCode: { type: Number, index: true, }, - message: { type: String, }, - expiry: { type: Date, default: Date.now, }, - ack: { type: Boolean, default: false, }, - ackAt: { type: Date, + default: undefined, }, - - // Hardware fields cpu: { type: cpuSchema, default: () => ({}), @@ -162,23 +238,18 @@ const CheckSchema = new mongoose.Schema( type: hostSchema, default: () => ({}), }, - errors: { type: [errorSchema], default: () => [], }, - capture: { type: captureSchema, default: () => ({}), }, - net: { type: [networkInterfaceSchema], default: () => [], }, - - // PageSpeed fields accessibility: { type: Number, }, @@ -192,7 +263,8 @@ const CheckSchema = new mongoose.Schema( type: Number, }, audits: { - type: Object, + type: auditsSchema, + default: undefined, }, }, { @@ -211,4 +283,8 @@ CheckSchema.index({ "metadata.monitorId": 1, updatedAt: 1 }); CheckSchema.index({ "metadata.monitorId": 1, updatedAt: -1 }); CheckSchema.index({ "metadata.teamId": 1, updatedAt: -1 }); -export default mongoose.model("Check", CheckSchema); +const CheckModel = model("Check", CheckSchema); + +export type { CheckDocument, CheckMetadataDocument }; +export { CheckModel }; +export default CheckModel; diff --git a/server/src/db/models/Monitor.js b/server/src/db/models/Monitor.ts old mode 100755 new mode 100644 similarity index 70% rename from server/src/db/models/Monitor.js rename to server/src/db/models/Monitor.ts index 901dc39d1..f6f9d4371 --- a/server/src/db/models/Monitor.js +++ b/server/src/db/models/Monitor.ts @@ -1,18 +1,49 @@ -import mongoose from "mongoose"; +import { Schema, model, Types, type UpdateQuery } from "mongoose"; +import type { Monitor, MonitorMatchMethod, MonitorThresholds } from "@/types/monitor.js"; +import { MonitorTypes } from "@/types/monitor.js"; import Check from "./Check.js"; import MonitorStats from "./MonitorStats.js"; import StatusPage from "./StatusPage.js"; -const MonitorSchema = mongoose.Schema( +type MonitorDocumentBase = Omit< + Monitor, + "id" | "userId" | "teamId" | "notifications" | "selectedDisks" | "statusWindow" | "createdAt" | "updatedAt" +> & { + statusWindow: boolean[]; + notifications: Types.ObjectId[]; + selectedDisks: string[]; + matchMethod?: MonitorMatchMethod; + thresholds?: MonitorThresholds; +}; + +interface MonitorDocument extends MonitorDocumentBase { + _id: Types.ObjectId; + userId: Types.ObjectId; + teamId: Types.ObjectId; + createdAt: Date; + updatedAt: Date; +} + +const thresholdsSchema = new Schema( + { + usage_cpu: { type: Number }, + usage_memory: { type: Number }, + usage_disk: { type: Number }, + usage_temperature: { type: Number }, + }, + { _id: false } +); + +const MonitorSchema = new Schema( { userId: { - type: mongoose.Schema.Types.ObjectId, + type: Schema.Types.ObjectId, ref: "User", immutable: true, required: true, }, teamId: { - type: mongoose.Schema.Types.ObjectId, + type: Schema.Types.ObjectId, ref: "Team", immutable: true, required: true, @@ -43,7 +74,7 @@ const MonitorSchema = mongoose.Schema( type: { type: String, required: true, - enum: ["http", "ping", "pagespeed", "hardware", "docker", "port", "game"], + enum: MonitorTypes, }, ignoreTlsErrors: { type: Boolean, @@ -71,7 +102,6 @@ const MonitorSchema = mongoose.Schema( default: true, }, interval: { - // in milliseconds type: Number, default: 60000, }, @@ -81,7 +111,7 @@ const MonitorSchema = mongoose.Schema( }, notifications: [ { - type: mongoose.Schema.Types.ObjectId, + type: Schema.Types.ObjectId, ref: "Notification", }, ], @@ -89,13 +119,7 @@ const MonitorSchema = mongoose.Schema( type: String, }, thresholds: { - type: { - usage_cpu: { type: Number }, - usage_memory: { type: Number }, - usage_disk: { type: Number }, - usage_temperature: { type: Number }, - }, - _id: false, + type: thresholdsSchema, }, alertThreshold: { type: Number, @@ -137,7 +161,7 @@ const MonitorSchema = mongoose.Schema( trim: true, maxLength: 50, default: null, - set: function (value) { + set(value: string | null) { return value && value.trim() ? value.trim() : null; }, }, @@ -148,7 +172,6 @@ const MonitorSchema = mongoose.Schema( ); MonitorSchema.pre("findOneAndDelete", async function (next) { - // Delete checks and stats try { const doc = await this.model.findOne(this.getFilter()); @@ -157,20 +180,17 @@ MonitorSchema.pre("findOneAndDelete", async function (next) { } await Check.deleteMany({ monitorId: doc._id }); - - // Deal with status pages await StatusPage.updateMany({ monitors: doc?._id }, { $pull: { monitors: doc?._id } }); - await MonitorStats.deleteMany({ monitorId: doc?._id.toString() }); next(); } catch (error) { - next(error); + next(error as Error); } }); MonitorSchema.pre("deleteMany", async function (next) { const filter = this.getFilter(); - const monitors = await this.model.find(filter).select(["_id", "type"]).lean(); + const monitors = (await this.model.find(filter).select(["_id", "type"]).lean()) as { _id: Types.ObjectId }[]; for (const monitor of monitors) { await Check.deleteMany({ monitorId: monitor._id }); @@ -197,8 +217,8 @@ MonitorSchema.pre("save", function (next) { }); MonitorSchema.pre("findOneAndUpdate", function (next) { - const update = this.getUpdate(); - if (update.alertThreshold) { + const update = this.getUpdate() as UpdateQuery | null; + if (update && !Array.isArray(update) && update.alertThreshold !== undefined) { update.cpuAlertThreshold = update.alertThreshold; update.memoryAlertThreshold = update.alertThreshold; update.diskAlertThreshold = update.alertThreshold; @@ -209,4 +229,8 @@ MonitorSchema.pre("findOneAndUpdate", function (next) { MonitorSchema.index({ teamId: 1, type: 1 }); -export default mongoose.model("Monitor", MonitorSchema); +const MonitorModel = model("Monitor", MonitorSchema); + +export type { MonitorDocument }; +export { MonitorModel }; +export default MonitorModel; diff --git a/server/src/db/models/index.ts b/server/src/db/models/index.ts new file mode 100644 index 000000000..473940874 --- /dev/null +++ b/server/src/db/models/index.ts @@ -0,0 +1,5 @@ +export * from "@/db/models/Monitor.js"; +export { default as MonitorModel } from "@/db/models/Monitor.js"; + +export * from "@/db/models/Check.js"; +export { default as CheckModel } from "@/db/models/Check.js"; diff --git a/server/src/db/modules/checkModule.js b/server/src/db/modules/checkModule.js index dfca02bed..871761290 100755 --- a/server/src/db/modules/checkModule.js +++ b/server/src/db/modules/checkModule.js @@ -1,4 +1,6 @@ import { ObjectId } from "mongodb"; +import mongoose from "mongoose"; +import { CheckModel } from "@/db/models/index.js"; import { buildChecksSummaryByTeamIdPipeline } from "./checkModuleQueries.js"; const SERVICE_NAME = "checkModule"; @@ -12,18 +14,15 @@ const dateRangeLookup = { }; class CheckModule { - constructor({ logger, Check, HardwareCheck, PageSpeedCheck, Monitor, User }) { + constructor({ logger, Monitor, User }) { this.logger = logger; - this.Check = Check; - this.HardwareCheck = HardwareCheck; - this.PageSpeedCheck = PageSpeedCheck; this.Monitor = Monitor; this.User = User; } createChecks = async (checks) => { try { - await this.Check.insertMany(checks, { ordered: false }); + await CheckModel.insertMany(checks, { ordered: false }); } catch (error) { error.service = SERVICE_NAME; error.method = "createCheck"; @@ -41,7 +40,7 @@ class CheckModule { // Match const matchStage = { - monitorId: new ObjectId(monitorId), + "metadata.monitorId": new ObjectId(monitorId), ...(typeof status !== "undefined" && { status }), ...(typeof ack !== "undefined" && ackStage), ...(dateRangeLookup[dateRange] && { @@ -79,7 +78,7 @@ class CheckModule { skip = page * rowsPerPage; } - const checks = await this.Check.aggregate([ + const checks = await CheckModel.aggregate([ { $match: matchStage }, { $sort: { createdAt: sortOrder } }, { @@ -115,7 +114,7 @@ class CheckModule { const ackStage = ack === "true" ? { ack: true } : { $or: [{ ack: false }, { ack: { $exists: false } }] }; const matchStage = { - teamId: new ObjectId(teamId), + "metadata.teamId": new ObjectId(teamId), status: false, ...(typeof ack !== "undefined" && ackStage), ...(dateRangeLookup[dateRange] && { @@ -170,7 +169,7 @@ class CheckModule { }, ]; - const checks = await this.Check.aggregate(aggregatePipeline); + const checks = await CheckModel.aggregate(aggregatePipeline); return checks[0]; } catch (error) { error.service = SERVICE_NAME; @@ -181,7 +180,11 @@ class CheckModule { ackCheck = async (checkId, teamId, ack) => { try { - const updatedCheck = await this.Check.findOneAndUpdate({ _id: checkId, teamId: teamId }, { $set: { ack, ackAt: new Date() } }, { new: true }); + const updatedCheck = await CheckModel.findOneAndUpdate( + { _id: checkId, "metadata.teamId": teamId }, + { $set: { ack, ackAt: new Date() } }, + { new: true } + ); if (!updatedCheck) { throw new Error("Check not found"); @@ -197,7 +200,11 @@ class CheckModule { ackAllChecks = async (monitorId, teamId, ack, path) => { try { - const updatedChecks = await this.Check.updateMany(path === "monitor" ? { monitorId } : { teamId }, { $set: { ack, ackAt: new Date() } }); + const filter = + path === "monitor" + ? { "metadata.monitorId": new mongoose.Types.ObjectId(monitorId) } + : { "metadata.teamId": new mongoose.Types.ObjectId(teamId) }; + const updatedChecks = await CheckModel.updateMany(filter, { $set: { ack, ackAt: new Date() } }); return updatedChecks.modifiedCount; } catch (error) { error.service = SERVICE_NAME; @@ -209,9 +216,9 @@ class CheckModule { getChecksSummaryByTeamId = async ({ teamId }) => { try { const matchStage = { - teamId: new ObjectId(teamId), + "metadata.teamId": new ObjectId(teamId), }; - const checks = await this.Check.aggregate(buildChecksSummaryByTeamIdPipeline({ matchStage })); + const checks = await CheckModel.aggregate(buildChecksSummaryByTeamIdPipeline({ matchStage })); return checks[0].summary; } catch (error) { error.service = SERVICE_NAME; @@ -221,7 +228,7 @@ class CheckModule { }; deleteChecks = async (monitorId) => { try { - const result = await this.Check.deleteMany({ monitorId }); + const result = await CheckModel.deleteMany({ "metadata.monitorId": monitorId }); return result.deletedCount; } catch (error) { error.service = SERVICE_NAME; @@ -237,7 +244,7 @@ class CheckModule { const monitorIds = teamMonitors.map((monitor) => monitor._id); // Delete all checks for these monitors in one operation - const deleteResult = await this.Check.deleteMany({ monitorId: { $in: monitorIds } }); + const deleteResult = await CheckModel.deleteMany({ "metadata.monitorId": { $in: monitorIds } }); return deleteResult.deletedCount; } catch (error) { @@ -249,7 +256,7 @@ class CheckModule { updateChecksTTL = async (teamId, ttl) => { try { - await this.Check.collection.dropIndex("expiry_1"); + await CheckModel.collection.dropIndex("expiry_1"); } catch (error) { this.logger.error({ message: error.message, @@ -260,9 +267,9 @@ class CheckModule { } try { - await this.Check.collection.createIndex( + await CheckModel.collection.createIndex( { expiry: 1 }, - { expireAfterSeconds: ttl } // TTL in seconds, adjust as necessary + { expireAfterSeconds: ttl, partialFilterExpression: { "metadata.mode": { $exists: true } } } ); } catch (error) { error.service = SERVICE_NAME; diff --git a/server/src/db/modules/monitorModule.js b/server/src/db/modules/monitorModule.js index 39168f0b2..316caa2ea 100755 --- a/server/src/db/modules/monitorModule.js +++ b/server/src/db/modules/monitorModule.js @@ -8,13 +8,14 @@ import { buildFilteredMonitorsByTeamIdPipeline, } from "./monitorModuleQueries.js"; +import { CheckModel } from "@/db/models/index.js"; + const SERVICE_NAME = "monitorModule"; class MonitorModule { - constructor({ Monitor, MonitorStats, Check, stringService, fs, path, fileURLToPath, ObjectId, NormalizeData, NormalizeDataUptimeDetails }) { + constructor({ Monitor, MonitorStats, stringService, fs, path, fileURLToPath, ObjectId, NormalizeData, NormalizeDataUptimeDetails }) { this.Monitor = Monitor; this.MonitorStats = MonitorStats; - this.Check = Check; this.stringService = stringService; this.fs = fs; this.path = path; @@ -123,15 +124,18 @@ class MonitorModule { //Helper getMonitorChecks = async (monitorId, dateRange, sortOrder) => { + const objectId = new this.ObjectId(monitorId); const indexSpec = { - monitorId: 1, - updatedAt: sortOrder, // This will be 1 or -1 + "metadata.monitorId": 1, + updatedAt: sortOrder, }; + const matchBase = { "metadata.monitorId": objectId }; + const [checksAll, checksForDateRange] = await Promise.all([ - this.Check.find({ monitorId }).sort({ createdAt: sortOrder }).hint(indexSpec).lean(), - this.Check.find({ - monitorId, + CheckModel.find(matchBase).sort({ createdAt: sortOrder }).hint(indexSpec).lean(), + CheckModel.find({ + ...matchBase, createdAt: { $gte: dateRange.start, $lte: dateRange.end }, }) .hint(indexSpec) @@ -238,7 +242,8 @@ class MonitorModule { const dateString = formatLookup[dateRange]; - const results = await this.Check.aggregate(buildUptimeDetailsPipeline(monitorId, dates, dateString)); + console.log("WTTTTFFF"); + const results = await CheckModel.aggregate(buildUptimeDetailsPipeline(monitorId, dates, dateString)); const monitorData = results[0]; @@ -313,7 +318,7 @@ class MonitorModule { }; const dateString = formatLookup[dateRange]; - const hardwareStats = await this.Check.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString)); + const hardwareStats = await CheckModel.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString)); const stats = hardwareStats[0]; diff --git a/server/src/db/modules/monitorModuleQueries.js b/server/src/db/modules/monitorModuleQueries.js index 32aed1529..e6c6b4ec6 100755 --- a/server/src/db/modules/monitorModuleQueries.js +++ b/server/src/db/modules/monitorModuleQueries.js @@ -4,7 +4,7 @@ const buildUptimeDetailsPipeline = (monitorId, dates, dateString) => { return [ { $match: { - monitorId: new ObjectId(monitorId), + "metadata.monitorId": new ObjectId(monitorId), updatedAt: { $gte: dates.start, $lte: dates.end }, }, }, @@ -172,8 +172,8 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { return [ { $match: { - monitorId: monitor._id, - type: "hardware", + "metadata.monitorId": monitor._id, + "metadata.type": "hardware", createdAt: { $gte: dates.start, $lte: dates.end }, }, }, @@ -233,11 +233,16 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, pipeline: [ { - $match: { - $expr: { - $and: [{ $eq: ["$monitorId", monitor._id] }, { $gte: ["$createdAt", dates.start] }, { $lte: ["$createdAt", dates.end] }], - }, - }, + $match: { + $expr: { + $and: [ + { $eq: ["$metadata.monitorId", monitor._id] }, + { $eq: ["$metadata.type", "hardware"] }, + { $gte: ["$createdAt", dates.start] }, + { $lte: ["$createdAt", dates.end] }, + ], + }, + }, }, { $group: { diff --git a/server/src/repositories/checks/IChecksRepository.ts b/server/src/repositories/checks/IChecksRepository.ts index e69de29bb..df69cf18a 100644 --- a/server/src/repositories/checks/IChecksRepository.ts +++ b/server/src/repositories/checks/IChecksRepository.ts @@ -0,0 +1,10 @@ +import type { LatestChecksMap } from "@/repositories/checks/MongoChecksRepistory.js"; + +export interface IChecksRepository { + // create + // single fetch + // collection fetch + findLatestChecksByMonitorIds(monitorIds: string[]): Promise; + // update + // delete +} diff --git a/server/src/repositories/checks/MongoChecksRepistory.ts b/server/src/repositories/checks/MongoChecksRepistory.ts index e69de29bb..d04118da0 100644 --- a/server/src/repositories/checks/MongoChecksRepistory.ts +++ b/server/src/repositories/checks/MongoChecksRepistory.ts @@ -0,0 +1,203 @@ +import { IChecksRepository } from "@/repositories/index.js"; +import type { + Check, + CheckAudits, + CheckCaptureInfo, + CheckCpuInfo, + CheckDiskInfo, + CheckErrorInfo, + CheckHostInfo, + CheckMemoryInfo, + CheckMetadata, + CheckNetworkInterfaceInfo, + CheckTimings, +} from "@/types/index.js"; +import { CheckModel, type CheckDocument } from "@/db/models/index.js"; +import mongoose from "mongoose"; + +export type LatestChecksMap = Record; + +class MongoChecksRepistory implements IChecksRepository { + private toEntity = (doc: CheckDocument): Check => { + const toStringId = (value: mongoose.Types.ObjectId | string | undefined | null): string => { + if (!value) { + return ""; + } + return value instanceof mongoose.Types.ObjectId ? value.toString() : String(value); + }; + + const toDateString = (value?: Date | string | null): string => { + if (!value) { + return new Date(0).toISOString(); + } + return value instanceof Date ? value.toISOString() : new Date(value).toISOString(); + }; + + const toOptionalDateString = (value?: Date | string | null): string | undefined => { + if (!value) { + return undefined; + } + return toDateString(value); + }; + + const mapTimings = (timings?: CheckTimings): CheckTimings => { + const phases = timings?.phases ?? { + wait: 0, + dns: 0, + tcp: 0, + tls: 0, + request: 0, + firstByte: 0, + download: 0, + total: 0, + }; + + return { + start: timings?.start ?? 0, + socket: timings?.socket ?? 0, + lookup: timings?.lookup ?? 0, + connect: timings?.connect ?? 0, + secureConnect: timings?.secureConnect ?? 0, + upload: timings?.upload ?? 0, + response: timings?.response ?? 0, + end: timings?.end ?? 0, + phases, + }; + }; + + const mapCpu = (cpu?: CheckCpuInfo): CheckCpuInfo => ({ + physical_core: cpu?.physical_core ?? 0, + logical_core: cpu?.logical_core ?? 0, + frequency: cpu?.frequency ?? 0, + temperature: cpu?.temperature ?? [], + free_percent: cpu?.free_percent ?? 0, + usage_percent: cpu?.usage_percent ?? 0, + }); + + const mapMemory = (memory?: CheckMemoryInfo): CheckMemoryInfo => ({ + total_bytes: memory?.total_bytes ?? 0, + available_bytes: memory?.available_bytes ?? 0, + used_bytes: memory?.used_bytes ?? 0, + usage_percent: memory?.usage_percent ?? 0, + }); + + const mapHost = (host?: CheckHostInfo): CheckHostInfo => ({ + os: host?.os ?? "", + platform: host?.platform ?? "", + kernel_version: host?.kernel_version ?? "", + }); + + const mapCapture = (capture?: CheckCaptureInfo): CheckCaptureInfo => ({ + version: capture?.version ?? "", + mode: capture?.mode ?? "", + }); + + const mapDisks = (disks?: CheckDiskInfo[]): CheckDiskInfo[] => + (disks ?? []).map((disk) => ({ + device: disk?.device ?? "", + mountpoint: disk?.mountpoint ?? "", + read_speed_bytes: disk?.read_speed_bytes ?? 0, + write_speed_bytes: disk?.write_speed_bytes ?? 0, + total_bytes: disk?.total_bytes ?? 0, + free_bytes: disk?.free_bytes ?? 0, + usage_percent: disk?.usage_percent ?? 0, + })); + + const mapErrors = (errors?: CheckErrorInfo[]): CheckErrorInfo[] => + (errors ?? []).map((error) => ({ + metric: error?.metric ?? [], + err: error?.err ?? "", + })); + + const mapNet = (net?: CheckNetworkInterfaceInfo[]): CheckNetworkInterfaceInfo[] => + (net ?? []).map((iface) => ({ + name: iface?.name ?? "", + bytes_sent: iface?.bytes_sent ?? 0, + bytes_recv: iface?.bytes_recv ?? 0, + packets_sent: iface?.packets_sent ?? 0, + packets_recv: iface?.packets_recv ?? 0, + err_in: iface?.err_in ?? 0, + err_out: iface?.err_out ?? 0, + drop_in: iface?.drop_in ?? 0, + drop_out: iface?.drop_out ?? 0, + fifo_in: iface?.fifo_in ?? 0, + fifo_out: iface?.fifo_out ?? 0, + })); + + const mapAudits = (audits?: CheckAudits): CheckAudits | undefined => { + if (!audits) { + return undefined; + } + return { + cls: audits.cls ?? 0, + si: audits.si ?? 0, + fcp: audits.fcp ?? 0, + lcp: audits.lcp ?? 0, + tbt: audits.tbt ?? 0, + }; + }; + + const mapMetadata = (metadata: CheckDocument["metadata"]): CheckMetadata => ({ + monitorId: toStringId(metadata.monitorId), + teamId: toStringId(metadata.teamId), + type: metadata.type, + }); + + return { + id: toStringId(doc._id), + metadata: mapMetadata(doc.metadata), + status: doc.status ?? false, + responseTime: doc.responseTime ?? 0, + timings: mapTimings(doc.timings), + statusCode: doc.statusCode ?? 0, + message: doc.message ?? "", + ack: doc.ack ?? false, + ackAt: toOptionalDateString(doc.ackAt), + expiry: toDateString(doc.expiry), + cpu: mapCpu(doc.cpu), + memory: mapMemory(doc.memory), + disk: mapDisks(doc.disk), + host: mapHost(doc.host), + errors: mapErrors(doc.errors), + capture: mapCapture(doc.capture), + net: mapNet(doc.net), + accessibility: doc.accessibility, + bestPractices: doc.bestPractices, + seo: doc.seo, + performance: doc.performance, + audits: mapAudits(doc.audits), + __v: doc.__v ?? 0, + createdAt: toDateString(doc.createdAt), + updatedAt: toDateString(doc.updatedAt), + }; + }; + + findLatestChecksByMonitorIds = async (monitorIds: string[]): Promise => { + const mongoIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id)); + const checkMap = await CheckModel.aggregate([ + { + $match: { + "metadata.monitorId": { $in: mongoIds }, + }, + }, + { $sort: { createdAt: -1 } }, + { + $group: { + _id: "$metadata.monitorId", + latestChecks: { $push: "$$ROOT" }, + }, + }, + { + $project: { + latestChecks: { $slice: [{ $ifNull: ["$latestChecks", []] }, 25] }, + }, + }, + ]); + return checkMap.reduce((acc, cm) => { + acc[cm._id.toString()] = cm.latestChecks.map((c: CheckDocument) => this.toEntity(c)); + return acc; + }, {}); + }; +} + +export default MongoChecksRepistory; diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts new file mode 100644 index 000000000..c2bae6939 --- /dev/null +++ b/server/src/repositories/index.ts @@ -0,0 +1,5 @@ +export * from "@/repositories/monitors/IMonitorsRepository.js"; +export { default as MongoMonitorsRepository } from "@/repositories/monitors/MongoMonitorsRepository.js"; + +export * from "@/repositories/checks/IChecksRepository.js"; +export { default as MongoChecksRepository } from "@/repositories/checks/MongoChecksRepistory.js"; diff --git a/server/src/repositories/monitors/IMonitorsRepository.ts b/server/src/repositories/monitors/IMonitorsRepository.ts index 458ee5bad..865d7f8e4 100644 --- a/server/src/repositories/monitors/IMonitorsRepository.ts +++ b/server/src/repositories/monitors/IMonitorsRepository.ts @@ -1,9 +1,24 @@ -import type { Monitor } from "@/types/index.js"; +import { type MonitorType, type Monitor } from "@/types/index.js"; + +export interface TeamQueryConfig { + limit?: number; + type?: MonitorType | MonitorType[]; + page?: number; + rowsPerPage?: number; + filter?: string; + field?: string; + order?: "asc" | "desc"; +} + export interface IMonitorsRepository { // create // single fetch // collection fetch findAll(): Promise; + findByTeamId(teamId: string, config: TeamQueryConfig): Promise; // update // delete + + // counts + findMonitorCountByTeamIdAndType(teamId: string, config: TeamQueryConfig): Promise; } diff --git a/server/src/repositories/monitors/MongoMonitorsRepository.ts b/server/src/repositories/monitors/MongoMonitorsRepository.ts index b4bae06c8..dd85f9a3d 100644 --- a/server/src/repositories/monitors/MongoMonitorsRepository.ts +++ b/server/src/repositories/monitors/MongoMonitorsRepository.ts @@ -1,5 +1,125 @@ +import { MonitorModel } from "@/db/models/index.js"; +import type { MonitorDocument } from "@/db/models/Monitor.js"; +import type { Monitor, MonitorType } from "@/types/monitor.js"; +import mongoose, { type FilterQuery } from "mongoose"; +import type { IMonitorsRepository, TeamQueryConfig } from "./IMonitorsRepository.js"; + class MongoMonitorsRepository implements IMonitorsRepository { - findAll = async () => { - return null; + findAll = async (): Promise => { + const documents = await MonitorModel.find().exec(); + return this.mapDocuments(documents); + }; + + findByTeamId = async (teamId: string, config: TeamQueryConfig): Promise => { + const { page = 0, rowsPerPage = 25, filter, field = "createdAt", order = "desc", type, limit } = config ?? {}; + + const query: Record = { + teamId: new mongoose.Types.ObjectId(teamId), + }; + + if (type !== undefined) { + query.type = Array.isArray(type) ? { $in: type } : type; + } + + if (filter !== undefined) { + switch (field) { + case "name": + query.$or = [{ name: { $regex: filter, $options: "i" } }, { url: { $regex: filter, $options: "i" } }]; + break; + case "isActive": + query.isActive = filter === "true"; + break; + case "status": + query.status = filter === "true"; + break; + case "type": + query.type = filter; + break; + default: + break; + } + } + + const sort = { [field]: order === "asc" ? 1 : -1 } as const; + const skip = Math.max(page, 0) * rowsPerPage; + const limitValue = limit ?? rowsPerPage; + + const documents = await MonitorModel.find(query).sort(sort).skip(skip).limit(limitValue).exec(); + + return this.mapDocuments(documents); + }; + + findMonitorCountByTeamIdAndType = async (teamId: string, config?: TeamQueryConfig): Promise => { + const { type } = config ?? {}; + + const query: FilterQuery = { + teamId: new mongoose.Types.ObjectId(teamId), + }; + + if (type !== undefined) { + query.type = Array.isArray(type) ? { $in: type } : type; + } + + const count = await MonitorModel.countDocuments(query); + return count; + }; + + private mapDocuments = (documents: MonitorDocument[]): Monitor[] | null => { + if (!documents?.length) { + return null; + } + return documents.map((doc) => this.toEntity(doc)); + }; + + private toEntity = (doc: MonitorDocument): Monitor => { + const toStringId = (value: unknown): string => { + if (value instanceof mongoose.Types.ObjectId) { + return value.toString(); + } + return value?.toString() ?? ""; + }; + + const toDateString = (value: Date | string): string => { + return value instanceof Date ? value.toISOString() : value; + }; + + const notificationIds = (doc.notifications ?? []).map((notification) => toStringId(notification)); + + return { + id: toStringId(doc._id), + userId: toStringId(doc.userId), + teamId: toStringId(doc.teamId), + name: doc.name, + description: doc.description ?? undefined, + status: doc.status ?? undefined, + statusWindow: doc.statusWindow ?? [], + statusWindowSize: doc.statusWindowSize, + statusWindowThreshold: doc.statusWindowThreshold, + type: doc.type, + ignoreTlsErrors: doc.ignoreTlsErrors, + jsonPath: doc.jsonPath ?? undefined, + expectedValue: doc.expectedValue ?? undefined, + matchMethod: doc.matchMethod ?? undefined, + url: doc.url, + port: doc.port ?? undefined, + isActive: doc.isActive, + interval: doc.interval, + uptimePercentage: doc.uptimePercentage ?? undefined, + notifications: notificationIds, + secret: doc.secret ?? undefined, + thresholds: doc.thresholds ?? undefined, + alertThreshold: doc.alertThreshold, + cpuAlertThreshold: doc.cpuAlertThreshold, + memoryAlertThreshold: doc.memoryAlertThreshold, + diskAlertThreshold: doc.diskAlertThreshold, + tempAlertThreshold: doc.tempAlertThreshold, + selectedDisks: doc.selectedDisks ?? [], + gameId: doc.gameId ?? undefined, + group: doc.group ?? null, + createdAt: toDateString(doc.createdAt), + updatedAt: toDateString(doc.updatedAt), + }; }; } + +export default MongoMonitorsRepository; diff --git a/server/src/routes/v1/monitorRoute.js b/server/src/routes/v1/monitorRoute.ts similarity index 96% rename from server/src/routes/v1/monitorRoute.js rename to server/src/routes/v1/monitorRoute.ts index 86463b6bb..e68e0019f 100755 --- a/server/src/routes/v1/monitorRoute.js +++ b/server/src/routes/v1/monitorRoute.ts @@ -7,7 +7,9 @@ const upload = multer({ }); class MonitorRoutes { - constructor(monitorController) { + private router: Router; + private monitorController: any; + constructor(monitorController: any) { this.router = Router(); this.monitorController = monitorController; this.initRoutes(); diff --git a/server/src/service/business/monitorService.js b/server/src/service/business/monitorService.js index 4eae0176f..f457e5c7a 100644 --- a/server/src/service/business/monitorService.js +++ b/server/src/service/business/monitorService.js @@ -1,10 +1,23 @@ import { createMonitorsBodyValidation } from "@/validation/joi.js"; +import { NormalizeData } from "@/utils/dataUtils.js"; const SERVICE_NAME = "MonitorService"; class MonitorService { static SERVICE_NAME = SERVICE_NAME; - constructor({ db, settingsService, jobQueue, stringService, emailService, papaparse, logger, errorService, games }) { + constructor({ + db, + settingsService, + jobQueue, + stringService, + emailService, + papaparse, + logger, + errorService, + games, + monitorsRepository, + checksRepository, + }) { this.db = db; this.settingsService = settingsService; this.jobQueue = jobQueue; @@ -14,6 +27,8 @@ class MonitorService { this.logger = logger; this.errorService = errorService; this.games = games; + this.monitorsRepository = monitorsRepository; + this.checksRepository = checksRepository; } get serviceName() { @@ -205,6 +220,7 @@ class MonitorService { }; getMonitorsByTeamId = async ({ teamId, limit, type, page, rowsPerPage, filter, field, order }) => { + console.log("que"); const monitors = await this.db.monitorModule.getMonitorsByTeamId({ limit, type, @@ -228,7 +244,8 @@ class MonitorService { }; getMonitorsWithChecksByTeamId = async ({ teamId, limit, type, page, rowsPerPage, filter, field, order, explain }) => { - const result = await this.db.monitorModule.getMonitorsWithChecksByTeamId({ + const count = await this.monitorsRepository.findMonitorCountByTeamIdAndType(teamId, { type, filter }); + const monitors = await this.monitorsRepository.findByTeamId(teamId, { limit, type, page, @@ -236,10 +253,19 @@ class MonitorService { filter, field, order, - teamId, - explain, }); - return result; + + const monitorIds = monitors?.map((m) => m.id) ?? []; + const checksMap = await this.checksRepository.findLatestChecksByMonitorIds(monitorIds); + const monitorsWithChecks = (monitors ?? []).map((monitor) => { + const checks = NormalizeData(checksMap[monitor.id] ?? [], 10, 100); + return { + ...monitor, + checks, + }; + }); + + return { count, monitors: monitorsWithChecks }; }; exportMonitorsToCSV = async ({ teamId }) => { diff --git a/server/src/service/infrastructure/statusService.js b/server/src/service/infrastructure/statusService.js index 3c1682818..843c356ed 100755 --- a/server/src/service/infrastructure/statusService.js +++ b/server/src/service/infrastructure/statusService.js @@ -1,5 +1,5 @@ import MonitorStats from "../../db/models/MonitorStats.js"; -import Check from "../../db/models/Check.js"; +import { CheckModel } from "@/db/models/index.js"; const SERVICE_NAME = "StatusService"; class StatusService { @@ -115,7 +115,7 @@ class StatusService { if (!check._id) { try { - const checkModel = new Check(check); + const checkModel = new CheckModel(check); savedCheck = await checkModel.save(); this.buffer.removeCheckFromBuffer(check); diff --git a/server/src/types/check.ts b/server/src/types/check.ts index f2ccc0b77..2120d5de1 100644 --- a/server/src/types/check.ts +++ b/server/src/types/check.ts @@ -1,5 +1,11 @@ import type { MonitorType } from "@/types/index.js"; +export interface CheckMetadata { + monitorId: string; + teamId: string; + type: MonitorType; +} + export interface CheckTimingPhases { wait: number; dns: number; @@ -50,29 +56,66 @@ export interface CheckCaptureInfo { mode: string; } -export interface CheckDiskInfo {} +export interface CheckDiskInfo { + device: string; + mountpoint: string; + read_speed_bytes: number; + write_speed_bytes: number; + total_bytes: number; + free_bytes: number; + usage_percent: number; +} -export interface CheckNetInfo {} +export interface CheckErrorInfo { + metric: string[]; + err: string; +} + +export interface CheckNetworkInterfaceInfo { + name: string; + bytes_sent: number; + bytes_recv: number; + packets_sent: number; + packets_recv: number; + err_in: number; + err_out: number; + drop_in: number; + drop_out: number; + fifo_in: number; + fifo_out: number; +} + +export interface CheckAudits { + cls: number; + si: number; + fcp: number; + lcp: number; + tbt: number; +} export interface Check { id: string; - monitorId: string; - teamId: string; - type: MonitorType; + metadata: CheckMetadata; status: boolean; responseTime: number; timings: CheckTimings; statusCode: number; message: string; ack: boolean; + ackAt?: string | null; expiry: string; cpu: CheckCpuInfo; memory: CheckMemoryInfo; disk: CheckDiskInfo[]; host: CheckHostInfo; - errors: string[]; + errors: CheckErrorInfo[]; capture: CheckCaptureInfo; - net: CheckNetInfo[]; + net: CheckNetworkInterfaceInfo[]; + accessibility?: number; + bestPractices?: number; + seo?: number; + performance?: number; + audits?: CheckAudits; __v: number; createdAt: string; updatedAt: string; diff --git a/server/src/types/monitor.ts b/server/src/types/monitor.ts index 6a386c2e4..4c8dc7528 100644 --- a/server/src/types/monitor.ts +++ b/server/src/types/monitor.ts @@ -1,29 +1,46 @@ export const MonitorTypes = ["http", "ping", "pagespeed", "hardware", "docker", "port", "game"] as const; export type MonitorType = (typeof MonitorTypes)[number]; +export interface MonitorThresholds { + usage_cpu?: number; + usage_memory?: number; + usage_disk?: number; + usage_temperature?: number; +} + +export type MonitorMatchMethod = "equal" | "include" | "regex" | ""; + export interface Monitor { id: string; userId: string; teamId: string; name: string; - description: string; + description?: string; + status?: boolean; statusWindow: boolean[]; statusWindowSize: number; statusWindowThreshold: number; type: MonitorType; ignoreTlsErrors: boolean; + jsonPath?: string; + expectedValue?: string; + matchMethod?: MonitorMatchMethod; url: string; + port?: number; isActive: boolean; interval: number; + uptimePercentage?: number; notifications: string[]; + secret?: string; + thresholds?: MonitorThresholds; alertThreshold: number; - selectedDisks: string[]; - group: string | null; cpuAlertThreshold: number; memoryAlertThreshold: number; diskAlertThreshold: number; tempAlertThreshold: number; + selectedDisks: string[]; + gameId?: string; + group: string | null; createdAt: string; updatedAt: string; - status: boolean; } From 25d1039376bdadb05854af79f70fbb16cca87362 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 12 Jan 2026 23:57:39 +0000 Subject: [PATCH 04/24] update pagespeed --- client/package-lock.json | 605 ++++++++++++++++++ .../src/Components/v1/ActionsMenu/index.jsx | 12 +- .../Components/v1/Charts/BarChart/index.jsx | 4 +- .../Components/v1/MonitorActions/index.jsx | 2 +- .../v1/MonitorDetailsControlHeader/index.jsx | 6 +- .../v1/MonitorDetailsControlHeader/status.jsx | 2 +- .../Components/v1/PageStateWrapper/index.jsx | 2 +- client/src/Components/v1/StatBox/index.jsx | 2 +- client/src/Hooks/{v1 => }/checkHooks.js | 4 +- client/src/Hooks/{v1 => }/inviteHooks.js | 2 +- client/src/Hooks/{v1 => }/logHooks.js | 4 +- client/src/Hooks/{v1 => }/monitorHooks.js | 10 +- client/src/Hooks/{v1 => }/settingsHooks.js | 14 +- client/src/Hooks/{v1 => }/useIsAdmin.js | 0 client/src/Hooks/{v1 => }/useMonitorUtils.js | 0 client/src/Hooks/{v1 => }/useNotifications.js | 6 +- client/src/Hooks/{v1 => }/useSendTestEmail.js | 4 +- client/src/Hooks/{v1 => }/userHooks.js | 4 +- client/src/Pages/{v1 => }/About/index.jsx | 0 .../Account/EditUser/hooks/editUser.js | 6 +- .../Pages/{v1 => }/Account/EditUser/index.jsx | 4 +- .../components/AddMemberMenu/index.jsx | 0 .../AddTeamMember/hooks/useAddTeamMember.jsx | 2 +- .../components/AddTeamMember/index.jsx | 4 +- .../components/ChangePasswordModal/index.jsx | 4 +- .../Account/components/PasswordPanel.jsx | 8 +- .../Account/components/ProfilePanel.jsx | 12 +- .../Account/components/RoleTable/index.jsx | 2 +- .../{v1 => }/Account/components/TeamPanel.jsx | 10 +- client/src/Pages/{v1 => }/Account/index.jsx | 0 client/src/Pages/{v1 => }/Auth/CheckEmail.jsx | 10 +- .../Pages/{v1 => }/Auth/ForgotPassword.jsx | 12 +- .../Auth/Login/hooks/useLoadingSubmit.jsx | 0 .../Auth/Login/hooks/useLoginForm.jsx | 0 .../Auth/Login/hooks/useLoginSubmit.jsx | 4 +- .../Auth/Login/hooks/useValidateLoginForm.jsx | 2 +- .../src/Pages/{v1 => }/Auth/Login/index.jsx | 0 .../{v1 => }/Auth/NewPasswordConfirmed.jsx | 8 +- .../Pages/{v1 => }/Auth/Register/index.jsx | 8 +- .../Pages/{v1 => }/Auth/SetNewPassword.jsx | 12 +- .../{v1 => }/Auth/components/AuthHeader.jsx | 4 +- .../Auth/components/AuthPageWrapper.jsx | 4 +- .../Auth/components/PasswordTooltip.jsx | 0 .../Auth/hooks/usePasswordFeedback.jsx | 2 +- .../Auth/hooks/useValidatePassword.jsx | 2 +- client/src/Pages/{v1 => }/Auth/index.css | 0 .../Components/IncidentTable/index.jsx | 6 +- .../Components/OptionsHeader/index.jsx | 0 .../Components/OptionsHeader/skeleton.jsx | 0 .../Components/StatusBoxes/StatusBox.jsx | 10 +- .../Components/StatusBoxes/index.jsx | 0 .../Components/StatusBoxes/skeleton.jsx | 0 client/src/Pages/{v1 => }/Incidents/index.jsx | 5 +- .../Components/ActiveIncidentsPanel/index.jsx | 0 .../Components/IncidentTable/index.jsx | 2 +- .../IncidentsSummaryPanel/index.jsx | 0 .../IncidentsSummaryPanel/skeleton.jsx | 0 .../LatestIncidentsPanel/IncidentItem.jsx | 0 .../Components/LatestIncidentsPanel/index.jsx | 0 .../Components/OptionsHeader/index.jsx | 0 .../Components/StatisticsPanel/index.jsx | 0 .../Components/SummaryCard/index.jsx | 0 .../Incidents2/hooks/useFetchIncidents.js | 4 +- .../src/Pages/{v1 => }/Incidents2/index.jsx | 2 +- .../Create/Components/CustomAlertsSection.jsx | 2 +- .../Components/CustomThreshold/index.jsx | 0 .../Create/Components/DiskSelection.jsx | 0 .../Components/MonitorActionButtons.jsx | 0 .../Create/Components/MonitorStatusHeader.jsx | 2 +- .../hooks/useInfrastructureMonitorForm.jsx | 0 .../Create/hooks/useInfrastructureSubmit.jsx | 5 +- .../hooks/useValidateInfrastructureForm.jsx | 4 +- .../{v1 => }/Infrastructure/Create/index.jsx | 9 +- .../AreaChartBoxes/InfraAreaChart.jsx | 0 .../Components/AreaChartBoxes/index.jsx | 0 .../Components/AreaChartBoxes/skeleton.jsx | 0 .../Components/BaseContainer/index.jsx | 0 .../Details/Components/GaugeBoxes/Gauge.jsx | 0 .../Details/Components/GaugeBoxes/index.jsx | 0 .../Components/GaugeBoxes/skeleton.jsx | 0 .../Components/NetworkStats/NetworkCharts.jsx | 0 .../NetworkStats/NetworkStatBoxes.jsx | 0 .../Details/Components/NetworkStats/index.jsx | 0 .../Components/NetworkStats/skeleton.jsx | 0 .../Details/Components/StatusBoxes/index.jsx | 2 +- .../Details/Hooks/useHardwareUtils.jsx | 0 .../{v1 => }/Infrastructure/Details/index.jsx | 4 +- .../Monitors/Components/Filters/index.jsx | 0 .../Components/MonitorsTable/index.jsx | 4 +- .../Components/MonitorsTableMenu/index.jsx | 8 +- .../Infrastructure/Monitors/index.jsx | 6 +- .../src/Pages/{v1 => }/Integrations/index.css | 0 .../src/Pages/{v1 => }/Integrations/index.jsx | 0 .../Diagnostics/components/gauges/index.jsx | 0 .../Diagnostics/components/stats/index.jsx | 2 +- .../Pages/{v1 => }/Logs/Diagnostics/index.jsx | 12 +- .../{v1 => }/Logs/Diagnostics/utils/utils.js | 0 client/src/Pages/{v1 => }/Logs/Logs/index.jsx | 3 +- .../Queue/components/FailedJobTable/index.jsx | 2 +- .../Logs/Queue/components/JobTable/index.jsx | 2 +- .../Logs/Queue/components/Metrics/index.jsx | 0 .../Queue/components/MetricsTable/index.jsx | 0 .../src/Pages/{v1 => }/Logs/Queue/index.jsx | 2 +- client/src/Pages/{v1 => }/Logs/index.jsx | 0 .../Components/ConfigSelect/index.jsx | 0 .../Components/MonitorList/index.jsx | 0 .../Components/MonitorsConfig/index.jsx | 0 .../hooks/useMaintenanceActions.jsx | 4 +- .../hooks/useMaintenanceData.jsx | 4 +- .../Maintenance/CreateMaintenance/index.css | 0 .../Maintenance/CreateMaintenance/index.jsx | 10 +- .../MaintenanceTable/ActionsMenu/index.jsx | 8 +- .../Maintenance/MaintenanceTable/index.jsx | 4 +- .../src/Pages/{v1 => }/Maintenance/index.css | 0 .../src/Pages/{v1 => }/Maintenance/index.jsx | 2 +- client/src/Pages/{v1 => }/NotFound/index.jsx | 2 +- .../Notifications/components/ActionMenu.jsx | 0 .../{v1 => }/Notifications/create/index.jsx | 6 +- .../Pages/{v1 => }/Notifications/index.jsx | 2 +- .../src/Pages/{v1 => }/Notifications/utils.js | 0 .../Pages/{v1 => }/PageSpeed/Create/index.css | 0 .../Pages/{v1 => }/PageSpeed/Create/index.jsx | 15 +- .../{v1 => }/PageSpeed/Create/skeleton.jsx | 0 .../Details/Components/Charts/AreaChart.jsx | 2 +- .../Components/Charts/AreaChartLegend.jsx | 2 +- .../Details/Components/Charts/PieChart.jsx | 0 .../Components/Charts/PieChartLegend.jsx | 2 +- .../Components/PageSpeedAreaChart/index.jsx | 2 +- .../PageSpeedAreaChart/skeleton.jsx | 0 .../Components/PageSpeedStatusBoxes/index.jsx | 2 +- .../Components/PerformanceReport/index.jsx | 2 +- .../Components/PerformanceReport/skeleton.jsx | 0 .../{v1 => }/PageSpeed/Details/index.jsx | 4 +- .../Monitors/Components/Card/index.jsx | 11 +- .../Monitors/Components/MonitorGrid/index.jsx | 0 .../{v1 => }/PageSpeed/Monitors/index.jsx | 45 +- .../src/Pages/{v1 => }/ServerUnreachable.jsx | 10 +- .../Pages/{v1 => }/Settings/SettingsAbout.jsx | 0 .../Settings/SettingsDemoMonitors.jsx | 0 .../Pages/{v1 => }/Settings/SettingsEmail.jsx | 4 +- .../{v1 => }/Settings/SettingsExport.jsx | 0 .../Settings/SettingsGlobalThresholds.jsx | 0 .../{v1 => }/Settings/SettingsPagespeed.jsx | 0 .../Pages/{v1 => }/Settings/SettingsStats.jsx | 10 - .../{v1 => }/Settings/SettingsTimeZone.jsx | 2 +- .../Pages/{v1 => }/Settings/SettingsUI.jsx | 0 .../Pages/{v1 => }/Settings/SettingsURL.jsx | 0 client/src/Pages/{v1 => }/Settings/index.jsx | 15 +- .../Create/Components/MonitorList/index.jsx | 0 .../Create/Components/Progress/index.jsx | 2 +- .../Create/Components/Skeleton/index.jsx | 0 .../Create/Components/Tabs/ConfigStack.jsx | 0 .../Create/Components/Tabs/Content.jsx | 0 .../Create/Components/Tabs/Settings.jsx | 2 +- .../Create/Components/Tabs/index.jsx | 0 .../Create/Hooks/useCreateStatusPage.jsx | 4 +- .../Create/Hooks/useMonitorsFetch.jsx | 4 +- .../{v1 => }/StatusPage/Create/index.jsx | 6 +- .../Status/Components/AdminLink/index.jsx | 0 .../Components/ControlsHeader/index.jsx | 2 +- .../Status/Components/MonitorsList/index.jsx | 2 +- .../Status/Components/Skeleton/index.jsx | 0 .../Status/Components/StatusBar/index.jsx | 0 .../Status/Hooks/useStatusPageDelete.jsx | 4 +- .../Status/Hooks/useStatusPageFetch.jsx | 6 +- .../{v1 => }/StatusPage/Status/index.jsx | 2 +- .../Components/StatusPagesTable/index.jsx | 0 .../StatusPages/Hooks/useStatusPagesFetch.jsx | 4 +- .../{v1 => }/StatusPage/StatusPages/index.jsx | 2 +- .../{v1 => }/Uptime/BulkImport/Upload.jsx | 0 .../{v1 => }/Uptime/BulkImport/index.jsx | 4 +- .../Pages/{v1 => }/Uptime/Create/index.css | 0 .../Pages/{v1 => }/Uptime/Create/index.jsx | 13 +- .../Pages/{v1 => }/Uptime/Create/skeleton.jsx | 0 .../Details/Components/ChartBoxes/index.jsx | 8 +- .../Components/ChartBoxes/skeleton.jsx | 0 .../Components/Charts/CustomLabels.jsx | 2 +- .../Components/Charts/DownBarChart.jsx | 0 .../Components/Charts/ResponseGaugeChart.jsx | 0 .../Components/Charts/ResponseTimeChart.jsx | 2 +- .../Charts/ResponseTimeChartSkeleton.jsx | 0 .../Details/Components/Charts/UpBarChart.jsx | 0 .../Components/ResponseTable/index.jsx | 4 +- .../Components/ResponseTable/skeleton.jsx | 0 .../Components/UptimeStatusBoxes/index.jsx | 4 +- .../Details/Hooks/useCertificateFetch.jsx | 6 +- .../Pages/{v1 => }/Uptime/Details/index.jsx | 6 +- .../Monitors/Components/Filter/index.jsx | 0 .../Components/LoadingSpinner/index.jsx | 0 .../Components/SearchComponent/index.jsx | 0 .../Monitors/Components/Skeleton/index.jsx | 0 .../Monitors/Components/StatusBoxes/index.jsx | 0 .../Components/StatusBoxes/skeleton.jsx | 0 .../Components/StatusBoxes/statusBox.jsx | 6 +- .../Components/UptimeDataTable/index.jsx | 10 +- .../Uptime/Monitors/Hooks/useDebounce.jsx | 0 .../Pages/{v1 => }/Uptime/Monitors/index.jsx | 8 +- client/src/Routes/index.jsx | 62 +- server/src/db/models/Check.ts | 23 +- server/src/db/modules/checkModule.js | 2 +- server/src/db/modules/monitorModule.js | 1 - server/src/service/business/monitorService.js | 1 - .../service/infrastructure/statusService.js | 28 +- server/src/types/check.ts | 19 +- 204 files changed, 977 insertions(+), 343 deletions(-) rename client/src/Hooks/{v1 => }/checkHooks.js (97%) rename client/src/Hooks/{v1 => }/inviteHooks.js (97%) rename client/src/Hooks/{v1 => }/logHooks.js (96%) rename client/src/Hooks/{v1 => }/monitorHooks.js (98%) rename client/src/Hooks/{v1 => }/settingsHooks.js (89%) rename client/src/Hooks/{v1 => }/useIsAdmin.js (100%) rename client/src/Hooks/{v1 => }/useMonitorUtils.js (100%) rename client/src/Hooks/{v1 => }/useNotifications.js (96%) rename client/src/Hooks/{v1 => }/useSendTestEmail.js (93%) rename client/src/Hooks/{v1 => }/userHooks.js (94%) rename client/src/Pages/{v1 => }/About/index.jsx (100%) rename client/src/Pages/{v1 => }/Account/EditUser/hooks/editUser.js (91%) rename client/src/Pages/{v1 => }/Account/EditUser/index.jsx (96%) rename client/src/Pages/{v1 => }/Account/components/AddMemberMenu/index.jsx (100%) rename client/src/Pages/{v1 => }/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx (93%) rename client/src/Pages/{v1 => }/Account/components/AddTeamMember/index.jsx (97%) rename client/src/Pages/{v1 => }/Account/components/PasswordPanel.jsx (95%) rename client/src/Pages/{v1 => }/Account/components/ProfilePanel.jsx (97%) rename client/src/Pages/{v1 => }/Account/components/RoleTable/index.jsx (93%) rename client/src/Pages/{v1 => }/Account/components/TeamPanel.jsx (96%) rename client/src/Pages/{v1 => }/Account/index.jsx (100%) rename client/src/Pages/{v1 => }/Auth/CheckEmail.jsx (94%) rename client/src/Pages/{v1 => }/Auth/ForgotPassword.jsx (93%) rename client/src/Pages/{v1 => }/Auth/Login/hooks/useLoadingSubmit.jsx (100%) rename client/src/Pages/{v1 => }/Auth/Login/hooks/useLoginForm.jsx (100%) rename client/src/Pages/{v1 => }/Auth/Login/hooks/useLoginSubmit.jsx (87%) rename client/src/Pages/{v1 => }/Auth/Login/hooks/useValidateLoginForm.jsx (90%) rename client/src/Pages/{v1 => }/Auth/Login/index.jsx (100%) rename client/src/Pages/{v1 => }/Auth/NewPasswordConfirmed.jsx (92%) rename client/src/Pages/{v1 => }/Auth/Register/index.jsx (97%) rename client/src/Pages/{v1 => }/Auth/SetNewPassword.jsx (95%) rename client/src/Pages/{v1 => }/Auth/components/AuthHeader.jsx (87%) rename client/src/Pages/{v1 => }/Auth/components/AuthPageWrapper.jsx (93%) rename client/src/Pages/{v1 => }/Auth/components/PasswordTooltip.jsx (100%) rename client/src/Pages/{v1 => }/Auth/hooks/usePasswordFeedback.jsx (96%) rename client/src/Pages/{v1 => }/Auth/hooks/useValidatePassword.jsx (96%) rename client/src/Pages/{v1 => }/Auth/index.css (100%) rename client/src/Pages/{v1 => }/Incidents/Components/IncidentTable/index.jsx (95%) rename client/src/Pages/{v1 => }/Incidents/Components/OptionsHeader/index.jsx (100%) rename client/src/Pages/{v1 => }/Incidents/Components/OptionsHeader/skeleton.jsx (100%) rename client/src/Pages/{v1 => }/Incidents/Components/StatusBoxes/StatusBox.jsx (87%) rename client/src/Pages/{v1 => }/Incidents/Components/StatusBoxes/index.jsx (100%) rename client/src/Pages/{v1 => }/Incidents/Components/StatusBoxes/skeleton.jsx (100%) rename client/src/Pages/{v1 => }/Incidents/index.jsx (93%) rename client/src/Pages/{v1 => }/Incidents2/Components/ActiveIncidentsPanel/index.jsx (100%) rename client/src/Pages/{v1 => }/Incidents2/Components/IncidentTable/index.jsx (98%) rename client/src/Pages/{v1 => }/Incidents2/Components/IncidentsSummaryPanel/index.jsx (100%) rename client/src/Pages/{v1 => }/Incidents2/Components/IncidentsSummaryPanel/skeleton.jsx (100%) rename client/src/Pages/{v1 => }/Incidents2/Components/LatestIncidentsPanel/IncidentItem.jsx (100%) rename client/src/Pages/{v1 => }/Incidents2/Components/LatestIncidentsPanel/index.jsx (100%) rename client/src/Pages/{v1 => }/Incidents2/Components/OptionsHeader/index.jsx (100%) rename client/src/Pages/{v1 => }/Incidents2/Components/StatisticsPanel/index.jsx (100%) rename client/src/Pages/{v1 => }/Incidents2/Components/SummaryCard/index.jsx (100%) rename client/src/Pages/{v1 => }/Incidents2/hooks/useFetchIncidents.js (97%) rename client/src/Pages/{v1 => }/Incidents2/index.jsx (97%) rename client/src/Pages/{v1 => }/Infrastructure/Create/Components/CustomAlertsSection.jsx (96%) rename client/src/Pages/{v1 => }/Infrastructure/Create/Components/CustomThreshold/index.jsx (100%) rename client/src/Pages/{v1 => }/Infrastructure/Create/Components/DiskSelection.jsx (100%) rename client/src/Pages/{v1 => }/Infrastructure/Create/Components/MonitorActionButtons.jsx (100%) rename client/src/Pages/{v1 => }/Infrastructure/Create/Components/MonitorStatusHeader.jsx (95%) rename client/src/Pages/{v1 => }/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx (100%) rename client/src/Pages/{v1 => }/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx (96%) rename client/src/Pages/{v1 => }/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx (85%) rename client/src/Pages/{v1 => }/Infrastructure/Create/index.jsx (97%) rename client/src/Pages/{v1 => }/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx (100%) rename client/src/Pages/{v1 => }/Infrastructure/Details/Components/AreaChartBoxes/index.jsx (100%) rename client/src/Pages/{v1 => }/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx (100%) rename client/src/Pages/{v1 => }/Infrastructure/Details/Components/BaseContainer/index.jsx (100%) rename client/src/Pages/{v1 => }/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx (100%) rename client/src/Pages/{v1 => }/Infrastructure/Details/Components/GaugeBoxes/index.jsx (100%) rename client/src/Pages/{v1 => }/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx (100%) rename client/src/Pages/{v1 => }/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx (100%) rename client/src/Pages/{v1 => }/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx (100%) rename client/src/Pages/{v1 => }/Infrastructure/Details/Components/NetworkStats/index.jsx (100%) rename client/src/Pages/{v1 => }/Infrastructure/Details/Components/NetworkStats/skeleton.jsx (100%) rename client/src/Pages/{v1 => }/Infrastructure/Details/Components/StatusBoxes/index.jsx (97%) rename client/src/Pages/{v1 => }/Infrastructure/Details/Hooks/useHardwareUtils.jsx (100%) rename client/src/Pages/{v1 => }/Infrastructure/Details/index.jsx (96%) rename client/src/Pages/{v1 => }/Infrastructure/Monitors/Components/Filters/index.jsx (100%) rename client/src/Pages/{v1 => }/Infrastructure/Monitors/Components/MonitorsTable/index.jsx (95%) rename client/src/Pages/{v1 => }/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx (94%) rename client/src/Pages/{v1 => }/Infrastructure/Monitors/index.jsx (94%) rename client/src/Pages/{v1 => }/Integrations/index.css (100%) rename client/src/Pages/{v1 => }/Integrations/index.jsx (100%) rename client/src/Pages/{v1 => }/Logs/Diagnostics/components/gauges/index.jsx (100%) rename client/src/Pages/{v1 => }/Logs/Diagnostics/components/stats/index.jsx (96%) rename client/src/Pages/{v1 => }/Logs/Diagnostics/index.jsx (88%) rename client/src/Pages/{v1 => }/Logs/Diagnostics/utils/utils.js (100%) rename client/src/Pages/{v1 => }/Logs/Logs/index.jsx (97%) rename client/src/Pages/{v1 => }/Logs/Queue/components/FailedJobTable/index.jsx (96%) rename client/src/Pages/{v1 => }/Logs/Queue/components/JobTable/index.jsx (97%) rename client/src/Pages/{v1 => }/Logs/Queue/components/Metrics/index.jsx (100%) rename client/src/Pages/{v1 => }/Logs/Queue/components/MetricsTable/index.jsx (100%) rename client/src/Pages/{v1 => }/Logs/Queue/index.jsx (95%) rename client/src/Pages/{v1 => }/Logs/index.jsx (100%) rename client/src/Pages/{v1 => }/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx (100%) rename client/src/Pages/{v1 => }/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx (100%) rename client/src/Pages/{v1 => }/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx (100%) rename client/src/Pages/{v1 => }/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx (93%) rename client/src/Pages/{v1 => }/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx (95%) rename client/src/Pages/{v1 => }/Maintenance/CreateMaintenance/index.css (100%) rename client/src/Pages/{v1 => }/Maintenance/CreateMaintenance/index.jsx (97%) rename client/src/Pages/{v1 => }/Maintenance/MaintenanceTable/ActionsMenu/index.jsx (93%) rename client/src/Pages/{v1 => }/Maintenance/MaintenanceTable/index.jsx (97%) rename client/src/Pages/{v1 => }/Maintenance/index.css (100%) rename client/src/Pages/{v1 => }/Maintenance/index.jsx (98%) rename client/src/Pages/{v1 => }/NotFound/index.jsx (96%) rename client/src/Pages/{v1 => }/Notifications/components/ActionMenu.jsx (100%) rename client/src/Pages/{v1 => }/Notifications/create/index.jsx (97%) rename client/src/Pages/{v1 => }/Notifications/index.jsx (98%) rename client/src/Pages/{v1 => }/Notifications/utils.js (100%) rename client/src/Pages/{v1 => }/PageSpeed/Create/index.css (100%) rename client/src/Pages/{v1 => }/PageSpeed/Create/index.jsx (96%) rename client/src/Pages/{v1 => }/PageSpeed/Create/skeleton.jsx (100%) rename client/src/Pages/{v1 => }/PageSpeed/Details/Components/Charts/AreaChart.jsx (99%) rename client/src/Pages/{v1 => }/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx (94%) rename client/src/Pages/{v1 => }/PageSpeed/Details/Components/Charts/PieChart.jsx (100%) rename client/src/Pages/{v1 => }/PageSpeed/Details/Components/Charts/PieChartLegend.jsx (96%) rename client/src/Pages/{v1 => }/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx (93%) rename client/src/Pages/{v1 => }/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx (100%) rename client/src/Pages/{v1 => }/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx (91%) rename client/src/Pages/{v1 => }/PageSpeed/Details/Components/PerformanceReport/index.jsx (93%) rename client/src/Pages/{v1 => }/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx (100%) rename client/src/Pages/{v1 => }/PageSpeed/Details/index.jsx (95%) rename client/src/Pages/{v1 => }/PageSpeed/Monitors/Components/Card/index.jsx (96%) rename client/src/Pages/{v1 => }/PageSpeed/Monitors/Components/MonitorGrid/index.jsx (100%) rename client/src/Pages/{v1 => }/PageSpeed/Monitors/index.jsx (64%) rename client/src/Pages/{v1 => }/ServerUnreachable.jsx (92%) rename client/src/Pages/{v1 => }/Settings/SettingsAbout.jsx (100%) rename client/src/Pages/{v1 => }/Settings/SettingsDemoMonitors.jsx (100%) rename client/src/Pages/{v1 => }/Settings/SettingsEmail.jsx (98%) rename client/src/Pages/{v1 => }/Settings/SettingsExport.jsx (100%) rename client/src/Pages/{v1 => }/Settings/SettingsGlobalThresholds.jsx (100%) rename client/src/Pages/{v1 => }/Settings/SettingsPagespeed.jsx (100%) rename client/src/Pages/{v1 => }/Settings/SettingsStats.jsx (86%) rename client/src/Pages/{v1 => }/Settings/SettingsTimeZone.jsx (97%) rename client/src/Pages/{v1 => }/Settings/SettingsUI.jsx (100%) rename client/src/Pages/{v1 => }/Settings/SettingsURL.jsx (100%) rename client/src/Pages/{v1 => }/Settings/index.jsx (95%) rename client/src/Pages/{v1 => }/StatusPage/Create/Components/MonitorList/index.jsx (100%) rename client/src/Pages/{v1 => }/StatusPage/Create/Components/Progress/index.jsx (92%) rename client/src/Pages/{v1 => }/StatusPage/Create/Components/Skeleton/index.jsx (100%) rename client/src/Pages/{v1 => }/StatusPage/Create/Components/Tabs/ConfigStack.jsx (100%) rename client/src/Pages/{v1 => }/StatusPage/Create/Components/Tabs/Content.jsx (100%) rename client/src/Pages/{v1 => }/StatusPage/Create/Components/Tabs/Settings.jsx (98%) rename client/src/Pages/{v1 => }/StatusPage/Create/Components/Tabs/index.jsx (100%) rename client/src/Pages/{v1 => }/StatusPage/Create/Hooks/useCreateStatusPage.jsx (83%) rename client/src/Pages/{v1 => }/StatusPage/Create/Hooks/useMonitorsFetch.jsx (88%) rename client/src/Pages/{v1 => }/StatusPage/Create/index.jsx (97%) rename client/src/Pages/{v1 => }/StatusPage/Status/Components/AdminLink/index.jsx (100%) rename client/src/Pages/{v1 => }/StatusPage/Status/Components/ControlsHeader/index.jsx (97%) rename client/src/Pages/{v1 => }/StatusPage/Status/Components/MonitorsList/index.jsx (95%) rename client/src/Pages/{v1 => }/StatusPage/Status/Components/Skeleton/index.jsx (100%) rename client/src/Pages/{v1 => }/StatusPage/Status/Components/StatusBar/index.jsx (100%) rename client/src/Pages/{v1 => }/StatusPage/Status/Hooks/useStatusPageDelete.jsx (89%) rename client/src/Pages/{v1 => }/StatusPage/Status/Hooks/useStatusPageFetch.jsx (87%) rename client/src/Pages/{v1 => }/StatusPage/Status/index.jsx (98%) rename client/src/Pages/{v1 => }/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx (100%) rename client/src/Pages/{v1 => }/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx (86%) rename client/src/Pages/{v1 => }/StatusPage/StatusPages/index.jsx (95%) rename client/src/Pages/{v1 => }/Uptime/BulkImport/Upload.jsx (100%) rename client/src/Pages/{v1 => }/Uptime/BulkImport/index.jsx (95%) rename client/src/Pages/{v1 => }/Uptime/Create/index.css (100%) rename client/src/Pages/{v1 => }/Uptime/Create/index.jsx (98%) rename client/src/Pages/{v1 => }/Uptime/Create/skeleton.jsx (100%) rename client/src/Pages/{v1 => }/Uptime/Details/Components/ChartBoxes/index.jsx (92%) rename client/src/Pages/{v1 => }/Uptime/Details/Components/ChartBoxes/skeleton.jsx (100%) rename client/src/Pages/{v1 => }/Uptime/Details/Components/Charts/CustomLabels.jsx (93%) rename client/src/Pages/{v1 => }/Uptime/Details/Components/Charts/DownBarChart.jsx (100%) rename client/src/Pages/{v1 => }/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx (100%) rename client/src/Pages/{v1 => }/Uptime/Details/Components/Charts/ResponseTimeChart.jsx (89%) rename client/src/Pages/{v1 => }/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx (100%) rename client/src/Pages/{v1 => }/Uptime/Details/Components/Charts/UpBarChart.jsx (100%) rename client/src/Pages/{v1 => }/Uptime/Details/Components/ResponseTable/index.jsx (97%) rename client/src/Pages/{v1 => }/Uptime/Details/Components/ResponseTable/skeleton.jsx (100%) rename client/src/Pages/{v1 => }/Uptime/Details/Components/UptimeStatusBoxes/index.jsx (92%) rename client/src/Pages/{v1 => }/Uptime/Details/Hooks/useCertificateFetch.jsx (84%) rename client/src/Pages/{v1 => }/Uptime/Details/index.jsx (95%) rename client/src/Pages/{v1 => }/Uptime/Monitors/Components/Filter/index.jsx (100%) rename client/src/Pages/{v1 => }/Uptime/Monitors/Components/LoadingSpinner/index.jsx (100%) rename client/src/Pages/{v1 => }/Uptime/Monitors/Components/SearchComponent/index.jsx (100%) rename client/src/Pages/{v1 => }/Uptime/Monitors/Components/Skeleton/index.jsx (100%) rename client/src/Pages/{v1 => }/Uptime/Monitors/Components/StatusBoxes/index.jsx (100%) rename client/src/Pages/{v1 => }/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx (100%) rename client/src/Pages/{v1 => }/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx (90%) rename client/src/Pages/{v1 => }/Uptime/Monitors/Components/UptimeDataTable/index.jsx (97%) rename client/src/Pages/{v1 => }/Uptime/Monitors/Hooks/useDebounce.jsx (100%) rename client/src/Pages/{v1 => }/Uptime/Monitors/index.jsx (96%) diff --git a/client/package-lock.json b/client/package-lock.json index 75dbe43da..e917619ae 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -499,6 +499,262 @@ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", "license": "MIT" }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/linux-x64": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", @@ -515,6 +771,134 @@ "node": ">=18" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", @@ -1696,6 +2080,123 @@ } } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz", + "integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz", + "integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz", + "integrity": "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz", + "integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz", + "integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz", + "integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz", + "integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz", + "integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz", + "integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-arm64-musl": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz", @@ -1709,6 +2210,71 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz", + "integrity": "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz", + "integrity": "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz", + "integrity": "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz", + "integrity": "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz", + "integrity": "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz", @@ -1735,6 +2301,45 @@ "linux" ] }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz", + "integrity": "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz", + "integrity": "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz", + "integrity": "sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", diff --git a/client/src/Components/v1/ActionsMenu/index.jsx b/client/src/Components/v1/ActionsMenu/index.jsx index 229c7c875..5cd0dfa5b 100644 --- a/client/src/Components/v1/ActionsMenu/index.jsx +++ b/client/src/Components/v1/ActionsMenu/index.jsx @@ -12,7 +12,7 @@ import { useNavigate } from "react-router-dom"; import { createToast } from "../../../Utils/toastUtils.jsx"; import PropTypes from "prop-types"; -import { usePauseMonitor, useDeleteMonitor } from "../../../Hooks/v1/monitorHooks.js"; +import { usePauseMonitor, useDeleteMonitor } from "../../../Hooks/monitorHooks.js"; const ActionsMenu = ({ monitor, @@ -31,7 +31,8 @@ const ActionsMenu = ({ const handleRemove = async (event) => { event.preventDefault(); event.stopPropagation(); - let monitor = { _id: actions.id }; + let monitor = { id: actions.id }; + console.log(monitor); await deleteMonitor({ monitor }); updateRowCallback(); }; @@ -39,7 +40,7 @@ const ActionsMenu = ({ const handlePause = async () => { try { setIsLoading(true); - await pauseMonitor({ monitorId: monitor._id }); + await pauseMonitor({ monitorId: monitor.id }); pauseCallback(); } catch (error) { createToast({ body: "Failed to pause monitor." }); @@ -72,7 +73,7 @@ const ActionsMenu = ({ aria-label="monitor actions" onClick={(event) => { event.stopPropagation(); - openMenu(event, monitor._id, monitor.type === "ping" ? null : monitor.url); + openMenu(event, monitor.id, monitor.type === "ping" ? null : monitor.url); }} sx={{ "&:focus": { @@ -197,6 +198,7 @@ const ActionsMenu = ({ confirmationButtonLabel="Delete" /* Do we need stop propagation? */ onConfirm={(e) => { + console.log(e); e.stopPropagation(); handleRemove(e); }} @@ -210,7 +212,7 @@ const ActionsMenu = ({ ActionsMenu.propTypes = { monitor: PropTypes.shape({ - _id: PropTypes.string, + id: PropTypes.string, url: PropTypes.string, type: PropTypes.string, isActive: PropTypes.bool, diff --git a/client/src/Components/v1/Charts/BarChart/index.jsx b/client/src/Components/v1/Charts/BarChart/index.jsx index 2bf8e1798..9f0ed8aa6 100644 --- a/client/src/Components/v1/Charts/BarChart/index.jsx +++ b/client/src/Components/v1/Charts/BarChart/index.jsx @@ -103,7 +103,7 @@ const BarChart = ({ checks = [] }) => { } placement="top" - key={`check-${check?._id}`} + key={`check-${check?.id}`} slotProps={{ popper: { className: "bar-tooltip", @@ -145,7 +145,7 @@ const BarChart = ({ checks = [] }) => { position="relative" width="9px" height="100%" - backgroundColor={theme.palette.primary.lowContrast} // CAIO_REVIEW + backgroundColor={theme.palette.primary.lowContrast} sx={{ borderRadius: theme.spacing(1.5), /* diff --git a/client/src/Components/v1/MonitorActions/index.jsx b/client/src/Components/v1/MonitorActions/index.jsx index 23cebc972..624d34b9a 100644 --- a/client/src/Components/v1/MonitorActions/index.jsx +++ b/client/src/Components/v1/MonitorActions/index.jsx @@ -11,7 +11,7 @@ import MenuList from "@mui/material/MenuList"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { createToast } from "../../../Utils/toastUtils.jsx"; -import { useExportMonitors } from "../../../Hooks/v1/monitorHooks.js"; +import { useExportMonitors } from "../../../Hooks/monitorHooks.js"; const MonitorActions = ({ isLoading }) => { const [open, setOpen] = React.useState(false); diff --git a/client/src/Components/v1/MonitorDetailsControlHeader/index.jsx b/client/src/Components/v1/MonitorDetailsControlHeader/index.jsx index ae565f3bb..bb4399b85 100644 --- a/client/src/Components/v1/MonitorDetailsControlHeader/index.jsx +++ b/client/src/Components/v1/MonitorDetailsControlHeader/index.jsx @@ -12,10 +12,10 @@ import EmailIcon from "@mui/icons-material/Email"; import PropTypes from "prop-types"; import { useNavigate } from "react-router-dom"; import { useTheme } from "@mui/material/styles"; -import { usePauseMonitor } from "../../../Hooks/v1/monitorHooks.js"; -import { useSendTestEmail } from "../../../Hooks/v1/useSendTestEmail.js"; +import { usePauseMonitor } from "../../../Hooks/monitorHooks.js"; +import { useSendTestEmail } from "../../../Hooks/useSendTestEmail.js"; import { useTranslation } from "react-i18next"; -import { useTestAllNotifications } from "../../../Hooks/v1/useNotifications.js"; +import { useTestAllNotifications } from "../../../Hooks/useNotifications.js"; /** * MonitorDetailsControlHeader component displays the control header for monitor details. * It includes status display, pause/resume button, and a configure button for admins. diff --git a/client/src/Components/v1/MonitorDetailsControlHeader/status.jsx b/client/src/Components/v1/MonitorDetailsControlHeader/status.jsx index d9b7afc40..febf449d4 100644 --- a/client/src/Components/v1/MonitorDetailsControlHeader/status.jsx +++ b/client/src/Components/v1/MonitorDetailsControlHeader/status.jsx @@ -7,7 +7,7 @@ import Dot from "../Dot/index.jsx"; import { formatDurationRounded } from "../../../Utils/timeUtils.js"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; -import { useMonitorUtils } from "../../../Hooks/v1/useMonitorUtils.js"; +import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js"; import { formatMonitorUrl } from "../../../Utils/utils.js"; /** * Status component displays the status information of a monitor. diff --git a/client/src/Components/v1/PageStateWrapper/index.jsx b/client/src/Components/v1/PageStateWrapper/index.jsx index ac97704df..08adbe514 100644 --- a/client/src/Components/v1/PageStateWrapper/index.jsx +++ b/client/src/Components/v1/PageStateWrapper/index.jsx @@ -3,7 +3,7 @@ import Fallback from "../Fallback/index.jsx"; import { Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import { useIsAdmin } from "../../../Hooks/v1/useIsAdmin.js"; +import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; import PropTypes from "prop-types"; const PageStateWrapper = ({ networkError, diff --git a/client/src/Components/v1/StatBox/index.jsx b/client/src/Components/v1/StatBox/index.jsx index 1b5f1cf6f..e72f947b3 100644 --- a/client/src/Components/v1/StatBox/index.jsx +++ b/client/src/Components/v1/StatBox/index.jsx @@ -2,7 +2,7 @@ import { Stack, Typography } from "@mui/material"; import Image from "../Image/index.jsx"; import { useTheme } from "@mui/material/styles"; import PropTypes from "prop-types"; -import { useMonitorUtils } from "../../../Hooks/v1/useMonitorUtils.js"; +import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js"; /** * StatBox Component diff --git a/client/src/Hooks/v1/checkHooks.js b/client/src/Hooks/checkHooks.js similarity index 97% rename from client/src/Hooks/v1/checkHooks.js rename to client/src/Hooks/checkHooks.js index 96fd1fe1c..b8af32073 100644 --- a/client/src/Hooks/v1/checkHooks.js +++ b/client/src/Hooks/checkHooks.js @@ -1,6 +1,6 @@ import { useState, useEffect } from "react"; -import { networkService } from "../../main.jsx"; -import { createToast } from "../../Utils/toastUtils.jsx"; +import { networkService } from "../main.jsx"; +import { createToast } from "../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; const useFetchChecksTeam = ({ diff --git a/client/src/Hooks/v1/inviteHooks.js b/client/src/Hooks/inviteHooks.js similarity index 97% rename from client/src/Hooks/v1/inviteHooks.js rename to client/src/Hooks/inviteHooks.js index b6a0851af..c862eec31 100644 --- a/client/src/Hooks/v1/inviteHooks.js +++ b/client/src/Hooks/inviteHooks.js @@ -1,5 +1,5 @@ import { useState } from "react"; -import { networkService } from "../../main.jsx"; +import { networkService } from "../main.jsx"; import { useTranslation } from "react-i18next"; const CLIENT_HOST = import.meta.env.VITE_APP_CLIENT_HOST; diff --git a/client/src/Hooks/v1/logHooks.js b/client/src/Hooks/logHooks.js similarity index 96% rename from client/src/Hooks/v1/logHooks.js rename to client/src/Hooks/logHooks.js index ff4d13872..d5162de5a 100644 --- a/client/src/Hooks/v1/logHooks.js +++ b/client/src/Hooks/logHooks.js @@ -1,6 +1,6 @@ import { useState, useEffect } from "react"; -import { networkService } from "../../main.jsx"; -import { createToast } from "../../Utils/toastUtils.jsx"; +import { networkService } from "../main.jsx"; +import { createToast } from "../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; const useFetchLogs = () => { diff --git a/client/src/Hooks/v1/monitorHooks.js b/client/src/Hooks/monitorHooks.js similarity index 98% rename from client/src/Hooks/v1/monitorHooks.js rename to client/src/Hooks/monitorHooks.js index 8acbea608..ffc356320 100644 --- a/client/src/Hooks/v1/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; -import { networkService } from "../../main.jsx"; -import { createToast } from "../../Utils/toastUtils.jsx"; +import { networkService } from "../main.jsx"; +import { createToast } from "../Utils/toastUtils.jsx"; import { useTheme } from "@emotion/react"; import { useMonitorUtils } from "./useMonitorUtils.js"; import { useNavigate } from "react-router-dom"; @@ -67,12 +67,13 @@ const useFetchMonitorsWithChecks = ({ field, order, }); + const { count, monitors } = res?.data?.data ?? {}; const mappedMonitors = monitors.map((monitor) => getMonitorWithPercentage(monitor, theme) ); setMonitors(mappedMonitors); - setCount(count?.monitorsCount ?? 0); + setCount(count || 0); } catch (error) { console.error(error); setNetworkError(true); @@ -347,7 +348,8 @@ const useDeleteMonitor = () => { const deleteMonitor = async ({ monitor, redirect }) => { try { setIsLoading(true); - await networkService.deleteMonitorById({ monitorId: monitor._id }); + console.log(monitor); + await networkService.deleteMonitorById({ monitorId: monitor.id }); createToast({ body: "Monitor deleted successfully!" }); if (redirect) { navigate(redirect); diff --git a/client/src/Hooks/v1/settingsHooks.js b/client/src/Hooks/settingsHooks.js similarity index 89% rename from client/src/Hooks/v1/settingsHooks.js rename to client/src/Hooks/settingsHooks.js index 787e46ff9..8c4bef385 100644 --- a/client/src/Hooks/v1/settingsHooks.js +++ b/client/src/Hooks/settingsHooks.js @@ -1,6 +1,6 @@ import { useState, useEffect } from "react"; -import { networkService } from "../../main.jsx"; -import { createToast } from "../../Utils/toastUtils.jsx"; +import { networkService } from "../main.jsx"; +import { createToast } from "../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; const useFetchSettings = ({ setSettingsData, setIsApiKeySet, setIsEmailPasswordSet }) => { @@ -42,11 +42,11 @@ const useSaveSettings = ({ setIsLoading(true); try { const settingsResponse = await networkService.updateAppSettings({ settings }); - if (settings.checkTTL) { - await networkService.updateChecksTTL({ - ttl: settings.checkTTL, - }); - } + // if (settings.checkTTL) { + // await networkService.updateChecksTTL({ + // ttl: settings.checkTTL, + // }); + // } setIsApiKeySet(settingsResponse.data.data.pagespeedKeySet); setIsEmailPasswordSet(settingsResponse.data.data.emailPasswordSet); if (settingsResponse.data.data.pagespeedKeySet === true) { diff --git a/client/src/Hooks/v1/useIsAdmin.js b/client/src/Hooks/useIsAdmin.js similarity index 100% rename from client/src/Hooks/v1/useIsAdmin.js rename to client/src/Hooks/useIsAdmin.js diff --git a/client/src/Hooks/v1/useMonitorUtils.js b/client/src/Hooks/useMonitorUtils.js similarity index 100% rename from client/src/Hooks/v1/useMonitorUtils.js rename to client/src/Hooks/useMonitorUtils.js diff --git a/client/src/Hooks/v1/useNotifications.js b/client/src/Hooks/useNotifications.js similarity index 96% rename from client/src/Hooks/v1/useNotifications.js rename to client/src/Hooks/useNotifications.js index d1b4a6652..5f2058b6a 100644 --- a/client/src/Hooks/v1/useNotifications.js +++ b/client/src/Hooks/useNotifications.js @@ -1,9 +1,9 @@ import { useState, useEffect, useCallback } from "react"; -import { createToast } from "../../Utils/toastUtils.jsx"; -import { networkService } from "../../main.jsx"; +import { createToast } from "../Utils/toastUtils.jsx"; +import { networkService } from "../main.jsx"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; -import { NOTIFICATION_TYPES } from "../../Pages/v1/Notifications/utils.js"; +import { NOTIFICATION_TYPES } from "../Pages/Notifications/utils.js"; const useCreateNotification = () => { const navigate = useNavigate(); diff --git a/client/src/Hooks/v1/useSendTestEmail.js b/client/src/Hooks/useSendTestEmail.js similarity index 93% rename from client/src/Hooks/v1/useSendTestEmail.js rename to client/src/Hooks/useSendTestEmail.js index 0be77954a..7223a489a 100644 --- a/client/src/Hooks/v1/useSendTestEmail.js +++ b/client/src/Hooks/useSendTestEmail.js @@ -1,7 +1,7 @@ import { useState } from "react"; -import { networkService } from "../../main.jsx"; +import { networkService } from "../main.jsx"; import { useSelector } from "react-redux"; -import { createToast } from "../../Utils/toastUtils.jsx"; +import { createToast } from "../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; const useSendTestEmail = () => { diff --git a/client/src/Hooks/v1/userHooks.js b/client/src/Hooks/userHooks.js similarity index 94% rename from client/src/Hooks/v1/userHooks.js rename to client/src/Hooks/userHooks.js index f9c454450..3d822f979 100644 --- a/client/src/Hooks/v1/userHooks.js +++ b/client/src/Hooks/userHooks.js @@ -1,6 +1,6 @@ import { useEffect, useState, useCallback } from "react"; -import { networkService } from "../../main.jsx"; -import { createToast } from "../../Utils/toastUtils.jsx"; +import { networkService } from "../main.jsx"; +import { createToast } from "../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; export const useGetUser = (userId) => { diff --git a/client/src/Pages/v1/About/index.jsx b/client/src/Pages/About/index.jsx similarity index 100% rename from client/src/Pages/v1/About/index.jsx rename to client/src/Pages/About/index.jsx diff --git a/client/src/Pages/v1/Account/EditUser/hooks/editUser.js b/client/src/Pages/Account/EditUser/hooks/editUser.js similarity index 91% rename from client/src/Pages/v1/Account/EditUser/hooks/editUser.js rename to client/src/Pages/Account/EditUser/hooks/editUser.js index 00660aead..aeb50ffff 100644 --- a/client/src/Pages/v1/Account/EditUser/hooks/editUser.js +++ b/client/src/Pages/Account/EditUser/hooks/editUser.js @@ -1,8 +1,8 @@ import { useState, useEffect } from "react"; -import { ROLES } from "../../../../../Utils/roleUtils.js"; -import { editUserValidation } from "../../../../../Validation/validation.js"; +import { ROLES } from "../../../../Utils/roleUtils.js"; +import { editUserValidation } from "../../../../Validation/validation.js"; import Joi from "joi"; -import { createToast } from "../../../../../Utils/toastUtils.jsx"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; export const useEditUserForm = (user) => { const [searchInput, setSearchInput] = useState(""); diff --git a/client/src/Pages/v1/Account/EditUser/index.jsx b/client/src/Pages/Account/EditUser/index.jsx similarity index 96% rename from client/src/Pages/v1/Account/EditUser/index.jsx rename to client/src/Pages/Account/EditUser/index.jsx index 674a522b8..2b2ac5128 100644 --- a/client/src/Pages/v1/Account/EditUser/index.jsx +++ b/client/src/Pages/Account/EditUser/index.jsx @@ -11,8 +11,8 @@ import ChangePasswordModal from "@/Pages/Account/components/ChangePasswordModal/ import { useParams } from "react-router-dom"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import { useGetUser, useEditUser } from "../../../../Hooks/v1/userHooks.js"; -import { EDITABLE_ROLES, ROLES } from "../../../../Utils/roleUtils.js"; +import { useGetUser, useEditUser } from "../../../Hooks/userHooks.js"; +import { EDITABLE_ROLES, ROLES } from "../../../Utils/roleUtils.js"; import { useEditUserForm, useValidateEditUserForm } from "./hooks/editUser.js"; import { useSelector } from "react-redux"; diff --git a/client/src/Pages/v1/Account/components/AddMemberMenu/index.jsx b/client/src/Pages/Account/components/AddMemberMenu/index.jsx similarity index 100% rename from client/src/Pages/v1/Account/components/AddMemberMenu/index.jsx rename to client/src/Pages/Account/components/AddMemberMenu/index.jsx diff --git a/client/src/Pages/v1/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx b/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx similarity index 93% rename from client/src/Pages/v1/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx rename to client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx index 58db019a3..1237777ff 100644 --- a/client/src/Pages/v1/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx +++ b/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { newOrChangedCredentials } from "../../../../../../Validation/validation.js"; +import { newOrChangedCredentials } from "../../../../../Validation/validation.js"; import { useTranslation } from "react-i18next"; const useAddTeamMember = () => { const { t } = useTranslation(); diff --git a/client/src/Pages/v1/Account/components/AddTeamMember/index.jsx b/client/src/Pages/Account/components/AddTeamMember/index.jsx similarity index 97% rename from client/src/Pages/v1/Account/components/AddTeamMember/index.jsx rename to client/src/Pages/Account/components/AddTeamMember/index.jsx index 219658b63..e5280af16 100644 --- a/client/src/Pages/v1/Account/components/AddTeamMember/index.jsx +++ b/client/src/Pages/Account/components/AddTeamMember/index.jsx @@ -2,10 +2,10 @@ import { Button, Stack } from "@mui/material"; import { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx"; import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import Select from "@/Components/v1/Inputs/Select/index.jsx"; -import { useGetInviteToken } from "../../../../../Hooks/v1/inviteHooks.js"; +import { useGetInviteToken } from "../../../../Hooks/inviteHooks.js"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import { createToast } from "../../../../../Utils/toastUtils.jsx"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; import { useState } from "react"; import PasswordTooltip from "../../../Auth/components/PasswordTooltip.jsx"; import useAddTeamMember from "./hooks/useAddTeamMember.jsx"; diff --git a/client/src/Pages/Account/components/ChangePasswordModal/index.jsx b/client/src/Pages/Account/components/ChangePasswordModal/index.jsx index cc284a675..725a9b00e 100644 --- a/client/src/Pages/Account/components/ChangePasswordModal/index.jsx +++ b/client/src/Pages/Account/components/ChangePasswordModal/index.jsx @@ -2,12 +2,12 @@ import { useState } from "react"; import { Button, Stack } from "@mui/material"; import { GenericDialog } from "@/Components/v1/Dialog/genericDialog"; import TextInput from "@/Components/v1/Inputs/TextInput"; -import PasswordTooltip from "@/Pages/v1/Auth/components/PasswordTooltip"; +import PasswordTooltip from "@/Pages/Auth/components/PasswordTooltip"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import { createToast } from "../../../../Utils/toastUtils"; import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments"; -import usePasswordFeedback from "@/Pages/v1/Auth/hooks/usePasswordFeedback"; +import usePasswordFeedback from "@/Pages/Auth/hooks/usePasswordFeedback"; import PropTypes from "prop-types"; const ChangePasswordModal = ({ isSaving, isLoading, changePassword }) => { diff --git a/client/src/Pages/v1/Account/components/PasswordPanel.jsx b/client/src/Pages/Account/components/PasswordPanel.jsx similarity index 95% rename from client/src/Pages/v1/Account/components/PasswordPanel.jsx rename to client/src/Pages/Account/components/PasswordPanel.jsx index ca2af9205..f54c61a16 100644 --- a/client/src/Pages/v1/Account/components/PasswordPanel.jsx +++ b/client/src/Pages/Account/components/PasswordPanel.jsx @@ -4,12 +4,12 @@ import { useTheme } from "@emotion/react"; import { Box, Stack, Typography, Button } from "@mui/material"; import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; -import { newOrChangedCredentials } from "../../../../Validation/validation.js"; +import { newOrChangedCredentials } from "../../../Validation/validation.js"; import Alert from "@/Components/v1/Alert/index.jsx"; -import { update } from "../../../../Features/Auth/authSlice.js"; +import { update } from "../../../Features/Auth/authSlice.js"; import { useDispatch, useSelector } from "react-redux"; -import { createToast } from "../../../../Utils/toastUtils.jsx"; -import { getTouchedFieldErrors } from "../../../../Validation/error.js"; +import { createToast } from "../../../Utils/toastUtils.jsx"; +import { getTouchedFieldErrors } from "../../../Validation/error.js"; import { useTranslation } from "react-i18next"; const defaultPasswordsState = { diff --git a/client/src/Pages/v1/Account/components/ProfilePanel.jsx b/client/src/Pages/Account/components/ProfilePanel.jsx similarity index 97% rename from client/src/Pages/v1/Account/components/ProfilePanel.jsx rename to client/src/Pages/Account/components/ProfilePanel.jsx index 2035861a2..d59923ae4 100644 --- a/client/src/Pages/v1/Account/components/ProfilePanel.jsx +++ b/client/src/Pages/Account/components/ProfilePanel.jsx @@ -5,15 +5,11 @@ import { Box, Button, Divider, Stack, Typography } from "@mui/material"; import Avatar from "@/Components/v1/Avatar/index.jsx"; import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import ImageUpload from "@/Components/v1/Inputs/ImageUpload/index.jsx"; -import { newOrChangedCredentials } from "../../../../Validation/validation.js"; +import { newOrChangedCredentials } from "../../../Validation/validation.js"; import { useDispatch, useSelector } from "react-redux"; -import { - clearAuthState, - deleteUser, - update, -} from "../../../../Features/Auth/authSlice.js"; -import { createToast } from "../../../../Utils/toastUtils.jsx"; -import { logger } from "../../../../Utils/Logger.js"; +import { clearAuthState, deleteUser, update } from "../../../Features/Auth/authSlice.js"; +import { createToast } from "../../../Utils/toastUtils.jsx"; +import { logger } from "../../../Utils/Logger.js"; import { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx"; import Dialog from "@/Components/v1/Dialog/index.jsx"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Account/components/RoleTable/index.jsx b/client/src/Pages/Account/components/RoleTable/index.jsx similarity index 93% rename from client/src/Pages/v1/Account/components/RoleTable/index.jsx rename to client/src/Pages/Account/components/RoleTable/index.jsx index 02dcaaf0d..5eea33685 100644 --- a/client/src/Pages/v1/Account/components/RoleTable/index.jsx +++ b/client/src/Pages/Account/components/RoleTable/index.jsx @@ -3,7 +3,7 @@ import DataTable from "@/Components/v1/Table/index.jsx"; import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded"; import { useTranslation } from "react-i18next"; -import { ROLES } from "../../../../../Utils/roleUtils.js"; +import { ROLES } from "../../../../Utils/roleUtils.js"; const RoleTable = ({ roles, handleDeleteRole }) => { const { t } = useTranslation(); diff --git a/client/src/Pages/v1/Account/components/TeamPanel.jsx b/client/src/Pages/Account/components/TeamPanel.jsx similarity index 96% rename from client/src/Pages/v1/Account/components/TeamPanel.jsx rename to client/src/Pages/Account/components/TeamPanel.jsx index 02918eb30..03a6145a5 100644 --- a/client/src/Pages/v1/Account/components/TeamPanel.jsx +++ b/client/src/Pages/Account/components/TeamPanel.jsx @@ -4,16 +4,16 @@ import { Button, ButtonGroup, Stack, Typography } from "@mui/material"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; -import { newOrChangedCredentials } from "../../../../Validation/validation.js"; -import { networkService } from "../../../../main.jsx"; -import { createToast } from "../../../../Utils/toastUtils.jsx"; +import { newOrChangedCredentials } from "../../../Validation/validation.js"; +import { networkService } from "../../../main.jsx"; +import { createToast } from "../../../Utils/toastUtils.jsx"; import Select from "@/Components/v1/Inputs/Select/index.jsx"; import { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx"; import AddTeamMember from "./AddTeamMember/index.jsx"; import DataTable from "@/Components/v1/Table/index.jsx"; -import { useGetInviteToken } from "../../../../Hooks/v1/inviteHooks.js"; +import { useGetInviteToken } from "../../../Hooks/inviteHooks.js"; import { useNavigate } from "react-router-dom"; -import { useIsSuperAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; +import { useIsSuperAdmin } from "@/Hooks/useIsAdmin.js"; import AddMemberMenu from "./AddMemberMenu/index.jsx"; /** * TeamPanel component manages the organization and team members, diff --git a/client/src/Pages/v1/Account/index.jsx b/client/src/Pages/Account/index.jsx similarity index 100% rename from client/src/Pages/v1/Account/index.jsx rename to client/src/Pages/Account/index.jsx diff --git a/client/src/Pages/v1/Auth/CheckEmail.jsx b/client/src/Pages/Auth/CheckEmail.jsx similarity index 94% rename from client/src/Pages/v1/Auth/CheckEmail.jsx rename to client/src/Pages/Auth/CheckEmail.jsx index c12a84804..6aee7a470 100644 --- a/client/src/Pages/v1/Auth/CheckEmail.jsx +++ b/client/src/Pages/Auth/CheckEmail.jsx @@ -3,12 +3,12 @@ import { useEffect, useState } from "react"; import { useTheme } from "@emotion/react"; import { useDispatch } from "react-redux"; import { useNavigate } from "react-router-dom"; -import { createToast } from "../../../Utils/toastUtils.jsx"; -import { forgotPassword } from "../../../Features/Auth/authSlice.js"; +import { createToast } from "../../Utils/toastUtils.jsx"; +import { forgotPassword } from "../../Features/Auth/authSlice.js"; import { Trans, useTranslation } from "react-i18next"; -import Background from "../../../assets/Images/background-grid.svg?react"; -import EmailIcon from "../../../assets/icons/email.svg?react"; -import Logo from "../../../assets/icons/checkmate-icon.svg?react"; +import Background from "@/assets/Images/background-grid.svg?react"; +import EmailIcon from "@/assets/icons/email.svg?react"; +import Logo from "@/assets/icons/checkmate-icon.svg?react"; import IconBox from "@/Components/v1/IconBox/index.jsx"; import "./index.css"; diff --git a/client/src/Pages/v1/Auth/ForgotPassword.jsx b/client/src/Pages/Auth/ForgotPassword.jsx similarity index 93% rename from client/src/Pages/v1/Auth/ForgotPassword.jsx rename to client/src/Pages/Auth/ForgotPassword.jsx index 8cbd85d8a..0a89b2987 100644 --- a/client/src/Pages/v1/Auth/ForgotPassword.jsx +++ b/client/src/Pages/Auth/ForgotPassword.jsx @@ -1,15 +1,15 @@ import { Box, Stack, Typography, Button } from "@mui/material"; import { useTheme } from "@emotion/react"; -import { createToast } from "../../../Utils/toastUtils.jsx"; +import { createToast } from "../../Utils/toastUtils.jsx"; import { useDispatch, useSelector } from "react-redux"; -import { forgotPassword } from "../../../Features/Auth/authSlice.js"; +import { forgotPassword } from "../../Features/Auth/authSlice.js"; import { useEffect, useState } from "react"; -import { newOrChangedCredentials } from "../../../Validation/validation.js"; +import { newOrChangedCredentials } from "../../Validation/validation.js"; import { useNavigate } from "react-router-dom"; import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; -import Logo from "../../../assets/icons/checkmate-icon.svg?react"; -import Key from "../../../assets/icons/key.svg?react"; -import Background from "../../../assets/Images/background-grid.svg?react"; +import Logo from "@/assets/icons/checkmate-icon.svg?react"; +import Key from "@/assets/icons/key.svg?react"; +import Background from "@/assets/Images/background-grid.svg?react"; import IconBox from "@/Components/v1/IconBox/index.jsx"; import { Trans, useTranslation } from "react-i18next"; import "./index.css"; diff --git a/client/src/Pages/v1/Auth/Login/hooks/useLoadingSubmit.jsx b/client/src/Pages/Auth/Login/hooks/useLoadingSubmit.jsx similarity index 100% rename from client/src/Pages/v1/Auth/Login/hooks/useLoadingSubmit.jsx rename to client/src/Pages/Auth/Login/hooks/useLoadingSubmit.jsx diff --git a/client/src/Pages/v1/Auth/Login/hooks/useLoginForm.jsx b/client/src/Pages/Auth/Login/hooks/useLoginForm.jsx similarity index 100% rename from client/src/Pages/v1/Auth/Login/hooks/useLoginForm.jsx rename to client/src/Pages/Auth/Login/hooks/useLoginForm.jsx diff --git a/client/src/Pages/v1/Auth/Login/hooks/useLoginSubmit.jsx b/client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx similarity index 87% rename from client/src/Pages/v1/Auth/Login/hooks/useLoginSubmit.jsx rename to client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx index 0eed9bd07..b009793d5 100644 --- a/client/src/Pages/v1/Auth/Login/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.js"; +import { login } from "../../../../Features/Auth/authSlice.js"; import { useNavigate } from "react-router-dom"; -import { createToast } from "../../../../../Utils/toastUtils.jsx"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; const useLoginSubmit = () => { diff --git a/client/src/Pages/v1/Auth/Login/hooks/useValidateLoginForm.jsx b/client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx similarity index 90% rename from client/src/Pages/v1/Auth/Login/hooks/useValidateLoginForm.jsx rename to client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx index ca9bef879..0872213ad 100644 --- a/client/src/Pages/v1/Auth/Login/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.js"; +import { loginCredentials } from "../../../../Validation/validation.js"; const useValidateLoginForm = () => { const [errors, setErrors] = useState({ diff --git a/client/src/Pages/v1/Auth/Login/index.jsx b/client/src/Pages/Auth/Login/index.jsx similarity index 100% rename from client/src/Pages/v1/Auth/Login/index.jsx rename to client/src/Pages/Auth/Login/index.jsx diff --git a/client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx b/client/src/Pages/Auth/NewPasswordConfirmed.jsx similarity index 92% rename from client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx rename to client/src/Pages/Auth/NewPasswordConfirmed.jsx index 629494098..53b352853 100644 --- a/client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx +++ b/client/src/Pages/Auth/NewPasswordConfirmed.jsx @@ -2,10 +2,10 @@ import { Box, Button, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; import { useDispatch } from "react-redux"; -import { clearAuthState } from "../../../Features/Auth/authSlice.js"; -import Background from "../../../assets/Images/background-grid.svg?react"; -import ConfirmIcon from "../../../assets/icons/check-outlined.svg?react"; -import Logo from "../../../assets/icons/checkmate-icon.svg?react"; +import { clearAuthState } from "../../Features/Auth/authSlice.js"; +import Background from "@/assets/Images/background-grid.svg?react"; +import ConfirmIcon from "@/assets/icons/check-outlined.svg?react"; +import Logo from "@/assets/icons/checkmate-icon.svg?react"; import IconBox from "@/Components/v1/IconBox/index.jsx"; import { Trans, useTranslation } from "react-i18next"; import "./index.css"; diff --git a/client/src/Pages/v1/Auth/Register/index.jsx b/client/src/Pages/Auth/Register/index.jsx similarity index 97% rename from client/src/Pages/v1/Auth/Register/index.jsx rename to client/src/Pages/Auth/Register/index.jsx index d6515b85c..3345d8aa0 100644 --- a/client/src/Pages/v1/Auth/Register/index.jsx +++ b/client/src/Pages/Auth/Register/index.jsx @@ -13,11 +13,11 @@ import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { useDispatch, useSelector } from "react-redux"; import { useParams } from "react-router-dom"; -import { networkService } from "../../../../main.jsx"; -import { newOrChangedCredentials } from "../../../../Validation/validation.js"; -import { register } from "../../../../Features/Auth/authSlice.js"; +import { networkService } from "../../../main.jsx"; +import { newOrChangedCredentials } from "../../../Validation/validation.js"; +import { register } from "../../../Features/Auth/authSlice.js"; import AuthPageWrapper from "../components/AuthPageWrapper.jsx"; -import { createToast } from "../../../../Utils/toastUtils.jsx"; +import { createToast } from "../../../Utils/toastUtils.jsx"; import PropTypes from "prop-types"; const getFeedbackStatus = (form, errors, field, criteria) => { diff --git a/client/src/Pages/v1/Auth/SetNewPassword.jsx b/client/src/Pages/Auth/SetNewPassword.jsx similarity index 95% rename from client/src/Pages/v1/Auth/SetNewPassword.jsx rename to client/src/Pages/Auth/SetNewPassword.jsx index 45f3351f3..6525c3f27 100644 --- a/client/src/Pages/v1/Auth/SetNewPassword.jsx +++ b/client/src/Pages/Auth/SetNewPassword.jsx @@ -4,16 +4,16 @@ import { useNavigate } from "react-router-dom"; import { useParams } from "react-router-dom"; import { useTheme } from "@emotion/react"; import { Box, Stack, Typography, Button } from "@mui/material"; -import { setNewPassword } from "../../../Features/Auth/authSlice.js"; -import { createToast } from "../../../Utils/toastUtils.jsx"; -import { newOrChangedCredentials } from "../../../Validation/validation.js"; +import { setNewPassword } from "../../Features/Auth/authSlice.js"; +import { createToast } from "../../Utils/toastUtils.jsx"; +import { newOrChangedCredentials } from "../../Validation/validation.js"; import Check from "@/Components/v1/Check/Check.jsx"; import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; import IconBox from "@/Components/v1/IconBox/index.jsx"; -import LockIcon from "../../../assets/icons/lock.svg?react"; -import Logo from "../../../assets/icons/checkmate-icon.svg?react"; -import Background from "../../../assets/Images/background-grid.svg?react"; +import LockIcon from "@/assets/icons/lock.svg?react"; +import Logo from "@/assets/icons/checkmate-icon.svg?react"; +import Background from "@/assets/Images/background-grid.svg?react"; import "./index.css"; import { useValidatePassword } from "./hooks/useValidatePassword.jsx"; import { Trans, useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Auth/components/AuthHeader.jsx b/client/src/Pages/Auth/components/AuthHeader.jsx similarity index 87% rename from client/src/Pages/v1/Auth/components/AuthHeader.jsx rename to client/src/Pages/Auth/components/AuthHeader.jsx index a34e83d6a..56b69ef14 100644 --- a/client/src/Pages/v1/Auth/components/AuthHeader.jsx +++ b/client/src/Pages/Auth/components/AuthHeader.jsx @@ -1,8 +1,8 @@ // Components import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import Logo from "../../../../assets/icons/checkmate-icon.svg?react"; -import LanguageSelector from "../../../../Components/LanguageSelector.jsx"; +import Logo from "@/assets/icons/checkmate-icon.svg?react"; +import LanguageSelector from "@/Components/LanguageSelector.jsx"; import ThemeSwitch from "@/Components/v1/ThemeSwitch/index.jsx"; // Utils diff --git a/client/src/Pages/v1/Auth/components/AuthPageWrapper.jsx b/client/src/Pages/Auth/components/AuthPageWrapper.jsx similarity index 93% rename from client/src/Pages/v1/Auth/components/AuthPageWrapper.jsx rename to client/src/Pages/Auth/components/AuthPageWrapper.jsx index 7e358e6ab..a5fbe311b 100644 --- a/client/src/Pages/v1/Auth/components/AuthPageWrapper.jsx +++ b/client/src/Pages/Auth/components/AuthPageWrapper.jsx @@ -1,10 +1,10 @@ -import Background from "../../../../assets/Images/background-grid.svg?react"; +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 "./AuthHeader.jsx"; import { useTheme } from "@mui/material/styles"; -import Logo from "../../../../assets/icons/checkmate-icon.svg?react"; +import Logo from "@/assets/icons/checkmate-icon.svg?react"; import PropTypes from "prop-types"; const AuthPageWrapper = ({ children, heading, welcome }) => { diff --git a/client/src/Pages/v1/Auth/components/PasswordTooltip.jsx b/client/src/Pages/Auth/components/PasswordTooltip.jsx similarity index 100% rename from client/src/Pages/v1/Auth/components/PasswordTooltip.jsx rename to client/src/Pages/Auth/components/PasswordTooltip.jsx diff --git a/client/src/Pages/v1/Auth/hooks/usePasswordFeedback.jsx b/client/src/Pages/Auth/hooks/usePasswordFeedback.jsx similarity index 96% rename from client/src/Pages/v1/Auth/hooks/usePasswordFeedback.jsx rename to client/src/Pages/Auth/hooks/usePasswordFeedback.jsx index 3c199e25f..73e8fb738 100644 --- a/client/src/Pages/v1/Auth/hooks/usePasswordFeedback.jsx +++ b/client/src/Pages/Auth/hooks/usePasswordFeedback.jsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { newOrChangedCredentials } from "../../../../Validation/validation.js"; +import { newOrChangedCredentials } from "../../../Validation/validation.js"; const usePasswordFeedback = () => { const [feedback, setFeedback] = useState({}); diff --git a/client/src/Pages/v1/Auth/hooks/useValidatePassword.jsx b/client/src/Pages/Auth/hooks/useValidatePassword.jsx similarity index 96% rename from client/src/Pages/v1/Auth/hooks/useValidatePassword.jsx rename to client/src/Pages/Auth/hooks/useValidatePassword.jsx index 231b8eeb8..014804d35 100644 --- a/client/src/Pages/v1/Auth/hooks/useValidatePassword.jsx +++ b/client/src/Pages/Auth/hooks/useValidatePassword.jsx @@ -1,5 +1,5 @@ import { useMemo, useState } from "react"; -import { newOrChangedCredentials } from "../../../../Validation/validation.js"; +import { newOrChangedCredentials } from "../../../Validation/validation.js"; const getFeedbackStatus = (form, errors, field, criteria) => { const fieldErrors = errors[field]; diff --git a/client/src/Pages/v1/Auth/index.css b/client/src/Pages/Auth/index.css similarity index 100% rename from client/src/Pages/v1/Auth/index.css rename to client/src/Pages/Auth/index.css diff --git a/client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx b/client/src/Pages/Incidents/Components/IncidentTable/index.jsx similarity index 95% rename from client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx rename to client/src/Pages/Incidents/Components/IncidentTable/index.jsx index df457c0e2..78d6d26be 100644 --- a/client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx +++ b/client/src/Pages/Incidents/Components/IncidentTable/index.jsx @@ -13,14 +13,12 @@ import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx"; //Utils -import { formatDateWithTz } from "../../../../../Utils/timeUtils.js"; +import { formatDateWithTz } from "@/Utils/timeUtils.js"; import { useSelector } from "react-redux"; import { useState } from "react"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; -import { useFetchChecksTeam } from "../../../../../Hooks/v1/checkHooks.js"; -import { useFetchChecksByMonitor } from "../../../../../Hooks/v1/checkHooks.js"; -import { useResolveIncident } from "../../../../../Hooks/v1/checkHooks.js"; +import { useFetchChecksTeam, useFetchChecksByMonitor, useResolveIncident } from "@/Hooks/checkHooks.js"; import { Button, Typography, useTheme } from "@mui/material"; import { lighten } from "@mui/material/styles"; diff --git a/client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx b/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx similarity index 100% rename from client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx rename to client/src/Pages/Incidents/Components/OptionsHeader/index.jsx diff --git a/client/src/Pages/v1/Incidents/Components/OptionsHeader/skeleton.jsx b/client/src/Pages/Incidents/Components/OptionsHeader/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/Incidents/Components/OptionsHeader/skeleton.jsx rename to client/src/Pages/Incidents/Components/OptionsHeader/skeleton.jsx diff --git a/client/src/Pages/v1/Incidents/Components/StatusBoxes/StatusBox.jsx b/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx similarity index 87% rename from client/src/Pages/v1/Incidents/Components/StatusBoxes/StatusBox.jsx rename to client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx index cd7078e6c..c27c5948c 100644 --- a/client/src/Pages/v1/Incidents/Components/StatusBoxes/StatusBox.jsx +++ b/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx @@ -1,11 +1,11 @@ import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { Box, Stack, Typography } from "@mui/material"; -import Background from "../../../../../assets/Images/background-grid.svg?react"; -import AlertIcon from "../../../../../assets/icons/alert-icon.svg?react"; -import CheckIcon from "../../../../../assets/icons/check-icon.svg?react"; -import CloseIcon from "../../../../../assets/icons/close-icon.svg?react"; -import WarningIcon from "../../../../../assets/icons/warning-icon.svg?react"; +import Background from "@/assets/Images/background-grid.svg?react"; +import AlertIcon from "@/assets/icons/alert-icon.svg?react"; +import CheckIcon from "@/assets/icons/check-icon.svg?react"; +import CloseIcon from "@/assets/icons/close-icon.svg?react"; +import WarningIcon from "@/assets/icons/warning-icon.svg?react"; const StatusBox = ({ title, value, status }) => { const theme = useTheme(); diff --git a/client/src/Pages/v1/Incidents/Components/StatusBoxes/index.jsx b/client/src/Pages/Incidents/Components/StatusBoxes/index.jsx similarity index 100% rename from client/src/Pages/v1/Incidents/Components/StatusBoxes/index.jsx rename to client/src/Pages/Incidents/Components/StatusBoxes/index.jsx diff --git a/client/src/Pages/v1/Incidents/Components/StatusBoxes/skeleton.jsx b/client/src/Pages/Incidents/Components/StatusBoxes/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/Incidents/Components/StatusBoxes/skeleton.jsx rename to client/src/Pages/Incidents/Components/StatusBoxes/skeleton.jsx diff --git a/client/src/Pages/v1/Incidents/index.jsx b/client/src/Pages/Incidents/index.jsx similarity index 93% rename from client/src/Pages/v1/Incidents/index.jsx rename to client/src/Pages/Incidents/index.jsx index 91cab2a40..c8462f649 100644 --- a/client/src/Pages/v1/Incidents/index.jsx +++ b/client/src/Pages/Incidents/index.jsx @@ -9,9 +9,8 @@ import { Box, Button } from "@mui/material"; //Utils import { useTheme } from "@emotion/react"; -import { useFetchMonitorsByTeamId } from "../../../Hooks/v1/monitorHooks.js"; -import { useFetchChecksSummaryByTeamId } from "../../../Hooks/v1/checkHooks.js"; -import { useAcknowledgeChecks } from "../../../Hooks/v1/checkHooks.js"; +import { useFetchMonitorsByTeamId } from "@/Hooks/monitorHooks.js"; +import { useFetchChecksSummaryByTeamId, useAcknowledgeChecks } from "@/Hooks/checkHooks.js"; import { useState, useEffect } from "react"; import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Incidents2/Components/ActiveIncidentsPanel/index.jsx b/client/src/Pages/Incidents2/Components/ActiveIncidentsPanel/index.jsx similarity index 100% rename from client/src/Pages/v1/Incidents2/Components/ActiveIncidentsPanel/index.jsx rename to client/src/Pages/Incidents2/Components/ActiveIncidentsPanel/index.jsx diff --git a/client/src/Pages/v1/Incidents2/Components/IncidentTable/index.jsx b/client/src/Pages/Incidents2/Components/IncidentTable/index.jsx similarity index 98% rename from client/src/Pages/v1/Incidents2/Components/IncidentTable/index.jsx rename to client/src/Pages/Incidents2/Components/IncidentTable/index.jsx index 8cac4e07b..d613e15c7 100644 --- a/client/src/Pages/v1/Incidents2/Components/IncidentTable/index.jsx +++ b/client/src/Pages/Incidents2/Components/IncidentTable/index.jsx @@ -8,7 +8,7 @@ import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx"; //Utils -import { formatDateWithTz } from "../../../../../Utils/timeUtils.js"; +import { formatDateWithTz } from "../../../../Utils/timeUtils.js"; import { useSelector } from "react-redux"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Incidents2/Components/IncidentsSummaryPanel/index.jsx b/client/src/Pages/Incidents2/Components/IncidentsSummaryPanel/index.jsx similarity index 100% rename from client/src/Pages/v1/Incidents2/Components/IncidentsSummaryPanel/index.jsx rename to client/src/Pages/Incidents2/Components/IncidentsSummaryPanel/index.jsx diff --git a/client/src/Pages/v1/Incidents2/Components/IncidentsSummaryPanel/skeleton.jsx b/client/src/Pages/Incidents2/Components/IncidentsSummaryPanel/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/Incidents2/Components/IncidentsSummaryPanel/skeleton.jsx rename to client/src/Pages/Incidents2/Components/IncidentsSummaryPanel/skeleton.jsx diff --git a/client/src/Pages/v1/Incidents2/Components/LatestIncidentsPanel/IncidentItem.jsx b/client/src/Pages/Incidents2/Components/LatestIncidentsPanel/IncidentItem.jsx similarity index 100% rename from client/src/Pages/v1/Incidents2/Components/LatestIncidentsPanel/IncidentItem.jsx rename to client/src/Pages/Incidents2/Components/LatestIncidentsPanel/IncidentItem.jsx diff --git a/client/src/Pages/v1/Incidents2/Components/LatestIncidentsPanel/index.jsx b/client/src/Pages/Incidents2/Components/LatestIncidentsPanel/index.jsx similarity index 100% rename from client/src/Pages/v1/Incidents2/Components/LatestIncidentsPanel/index.jsx rename to client/src/Pages/Incidents2/Components/LatestIncidentsPanel/index.jsx diff --git a/client/src/Pages/v1/Incidents2/Components/OptionsHeader/index.jsx b/client/src/Pages/Incidents2/Components/OptionsHeader/index.jsx similarity index 100% rename from client/src/Pages/v1/Incidents2/Components/OptionsHeader/index.jsx rename to client/src/Pages/Incidents2/Components/OptionsHeader/index.jsx diff --git a/client/src/Pages/v1/Incidents2/Components/StatisticsPanel/index.jsx b/client/src/Pages/Incidents2/Components/StatisticsPanel/index.jsx similarity index 100% rename from client/src/Pages/v1/Incidents2/Components/StatisticsPanel/index.jsx rename to client/src/Pages/Incidents2/Components/StatisticsPanel/index.jsx diff --git a/client/src/Pages/v1/Incidents2/Components/SummaryCard/index.jsx b/client/src/Pages/Incidents2/Components/SummaryCard/index.jsx similarity index 100% rename from client/src/Pages/v1/Incidents2/Components/SummaryCard/index.jsx rename to client/src/Pages/Incidents2/Components/SummaryCard/index.jsx diff --git a/client/src/Pages/v1/Incidents2/hooks/useFetchIncidents.js b/client/src/Pages/Incidents2/hooks/useFetchIncidents.js similarity index 97% rename from client/src/Pages/v1/Incidents2/hooks/useFetchIncidents.js rename to client/src/Pages/Incidents2/hooks/useFetchIncidents.js index a67f6b7ee..ab22c42e0 100644 --- a/client/src/Pages/v1/Incidents2/hooks/useFetchIncidents.js +++ b/client/src/Pages/Incidents2/hooks/useFetchIncidents.js @@ -1,6 +1,6 @@ import { useState, useCallback } from "react"; -import { networkService } from "../../../../main.jsx"; -import { createToast } from "../../../../Utils/toastUtils.jsx"; +import { networkService } from "../../../main.jsx"; +import { createToast } from "../../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; /** * Hook to fetch and manage incidents diff --git a/client/src/Pages/v1/Incidents2/index.jsx b/client/src/Pages/Incidents2/index.jsx similarity index 97% rename from client/src/Pages/v1/Incidents2/index.jsx rename to client/src/Pages/Incidents2/index.jsx index 2983a38e8..eda68d976 100644 --- a/client/src/Pages/v1/Incidents2/index.jsx +++ b/client/src/Pages/Incidents2/index.jsx @@ -14,7 +14,7 @@ import { useTranslation } from "react-i18next"; // Hooks import useFetchIncidents from "./hooks/useFetchIncidents.js"; -import { useFetchMonitorsByTeamId } from "../../../Hooks/v1/monitorHooks.js"; +import { useFetchMonitorsByTeamId } from "../../Hooks/monitorHooks.js"; const Incidents2 = () => { const { t } = useTranslation(); diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx b/client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx similarity index 96% rename from client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx rename to client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx index 5fe1d8984..64f82cec2 100644 --- a/client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx +++ b/client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx @@ -1,7 +1,7 @@ import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; import { Box, Stack, Typography } from "@mui/material"; import { CustomThreshold } from "./CustomThreshold/index.jsx"; -import { capitalizeFirstLetter } from "../../../../../Utils/stringUtils.js"; +import { capitalizeFirstLetter } from "../../../../Utils/stringUtils.js"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import PropTypes from "prop-types"; diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/CustomThreshold/index.jsx b/client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Create/Components/CustomThreshold/index.jsx rename to client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/DiskSelection.jsx b/client/src/Pages/Infrastructure/Create/Components/DiskSelection.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Create/Components/DiskSelection.jsx rename to client/src/Pages/Infrastructure/Create/Components/DiskSelection.jsx diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/MonitorActionButtons.jsx b/client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Create/Components/MonitorActionButtons.jsx rename to client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx b/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx similarity index 95% rename from client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx rename to client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx index 570f38a7e..8fa6f4ff1 100644 --- a/client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx +++ b/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx @@ -1,5 +1,5 @@ import { Box, Stack, Tooltip, Typography } from "@mui/material"; -import { useMonitorUtils } from "../../../../../Hooks/v1/useMonitorUtils.js"; +import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils.js"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import PulseDot from "@/Components/v1/Animated/PulseDot.jsx"; diff --git a/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx rename to client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx diff --git a/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx similarity index 96% rename from client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx rename to client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx index 15a5f3c5d..c5c7aacf2 100644 --- a/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx +++ b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx @@ -1,7 +1,4 @@ -import { - useCreateMonitor, - useUpdateMonitor, -} from "../../../../../Hooks/v1/monitorHooks.js"; +import { useCreateMonitor, useUpdateMonitor } from "../../../../Hooks/monitorHooks.js"; const useInfrastructureSubmit = () => { const [createMonitor, isCreating] = useCreateMonitor(); const [updateMonitor, isUpdating] = useUpdateMonitor(); diff --git a/client/src/Pages/v1/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx b/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx similarity index 85% rename from client/src/Pages/v1/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx rename to client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx index 73097d0d8..f7665e65f 100644 --- a/client/src/Pages/v1/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx +++ b/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx @@ -1,6 +1,6 @@ import { useState } from "react"; -import { infrastructureMonitorValidation } from "../../../../../Validation/validation.js"; -import { createToast } from "../../../../../Utils/toastUtils.jsx"; +import { infrastructureMonitorValidation } from "../../../../Validation/validation.js"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; const useValidateInfrastructureForm = () => { const [errors, setErrors] = useState({}); diff --git a/client/src/Pages/v1/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx similarity index 97% rename from client/src/Pages/v1/Infrastructure/Create/index.jsx rename to client/src/Pages/Infrastructure/Create/index.jsx index c74d49816..b27c11cd4 100644 --- a/client/src/Pages/v1/Infrastructure/Create/index.jsx +++ b/client/src/Pages/Infrastructure/Create/index.jsx @@ -13,8 +13,8 @@ import CustomAlertsSection from "./Components/CustomAlertsSection.jsx"; import DiskSelection from "./Components/DiskSelection.jsx"; // Utils import NotificationsConfig from "@/Components/v1/NotificationConfig/index.jsx"; -import { useGetNotificationsByTeamId } from "../../../../Hooks/v1/useNotifications.js"; -import { networkService } from "../../../../Utils/NetworkService"; +import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications.js"; +import { networkService } from "../../../Utils/NetworkService.js"; import { useParams } from "react-router-dom"; import { useState, useEffect } from "react"; import { useTheme } from "@emotion/react"; @@ -24,7 +24,7 @@ import { useFetchGlobalSettings, useFetchHardwareMonitorById, usePauseMonitor, -} from "../../../../Hooks/v1/monitorHooks.js"; +} from "../../../Hooks/monitorHooks.js"; import useInfrastructureMonitorForm from "./hooks/useInfrastructureMonitorForm.jsx"; import useValidateInfrastructureForm from "./hooks/useValidateInfrastructureForm.jsx"; import useInfrastructureSubmit from "./hooks/useInfrastructureSubmit.jsx"; @@ -150,7 +150,8 @@ const CreateInfrastructureMonitor = () => { const handleRemove = async (event) => { event.preventDefault(); - await deleteMonitor({ monitor, redirect: "/infrastructure" }); + const TEMP_MONITOR = { id: monitor._id }; + await deleteMonitor({ monitor: TEMP_MONITOR, redirect: "/infrastructure" }); }; const isBusy = diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx b/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx rename to client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/index.jsx b/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/index.jsx rename to client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx b/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx rename to client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/BaseContainer/index.jsx b/client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Components/BaseContainer/index.jsx rename to client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx b/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx rename to client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx b/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx rename to client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx b/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx rename to client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx rename to client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx rename to client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/index.jsx rename to client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/skeleton.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/skeleton.jsx rename to client/src/Pages/Infrastructure/Details/Components/NetworkStats/skeleton.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx b/client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx similarity index 97% rename from client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx rename to client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx index 7f09e82d5..ef91709cf 100644 --- a/client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx @@ -4,7 +4,7 @@ import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx"; import StatBox from "@/Components/v1/StatBox/index.jsx"; //Utils -import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; +import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils.js"; import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Infrastructure/Details/Hooks/useHardwareUtils.jsx b/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Hooks/useHardwareUtils.jsx rename to client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/index.jsx b/client/src/Pages/Infrastructure/Details/index.jsx similarity index 96% rename from client/src/Pages/v1/Infrastructure/Details/index.jsx rename to client/src/Pages/Infrastructure/Details/index.jsx index f9c51c27d..62a5c2642 100644 --- a/client/src/Pages/v1/Infrastructure/Details/index.jsx +++ b/client/src/Pages/Infrastructure/Details/index.jsx @@ -13,8 +13,8 @@ import TabContext from "@mui/lab/TabContext"; // Utils import { useTheme } from "@emotion/react"; -import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; -import { useFetchHardwareMonitorById } from "../../../../Hooks/v1/monitorHooks.js"; +import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; +import { useFetchHardwareMonitorById } from "../../../Hooks/monitorHooks.js"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { useParams } from "react-router-dom"; diff --git a/client/src/Pages/v1/Infrastructure/Monitors/Components/Filters/index.jsx b/client/src/Pages/Infrastructure/Monitors/Components/Filters/index.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Monitors/Components/Filters/index.jsx rename to client/src/Pages/Infrastructure/Monitors/Components/Filters/index.jsx diff --git a/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx similarity index 95% rename from client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx rename to client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx index cc2cd5f98..510115d8a 100644 --- a/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx +++ b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx @@ -7,12 +7,12 @@ import { Stack } from "@mui/material"; import { InfrastructureMenu } from "../MonitorsTableMenu/index.jsx"; import LoadingSpinner from "../../../../Uptime/Monitors/Components/LoadingSpinner/index.jsx"; // Assets -import CPUChipIcon from "../../../../../../assets/icons/cpu-chip.svg?react"; +import CPUChipIcon from "@/assets/icons/cpu-chip.svg?react"; import CustomGauge from "@/Components/v1/Charts/CustomGauge/index.jsx"; // Utils import { useTheme } from "@emotion/react"; -import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; +import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils.js"; import { useNavigate } from "react-router-dom"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx similarity index 94% rename from client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx rename to client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx index d3af34d00..39aa84e90 100644 --- a/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx +++ b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx @@ -3,13 +3,13 @@ import { useRef, useState } from "react"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import { createToast } from "../../../../../../Utils/toastUtils.jsx"; +import { createToast } from "@/Utils/toastUtils.jsx"; import { IconButton, Menu, MenuItem } from "@mui/material"; -import Settings from "../../../../../../assets/icons/settings-bold.svg?react"; +import Settings from "@/assets/icons/settings-bold.svg?react"; import PropTypes from "prop-types"; import Dialog from "@/Components/v1/Dialog/index.jsx"; -import { networkService } from "../../../../../../Utils/NetworkService.js"; -import { usePauseMonitor } from "../../../../../../Hooks/v1/monitorHooks.js"; +import { networkService } from "@/Utils/NetworkService.js"; +import { usePauseMonitor } from "@/Hooks/monitorHooks.js"; import { useTranslation } from "react-i18next"; /** diff --git a/client/src/Pages/v1/Infrastructure/Monitors/index.jsx b/client/src/Pages/Infrastructure/Monitors/index.jsx similarity index 94% rename from client/src/Pages/v1/Infrastructure/Monitors/index.jsx rename to client/src/Pages/Infrastructure/Monitors/index.jsx index 6c3a657ab..90817f116 100644 --- a/client/src/Pages/v1/Infrastructure/Monitors/index.jsx +++ b/client/src/Pages/Infrastructure/Monitors/index.jsx @@ -11,11 +11,11 @@ import SearchComponent from "../../Uptime/Monitors/Components/SearchComponent/in // Utils import { useTheme } from "@emotion/react"; import { useEffect, useState } from "react"; -import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; +import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; import { useTranslation } from "react-i18next"; -import { useFetchMonitorsByTeamId } from "../../../../Hooks/v1/monitorHooks.js"; +import { useFetchMonitorsByTeamId } from "../../../Hooks/monitorHooks.js"; import { useDispatch, useSelector } from "react-redux"; -import { setRowsPerPage } from "../../../../Features/UI/uiSlice.js"; +import { setRowsPerPage } from "../../../Features/UI/uiSlice.js"; // Constants const TYPES = ["hardware"]; const BREADCRUMBS = [{ name: `infrastructure`, path: "/infrastructure" }]; diff --git a/client/src/Pages/v1/Integrations/index.css b/client/src/Pages/Integrations/index.css similarity index 100% rename from client/src/Pages/v1/Integrations/index.css rename to client/src/Pages/Integrations/index.css diff --git a/client/src/Pages/v1/Integrations/index.jsx b/client/src/Pages/Integrations/index.jsx similarity index 100% rename from client/src/Pages/v1/Integrations/index.jsx rename to client/src/Pages/Integrations/index.jsx diff --git a/client/src/Pages/v1/Logs/Diagnostics/components/gauges/index.jsx b/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx similarity index 100% rename from client/src/Pages/v1/Logs/Diagnostics/components/gauges/index.jsx rename to client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx diff --git a/client/src/Pages/v1/Logs/Diagnostics/components/stats/index.jsx b/client/src/Pages/Logs/Diagnostics/components/stats/index.jsx similarity index 96% rename from client/src/Pages/v1/Logs/Diagnostics/components/stats/index.jsx rename to client/src/Pages/Logs/Diagnostics/components/stats/index.jsx index b99f34506..b169a6a85 100644 --- a/client/src/Pages/v1/Logs/Diagnostics/components/stats/index.jsx +++ b/client/src/Pages/Logs/Diagnostics/components/stats/index.jsx @@ -7,7 +7,7 @@ import CircularProgress from "@mui/material/CircularProgress"; import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; -import { getHumanReadableDuration } from "../../../../../../Utils/timeUtils.js"; +import { getHumanReadableDuration } from "../../../../../Utils/timeUtils.js"; import { formatBytes } from "../../utils/utils.js"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Logs/Diagnostics/index.jsx b/client/src/Pages/Logs/Diagnostics/index.jsx similarity index 88% rename from client/src/Pages/v1/Logs/Diagnostics/index.jsx rename to client/src/Pages/Logs/Diagnostics/index.jsx index 54feee516..07b504c77 100644 --- a/client/src/Pages/v1/Logs/Diagnostics/index.jsx +++ b/client/src/Pages/Logs/Diagnostics/index.jsx @@ -6,8 +6,8 @@ import StatBox from "@/Components/v1/StatBox/index.jsx"; import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import { useFetchDiagnostics } from "../../../../Hooks/v1/logHooks.js"; -import { getHumanReadableDuration } from "../../../../Utils/timeUtils.js"; +import { useFetchDiagnostics } from "../../../Hooks/logHooks.js"; +import { getHumanReadableDuration } from "../../../Utils/timeUtils.js"; import { formatBytes, getPercentage } from "./utils/utils.js"; const Diagnostics = () => { @@ -26,12 +26,12 @@ const Diagnostics = () => { status="up" heading={t("status")} subHeading={ - error + error ? t("logsPage.logLevelSelect.values.error") - : isLoading + : isLoading ? t("commonSaving") - : diagnostics - ? t("diagnosticsPage.diagnosticDescription") + : diagnostics + ? t("diagnosticsPage.diagnosticDescription") : t("general.noOptionsFound", { unit: "data" }) } /> diff --git a/client/src/Pages/v1/Logs/Diagnostics/utils/utils.js b/client/src/Pages/Logs/Diagnostics/utils/utils.js similarity index 100% rename from client/src/Pages/v1/Logs/Diagnostics/utils/utils.js rename to client/src/Pages/Logs/Diagnostics/utils/utils.js diff --git a/client/src/Pages/v1/Logs/Logs/index.jsx b/client/src/Pages/Logs/Logs/index.jsx similarity index 97% rename from client/src/Pages/v1/Logs/Logs/index.jsx rename to client/src/Pages/Logs/Logs/index.jsx index 64c4cb97a..c44a83ed9 100644 --- a/client/src/Pages/v1/Logs/Logs/index.jsx +++ b/client/src/Pages/Logs/Logs/index.jsx @@ -4,7 +4,7 @@ import Select from "@/Components/v1/Inputs/Select/index.jsx"; import Typography from "@mui/material/Typography"; import Divider from "@mui/material/Divider"; -import { useFetchLogs } from "../../../../Hooks/v1/logHooks.js"; +import { useFetchLogs } from "../../../Hooks/logHooks.js"; import { useTheme } from "@emotion/react"; import { useState } from "react"; import { useTranslation } from "react-i18next"; @@ -51,7 +51,6 @@ const Logs = () => { ]; return ( - { const MS_LOOKUP = { diff --git a/client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx b/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx similarity index 95% rename from client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx rename to client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx index d21906e41..c3be0091a 100644 --- a/client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx +++ b/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx @@ -1,4 +1,4 @@ -import { networkService } from "../../../../../main.jsx"; +import { networkService } from "../../../../main.jsx"; import dayjs from "dayjs"; import { MS_PER_SECOND, @@ -6,7 +6,7 @@ import { MS_PER_HOUR, MS_PER_DAY, MS_PER_WEEK, -} from "../../../../../Utils/timeUtils.js"; +} from "../../../../Utils/timeUtils.js"; const useMaintenanceData = () => { const REVERSE_REPEAT_LOOKUP = { 0: "none", diff --git a/client/src/Pages/v1/Maintenance/CreateMaintenance/index.css b/client/src/Pages/Maintenance/CreateMaintenance/index.css similarity index 100% rename from client/src/Pages/v1/Maintenance/CreateMaintenance/index.css rename to client/src/Pages/Maintenance/CreateMaintenance/index.css diff --git a/client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx similarity index 97% rename from client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx rename to client/src/Pages/Maintenance/CreateMaintenance/index.jsx index d0e7b03cd..384805f3a 100644 --- a/client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx +++ b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx @@ -7,8 +7,8 @@ import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { LocalizationProvider } from "@mui/x-date-pickers"; import { DatePicker } from "@mui/x-date-pickers/DatePicker"; import { MobileTimePicker } from "@mui/x-date-pickers/MobileTimePicker"; -import { maintenanceWindowValidation } from "../../../../Validation/validation.js"; -import { createToast } from "../../../../Utils/toastUtils.jsx"; +import { maintenanceWindowValidation } from "@/Validation/validation.js"; +import { createToast } from "@/Utils/toastUtils.jsx"; import MonitorList from "./Components/MonitorList/index.jsx"; import ConfigSelect from "./Components/ConfigSelect/index.jsx"; import useMaintenanceData from "./hooks/useMaintenanceData.jsx"; @@ -16,13 +16,13 @@ import useMaintenanceActions from "./hooks/useMaintenanceActions.jsx"; import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; -import CalendarIcon from "../../../../assets/icons/calendar.svg?react"; +import CalendarIcon from "@/assets/icons/calendar.svg?react"; import "./index.css"; import Search from "@/Components/v1/Inputs/Search/index.jsx"; -import { logger } from "../../../../Utils/Logger.js"; +import { logger } from "@/Utils/Logger.js"; import { useNavigate, useParams } from "react-router-dom"; -import { buildErrors, hasValidationErrors } from "../../../../Validation/error.js"; +import { buildErrors, hasValidationErrors } from "@/Validation/error.js"; import { useTranslation } from "react-i18next"; import dayjs from "dayjs"; diff --git a/client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx b/client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx similarity index 93% rename from client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx rename to client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx index b2654da49..38a3d8e64 100644 --- a/client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx +++ b/client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx @@ -3,11 +3,11 @@ import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; import { useSelector } from "react-redux"; import { IconButton, Menu, MenuItem } from "@mui/material"; -import { logger } from "../../../../../Utils/Logger.js"; -import Settings from "../../../../../assets/icons/settings-bold.svg?react"; +import { logger } from "@/Utils/Logger.js"; +import Settings from "@/assets/icons/settings-bold.svg?react"; import PropTypes from "prop-types"; -import { networkService } from "../../../../../main.jsx"; -import { createToast } from "../../../../../Utils/toastUtils.jsx"; +import { networkService } from "../../../../main.jsx"; +import { createToast } from "@/Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; import Dialog from "@/Components/v1/Dialog/index.jsx"; diff --git a/client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx b/client/src/Pages/Maintenance/MaintenanceTable/index.jsx similarity index 97% rename from client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx rename to client/src/Pages/Maintenance/MaintenanceTable/index.jsx index f1d44f944..faeb2072c 100644 --- a/client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx +++ b/client/src/Pages/Maintenance/MaintenanceTable/index.jsx @@ -7,9 +7,9 @@ import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded"; import ActionsMenu from "./ActionsMenu/index.jsx"; import { memo } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { formatDurationRounded } from "../../../../Utils/timeUtils.js"; +import { formatDurationRounded } from "../../../Utils/timeUtils.js"; import { StatusLabel } from "@/Components/v1/Label/index.jsx"; -import { setRowsPerPage } from "../../../../Features/UI/uiSlice.js"; +import { setRowsPerPage } from "../../../Features/UI/uiSlice.js"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; diff --git a/client/src/Pages/v1/Maintenance/index.css b/client/src/Pages/Maintenance/index.css similarity index 100% rename from client/src/Pages/v1/Maintenance/index.css rename to client/src/Pages/Maintenance/index.css diff --git a/client/src/Pages/v1/Maintenance/index.jsx b/client/src/Pages/Maintenance/index.jsx similarity index 98% rename from client/src/Pages/v1/Maintenance/index.jsx rename to client/src/Pages/Maintenance/index.jsx index cba0e9f18..b49cd9f6e 100644 --- a/client/src/Pages/v1/Maintenance/index.jsx +++ b/client/src/Pages/Maintenance/index.jsx @@ -4,7 +4,7 @@ import { useState, useEffect } from "react"; import "./index.css"; import MaintenanceTable from "./MaintenanceTable/index.jsx"; import { useSelector } from "react-redux"; -import { networkService } from "../../../main.jsx"; +import { networkService } from "../../main.jsx"; import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/NotFound/index.jsx b/client/src/Pages/NotFound/index.jsx similarity index 96% rename from client/src/Pages/v1/NotFound/index.jsx rename to client/src/Pages/NotFound/index.jsx index 48825c9f6..27e3173b0 100644 --- a/client/src/Pages/v1/NotFound/index.jsx +++ b/client/src/Pages/NotFound/index.jsx @@ -1,5 +1,5 @@ import PropTypes from "prop-types"; -import NotFoundSvg from "../../../assets/Images/sushi_404.svg"; +import NotFoundSvg from "@/assets/Images/sushi_404.svg"; import { Button, Stack, Typography } from "@mui/material"; import { useNavigate } from "react-router-dom"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/Notifications/components/ActionMenu.jsx b/client/src/Pages/Notifications/components/ActionMenu.jsx similarity index 100% rename from client/src/Pages/v1/Notifications/components/ActionMenu.jsx rename to client/src/Pages/Notifications/components/ActionMenu.jsx diff --git a/client/src/Pages/v1/Notifications/create/index.jsx b/client/src/Pages/Notifications/create/index.jsx similarity index 97% rename from client/src/Pages/v1/Notifications/create/index.jsx rename to client/src/Pages/Notifications/create/index.jsx index b1c50493c..11bf2a95d 100644 --- a/client/src/Pages/v1/Notifications/create/index.jsx +++ b/client/src/Pages/Notifications/create/index.jsx @@ -18,9 +18,9 @@ import { useEditNotification, useTestNotification, useDeleteNotification, -} from "../../../../Hooks/v1/useNotifications.js"; -import { notificationValidation } from "../../../../Validation/validation.js"; -import { createToast } from "../../../../Utils/toastUtils.jsx"; +} from "../../../Hooks/useNotifications.js"; +import { notificationValidation } from "../../../Validation/validation.js"; +import { createToast } from "../../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; import { useParams, useNavigate } from "react-router-dom"; import { diff --git a/client/src/Pages/v1/Notifications/index.jsx b/client/src/Pages/Notifications/index.jsx similarity index 98% rename from client/src/Pages/v1/Notifications/index.jsx rename to client/src/Pages/Notifications/index.jsx index 78ec6e9bb..8ebdb22ec 100644 --- a/client/src/Pages/v1/Notifications/index.jsx +++ b/client/src/Pages/Notifications/index.jsx @@ -14,7 +14,7 @@ import { useNavigate } from "react-router-dom"; import { useGetNotificationsByTeamId, useDeleteNotification, -} from "../../../Hooks/v1/useNotifications.js"; +} from "../../Hooks/useNotifications.js"; import { useTranslation } from "react-i18next"; const Notifications = () => { diff --git a/client/src/Pages/v1/Notifications/utils.js b/client/src/Pages/Notifications/utils.js similarity index 100% rename from client/src/Pages/v1/Notifications/utils.js rename to client/src/Pages/Notifications/utils.js diff --git a/client/src/Pages/v1/PageSpeed/Create/index.css b/client/src/Pages/PageSpeed/Create/index.css similarity index 100% rename from client/src/Pages/v1/PageSpeed/Create/index.css rename to client/src/Pages/PageSpeed/Create/index.css diff --git a/client/src/Pages/v1/PageSpeed/Create/index.jsx b/client/src/Pages/PageSpeed/Create/index.jsx similarity index 96% rename from client/src/Pages/v1/PageSpeed/Create/index.jsx rename to client/src/Pages/PageSpeed/Create/index.jsx index cb856bd37..2cce26159 100644 --- a/client/src/Pages/v1/PageSpeed/Create/index.jsx +++ b/client/src/Pages/PageSpeed/Create/index.jsx @@ -16,22 +16,22 @@ import Radio from "@/Components/v1/Inputs/Radio/index.jsx"; // Utils import { useState, useEffect } from "react"; import { useSelector } from "react-redux"; -import { monitorValidation } from "../../../../Validation/validation.js"; -import { parseDomainName } from "../../../../Utils/monitorUtils.js"; +import { monitorValidation } from "../../../Validation/validation.js"; +import { parseDomainName } from "../../../Utils/monitorUtils.js"; import { useTranslation } from "react-i18next"; -import { useGetNotificationsByTeamId } from "../../../../Hooks/v1/useNotifications.js"; +import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications.js"; import { useTheme } from "@emotion/react"; -import { createToast } from "../../../../Utils/toastUtils.jsx"; +import { createToast } from "../../../Utils/toastUtils.jsx"; import { useParams } from "react-router"; -import { useMonitorUtils } from "../../../../Hooks/v1/useMonitorUtils.js"; +import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js"; import { useCreateMonitor, useFetchMonitorById, useDeleteMonitor, useUpdateMonitor, usePauseMonitor, -} from "../../../../Hooks/v1/monitorHooks.js"; +} from "../../../Hooks/monitorHooks.js"; const PageSpeedSetup = () => { const { monitorId } = useParams(); @@ -178,7 +178,8 @@ const PageSpeedSetup = () => { const handleRemove = async (event) => { event.preventDefault(); - await deleteMonitor({ monitor, redirect: "/pagespeed" }); + const TEMP_MONITOR = { id: monitor._id }; + await deleteMonitor({ monitor: TEMP_MONITOR, redirect: "/pagespeed" }); }; const isBusy = isLoading || isCreating || isDeleting || isUpdating || isPausing; diff --git a/client/src/Pages/v1/PageSpeed/Create/skeleton.jsx b/client/src/Pages/PageSpeed/Create/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/PageSpeed/Create/skeleton.jsx rename to client/src/Pages/PageSpeed/Create/skeleton.jsx diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChart.jsx b/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx similarity index 99% rename from client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChart.jsx rename to client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx index 5c79cc06c..06b92c933 100644 --- a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChart.jsx +++ b/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx @@ -11,7 +11,7 @@ import { import { useTheme } from "@emotion/react"; import { useMemo, useState } from "react"; import { Box, Stack, Typography } from "@mui/material"; -import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js"; +import { formatDateWithTz } from "../../../../../Utils/timeUtils.js"; import { useSelector } from "react-redux"; const config = { diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx b/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx similarity index 94% rename from client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx rename to client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx index c0da1600f..113e4d618 100644 --- a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx +++ b/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx @@ -1,6 +1,6 @@ import { Box, Typography, Divider } from "@mui/material"; import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx"; -import MetricsIcon from "../../../../../../assets/icons/ruler-icon.svg?react"; +import MetricsIcon from "@/assets/icons/ruler-icon.svg?react"; import LegendBox from "@/Components/v1/Charts/LegendBox/index.jsx"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChart.jsx b/client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx similarity index 100% rename from client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChart.jsx rename to client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx b/client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx similarity index 96% rename from client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx rename to client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx index 1297bd699..43e2371b4 100644 --- a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx +++ b/client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx @@ -1,7 +1,7 @@ import { Stack, Box, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import LegendBox from "@/Components/v1/Charts/LegendBox/index.jsx"; -import SpeedometerIcon from "../../../../../../assets/icons/speedometer-icon.svg?react"; +import SpeedometerIcon from "@/assets/icons/speedometer-icon.svg?react"; import PropTypes from "prop-types"; const PieChartLegend = ({ audits }) => { diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx b/client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx similarity index 93% rename from client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx rename to client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx index 1281a396e..aa88d57e5 100644 --- a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx +++ b/client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx @@ -2,7 +2,7 @@ import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx"; import AreaChart from "../Charts/AreaChart.jsx"; import AreaChartLegend from "../Charts/AreaChartLegend.jsx"; import SkeletonLayout from "./skeleton.jsx"; -import ScoreIcon from "../../../../../../assets/icons/monitor-graph-line.svg?react"; +import ScoreIcon from "@/assets/icons/monitor-graph-line.svg?react"; import { Stack } from "@mui/material"; import PropTypes from "prop-types"; diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx b/client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx rename to client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx b/client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx similarity index 91% rename from client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx rename to client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx index ff265317c..493d1ece6 100644 --- a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx +++ b/client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx @@ -1,7 +1,7 @@ import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx"; import StatBox from "@/Components/v1/StatBox/index.jsx"; import { Typography } from "@mui/material"; -import { getHumanReadableDuration } from "../../../../../../Utils/timeUtils.js"; +import { getHumanReadableDuration } from "../../../../../Utils/timeUtils.js"; import { useTranslation } from "react-i18next"; const PageSpeedStatusBoxes = ({ shouldRender, monitor }) => { diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx b/client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx similarity index 93% rename from client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx rename to client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx index 27f9e885b..c2eecaa6a 100644 --- a/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx +++ b/client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx @@ -1,5 +1,5 @@ import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx"; -import PerformanceIcon from "../../../../../../assets/icons/performance-report.svg?react"; +import PerformanceIcon from "@/assets/icons/performance-report.svg?react"; import PieChart from "../Charts/PieChart.jsx"; import { Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx b/client/src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx rename to client/src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx diff --git a/client/src/Pages/v1/PageSpeed/Details/index.jsx b/client/src/Pages/PageSpeed/Details/index.jsx similarity index 95% rename from client/src/Pages/v1/PageSpeed/Details/index.jsx rename to client/src/Pages/PageSpeed/Details/index.jsx index 836f0ca37..8a87133df 100644 --- a/client/src/Pages/v1/PageSpeed/Details/index.jsx +++ b/client/src/Pages/PageSpeed/Details/index.jsx @@ -9,9 +9,9 @@ import PerformanceReport from "./Components/PerformanceReport/index.jsx"; import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; // Utils import { useTheme } from "@emotion/react"; -import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; +import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; import { useParams } from "react-router-dom"; -import { useFetchStatsByMonitorId } from "../../../../Hooks/v1/monitorHooks.js"; +import { useFetchStatsByMonitorId } from "../../../Hooks/monitorHooks.js"; import { useState } from "react"; import { useTranslation } from "react-i18next"; // Constants diff --git a/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx b/client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx similarity index 96% rename from client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx rename to client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx index 4e551526b..8a6444d1b 100644 --- a/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx +++ b/client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx @@ -1,17 +1,14 @@ import PropTypes from "prop-types"; // import PageSpeedIcon from "../../../../assets/icons/page-speed.svg?react"; -import PageSpeedIcon from "../../../../../../assets/icons/page-speed.svg?react"; +import PageSpeedIcon from "@/assets/icons/page-speed.svg?react"; import { StatusLabel } from "@/Components/v1/Label/index.jsx"; import { Box, Grid, Stack, Typography } from "@mui/material"; import { useNavigate } from "react-router-dom"; import { useTheme } from "@emotion/react"; import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip } from "recharts"; import { useSelector } from "react-redux"; -import { - formatDateWithTz, - formatDurationSplit, -} from "../../../../../../Utils/timeUtils.js"; -import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; +import { formatDateWithTz, formatDurationSplit } from "@/Utils/timeUtils.js"; +import { useMonitorUtils } from "@/Hooks/useMonitorUtils.js"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import IconBox from "@/Components/v1/IconBox/index.jsx"; @@ -224,7 +221,7 @@ const Card = ({ monitor }) => { navigate(`/pagespeed/${monitor._id}`)} + onClick={() => navigate(`/pagespeed/${monitor.id}`)} border={1} borderColor={theme.palette.primary.lowContrast} borderRadius={theme.shape.borderRadius} diff --git a/client/src/Pages/v1/PageSpeed/Monitors/Components/MonitorGrid/index.jsx b/client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx similarity index 100% rename from client/src/Pages/v1/PageSpeed/Monitors/Components/MonitorGrid/index.jsx rename to client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx diff --git a/client/src/Pages/v1/PageSpeed/Monitors/index.jsx b/client/src/Pages/PageSpeed/Monitors/index.jsx similarity index 64% rename from client/src/Pages/v1/PageSpeed/Monitors/index.jsx rename to client/src/Pages/PageSpeed/Monitors/index.jsx index 7485a72c9..548cd3491 100644 --- a/client/src/Pages/v1/PageSpeed/Monitors/index.jsx +++ b/client/src/Pages/PageSpeed/Monitors/index.jsx @@ -10,9 +10,12 @@ import FallbackPageSpeedWarning from "@/Components/v1/Fallback/FallbackPageSpeed // Utils import { useTheme } from "@emotion/react"; -import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; -import { useFetchMonitorsByTeamId } from "../../../../Hooks/v1/monitorHooks.js"; -import { useFetchSettings } from "../../../../Hooks/v1/settingsHooks.js"; +import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; +import { + useFetchMonitorsByTeamId, + useFetchMonitorsWithChecks, +} from "@/Hooks/monitorHooks.js"; +import { useFetchSettings } from "@/Hooks/settingsHooks.js"; // Constants const BREADCRUMBS = [{ name: `pagespeed`, path: "/pagespeed" }]; const TYPES = ["pagespeed"]; @@ -20,14 +23,30 @@ const PageSpeed = () => { const theme = useTheme(); const isAdmin = useIsAdmin(); - const [monitors, monitorsSummary, isLoading, networkError] = useFetchMonitorsByTeamId({ - limit: 10, + // const [monitors, monitorsSummary, isLoading, networkError] = useFetchMonitorsByTeamId({ + // limit: 10, + // types: TYPES, + // page: null, + // rowsPerPage: null, + // filter: null, + // field: null, + // order: null, + // }); + + const [ + monitorsWithChecks, + monitorsWithChecksCount, + monitorsWithChecksIsLoading, + monitorsWithChecksNetworkError, + ] = useFetchMonitorsWithChecks({ types: TYPES, + limit: 10, page: null, rowsPerPage: null, filter: null, field: null, order: null, + monitorUpdateTrigger: null, }); const [settingsData, setSettingsData] = useState(undefined); @@ -40,9 +59,9 @@ const PageSpeed = () => { return ( <> { diff --git a/client/src/Pages/v1/ServerUnreachable.jsx b/client/src/Pages/ServerUnreachable.jsx similarity index 92% rename from client/src/Pages/v1/ServerUnreachable.jsx rename to client/src/Pages/ServerUnreachable.jsx index 8230f473b..bfbbfa90a 100644 --- a/client/src/Pages/v1/ServerUnreachable.jsx +++ b/client/src/Pages/ServerUnreachable.jsx @@ -2,14 +2,14 @@ import React, { useState } from "react"; import { Box, Typography, Button, Stack } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import { networkService } from "../../Utils/NetworkService.js"; +import { networkService } from "@/Utils/NetworkService.js"; import Alert from "@/Components/v1/Alert/index.jsx"; -import { createToast } from "../../Utils/toastUtils.jsx"; +import { createToast } from "@/Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; -import Background from "../../assets/Images/background-grid.svg?react"; -import Logo from "../../assets/icons/checkmate-icon.svg?react"; +import Background from "@/assets/Images/background-grid.svg?react"; +import Logo from "@/assets/icons/checkmate-icon.svg?react"; import ThemeSwitch from "@/Components/v1/ThemeSwitch/index.jsx"; -import LanguageSelector from "../../Components/LanguageSelector.jsx"; +import LanguageSelector from "@/Components/LanguageSelector.jsx"; const ServerUnreachable = () => { const theme = useTheme(); diff --git a/client/src/Pages/v1/Settings/SettingsAbout.jsx b/client/src/Pages/Settings/SettingsAbout.jsx similarity index 100% rename from client/src/Pages/v1/Settings/SettingsAbout.jsx rename to client/src/Pages/Settings/SettingsAbout.jsx diff --git a/client/src/Pages/v1/Settings/SettingsDemoMonitors.jsx b/client/src/Pages/Settings/SettingsDemoMonitors.jsx similarity index 100% rename from client/src/Pages/v1/Settings/SettingsDemoMonitors.jsx rename to client/src/Pages/Settings/SettingsDemoMonitors.jsx diff --git a/client/src/Pages/v1/Settings/SettingsEmail.jsx b/client/src/Pages/Settings/SettingsEmail.jsx similarity index 98% rename from client/src/Pages/v1/Settings/SettingsEmail.jsx rename to client/src/Pages/Settings/SettingsEmail.jsx index 7b5ad175e..6f0b02137 100644 --- a/client/src/Pages/v1/Settings/SettingsEmail.jsx +++ b/client/src/Pages/Settings/SettingsEmail.jsx @@ -12,8 +12,8 @@ import { PropTypes } from "prop-types"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; -import { useSendTestEmail } from "../../../Hooks/v1/useSendTestEmail.js"; -import { createToast } from "../../../Utils/toastUtils.jsx"; +import { useSendTestEmail } from "@/Hooks/useSendTestEmail.js"; +import { createToast } from "@/Utils/toastUtils.jsx"; const SettingsEmail = ({ isAdmin, diff --git a/client/src/Pages/v1/Settings/SettingsExport.jsx b/client/src/Pages/Settings/SettingsExport.jsx similarity index 100% rename from client/src/Pages/v1/Settings/SettingsExport.jsx rename to client/src/Pages/Settings/SettingsExport.jsx diff --git a/client/src/Pages/v1/Settings/SettingsGlobalThresholds.jsx b/client/src/Pages/Settings/SettingsGlobalThresholds.jsx similarity index 100% rename from client/src/Pages/v1/Settings/SettingsGlobalThresholds.jsx rename to client/src/Pages/Settings/SettingsGlobalThresholds.jsx diff --git a/client/src/Pages/v1/Settings/SettingsPagespeed.jsx b/client/src/Pages/Settings/SettingsPagespeed.jsx similarity index 100% rename from client/src/Pages/v1/Settings/SettingsPagespeed.jsx rename to client/src/Pages/Settings/SettingsPagespeed.jsx diff --git a/client/src/Pages/v1/Settings/SettingsStats.jsx b/client/src/Pages/Settings/SettingsStats.jsx similarity index 86% rename from client/src/Pages/v1/Settings/SettingsStats.jsx rename to client/src/Pages/Settings/SettingsStats.jsx index b68006ab2..690b306be 100644 --- a/client/src/Pages/v1/Settings/SettingsStats.jsx +++ b/client/src/Pages/Settings/SettingsStats.jsx @@ -36,16 +36,6 @@ const SettingsStats = ({ isAdmin, HEADING_SX, handleChange, settingsData, errors - {t("settingsPage.statsSettings.clearAllStatsDescription")} diff --git a/client/src/Pages/v1/Settings/SettingsTimeZone.jsx b/client/src/Pages/Settings/SettingsTimeZone.jsx similarity index 97% rename from client/src/Pages/v1/Settings/SettingsTimeZone.jsx rename to client/src/Pages/Settings/SettingsTimeZone.jsx index f684c871d..8ed7e5b3b 100644 --- a/client/src/Pages/v1/Settings/SettingsTimeZone.jsx +++ b/client/src/Pages/Settings/SettingsTimeZone.jsx @@ -3,7 +3,7 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; import Search from "@/Components/v1/Inputs/Search/index.jsx"; -import timezones from "../../../Utils/timezones.json"; +import timezones from "@/Utils/timezones.json"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/Settings/SettingsUI.jsx b/client/src/Pages/Settings/SettingsUI.jsx similarity index 100% rename from client/src/Pages/v1/Settings/SettingsUI.jsx rename to client/src/Pages/Settings/SettingsUI.jsx diff --git a/client/src/Pages/v1/Settings/SettingsURL.jsx b/client/src/Pages/Settings/SettingsURL.jsx similarity index 100% rename from client/src/Pages/v1/Settings/SettingsURL.jsx rename to client/src/Pages/Settings/SettingsURL.jsx diff --git a/client/src/Pages/v1/Settings/index.jsx b/client/src/Pages/Settings/index.jsx similarity index 95% rename from client/src/Pages/v1/Settings/index.jsx rename to client/src/Pages/Settings/index.jsx index b6137121b..27cc6bb21 100644 --- a/client/src/Pages/v1/Settings/index.jsx +++ b/client/src/Pages/Settings/index.jsx @@ -12,27 +12,22 @@ import SettingsGlobalThresholds from "./SettingsGlobalThresholds.jsx"; import SettingsExport from "./SettingsExport.jsx"; import Button from "@mui/material/Button"; // Utils -import { settingsValidation } from "../../../Validation/validation.js"; +import { settingsValidation } from "@/Validation/validation.js"; import { useState } from "react"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import { useSelector, useDispatch } from "react-redux"; -import { - setTimezone, - setMode, - setLanguage, - setShowURL, -} from "../../../Features/UI/uiSlice.js"; +import { setTimezone, setMode, setLanguage, setShowURL } from "@/Features/UI/uiSlice.js"; import SettingsStats from "./SettingsStats.jsx"; -import { useFetchSettings, useSaveSettings } from "../../../Hooks/v1/settingsHooks.js"; -import { useIsAdmin } from "../../../Hooks/v1/useIsAdmin.js"; +import { useFetchSettings, useSaveSettings } from "@/Hooks/settingsHooks.js"; +import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; import { useAddDemoMonitors, useDeleteAllMonitors, useDeleteMonitorStats, useFetchJson, -} from "../../../Hooks/v1/monitorHooks.js"; +} from "@/Hooks/monitorHooks.js"; // Constants const BREADCRUMBS = [{ name: `Settings`, path: "/settings" }]; diff --git a/client/src/Pages/v1/StatusPage/Create/Components/MonitorList/index.jsx b/client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx similarity index 100% rename from client/src/Pages/v1/StatusPage/Create/Components/MonitorList/index.jsx rename to client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx b/client/src/Pages/StatusPage/Create/Components/Progress/index.jsx similarity index 92% rename from client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx rename to client/src/Pages/StatusPage/Create/Components/Progress/index.jsx index 232a12f1d..2d9731ea0 100644 --- a/client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx +++ b/client/src/Pages/StatusPage/Create/Components/Progress/index.jsx @@ -2,7 +2,7 @@ import { Button, Box } from "@mui/material"; import ProgressUpload from "@/Components/v1/ProgressBars/index.jsx"; import ImageIcon from "@mui/icons-material/Image"; import { useTranslation } from "react-i18next"; -import { formatBytes } from "../../../../../../Utils/fileUtils.js"; +import { formatBytes } from "../../../../../Utils/fileUtils.js"; const Progress = ({ isLoading, progressValue, logo, logoType, removeLogo, errors }) => { const { t } = useTranslation(); if (isLoading) { diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Skeleton/index.jsx b/client/src/Pages/StatusPage/Create/Components/Skeleton/index.jsx similarity index 100% rename from client/src/Pages/v1/StatusPage/Create/Components/Skeleton/index.jsx rename to client/src/Pages/StatusPage/Create/Components/Skeleton/index.jsx diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/ConfigStack.jsx similarity index 100% rename from client/src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx rename to client/src/Pages/StatusPage/Create/Components/Tabs/ConfigStack.jsx diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx similarity index 100% rename from client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx rename to client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx similarity index 98% rename from client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx rename to client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx index a668a9db8..d4c583d3a 100644 --- a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx +++ b/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx @@ -11,7 +11,7 @@ import Progress from "../Progress/index.jsx"; // Utils import { useTheme } from "@emotion/react"; -import timezones from "../../../../../../Utils/timezones.json"; +import timezones from "../../../../../Utils/timezones.json"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; import { useMemo, useState, useCallback } from "react"; diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx similarity index 100% rename from client/src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx rename to client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx diff --git a/client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx b/client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx similarity index 83% rename from client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx rename to client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx index 808598052..dac5997f9 100644 --- a/client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx +++ b/client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx @@ -1,6 +1,6 @@ import { useState } from "react"; -import { networkService } from "../../../../../main.jsx"; -import { createToast } from "../../../../../Utils/toastUtils.jsx"; +import { networkService } from "../../../../main.jsx"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; const useCreateStatusPage = (isCreate, url) => { const [isLoading, setIsLoading] = useState(false); diff --git a/client/src/Pages/v1/StatusPage/Create/Hooks/useMonitorsFetch.jsx b/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx similarity index 88% rename from client/src/Pages/v1/StatusPage/Create/Hooks/useMonitorsFetch.jsx rename to client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx index 81fefd65c..95ce2b8c0 100644 --- a/client/src/Pages/v1/StatusPage/Create/Hooks/useMonitorsFetch.jsx +++ b/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; -import { networkService } from "../../../../../main.jsx"; +import { networkService } from "../../../../main.jsx"; import { useSelector } from "react-redux"; -import { createToast } from "../../../../../Utils/toastUtils.jsx"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; const useMonitorsFetch = () => { const { user } = useSelector((state) => state.auth); diff --git a/client/src/Pages/v1/StatusPage/Create/index.jsx b/client/src/Pages/StatusPage/Create/index.jsx similarity index 97% rename from client/src/Pages/v1/StatusPage/Create/index.jsx rename to client/src/Pages/StatusPage/Create/index.jsx index d9fafed70..7fbe6a8dc 100644 --- a/client/src/Pages/v1/StatusPage/Create/index.jsx +++ b/client/src/Pages/StatusPage/Create/index.jsx @@ -8,11 +8,11 @@ import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; //Utils import { useTheme } from "@emotion/react"; import { useState, useEffect, useRef, useCallback } from "react"; -import { statusPageValidation } from "../../../../Validation/validation.js"; -import { buildErrors } from "../../../../Validation/error.js"; +import { statusPageValidation } from "../../../Validation/validation.js"; +import { buildErrors } from "../../../Validation/error.js"; import { useMonitorsFetch } from "./Hooks/useMonitorsFetch.jsx"; import { useCreateStatusPage } from "./Hooks/useCreateStatusPage.jsx"; -import { createToast } from "../../../../Utils/toastUtils.jsx"; +import { createToast } from "../../../Utils/toastUtils.jsx"; import { useNavigate } from "react-router-dom"; import { useStatusPageFetch } from "../Status/Hooks/useStatusPageFetch.jsx"; import { useParams } from "react-router-dom"; diff --git a/client/src/Pages/v1/StatusPage/Status/Components/AdminLink/index.jsx b/client/src/Pages/StatusPage/Status/Components/AdminLink/index.jsx similarity index 100% rename from client/src/Pages/v1/StatusPage/Status/Components/AdminLink/index.jsx rename to client/src/Pages/StatusPage/Status/Components/AdminLink/index.jsx diff --git a/client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx b/client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx similarity index 97% rename from client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx rename to client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx index c56aa40cc..60a4882f5 100644 --- a/client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx +++ b/client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx @@ -1,7 +1,7 @@ // Components import { Box, Stack, Typography, Button } from "@mui/material"; import Image from "@/Components/v1/Image/index.jsx"; -import SettingsIcon from "../../../../../../assets/icons/settings-bold.svg?react"; +import SettingsIcon from "@/assets/icons/settings-bold.svg?react"; import ThemeSwitch from "@/Components/v1/ThemeSwitch/index.jsx"; import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward"; //Utils diff --git a/client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx b/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx similarity index 95% rename from client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx rename to client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx index 1628b3d44..89e32e5d5 100644 --- a/client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx +++ b/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx @@ -6,7 +6,7 @@ import { StatusLabel } from "@/Components/v1/Label/index.jsx"; //Utils import { useTheme } from "@mui/material/styles"; -import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; +import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils.js"; import PropTypes from "prop-types"; import { useSelector } from "react-redux"; diff --git a/client/src/Pages/v1/StatusPage/Status/Components/Skeleton/index.jsx b/client/src/Pages/StatusPage/Status/Components/Skeleton/index.jsx similarity index 100% rename from client/src/Pages/v1/StatusPage/Status/Components/Skeleton/index.jsx rename to client/src/Pages/StatusPage/Status/Components/Skeleton/index.jsx diff --git a/client/src/Pages/v1/StatusPage/Status/Components/StatusBar/index.jsx b/client/src/Pages/StatusPage/Status/Components/StatusBar/index.jsx similarity index 100% rename from client/src/Pages/v1/StatusPage/Status/Components/StatusBar/index.jsx rename to client/src/Pages/StatusPage/Status/Components/StatusBar/index.jsx diff --git a/client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageDelete.jsx b/client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx similarity index 89% rename from client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageDelete.jsx rename to client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx index 038a771ba..73b9a8a5f 100644 --- a/client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageDelete.jsx +++ b/client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx @@ -1,6 +1,6 @@ import { useState } from "react"; -import { networkService } from "../../../../../Utils/NetworkService.js"; -import { createToast } from "../../../../../Utils/toastUtils.jsx"; +import { networkService } from "../../../../Utils/NetworkService.js"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; /** diff --git a/client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx b/client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx similarity index 87% rename from client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx rename to client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx index 506082887..7db6b6c5e 100644 --- a/client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx +++ b/client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx @@ -1,9 +1,9 @@ import { useEffect, useState, useCallback } from "react"; -import { networkService } from "../../../../../main.jsx"; +import { networkService } from "../../../../main.jsx"; import { useSelector } from "react-redux"; -import { createToast } from "../../../../../Utils/toastUtils.jsx"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; import { useTheme } from "@emotion/react"; -import { useMonitorUtils } from "../../../../../Hooks/v1/useMonitorUtils.js"; +import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils.js"; const useStatusPageFetch = (isCreate = false, url) => { const [isLoading, setIsLoading] = useState(true); diff --git a/client/src/Pages/v1/StatusPage/Status/index.jsx b/client/src/Pages/StatusPage/Status/index.jsx similarity index 98% rename from client/src/Pages/v1/StatusPage/Status/index.jsx rename to client/src/Pages/StatusPage/Status/index.jsx index e700bbbd9..adcd9c2c7 100644 --- a/client/src/Pages/v1/StatusPage/Status/index.jsx +++ b/client/src/Pages/StatusPage/Status/index.jsx @@ -12,7 +12,7 @@ import TextLink from "@/Components/v1/TextLink/index.jsx"; // Utils import { useStatusPageFetch } from "./Hooks/useStatusPageFetch.jsx"; import { useTheme } from "@emotion/react"; -import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; +import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; import { useLocation } from "react-router-dom"; import { useParams } from "react-router-dom"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx b/client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx similarity index 100% rename from client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx rename to client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx diff --git a/client/src/Pages/v1/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx b/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx similarity index 86% rename from client/src/Pages/v1/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx rename to client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx index 89df1b84d..839ff4cdf 100644 --- a/client/src/Pages/v1/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx +++ b/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx @@ -1,7 +1,7 @@ import { useState, useEffect } from "react"; -import { networkService } from "../../../../../main.jsx"; +import { networkService } from "../../../../main.jsx"; import { useSelector } from "react-redux"; -import { createToast } from "../../../../../Utils/toastUtils.jsx"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; const useStatusPagesFetch = () => { const { user } = useSelector((state) => state.auth); diff --git a/client/src/Pages/v1/StatusPage/StatusPages/index.jsx b/client/src/Pages/StatusPage/StatusPages/index.jsx similarity index 95% rename from client/src/Pages/v1/StatusPage/StatusPages/index.jsx rename to client/src/Pages/StatusPage/StatusPages/index.jsx index 3c60fe950..7137d01ad 100644 --- a/client/src/Pages/v1/StatusPage/StatusPages/index.jsx +++ b/client/src/Pages/StatusPage/StatusPages/index.jsx @@ -7,7 +7,7 @@ import PageStateWrapper from "@/Components/v1/PageStateWrapper/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { useStatusPagesFetch } from "./Hooks/useStatusPagesFetch.jsx"; -import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; +import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; const BREADCRUMBS = [{ name: `Status Pages`, path: "" }]; const StatusPages = () => { diff --git a/client/src/Pages/v1/Uptime/BulkImport/Upload.jsx b/client/src/Pages/Uptime/BulkImport/Upload.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/BulkImport/Upload.jsx rename to client/src/Pages/Uptime/BulkImport/Upload.jsx diff --git a/client/src/Pages/v1/Uptime/BulkImport/index.jsx b/client/src/Pages/Uptime/BulkImport/index.jsx similarity index 95% rename from client/src/Pages/v1/Uptime/BulkImport/index.jsx rename to client/src/Pages/Uptime/BulkImport/index.jsx index 9f3efcd67..6f2f7bcb5 100644 --- a/client/src/Pages/v1/Uptime/BulkImport/index.jsx +++ b/client/src/Pages/Uptime/BulkImport/index.jsx @@ -5,14 +5,14 @@ import { useState } from "react"; import { Box, Stack, Typography, Button, Link } from "@mui/material"; //Components -import { createToast } from "../../../../Utils/toastUtils.jsx"; +import { createToast } from "../../../Utils/toastUtils.jsx"; import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; import UploadFile from "./Upload.jsx"; import { useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; import { Trans, useTranslation } from "react-i18next"; -import { useCreateBulkMonitors } from "../../../../Hooks/v1/monitorHooks.js"; +import { useCreateBulkMonitors } from "../../../Hooks/monitorHooks.js"; const BulkImport = () => { const theme = useTheme(); diff --git a/client/src/Pages/v1/Uptime/Create/index.css b/client/src/Pages/Uptime/Create/index.css similarity index 100% rename from client/src/Pages/v1/Uptime/Create/index.css rename to client/src/Pages/Uptime/Create/index.css diff --git a/client/src/Pages/v1/Uptime/Create/index.jsx b/client/src/Pages/Uptime/Create/index.jsx similarity index 98% rename from client/src/Pages/v1/Uptime/Create/index.jsx rename to client/src/Pages/Uptime/Create/index.jsx index 202f64d85..c3421e875 100644 --- a/client/src/Pages/v1/Uptime/Create/index.jsx +++ b/client/src/Pages/Uptime/Create/index.jsx @@ -26,14 +26,14 @@ import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { monitorValidation } from "../../../../Validation/validation.js"; -import { createToast } from "../../../../Utils/toastUtils.jsx"; +import { monitorValidation } from "../../../Validation/validation.js"; +import { createToast } from "../../../Utils/toastUtils.jsx"; import { PauseOutlined as PauseOutlinedIcon, PlayArrowOutlined as PlayArrowOutlinedIcon, } from "@mui/icons-material"; -import { useMonitorUtils } from "../../../../Hooks/v1/useMonitorUtils.js"; -import { useGetNotificationsByTeamId } from "../../../../Hooks/v1/useNotifications.js"; +import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js"; +import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications.js"; import { useParams } from "react-router-dom"; import { useCreateMonitor, @@ -42,7 +42,7 @@ import { usePauseMonitor, useFetchMonitorById, useFetchMonitorGames, -} from "../../../../Hooks/v1/monitorHooks.js"; +} from "../../../Hooks/monitorHooks.js"; /** * Create page renders monitor creation or configuration views. @@ -287,7 +287,8 @@ const UptimeCreate = ({ isClone = false }) => { const handleRemove = async (event) => { event.preventDefault(); - await deleteMonitor({ monitor, redirect: "/uptime" }); + const TEMP_MONITOR = { id: monitor._id }; + await deleteMonitor({ monitor: TEMP_MONITOR, redirect: "/uptime" }); }; const isBusy = isLoading || isCreating || isDeleting || isUpdating || isPausing; diff --git a/client/src/Pages/v1/Uptime/Create/skeleton.jsx b/client/src/Pages/Uptime/Create/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Create/skeleton.jsx rename to client/src/Pages/Uptime/Create/skeleton.jsx diff --git a/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx b/client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx similarity index 92% rename from client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx rename to client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx index 45f35a42e..582c7891f 100644 --- a/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx +++ b/client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx @@ -1,15 +1,15 @@ // Components import { Stack, Typography, Box } from "@mui/material"; import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx"; -import UptimeIcon from "../../../../../../assets/icons/uptime-icon.svg?react"; -import IncidentsIcon from "../../../../../../assets/icons/incidents.svg?react"; -import AverageResponseIcon from "../../../../../../assets/icons/average-response-icon.svg?react"; +import UptimeIcon from "@/assets/icons/uptime-icon.svg?react"; +import IncidentsIcon from "@/assets/icons/incidents.svg?react"; +import AverageResponseIcon from "@/assets/icons/average-response-icon.svg?react"; import UpBarChart from "../Charts/UpBarChart.jsx"; import DownBarChart from "../Charts/DownBarChart.jsx"; import ResponseGaugeChart from "../Charts/ResponseGaugeChart.jsx"; import SkeletonLayout from "./skeleton.jsx"; // Utils -import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js"; +import { formatDateWithTz } from "../../../../../Utils/timeUtils.js"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { useState } from "react"; diff --git a/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/skeleton.jsx b/client/src/Pages/Uptime/Details/Components/ChartBoxes/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/skeleton.jsx rename to client/src/Pages/Uptime/Details/Components/ChartBoxes/skeleton.jsx diff --git a/client/src/Pages/v1/Uptime/Details/Components/Charts/CustomLabels.jsx b/client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx similarity index 93% rename from client/src/Pages/v1/Uptime/Details/Components/Charts/CustomLabels.jsx rename to client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx index c9c1ff58d..472c8d058 100644 --- a/client/src/Pages/v1/Uptime/Details/Components/Charts/CustomLabels.jsx +++ b/client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx @@ -1,6 +1,6 @@ import PropTypes from "prop-types"; import { useSelector } from "react-redux"; -import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js"; +import { formatDateWithTz } from "../../../../../Utils/timeUtils.js"; const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint, type }) => { const uiTimezone = useSelector((state) => state.ui.timezone); diff --git a/client/src/Pages/v1/Uptime/Details/Components/Charts/DownBarChart.jsx b/client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Details/Components/Charts/DownBarChart.jsx rename to client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx diff --git a/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx b/client/src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx rename to client/src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx diff --git a/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx b/client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx similarity index 89% rename from client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx rename to client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx index daa7b4f53..861b600d4 100644 --- a/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx +++ b/client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx @@ -1,6 +1,6 @@ import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx"; import MonitorDetailsAreaChart from "@/Components/v1/Charts/MonitorDetailsAreaChart/index.jsx"; -import ResponseTimeIcon from "../../../../../../assets/icons/response-time-icon.svg?react"; +import ResponseTimeIcon from "@/assets/icons/response-time-icon.svg?react"; import SkeletonLayout from "./ResponseTimeChartSkeleton.jsx"; import PropTypes from "prop-types"; diff --git a/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx b/client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx rename to client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx diff --git a/client/src/Pages/v1/Uptime/Details/Components/Charts/UpBarChart.jsx b/client/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Details/Components/Charts/UpBarChart.jsx rename to client/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx diff --git a/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx b/client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx similarity index 97% rename from client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx rename to client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx index 0d0defc3b..0715561d4 100644 --- a/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx +++ b/client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx @@ -1,11 +1,11 @@ import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx"; import PropTypes from "prop-types"; -import HistoryIcon from "../../../../../../assets/icons/history-icon.svg?react"; +import HistoryIcon from "@/assets/icons/history-icon.svg?react"; import Table from "@/Components/v1/Table/index.jsx"; import TablePagination from "@/Components/v1/Table/TablePagination/index.jsx"; import { StatusLabel } from "@/Components/v1/Label/index.jsx"; import { useTranslation } from "react-i18next"; -import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js"; +import { formatDateWithTz } from "../../../../../Utils/timeUtils.js"; import SkeletonLayout from "./skeleton.jsx"; import Tooltip from "@mui/material/Tooltip"; import Box from "@mui/material/Box"; diff --git a/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/skeleton.jsx b/client/src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Details/Components/ResponseTable/skeleton.jsx rename to client/src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx diff --git a/client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx b/client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx similarity index 92% rename from client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx rename to client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx index a7b971839..0bfdc1096 100644 --- a/client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx +++ b/client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx @@ -2,10 +2,10 @@ import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx"; import StatBox from "@/Components/v1/StatBox/index.jsx"; import PropTypes from "prop-types"; -import { getHumanReadableDuration } from "../../../../../../Utils/timeUtils.js"; +import { getHumanReadableDuration } from "../../../../../Utils/timeUtils.js"; import { useTheme } from "@mui/material/styles"; import { Typography } from "@mui/material"; -import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; +import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils.js"; const UptimeStatusBoxes = ({ isLoading = false, diff --git a/client/src/Pages/v1/Uptime/Details/Hooks/useCertificateFetch.jsx b/client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx similarity index 84% rename from client/src/Pages/v1/Uptime/Details/Hooks/useCertificateFetch.jsx rename to client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx index f85111f97..e74a99d82 100644 --- a/client/src/Pages/v1/Uptime/Details/Hooks/useCertificateFetch.jsx +++ b/client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx @@ -1,7 +1,7 @@ -import { logger } from "../../../../../Utils/Logger.js"; +import { logger } from "../../../../Utils/Logger.js"; import { useEffect, useState } from "react"; -import { networkService } from "../../../../../main.jsx"; -import { formatDateWithTz } from "../../../../../Utils/timeUtils.js"; +import { networkService } from "../../../../main.jsx"; +import { formatDateWithTz } from "../../../../Utils/timeUtils.js"; const useCertificateFetch = ({ monitor, diff --git a/client/src/Pages/v1/Uptime/Details/index.jsx b/client/src/Pages/Uptime/Details/index.jsx similarity index 95% rename from client/src/Pages/v1/Uptime/Details/index.jsx rename to client/src/Pages/Uptime/Details/index.jsx index f5e283495..144ceb4dc 100644 --- a/client/src/Pages/v1/Uptime/Details/index.jsx +++ b/client/src/Pages/Uptime/Details/index.jsx @@ -15,10 +15,10 @@ import { useState } from "react"; import { useParams } from "react-router-dom"; import { useSelector } from "react-redux"; import { useTheme } from "@emotion/react"; -import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; -import { useFetchUptimeMonitorById } from "../../../../Hooks/v1/monitorHooks.js"; +import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; +import { useFetchUptimeMonitorById } from "../../../Hooks/monitorHooks.js"; import useCertificateFetch from "./Hooks/useCertificateFetch.jsx"; -import { useFetchChecksByMonitor } from "../../../../Hooks/v1/checkHooks.js"; +import { useFetchChecksByMonitor } from "../../../Hooks/checkHooks.js"; import { useTranslation } from "react-i18next"; // Constants diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx b/client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx rename to client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/LoadingSpinner/index.jsx b/client/src/Pages/Uptime/Monitors/Components/LoadingSpinner/index.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Monitors/Components/LoadingSpinner/index.jsx rename to client/src/Pages/Uptime/Monitors/Components/LoadingSpinner/index.jsx diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx b/client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx rename to client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/Skeleton/index.jsx b/client/src/Pages/Uptime/Monitors/Components/Skeleton/index.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Monitors/Components/Skeleton/index.jsx rename to client/src/Pages/Uptime/Monitors/Components/Skeleton/index.jsx diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/index.jsx b/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/index.jsx rename to client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx b/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx rename to client/src/Pages/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx b/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx similarity index 90% rename from client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx rename to client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx index c745e7935..c9015816a 100644 --- a/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx +++ b/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx @@ -1,9 +1,9 @@ import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { Box, Stack, Typography } from "@mui/material"; -import Arrow from "../../../../../../assets/icons/top-right-arrow.svg?react"; -import Background from "../../../../../../assets/Images/background-grid.svg?react"; -import ClockSnooze from "../../../../../../assets/icons/clock-snooze.svg?react"; +import Arrow from "@/assets/icons/top-right-arrow.svg?react"; +import Background from "@/assets/Images/background-grid.svg?react"; +import ClockSnooze from "@/assets/icons/clock-snooze.svg?react"; const StatusBox = ({ title, value, status }) => { const theme = useTheme(); diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx b/client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx similarity index 97% rename from client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx rename to client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx index 9cf2d247d..fe220251d 100644 --- a/client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx +++ b/client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx @@ -13,7 +13,7 @@ import TableSkeleton from "@/Components/v1/Table/skeleton.jsx"; // Utils import { useTheme } from "@emotion/react"; -import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; +import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils.js"; import { useNavigate } from "react-router-dom"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; @@ -24,13 +24,13 @@ import { useTranslation } from "react-i18next"; * @param {boolean} props.isAdmin - Whether the current user has admin privileges * @param {boolean} props.isLoading - Loading state of the table * @param {Array<{ - * _id: string, + * id: string, * url: string, * title: string, * percentage: number, * percentageColor: string, * monitor: { - * _id: string, + * id: string, * type: string, * checks: Array * } @@ -101,7 +101,7 @@ const UptimeDataTable = ({ ), render: (row) => ( { - navigate(`/uptime/${row._id}`); + navigate(`/uptime/${row.id}`); }, emptyView: "No monitors found", }} diff --git a/client/src/Pages/v1/Uptime/Monitors/Hooks/useDebounce.jsx b/client/src/Pages/Uptime/Monitors/Hooks/useDebounce.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Monitors/Hooks/useDebounce.jsx rename to client/src/Pages/Uptime/Monitors/Hooks/useDebounce.jsx diff --git a/client/src/Pages/v1/Uptime/Monitors/index.jsx b/client/src/Pages/Uptime/Monitors/index.jsx similarity index 96% rename from client/src/Pages/v1/Uptime/Monitors/index.jsx rename to client/src/Pages/Uptime/Monitors/index.jsx index cacfc23b4..9a73fae63 100644 --- a/client/src/Pages/v1/Uptime/Monitors/index.jsx +++ b/client/src/Pages/Uptime/Monitors/index.jsx @@ -5,7 +5,7 @@ // Components import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; -import Greeting from "../../../../Utils/greeting.jsx"; +import Greeting from "../../../Utils/greeting.jsx"; import StatusBoxes from "./Components/StatusBoxes/index.jsx"; import UptimeDataTable from "./Components/UptimeDataTable/index.jsx"; import Pagination from "@/Components/v1/Table/TablePagination/index.jsx"; @@ -20,16 +20,16 @@ import MonitorCountHeader from "@/Components/v1/MonitorCountHeader/index.jsx"; import { Stack, Box, Button } from "@mui/material"; // Utils import { useState, useCallback, useEffect } from "react"; -import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; +import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; import { useSelector, useDispatch } from "react-redux"; -import { setRowsPerPage } from "../../../../Features/UI/uiSlice.js"; +import { setRowsPerPage } from "../../../Features/UI/uiSlice.js"; import PropTypes from "prop-types"; import { useFetchMonitorsWithSummary, useFetchMonitorsWithChecks, -} from "../../../../Hooks/v1/monitorHooks.js"; +} from "../../../Hooks/monitorHooks.js"; import { useTranslation } from "react-i18next"; const TYPES = ["http", "ping", "docker", "port", "game"]; diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index 255d913c1..d9f6d58cb 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -1,58 +1,58 @@ import { useSelector } from "react-redux"; import { Navigate, Route, Routes as LibRoutes } from "react-router"; import HomeLayout from "@/Components/v1/Layouts/HomeLayout"; -import NotFound from "../Pages/v1/NotFound"; +import NotFound from "../Pages/NotFound/index.jsx"; // Auth -import AuthLogin from "../Pages/v1/Auth/Login"; -import AuthRegister from "../Pages/v1/Auth/Register/"; -import AuthForgotPassword from "../Pages/v1/Auth/ForgotPassword"; -import AuthCheckEmail from "../Pages/v1/Auth/CheckEmail"; -import AuthSetNewPassword from "../Pages/v1/Auth/SetNewPassword"; -import AuthNewPasswordConfirmed from "../Pages/v1/Auth/NewPasswordConfirmed"; +import AuthLogin from "../Pages/Auth/Login/index.jsx"; +import AuthRegister from "../Pages/Auth/Register/index.jsx"; +import AuthForgotPassword from "../Pages/Auth/ForgotPassword.jsx"; +import AuthCheckEmail from "../Pages/Auth/CheckEmail.jsx"; +import AuthSetNewPassword from "../Pages/Auth/SetNewPassword.jsx"; +import AuthNewPasswordConfirmed from "../Pages/Auth/NewPasswordConfirmed.jsx"; // Uptime -import Uptime from "../Pages/v1/Uptime/Monitors"; -import UptimeDetails from "../Pages/v1/Uptime/Details"; -import UptimeCreate from "../Pages/v1/Uptime/Create"; +import Uptime from "../Pages/Uptime/Monitors/index.jsx"; +import UptimeDetails from "../Pages/Uptime/Details/index.jsx"; +import UptimeCreate from "../Pages/Uptime/Create/index.jsx"; // PageSpeed -import PageSpeed from "../Pages/v1/PageSpeed/Monitors"; -import PageSpeedDetails from "../Pages/v1/PageSpeed/Details"; -import PageSpeedCreate from "../Pages/v1/PageSpeed/Create"; +import PageSpeed from "../Pages/PageSpeed/Monitors/index.jsx"; +import PageSpeedDetails from "../Pages/PageSpeed/Details/index.jsx"; +import PageSpeedCreate from "../Pages/PageSpeed/Create/index.jsx"; // Infrastructure -import Infrastructure from "../Pages/v1/Infrastructure/Monitors"; -import InfrastructureCreate from "../Pages/v1/Infrastructure/Create"; -import InfrastructureDetails from "../Pages/v1/Infrastructure/Details"; +import Infrastructure from "../Pages/Infrastructure/Monitors/index.jsx"; +import InfrastructureCreate from "../Pages/Infrastructure/Create/index.jsx"; +import InfrastructureDetails from "../Pages/Infrastructure/Details/index.jsx"; // Server Status -import ServerUnreachable from "../Pages/v1/ServerUnreachable.jsx"; +import ServerUnreachable from "../Pages/ServerUnreachable.jsx"; // Incidents -import Checks from "../Pages/v1/Incidents"; -import Incidents2 from "../Pages/v1/Incidents2"; +import Checks from "../Pages/Incidents"; +import Incidents2 from "../Pages/Incidents2/index.jsx"; // Status pages -import CreateStatus from "../Pages/v1/StatusPage/Create"; -import StatusPages from "../Pages/v1/StatusPage/StatusPages"; -import Status from "../Pages/v1/StatusPage/Status"; +import CreateStatus from "../Pages/StatusPage/Create/index.jsx"; +import StatusPages from "../Pages/StatusPage/StatusPages/index.jsx"; +import Status from "../Pages/StatusPage/Status/index.jsx"; -import Notifications from "../Pages/v1/Notifications"; -import CreateNotifications from "../Pages/v1/Notifications/create"; +import Notifications from "../Pages/Notifications/index.jsx"; +import CreateNotifications from "../Pages/Notifications/create/index.jsx"; // Settings -import Account from "../Pages/v1/Account"; -import EditUser from "../Pages/v1/Account/EditUser"; -import Settings from "../Pages/v1/Settings"; +import Account from "../Pages/Account/index.jsx"; +import EditUser from "../Pages/Account/EditUser/index.jsx"; +import Settings from "../Pages/Settings"; -import Maintenance from "../Pages/v1/Maintenance"; +import Maintenance from "../Pages/Maintenance/index.jsx"; import ProtectedRoute from "../Components/v1/ProtectedRoute"; import RoleProtectedRoute from "../Components/v1/RoleProtectedRoute"; -import CreateNewMaintenanceWindow from "../Pages/v1/Maintenance/CreateMaintenance"; +import CreateNewMaintenanceWindow from "../Pages/Maintenance/CreateMaintenance/index.jsx"; import withAdminCheck from "@/Components/v1/HOC/withAdminCheck"; -import BulkImport from "../Pages/v1/Uptime/BulkImport"; -import Logs from "../Pages/v1/Logs"; +import BulkImport from "../Pages/Uptime/BulkImport/index.jsx"; +import Logs from "../Pages/Logs/index.jsx"; const Routes = () => { const mode = useSelector((state) => state.ui.mode); diff --git a/server/src/db/models/Check.ts b/server/src/db/models/Check.ts index b4a31f8f7..3ebacd3f6 100644 --- a/server/src/db/models/Check.ts +++ b/server/src/db/models/Check.ts @@ -16,6 +16,7 @@ import type { CheckNetworkInterfaceInfo, CheckTimings, CheckTimingPhases, + ILighthouseAudit, } from "@/types/check.js"; type CheckMetadataDocument = Omit & { @@ -149,13 +150,25 @@ const networkInterfaceSchema = new Schema( { _id: false } ); +const lighthouseAuditSchema = new Schema( + { + id: { type: String }, + title: { type: String }, + score: { type: Number }, + displayValue: { type: String }, + numericValue: { type: Number }, + numericUnit: { type: String }, + }, + { _id: false } +); + const auditsSchema = new Schema( { - cls: { type: Number, default: 0 }, - si: { type: Number, default: 0 }, - fcp: { type: Number, default: 0 }, - lcp: { type: Number, default: 0 }, - tbt: { type: Number, default: 0 }, + cls: { type: lighthouseAuditSchema, default: undefined }, + si: { type: lighthouseAuditSchema, default: undefined }, + fcp: { type: lighthouseAuditSchema, default: undefined }, + lcp: { type: lighthouseAuditSchema, default: undefined }, + tbt: { type: lighthouseAuditSchema, default: undefined }, }, { _id: false } ); diff --git a/server/src/db/modules/checkModule.js b/server/src/db/modules/checkModule.js index 871761290..e420d5ce7 100755 --- a/server/src/db/modules/checkModule.js +++ b/server/src/db/modules/checkModule.js @@ -22,7 +22,7 @@ class CheckModule { createChecks = async (checks) => { try { - await CheckModel.insertMany(checks, { ordered: false }); + await CheckModel.insertMany(checks, { ordered: false, runValidators: true }); } catch (error) { error.service = SERVICE_NAME; error.method = "createCheck"; diff --git a/server/src/db/modules/monitorModule.js b/server/src/db/modules/monitorModule.js index 316caa2ea..d1d4abc5a 100755 --- a/server/src/db/modules/monitorModule.js +++ b/server/src/db/modules/monitorModule.js @@ -242,7 +242,6 @@ class MonitorModule { const dateString = formatLookup[dateRange]; - console.log("WTTTTFFF"); const results = await CheckModel.aggregate(buildUptimeDetailsPipeline(monitorId, dates, dateString)); const monitorData = results[0]; diff --git a/server/src/service/business/monitorService.js b/server/src/service/business/monitorService.js index f457e5c7a..4bdc45eb4 100644 --- a/server/src/service/business/monitorService.js +++ b/server/src/service/business/monitorService.js @@ -220,7 +220,6 @@ class MonitorService { }; getMonitorsByTeamId = async ({ teamId, limit, type, page, rowsPerPage, filter, field, order }) => { - console.log("que"); const monitors = await this.db.monitorModule.getMonitorsByTeamId({ limit, type, diff --git a/server/src/service/infrastructure/statusService.js b/server/src/service/infrastructure/statusService.js index 843c356ed..5b64c7419 100755 --- a/server/src/service/infrastructure/statusService.js +++ b/server/src/service/infrastructure/statusService.js @@ -367,18 +367,30 @@ class StatusService { } const categories = payload?.lighthouseResult?.categories ?? {}; const audits = payload?.lighthouseResult?.audits ?? {}; - const { - "cumulative-layout-shift": cls = 0, - "speed-index": si = 0, - "first-contentful-paint": fcp = 0, - "largest-contentful-paint": lcp = 0, - "total-blocking-time": tbt = 0, - } = audits; + const mapAudit = (audit) => { + if (!audit || typeof audit !== "object") { + return undefined; + } + return { + id: audit.id, + title: audit.title, + score: typeof audit.score === "number" ? audit.score : audit.score ?? null, + displayValue: audit.displayValue, + numericValue: typeof audit.numericValue === "number" ? audit.numericValue : undefined, + numericUnit: audit.numericUnit, + }; + }; check.accessibility = (categories?.accessibility?.score || 0) * 100; check.bestPractices = (categories?.["best-practices"]?.score || 0) * 100; check.seo = (categories?.seo?.score || 0) * 100; check.performance = (categories?.performance?.score || 0) * 100; - check.audits = { cls, si, fcp, lcp, tbt }; + check.audits = { + cls: mapAudit(audits?.["cumulative-layout-shift"]), + si: mapAudit(audits?.["speed-index"]), + fcp: mapAudit(audits?.["first-contentful-paint"]), + lcp: mapAudit(audits?.["largest-contentful-paint"]), + tbt: mapAudit(audits?.["total-blocking-time"]), + }; } if (type === "hardware") { diff --git a/server/src/types/check.ts b/server/src/types/check.ts index 2120d5de1..28353497e 100644 --- a/server/src/types/check.ts +++ b/server/src/types/check.ts @@ -86,11 +86,20 @@ export interface CheckNetworkInterfaceInfo { } export interface CheckAudits { - cls: number; - si: number; - fcp: number; - lcp: number; - tbt: number; + cls: ILighthouseAudit; + si: ILighthouseAudit; + fcp: ILighthouseAudit; + lcp: ILighthouseAudit; + tbt: ILighthouseAudit; +} + +export interface ILighthouseAudit { + id?: string; + title?: string; + score?: number | null; + displayValue?: string; + numericValue?: number; + numericUnit?: string; } export interface Check { From 2b0a51520a40f28b54877b1658f4c44fbcdc6e35 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 13 Jan 2026 17:50:31 +0000 Subject: [PATCH 05/24] infra --- .../Pages/Infrastructure/Monitors/index.jsx | 20 +++++++++++-------- client/src/Pages/Uptime/Monitors/index.jsx | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/client/src/Pages/Infrastructure/Monitors/index.jsx b/client/src/Pages/Infrastructure/Monitors/index.jsx index 90817f116..4d739417c 100644 --- a/client/src/Pages/Infrastructure/Monitors/index.jsx +++ b/client/src/Pages/Infrastructure/Monitors/index.jsx @@ -13,7 +13,11 @@ import { useTheme } from "@emotion/react"; import { useEffect, useState } from "react"; import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; import { useTranslation } from "react-i18next"; -import { useFetchMonitorsByTeamId } from "../../../Hooks/monitorHooks.js"; +import { + useFetchMonitorsByTeamId, + useFetchMonitorsWithChecks, +} from "@/Hooks/monitorHooks.js"; + import { useDispatch, useSelector } from "react-redux"; import { setRowsPerPage } from "../../../Features/UI/uiSlice.js"; // Constants @@ -70,14 +74,14 @@ const InfrastructureMonitors = () => { const field = toFilterStatus !== undefined ? "status" : undefined; - const [monitors, summary, isLoading, networkError] = useFetchMonitorsByTeamId({ - limit: 1, + const [monitors, count, isLoading, networkError] = useFetchMonitorsWithChecks({ types: TYPES, - page, + limit: 1, + page: page, field: field, filter: toFilterStatus ?? search, - rowsPerPage, - updateTrigger, + rowsPerPage: rowsPerPage, + monitorUpdateTrigger: updateTrigger, }); return ( @@ -99,7 +103,7 @@ const InfrastructureMonitors = () => { { isSearching={isSearching} /> Date: Tue, 13 Jan 2026 18:37:22 +0000 Subject: [PATCH 06/24] _id -> id --- .../Infrastructure/Monitors/Components/MonitorsTable/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx index 510115d8a..5f0360312 100644 --- a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx +++ b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx @@ -115,7 +115,7 @@ const MonitorsTable = ({ : theme.palette.success.main; return { - id: monitor._id, + id: monitor.id, name: monitor.name, url: monitor.url, processor, From f8e88623a881fb5b55126de8b325a1bed3c225ac Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 13 Jan 2026 18:54:28 +0000 Subject: [PATCH 07/24] hardware details pipeline refactor --- server/src/db/modules/monitorModule.js | 16 +- server/src/db/modules/monitorModuleQueries.js | 829 ++++-------------- 2 files changed, 188 insertions(+), 657 deletions(-) diff --git a/server/src/db/modules/monitorModule.js b/server/src/db/modules/monitorModule.js index d1d4abc5a..9c8ca7e32 100755 --- a/server/src/db/modules/monitorModule.js +++ b/server/src/db/modules/monitorModule.js @@ -1,11 +1,13 @@ import { buildUptimeDetailsPipeline, - buildHardwareDetailsPipeline, buildMonitorSummaryByTeamIdPipeline, buildMonitorsByTeamIdPipeline, buildMonitorsAndSummaryByTeamIdPipeline, buildMonitorsWithChecksByTeamIdPipeline, buildFilteredMonitorsByTeamIdPipeline, + getHardwareStats, + getUpChecks, + getAggregateData, } from "./monitorModuleQueries.js"; import { CheckModel } from "@/db/models/index.js"; @@ -317,9 +319,17 @@ class MonitorModule { }; const dateString = formatLookup[dateRange]; - const hardwareStats = await CheckModel.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString)); + const [aggregateData, upChecksCount, metrics] = await Promise.all([ + getAggregateData(monitorId, dates), + getUpChecks(monitorId, dates), + getHardwareStats(monitorId, dates, dateString), + ]); - const stats = hardwareStats[0]; + const stats = { + aggregateData: aggregateData, + upChecks: upChecksCount, + checks: metrics, + }; return { ...monitor.toObject(), diff --git a/server/src/db/modules/monitorModuleQueries.js b/server/src/db/modules/monitorModuleQueries.js index e6c6b4ec6..596f11791 100755 --- a/server/src/db/modules/monitorModuleQueries.js +++ b/server/src/db/modules/monitorModuleQueries.js @@ -1,4 +1,5 @@ import { ObjectId } from "mongodb"; +import { CheckModel } from "@/db/models/index.js"; const buildUptimeDetailsPipeline = (monitorId, dates, dateString) => { return [ @@ -168,659 +169,6 @@ const buildUptimeDetailsPipeline = (monitorId, dates, dateString) => { ]; }; -const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { - return [ - { - $match: { - "metadata.monitorId": monitor._id, - "metadata.type": "hardware", - createdAt: { $gte: dates.start, $lte: dates.end }, - }, - }, - { - $sort: { - createdAt: 1, - }, - }, - { - $facet: { - aggregateData: [ - { - $group: { - _id: null, - latestCheck: { - $last: "$$ROOT", - }, - totalChecks: { - $sum: 1, - }, - }, - }, - ], - upChecks: [ - { - $match: { - status: true, - }, - }, - { - $group: { - _id: null, - totalChecks: { - $sum: 1, - }, - }, - }, - ], - checks: [ - { - $limit: 1, - }, - { - $project: { - diskCount: { - $size: { $ifNull: ["$disk", []] }, - }, - netCount: { $size: { $ifNull: ["$net", []] } }, - }, - }, - { - $lookup: { - from: "checks", - let: { - diskCount: "$diskCount", - netCount: "$netCount", - }, - pipeline: [ - { - $match: { - $expr: { - $and: [ - { $eq: ["$metadata.monitorId", monitor._id] }, - { $eq: ["$metadata.type", "hardware"] }, - { $gte: ["$createdAt", dates.start] }, - { $lte: ["$createdAt", dates.end] }, - ], - }, - }, - }, - { - $group: { - _id: { - $dateToString: { - format: dateString, - date: "$createdAt", - }, - }, - avgCpuUsage: { - $avg: "$cpu.usage_percent", - }, - avgMemoryUsage: { - $avg: "$memory.usage_percent", - }, - avgTemperatures: { - $push: { - $ifNull: ["$cpu.temperature", [0]], - }, - }, - disks: { - $push: "$disk", - }, - net: { - $push: "$net", - }, - updatedAts: { - $push: "$updatedAt", - }, - }, - }, - { - $project: { - _id: 1, - avgCpuUsage: 1, - avgMemoryUsage: 1, - avgTemperature: { - $map: { - input: { - $range: [ - 0, - { - $size: { - // Handle null temperatures array - $ifNull: [ - { $arrayElemAt: ["$avgTemperatures", 0] }, - [0], // Default to single-element array if null - ], - }, - }, - ], - }, - as: "index", - in: { - $avg: { - $map: { - input: "$avgTemperatures", - as: "tempArray", - in: { - $ifNull: [ - { $arrayElemAt: ["$$tempArray", "$$index"] }, - 0, // Default to 0 if element is null - ], - }, - }, - }, - }, - }, - }, - disks: { - $map: { - input: { - $range: [0, "$$diskCount"], - }, - as: "diskIndex", - in: { - name: { - $concat: [ - "disk", - { - $toString: "$$diskIndex", - }, - ], - }, - readSpeed: { - $avg: { - $map: { - input: "$disks", - as: "diskArray", - in: { - $arrayElemAt: ["$$diskArray.read_speed_bytes", "$$diskIndex"], - }, - }, - }, - }, - writeSpeed: { - $avg: { - $map: { - input: "$disks", - as: "diskArray", - in: { - $arrayElemAt: ["$$diskArray.write_speed_bytes", "$$diskIndex"], - }, - }, - }, - }, - totalBytes: { - $avg: { - $map: { - input: "$disks", - as: "diskArray", - in: { - $arrayElemAt: ["$$diskArray.total_bytes", "$$diskIndex"], - }, - }, - }, - }, - freeBytes: { - $avg: { - $map: { - input: "$disks", - as: "diskArray", - in: { - $arrayElemAt: ["$$diskArray.free_bytes", "$$diskIndex"], - }, - }, - }, - }, - usagePercent: { - $avg: { - $map: { - input: "$disks", - as: "diskArray", - in: { - $arrayElemAt: ["$$diskArray.usage_percent", "$$diskIndex"], - }, - }, - }, - }, - }, - }, - }, - net: { - $map: { - input: { $range: [0, { $size: { $ifNull: [{ $arrayElemAt: ["$net", 0] }, []] } }] }, - as: "netIndex", - in: { - name: { - $arrayElemAt: [ - { - $map: { - input: { $arrayElemAt: ["$net", 0] }, - as: "iface", - in: "$$iface.name", - }, - }, - "$$netIndex", - ], - }, - bytesSentPerSecond: { - $let: { - vars: { - first: { - $arrayElemAt: [ - { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.bytes_sent" } }, - "$$netIndex", - ], - }, - last: { - $arrayElemAt: [ - { - $map: { - input: { - $arrayElemAt: [{ $ifNull: ["$net", []] }, { $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1] }], - }, - as: "iface", - in: "$$iface.bytes_sent", - }, - }, - "$$netIndex", - ], - }, - tFirst: { $arrayElemAt: ["$updatedAts", 0] }, - tLast: { - $arrayElemAt: [{ $ifNull: ["$updatedAts", []] }, { $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1] }], - }, - }, - in: { - $cond: [ - { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, - { - $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], - }, - 0, - ], - }, - }, - }, - deltaBytesRecv: { - $let: { - vars: { - first: { - $arrayElemAt: [ - { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.bytes_recv" } }, - "$$netIndex", - ], - }, - last: { - $arrayElemAt: [ - { - $map: { - input: { - $arrayElemAt: [{ $ifNull: ["$net", []] }, { $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1] }], - }, - as: "iface", - in: "$$iface.bytes_recv", - }, - }, - "$$netIndex", - ], - }, - tFirst: { $arrayElemAt: ["$updatedAts", 0] }, - tLast: { - $arrayElemAt: [{ $ifNull: ["$updatedAts", []] }, { $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1] }], - }, - }, - in: { - $cond: [ - { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, - { - $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], - }, - 0, - ], - }, - }, - }, - deltaPacketsSent: { - $let: { - vars: { - first: { - $arrayElemAt: [ - { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.packets_sent" } }, - "$$netIndex", - ], - }, - last: { - $arrayElemAt: [ - { - $map: { - input: { - $arrayElemAt: [{ $ifNull: ["$net", []] }, { $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1] }], - }, - as: "iface", - in: "$$iface.packets_sent", - }, - }, - "$$netIndex", - ], - }, - tFirst: { $arrayElemAt: ["$updatedAts", 0] }, - tLast: { - $arrayElemAt: [{ $ifNull: ["$updatedAts", []] }, { $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1] }], - }, - }, - in: { - $cond: [ - { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, - { - $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], - }, - 0, - ], - }, - }, - }, - deltaPacketsRecv: { - $let: { - vars: { - first: { - $arrayElemAt: [ - { - $map: { - input: { $arrayElemAt: ["$net", 0] }, - as: "iface", - in: "$$iface.packets_recv", - }, - }, - "$$netIndex", - ], - }, - last: { - $arrayElemAt: [ - { - $map: { - input: { - $arrayElemAt: [ - { $ifNull: ["$net", []] }, - { - $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1], - }, - ], - }, - as: "iface", - in: "$$iface.packets_recv", - }, - }, - "$$netIndex", - ], - }, - tFirst: { $arrayElemAt: ["$updatedAts", 0] }, - tLast: { - $arrayElemAt: [ - { $ifNull: ["$updatedAts", []] }, - { - $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1], - }, - ], - }, - }, - in: { - $cond: [ - { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, - { - $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], - }, - 0, - ], - }, - }, - }, - deltaErrIn: { - $let: { - vars: { - first: { - $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.err_in" } }, "$$netIndex"], - }, - last: { - $arrayElemAt: [ - { - $map: { - input: { - $arrayElemAt: [{ $ifNull: ["$net", []] }, { $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1] }], - }, - as: "iface", - in: "$$iface.err_in", - }, - }, - "$$netIndex", - ], - }, - tFirst: { $arrayElemAt: ["$updatedAts", 0] }, - tLast: { - $arrayElemAt: [{ $ifNull: ["$updatedAts", []] }, { $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1] }], - }, - }, - in: { - $cond: [ - { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, - { - $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], - }, - 0, - ], - }, - }, - }, - deltaErrOut: { - $let: { - vars: { - first: { - $arrayElemAt: [ - { - $map: { - input: { $arrayElemAt: ["$net", 0] }, - as: "iface", - in: "$$iface.err_out", - }, - }, - "$$netIndex", - ], - }, - last: { - $arrayElemAt: [ - { - $map: { - input: { - $arrayElemAt: [ - { $ifNull: ["$net", []] }, - { - $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1], - }, - ], - }, - as: "iface", - in: "$$iface.err_out", - }, - }, - "$$netIndex", - ], - }, - tFirst: { $arrayElemAt: ["$updatedAts", 0] }, - tLast: { - $arrayElemAt: [ - { $ifNull: ["$updatedAts", []] }, - { - $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1], - }, - ], - }, - }, - in: { - $cond: [ - { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, - { - $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], - }, - 0, - ], - }, - }, - }, - deltaDropIn: { - $let: { - vars: { - first: { - $arrayElemAt: [ - { - $map: { - input: { $arrayElemAt: ["$net", 0] }, - as: "iface", - in: "$$iface.drop_in", - }, - }, - "$$netIndex", - ], - }, - last: { - $arrayElemAt: [ - { - $map: { - input: { - $arrayElemAt: [ - { $ifNull: ["$net", []] }, - { - $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1], - }, - ], - }, - as: "iface", - in: "$$iface.drop_in", - }, - }, - "$$netIndex", - ], - }, - tFirst: { $arrayElemAt: ["$updatedAts", 0] }, - tLast: { - $arrayElemAt: [ - { $ifNull: ["$updatedAts", []] }, - { - $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1], - }, - ], - }, - }, - in: { - $cond: [ - { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, - { - $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], - }, - 0, - ], - }, - }, - }, - deltaDropOut: { - $let: { - vars: { - first: { - $arrayElemAt: [ - { - $map: { - input: { $arrayElemAt: ["$net", 0] }, - as: "iface", - in: "$$iface.drop_out", - }, - }, - "$$netIndex", - ], - }, - last: { - $arrayElemAt: [ - { - $map: { - input: { - $arrayElemAt: [ - { $ifNull: ["$net", []] }, - { - $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1], - }, - ], - }, - as: "iface", - in: "$$iface.drop_out", - }, - }, - "$$netIndex", - ], - }, - tFirst: { $arrayElemAt: ["$updatedAts", 0] }, - tLast: { - $arrayElemAt: [ - { $ifNull: ["$updatedAts", []] }, - { - $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1], - }, - ], - }, - }, - in: { - $cond: [ - { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, - { - $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], - }, - 0, - ], - }, - }, - }, - }, - }, - }, - firstUpdatedAt: 1, - lastUpdatedAt: 1, - }, - }, - ], - as: "hourlyStats", - }, - }, - { - $unwind: "$hourlyStats", - }, - { - $replaceRoot: { - newRoot: "$hourlyStats", - }, - }, - ], - }, - }, - { $unwind: "$checks" }, - { $sort: { "checks._id": 1 } }, - { - $group: { - _id: "$_id", - checks: { $push: "$checks" }, - aggregateData: { $first: "$aggregateData" }, - upChecks: { $first: "$upChecks" }, - }, - }, - { - $project: { - aggregateData: { - $arrayElemAt: ["$aggregateData", 0], - }, - upChecks: { - $arrayElemAt: ["$upChecks", 0], - }, - checks: 1, - }, - }, - ]; -}; - const buildMonitorStatsPipeline = (monitor) => { return [ { @@ -1208,7 +556,6 @@ const buildGetMonitorsByTeamIdPipeline = (req) => { export { buildUptimeDetailsPipeline, - buildHardwareDetailsPipeline, buildMonitorStatsPipeline, buildGetMonitorsByTeamIdPipeline, buildMonitorSummaryByTeamIdPipeline, @@ -1217,3 +564,177 @@ export { buildMonitorsWithChecksByTeamIdPipeline, buildFilteredMonitorsByTeamIdPipeline, }; + +export const getAggregateData = async (monitorId, dates) => { + const result = await CheckModel.aggregate([ + { + $match: { + "metadata.monitorId": new ObjectId(monitorId), + "metadata.type": "hardware", + createdAt: { $gte: dates.start, $lte: dates.end }, + }, + }, + { $sort: { createdAt: -1 } }, + { + $group: { + _id: null, + latestCheck: { $first: "$$ROOT" }, + totalChecks: { $sum: 1 }, + }, + }, + ]); + return result[0] || { totalChecks: 0, latestCheck: null }; +}; + +export const getUpChecks = async (monitorId, dates) => { + const count = await CheckModel.countDocuments({ + "metadata.monitorId": new ObjectId(monitorId), + "metadata.type": "hardware", + createdAt: { $gte: dates.start, $lte: dates.end }, + status: true, + }); + return { totalChecks: count }; +}; + +export const getHardwareStats = async (monitorId, dates, dateString) => { + return await CheckModel.aggregate([ + { + $match: { + "metadata.monitorId": new ObjectId(monitorId), + "metadata.type": "hardware", + createdAt: { $gte: dates.start, $lte: dates.end }, + }, + }, + { $sort: { createdAt: 1 } }, + { + $group: { + _id: { $dateToString: { format: dateString, date: "$createdAt" } }, + avgCpuUsage: { $avg: "$cpu.usage_percent" }, + avgMemoryUsage: { $avg: "$memory.usage_percent" }, + avgTemperatures: { $push: { $ifNull: ["$cpu.temperature", [0]] } }, + disks: { $push: "$disk" }, + net: { $push: "$net" }, + updatedAts: { $push: "$updatedAt" }, + sampleDoc: { $first: "$$ROOT" }, + }, + }, + { + $project: { + _id: 1, + avgCpuUsage: 1, + avgMemoryUsage: 1, + avgTemperature: { + $map: { + input: { $range: [0, { $size: { $ifNull: [{ $arrayElemAt: ["$avgTemperatures", 0] }, [0]] } }] }, + as: "idx", + in: { $avg: { $map: { input: "$avgTemperatures", as: "t", in: { $arrayElemAt: ["$$t", "$$idx"] } } } }, + }, + }, + disks: { + $map: { + input: { $range: [0, { $size: { $ifNull: ["$sampleDoc.disk", []] } }] }, + as: "dIdx", + in: { + name: { $concat: ["disk", { $toString: "$$dIdx" }] }, + readSpeed: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.read_speed_bytes", "$$dIdx"] } } } }, + writeSpeed: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.write_speed_bytes", "$$dIdx"] } } } }, + totalBytes: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.total_bytes", "$$dIdx"] } } } }, + freeBytes: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.free_bytes", "$$dIdx"] } } } }, + usagePercent: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.usage_percent", "$$dIdx"] } } } }, + }, + }, + }, + net: { + $map: { + input: { $range: [0, { $size: { $ifNull: ["$sampleDoc.net", []] } }] }, + as: "nIdx", + in: { + name: { $arrayElemAt: ["$sampleDoc.net.name", "$$nIdx"] }, + bytesSentPerSecond: { + $let: { + vars: { + tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] }, + f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.bytes_sent" } }, "$$nIdx"] }, + l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.bytes_sent" } }, "$$nIdx"] }, + }, + in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] }, + }, + }, + deltaBytesRecv: { + $let: { + vars: { + tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] }, + f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.bytes_recv" } }, "$$nIdx"] }, + l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.bytes_recv" } }, "$$nIdx"] }, + }, + in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] }, + }, + }, + deltaPacketsSent: { + $let: { + vars: { + tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] }, + f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.packets_sent" } }, "$$nIdx"] }, + l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.packets_sent" } }, "$$nIdx"] }, + }, + in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] }, + }, + }, + deltaPacketsRecv: { + $let: { + vars: { + tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] }, + f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.packets_recv" } }, "$$nIdx"] }, + l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.packets_recv" } }, "$$nIdx"] }, + }, + in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] }, + }, + }, + deltaErrIn: { + $let: { + vars: { + tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] }, + f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.err_in" } }, "$$nIdx"] }, + l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.err_in" } }, "$$nIdx"] }, + }, + in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] }, + }, + }, + deltaErrOut: { + $let: { + vars: { + tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] }, + f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.err_out" } }, "$$nIdx"] }, + l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.err_out" } }, "$$nIdx"] }, + }, + in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] }, + }, + }, + deltaDropIn: { + $let: { + vars: { + tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] }, + f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.drop_in" } }, "$$nIdx"] }, + l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.drop_in" } }, "$$nIdx"] }, + }, + in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] }, + }, + }, + deltaDropOut: { + $let: { + vars: { + tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] }, + f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.drop_out" } }, "$$nIdx"] }, + l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.drop_out" } }, "$$nIdx"] }, + }, + in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] }, + }, + }, + }, + }, + }, + }, + }, + { $sort: { _id: 1 } }, + ]); +}; From 117a2e00c44604028d4cc39af3a97d31c4c741e9 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 13 Jan 2026 19:01:56 +0000 Subject: [PATCH 08/24] status page fix --- server/src/db/modules/statusPageModule.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/db/modules/statusPageModule.js b/server/src/db/modules/statusPageModule.js index aadde0c81..999e0bdc9 100755 --- a/server/src/db/modules/statusPageModule.js +++ b/server/src/db/modules/statusPageModule.js @@ -153,7 +153,7 @@ class StatusPageModule { pipeline: [ { $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, + $expr: { $eq: ["$metadata.monitorId", "$$monitorId"] }, }, }, { $sort: { createdAt: -1 } }, From d0b3bc60dffa9f4f8cb48637b641cca013dc1a40 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 13 Jan 2026 20:20:00 +0000 Subject: [PATCH 09/24] create and update --- server/src/config/services.js | 6 +- server/src/controllers/monitorController.ts | 2 +- .../monitors/IMonitorsRepository.ts | 4 + .../monitors/MongoMonitorsRepository.ts | 46 +- .../{monitorService.js => monitorService.ts} | 436 ++++++++++++------ server/src/service/index.ts | 1 + .../SuperSimpleQueue/SuperSimpleQueue.js | 27 +- .../SuperSimpleQueueHelper.js | 4 +- .../service/infrastructure/networkService.js | 12 +- .../service/infrastructure/statusService.js | 32 +- 10 files changed, 384 insertions(+), 186 deletions(-) rename server/src/service/business/{monitorService.js => monitorService.ts} (53%) create mode 100644 server/src/service/index.ts diff --git a/server/src/config/services.js b/server/src/config/services.js index 0916752ab..6878026d6 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -16,7 +16,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"; +import { MonitorService } from "@/service/index.js"; import IncidentService from "../service/business/incidentService.js"; import papaparse from "papaparse"; import axios from "axios"; @@ -152,7 +152,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService const bufferService = new BufferService({ db, logger, envSettings, incidentService }); - const statusService = new StatusService({ db, logger, buffer: bufferService, incidentService }); + const statusService = new StatusService({ db, logger, buffer: bufferService, incidentService, monitorsRepository }); const notificationUtils = new NotificationUtils({ stringService, @@ -182,6 +182,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService db, logger, helper: superSimpleQueueHelper, + monitorsRepository, }); // Business services @@ -218,7 +219,6 @@ export const initializeServices = async ({ logger, envSettings, settingsService }); const monitorService = new MonitorService({ db, - settingsService, jobQueue: superSimpleQueue, stringService, emailService, diff --git a/server/src/controllers/monitorController.ts b/server/src/controllers/monitorController.ts index 425979062..ba0a237bf 100644 --- a/server/src/controllers/monitorController.ts +++ b/server/src/controllers/monitorController.ts @@ -193,7 +193,7 @@ class MonitorController { const userId = req?.user?._id; const teamId = req?.user?.teamId; - const monitor = await this.monitorService.createMonitor({ teamId, userId, body: req.body }); + const monitor = await this.monitorService.createMonitor(teamId, userId, req.body); return res.status(200).json({ success: true, diff --git a/server/src/repositories/monitors/IMonitorsRepository.ts b/server/src/repositories/monitors/IMonitorsRepository.ts index 865d7f8e4..99131cb4b 100644 --- a/server/src/repositories/monitors/IMonitorsRepository.ts +++ b/server/src/repositories/monitors/IMonitorsRepository.ts @@ -12,11 +12,15 @@ export interface TeamQueryConfig { export interface IMonitorsRepository { // create + create(monitor: Monitor, teamId: string, userId: string): Promise; // single fetch + findById(monitorId: string): Promise; + // collection fetch findAll(): Promise; findByTeamId(teamId: string, config: TeamQueryConfig): Promise; // update + update(monitorId: string, updates: Partial): Promise; // delete // counts diff --git a/server/src/repositories/monitors/MongoMonitorsRepository.ts b/server/src/repositories/monitors/MongoMonitorsRepository.ts index dd85f9a3d..49687c339 100644 --- a/server/src/repositories/monitors/MongoMonitorsRepository.ts +++ b/server/src/repositories/monitors/MongoMonitorsRepository.ts @@ -3,11 +3,31 @@ import type { MonitorDocument } from "@/db/models/Monitor.js"; import type { Monitor, MonitorType } from "@/types/monitor.js"; import mongoose, { type FilterQuery } from "mongoose"; import type { IMonitorsRepository, TeamQueryConfig } from "./IMonitorsRepository.js"; +import { AppError } from "@/utils/AppError.js"; class MongoMonitorsRepository implements IMonitorsRepository { - findAll = async (): Promise => { - const documents = await MonitorModel.find().exec(); - return this.mapDocuments(documents); + create = async (monitor: Monitor, teamId: string, userId: string) => { + const monitorModel = new MonitorModel({ ...monitor, teamId, userId }); + const saved = await monitorModel.save(); + return this.toEntity(saved); + }; + + findById = async (MonitorModelId: string): Promise => { + const monitor = await MonitorModel.findById(MonitorModelId); + if (!monitor) { + if (monitor === null || monitor === undefined) { + throw new AppError({ + message: `Monitor with ID ${MonitorModelId} not found.`, + status: 404, + }); + } + } + return this.toEntity(monitor); + }; + + findAll = async (): Promise => { + const monitors = await MonitorModel.find(); + return this.mapDocuments(monitors); }; findByTeamId = async (teamId: string, config: TeamQueryConfig): Promise => { @@ -64,9 +84,25 @@ class MongoMonitorsRepository implements IMonitorsRepository { return count; }; - private mapDocuments = (documents: MonitorDocument[]): Monitor[] | null => { + update = async (monitorId: string, patch: Partial) => { + const updatedMonitor = await MonitorModel.findOneAndUpdate( + { _id: monitorId }, + { + $set: { + ...patch, + }, + }, + { new: true, runValidators: true } + ); + if (!updatedMonitor) { + throw new AppError({ message: `Failed to update monitor with id ${monitorId}`, status: 500 }); + } + return this.toEntity(updatedMonitor); + }; + + private mapDocuments = (documents: MonitorDocument[]): Monitor[] => { if (!documents?.length) { - return null; + return []; } return documents.map((doc) => this.toEntity(doc)); }; diff --git a/server/src/service/business/monitorService.js b/server/src/service/business/monitorService.ts similarity index 53% rename from server/src/service/business/monitorService.js rename to server/src/service/business/monitorService.ts index 4bdc45eb4..45a2fc235 100644 --- a/server/src/service/business/monitorService.js +++ b/server/src/service/business/monitorService.ts @@ -1,13 +1,88 @@ import { createMonitorsBodyValidation } from "@/validation/joi.js"; import { NormalizeData } from "@/utils/dataUtils.js"; - +import { type Monitor } from "@/types/index.js"; +import type { IMonitorsRepository } from "@/repositories/index.js"; +import { AppError } from "../infrastructure/errorService.js"; const SERVICE_NAME = "MonitorService"; -class MonitorService { + +export interface IMonitorService { + readonly serviceName: string; + verifyTeamAccess(args: { teamId: string; monitorId: string }): Promise; + + // create + createMonitor(teamId: string, userId: string, body: Monitor): Promise; + createBulkMonitors(args: { fileData: string; userId: string; teamId: string }): Promise; + addDemoMonitors(args: { userId: string; teamId: string }): Promise; + + // read + getAllMonitors(): Promise; + getUptimeDetailsById(args: { teamId: string; monitorId: string; dateRange: string; normalize?: boolean }): Promise; + getMonitorStatsById(args: { + teamId: string; + monitorId: string; + limit?: number; + sortOrder?: 1 | -1; + dateRange?: string; + numToDisplay?: number; + normalize?: boolean; + }): Promise; + getHardwareDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise; + getMonitorById(args: { teamId: string; monitorId: string }): Promise; + getMonitorsByTeamId(args: { + teamId: string; + limit?: number; + type?: string | string[]; + page?: number; + rowsPerPage?: number; + filter?: string; + field?: string; + order?: "asc" | "desc"; + }): Promise; + getMonitorsAndSummaryByTeamId(args: { teamId: string; type?: string | string[]; explain?: boolean }): Promise; + getMonitorsWithChecksByTeamId(args: { + teamId: string; + limit?: number; + type?: string | string[]; + page?: number; + rowsPerPage?: number; + filter?: string; + field?: string; + order?: "asc" | "desc"; + explain?: boolean; + }): Promise<{ count: number; monitors: any[] }>; + getAllGames(): any; + getGroupsByTeamId(args: { teamId: string }): Promise; + + // update + editMonitor(args: { teamId: string; monitorId: string; body: any }): Promise; + pauseMonitor(args: { teamId: string; monitorId: string }): Promise; + + // delete + deleteMonitor(args: { teamId: string; monitorId: string }): Promise; + deleteAllMonitors(args: { teamId: string }): Promise; + + // other + sendTestEmail(args: { to: string }): Promise; + exportMonitorsToCSV(args: { teamId: string }): Promise; + exportMonitorsToJSON(args: { teamId: string }): Promise; +} + +export class MonitorService implements IMonitorService { static SERVICE_NAME = SERVICE_NAME; + private db: any; + private jobQueue: any; + private stringService: any; + private emailService: any; + private papaparse: any; + private logger: any; + private errorService: any; + private games: any; + private monitorsRepository: IMonitorsRepository; + private checksRepository: any; + constructor({ db, - settingsService, jobQueue, stringService, emailService, @@ -17,9 +92,19 @@ class MonitorService { games, monitorsRepository, checksRepository, + }: { + db: any; + jobQueue: any; + stringService: any; + emailService: any; + papaparse: any; + logger: any; + errorService: any; + games: any; + monitorsRepository: IMonitorsRepository; + checksRepository: any; }) { this.db = db; - this.settingsService = settingsService; this.jobQueue = jobQueue; this.stringService = stringService; this.emailService = emailService; @@ -31,79 +116,34 @@ class MonitorService { this.checksRepository = checksRepository; } - get serviceName() { + get serviceName(): string { return MonitorService.SERVICE_NAME; } - verifyTeamAccess = async ({ teamId, monitorId }) => { + verifyTeamAccess = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise => { const monitor = await this.db.monitorModule.getMonitorById(monitorId); if (!monitor?.teamId?.equals(teamId)) { throw this.errorService.createAuthorizationError(); } }; - getAllMonitors = async () => { - const monitors = await this.db.monitorModule.getAllMonitors(); - return monitors; + createMonitor = async (teamId: string, userId: string, body: Monitor): Promise => { + const monitor = await this.monitorsRepository.create(body, teamId, userId); + if (!monitor) { + throw new AppError("Failed to create monitor", 500); + } + + this.jobQueue.addJob(monitor.id, monitor); }; - getUptimeDetailsById = async ({ teamId, monitorId, dateRange, normalize }) => { - await this.verifyTeamAccess({ teamId, monitorId }); - const data = await this.db.monitorModule.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.monitorModule.getMonitorStatsById({ - monitorId, - limit, - sortOrder, - dateRange, - numToDisplay, - normalize, - }); - - return monitorStats; - }; - - getHardwareDetailsById = async ({ teamId, monitorId, dateRange }) => { - await this.verifyTeamAccess({ teamId, monitorId }); - const monitor = await this.db.monitorModule.getHardwareDetailsById({ monitorId, dateRange }); - - return monitor; - }; - - getMonitorById = async ({ teamId, monitorId }) => { - await this.verifyTeamAccess({ teamId, monitorId }); - const monitor = await this.db.monitorModule.getMonitorById(monitorId); - - return monitor; - }; - - createMonitor = async ({ teamId, userId, body }) => { - const monitor = await this.db.monitorModule.createMonitor({ - body, - teamId, - userId, - }); - - this.jobQueue.addJob(monitor._id, monitor); - }; - - createBulkMonitors = async ({ fileData, userId, teamId }) => { + createBulkMonitors = async ({ fileData, userId, teamId }: { fileData: string; userId: string; teamId: string }): Promise => { const { parse } = this.papaparse; - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { parse(fileData, { header: true, skipEmptyLines: true, - transform: (value, header) => { + transform: (value: string, header: string): string | number | undefined => { if (value === "") return undefined; // Empty fields become undefined // Handle 'port' and 'interval' fields, check if they're valid numbers @@ -117,7 +157,7 @@ class MonitorService { return value; }, - complete: async ({ data, errors }) => { + complete: async ({ data, errors }: { data: any[]; errors: Error[] }): Promise => { try { if (errors.length > 0) { throw this.errorService.createServerError("Error parsing CSV"); @@ -127,7 +167,7 @@ class MonitorService { throw this.errorService.createServerError("CSV file contains no data rows"); } - const enrichedData = data.map((monitor) => ({ + const enrichedData = data.map((monitor: any) => ({ userId, teamId, ...monitor, @@ -141,7 +181,7 @@ class MonitorService { const monitors = await this.db.monitorModule.createBulkMonitors(enrichedData); await Promise.all( - monitors.map(async (monitor) => { + monitors.map(async (monitor: any) => { this.jobQueue.addJob(monitor._id, monitor); }) ); @@ -155,71 +195,102 @@ class MonitorService { }); }; - deleteMonitor = async ({ teamId, monitorId }) => { - await this.verifyTeamAccess({ 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.monitorModule.deleteAllMonitors(teamId); - await Promise.all( - monitors.map(async (monitor) => { - try { - await this.jobQueue.deleteJob(monitor); - await this.db.checkModule.deleteChecks(monitor._id); - await this.db.pageSpeedCheckModule.deletePageSpeedChecksByMonitorId(monitor._id); - await this.db.notificationsModule.deleteNotificationsByMonitorId(monitor._id); - } catch (error) { - this.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.monitorModule.editMonitor({ monitorId, body }); - await this.jobQueue.updateJob(editedMonitor); - }; - - pauseMonitor = async ({ teamId, monitorId }) => { - await this.verifyTeamAccess({ teamId, 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 }) => { + addDemoMonitors = async ({ userId, teamId }: { userId: string; teamId: string }): Promise => { const demoMonitors = await this.db.monitorModule.addDemoMonitors(userId, teamId); - await Promise.all(demoMonitors.map((monitor) => this.jobQueue.addJob(monitor._id, monitor))); + await Promise.all(demoMonitors.map((monitor: any) => 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 this.errorService.createServerError("Failed to send test email."); - } - - return messageId; + getAllMonitors = async (): Promise => { + const monitors = await this.db.monitorModule.getAllMonitors(); + return monitors; }; - getMonitorsByTeamId = async ({ teamId, limit, type, page, rowsPerPage, filter, field, order }) => { + getUptimeDetailsById = async ({ + teamId, + monitorId, + dateRange, + normalize, + }: { + teamId: string; + monitorId: string; + dateRange: string; + normalize?: boolean; + }): Promise => { + await this.verifyTeamAccess({ teamId, monitorId }); + const data = await this.db.monitorModule.getUptimeDetailsById({ + monitorId, + dateRange, + normalize, + }); + + return data; + }; + + getMonitorStatsById = async ({ + teamId, + monitorId, + limit, + sortOrder, + dateRange, + numToDisplay, + normalize, + }: { + teamId: string; + monitorId: string; + limit?: number; + sortOrder?: 1 | -1; + dateRange?: string; + numToDisplay?: number; + normalize?: boolean; + }): Promise => { + await this.verifyTeamAccess({ teamId, monitorId }); + const monitorStats = await this.db.monitorModule.getMonitorStatsById({ + monitorId, + limit, + sortOrder, + dateRange, + numToDisplay, + normalize, + }); + + return monitorStats; + }; + + getHardwareDetailsById = async ({ teamId, monitorId, dateRange }: { teamId: string; monitorId: string; dateRange: string }): Promise => { + await this.verifyTeamAccess({ teamId, monitorId }); + const monitor = await this.db.monitorModule.getHardwareDetailsById({ monitorId, dateRange }); + + return monitor; + }; + + getMonitorById = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise => { + await this.verifyTeamAccess({ teamId, monitorId }); + const monitor = await this.db.monitorModule.getMonitorById(monitorId); + + return monitor; + }; + + getMonitorsByTeamId = async ({ + teamId, + limit, + type, + page, + rowsPerPage, + filter, + field, + order, + }: { + teamId: string; + limit?: number; + type?: string | string[]; + page?: number; + rowsPerPage?: number; + filter?: string; + field?: string; + order?: "asc" | "desc"; + }): Promise => { const monitors = await this.db.monitorModule.getMonitorsByTeamId({ limit, type, @@ -233,7 +304,15 @@ class MonitorService { return monitors; }; - getMonitorsAndSummaryByTeamId = async ({ teamId, type, explain }) => { + getMonitorsAndSummaryByTeamId = async ({ + teamId, + type, + explain, + }: { + teamId: string; + type?: string | string[]; + explain?: boolean; + }): Promise => { const result = await this.db.monitorModule.getMonitorsAndSummaryByTeamId({ type, explain, @@ -242,7 +321,27 @@ class MonitorService { return result; }; - getMonitorsWithChecksByTeamId = async ({ teamId, limit, type, page, rowsPerPage, filter, field, order, explain }) => { + getMonitorsWithChecksByTeamId = async ({ + teamId, + limit, + type, + page, + rowsPerPage, + filter, + field, + order, + explain, + }: { + teamId: string; + limit?: number; + type?: string | string[]; + page?: number; + rowsPerPage?: number; + filter?: string; + field?: string; + order?: "asc" | "desc"; + explain?: boolean; + }): Promise<{ count: number; monitors: any[] }> => { const count = await this.monitorsRepository.findMonitorCountByTeamIdAndType(teamId, { type, filter }); const monitors = await this.monitorsRepository.findByTeamId(teamId, { limit, @@ -254,9 +353,9 @@ class MonitorService { order, }); - const monitorIds = monitors?.map((m) => m.id) ?? []; + const monitorIds = monitors?.map((m: any) => m.id) ?? []; const checksMap = await this.checksRepository.findLatestChecksByMonitorIds(monitorIds); - const monitorsWithChecks = (monitors ?? []).map((monitor) => { + const monitorsWithChecks = (monitors ?? []).map((monitor: any) => { const checks = NormalizeData(checksMap[monitor.id] ?? [], 10, 100); return { ...monitor, @@ -267,14 +366,80 @@ class MonitorService { return { count, monitors: monitorsWithChecks }; }; - exportMonitorsToCSV = async ({ teamId }) => { + getAllGames = (): any => { + return this.games; + }; + + getGroupsByTeamId = async ({ teamId }: { teamId: string }): Promise => { + const groups = await this.db.monitorModule.getGroupsByTeamId({ teamId }); + return groups; + }; + + editMonitor = async ({ teamId, monitorId, body }: { teamId: string; monitorId: string; body: any }): Promise => { + await this.verifyTeamAccess({ teamId, monitorId }); + const editedMonitor = await this.db.monitorModule.editMonitor({ monitorId, body }); + await this.jobQueue.updateJob(editedMonitor); + }; + + pauseMonitor = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise => { + await this.verifyTeamAccess({ teamId, 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; + }; + + deleteMonitor = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise => { + await this.verifyTeamAccess({ 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 }: { teamId: string }): Promise => { + const { monitors, deletedCount } = await this.db.monitorModule.deleteAllMonitors(teamId); + await Promise.all( + monitors.map(async (monitor: any) => { + try { + await this.jobQueue.deleteJob(monitor); + await this.db.checkModule.deleteChecks(monitor._id); + await this.db.pageSpeedCheckModule.deletePageSpeedChecksByMonitorId(monitor._id); + await this.db.notificationsModule.deleteNotificationsByMonitorId(monitor._id); + } catch (error: any) { + this.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; + }; + + sendTestEmail = async ({ to }: { to: string }): Promise => { + 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 this.errorService.createServerError("Failed to send test email."); + } + + return messageId; + }; + + exportMonitorsToCSV = async ({ teamId }: { teamId: string }): Promise => { const monitors = await this.db.monitorModule.getMonitorsByTeamId({ teamId }); if (!monitors || monitors.length === 0) { throw this.errorService.createNotFoundError("No monitors to export"); } - const csvData = monitors?.filteredMonitors?.map((monitor) => ({ + const csvData = monitors?.filteredMonitors?.map((monitor: any) => ({ name: monitor.name, description: monitor.description, type: monitor.type, @@ -288,7 +453,7 @@ class MonitorService { const csv = this.papaparse.unparse(csvData); return csv; }; - exportMonitorsToJSON = async ({ teamId }) => { + exportMonitorsToJSON = async ({ teamId }: { teamId: string }): Promise => { const monitors = await this.db.monitorModule.getMonitorsByTeamId({ teamId }); if (!monitors || monitors.length === 0) { @@ -296,7 +461,7 @@ class MonitorService { } const json = monitors?.filteredMonitors - ?.map((monitor) => { + ?.map((monitor: any) => { const initialType = monitor.type; let parsedType; @@ -328,15 +493,4 @@ class MonitorService { return json; }; - - getAllGames = () => { - return this.games; - }; - - getGroupsByTeamId = async ({ teamId }) => { - const groups = await this.db.monitorModule.getGroupsByTeamId({ teamId }); - return groups; - }; } - -export default MonitorService; diff --git a/server/src/service/index.ts b/server/src/service/index.ts new file mode 100644 index 000000000..4793f055b --- /dev/null +++ b/server/src/service/index.ts @@ -0,0 +1 @@ +export * from "@/service/business/monitorService.js"; diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js index a3cdea8e7..a6637d4cc 100644 --- a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js +++ b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js @@ -4,19 +4,20 @@ const SERVICE_NAME = "JobQueue"; class SuperSimpleQueue { static SERVICE_NAME = SERVICE_NAME; - constructor({ envSettings, db, logger, helper }) { + constructor({ envSettings, db, logger, helper, monitorsRepository }) { this.envSettings = envSettings; this.db = db; this.logger = logger; this.helper = helper; + this.monitorsRepository = monitorsRepository; } get serviceName() { return SuperSimpleQueue.SERVICE_NAME; } - static async create({ envSettings, db, logger, helper }) { - const instance = new SuperSimpleQueue({ envSettings, db, logger, helper }); + static async create({ envSettings, db, logger, helper, monitorsRepository }) { + const instance = new SuperSimpleQueue({ envSettings, db, logger, helper, monitorsRepository }); await instance.init(); return instance; } @@ -33,11 +34,11 @@ class SuperSimpleQueue { this.scheduler.start(); this.scheduler.addTemplate("monitor-job", this.helper.getMonitorJob()); - const monitors = await this.db.monitorModule.getAllMonitors(); + const monitors = await this.monitorsRepository.findAll(); for (const monitor of monitors) { const randomOffset = Math.floor(Math.random() * 100); setTimeout(() => { - this.addJob(monitor._id, monitor); + this.addJob(monitor.id, monitor); }, randomOffset); } @@ -55,44 +56,44 @@ class SuperSimpleQueue { addJob = async (monitorId, monitor) => { this.scheduler.addJob({ - id: monitorId.toString(), + id: monitorId, template: "monitor-job", repeat: monitor.interval, active: monitor.isActive, - data: monitor.toObject(), + data: monitor, }); }; deleteJob = async (monitor) => { - this.scheduler.removeJob(monitor._id.toString()); + this.scheduler.removeJob(monitor.id); }; pauseJob = async (monitor) => { - const result = this.scheduler.pauseJob(monitor._id.toString()); + const result = this.scheduler.pauseJob(monitor.id); if (result === false) { throw new Error("Failed to resume monitor"); } this.logger.debug({ - message: `Paused monitor ${monitor._id}`, + message: `Paused monitor ${monitor.id}`, service: SERVICE_NAME, method: "pauseJob", }); }; resumeJob = async (monitor) => { - const result = this.scheduler.resumeJob(monitor._id.toString()); + const result = this.scheduler.resumeJob(monitor.id); if (result === false) { throw new Error("Failed to resume monitor"); } this.logger.debug({ - message: `Resumed monitor ${monitor._id}`, + message: `Resumed monitor ${monitor.id}`, service: SERVICE_NAME, method: "resumeJob", }); }; updateJob = async (monitor) => { - this.scheduler.updateJob(monitor._id.toString(), { repeat: monitor.interval, data: monitor.toObject() }); + this.scheduler.updateJob(monitor.id, { repeat: monitor.interval, data: monitor }); }; shutdown = async () => { diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js index 2833a59f5..3cd6b37a8 100644 --- a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js +++ b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js @@ -27,7 +27,7 @@ class SuperSimpleQueueHelper { getMonitorJob = () => { return async (monitor) => { try { - const monitorId = monitor._id; + const monitorId = monitor.id; const teamId = monitor.teamId; if (!monitorId) { throw new Error("No monitor id"); @@ -62,7 +62,7 @@ class SuperSimpleQueueHelper { message: error.message, service: SERVICE_NAME, method: "getMonitorJob", - details: `Error sending notifications for job ${monitor._id}: ${error.message}`, + details: `Error sending notifications for job ${monitor.id}: ${error.message}`, stack: error.stack, }); }); diff --git a/server/src/service/infrastructure/networkService.js b/server/src/service/infrastructure/networkService.js index d364f373e..2375e7233 100644 --- a/server/src/service/infrastructure/networkService.js +++ b/server/src/service/infrastructure/networkService.js @@ -95,7 +95,7 @@ class NetworkService { } const pingResponse = { - monitorId: monitor._id, + monitorId: monitor.id, type: "ping", status: response.alive, code: 200, @@ -120,9 +120,9 @@ class NetworkService { } async requestHttp(monitor) { - const { url, secret, _id, teamId, type, ignoreTlsErrors, jsonPath, matchMethod, expectedValue } = monitor; + const { url, secret, id, teamId, type, ignoreTlsErrors, jsonPath, matchMethod, expectedValue } = monitor; const httpResponse = { - monitorId: _id, + monitorId: id, teamId: teamId, type, }; @@ -298,7 +298,7 @@ class NetworkService { }); const dockerResponse = { - monitorId: monitor._id, + monitorId: monitor.id, type: monitor.type, }; @@ -416,7 +416,7 @@ class NetworkService { code: 200, status: response.success, message: this.stringService.portSuccess, - monitorId: monitor._id, + monitorId: monitor.id, type: monitor.type, responseTime: responseTime, }; @@ -444,7 +444,7 @@ class NetworkService { code: 200, status: true, message: "Success", - monitorId: monitor._id, + monitorId: monitor.id, type: "game", }; diff --git a/server/src/service/infrastructure/statusService.js b/server/src/service/infrastructure/statusService.js index 5b64c7419..30a9fc12c 100755 --- a/server/src/service/infrastructure/statusService.js +++ b/server/src/service/infrastructure/statusService.js @@ -10,11 +10,12 @@ class StatusService { * buffer: import("./bufferService.js").BufferService * incidentService: import("../business/incidentService.js").IncidentService * }} - */ constructor({ db, logger, buffer, incidentService }) { + */ constructor({ db, logger, buffer, incidentService, monitorsRepository }) { this.db = db; this.logger = logger; this.buffer = buffer; this.incidentService = incidentService; + this.monitorsRepository = monitorsRepository; } get serviceName() { @@ -23,7 +24,7 @@ class StatusService { async updateRunningStats({ monitor, networkResponse }) { try { - const monitorId = monitor._id; + const monitorId = monitor.id; const { responseTime, status } = networkResponse; // Get stats let stats = await MonitorStats.findOne({ monitorId }); @@ -124,7 +125,7 @@ class StatusService { service: this.SERVICE_NAME, method: "handleIncidentForCheck", message: `Failed to save check immediately for ${errorContext}: ${checkError.message}`, - monitorId: monitor._id, + monitorId: monitor.id, stack: checkError.stack, }); savedCheck = null; @@ -139,7 +140,7 @@ class StatusService { service: this.SERVICE_NAME, method: "handleIncidentForCheck", message: `Failed to add incident to buffer for ${errorContext}: ${incidentError.message}`, - monitorId: monitor._id, + monitorId: monitor.id, action, stack: incidentError.stack, }); @@ -150,7 +151,7 @@ class StatusService { service: this.SERVICE_NAME, method: "handleIncidentForCheck", message: `Error in ${errorContext}: ${error.message}`, - monitorId: monitor?._id, + monitorId: monitor?.id, stack: error.stack, }); } @@ -172,7 +173,8 @@ class StatusService { await this.insertCheck(check); try { const { monitorId, status, code } = networkResponse; - const monitor = await this.db.monitorModule.getMonitorById(monitorId); + + const monitor = await this.monitorsRepository.findById(monitorId); // Update running stats this.updateRunningStats({ monitor, networkResponse }); @@ -198,9 +200,9 @@ class StatusService { // Return early if not enough data points if (monitor.statusWindow.length < monitor.statusWindowSize) { - await monitor.save(); + const updated = await this.monitorsRepository.update(monitor.id, monitor); return { - monitor, + monitor: updated, statusChanged: false, prevStatus, code, @@ -240,14 +242,14 @@ class StatusService { if (monitor.status === false && !statusChanged) { try { - const lastManuallyResolvedIncident = await this.db.incidentModule.getLastManuallyResolvedIncident(monitor._id); + const lastManuallyResolvedIncident = await this.db.incidentModule.getLastManuallyResolvedIncident(monitor.id); let calculatedFailureRate = failureRate; if (lastManuallyResolvedIncident && lastManuallyResolvedIncident.endTime) { try { const checksAfterResolution = await Check.find({ - monitorId: monitor._id, + monitorId: monitor.id, createdAt: { $gt: lastManuallyResolvedIncident.endTime }, }) .sort({ createdAt: 1 }) @@ -267,7 +269,7 @@ class StatusService { service: this.SERVICE_NAME, method: "updateStatus", message: `Failed to query checks after manual resolution: ${checkQueryError.message}`, - monitorId: monitor._id, + monitorId: monitor.id, stack: checkQueryError.stack, }); } @@ -281,17 +283,17 @@ class StatusService { service: this.SERVICE_NAME, method: "updateStatus", message: `Error handling threshold check without status change: ${error.message}`, - monitorId: monitor._id, + monitorId: monitor.id, stack: error.stack, }); } } monitor.status = newStatus; - await monitor.save(); + const updated = await this.monitorsRepository.update(monitor.id, monitor); return { - monitor, + monitor: updated, statusChanged, prevStatus, code, @@ -374,7 +376,7 @@ class StatusService { return { id: audit.id, title: audit.title, - score: typeof audit.score === "number" ? audit.score : audit.score ?? null, + score: typeof audit.score === "number" ? audit.score : (audit.score ?? null), displayValue: audit.displayValue, numericValue: typeof audit.numericValue === "number" ? audit.numericValue : undefined, numericUnit: audit.numericUnit, From e79bf28bdc8b58ff5bdf5180fbc52f89e9acbd0f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 13 Jan 2026 20:20:29 +0000 Subject: [PATCH 10/24] server format --- .../0002_convertChecksToTimeSeries.ts | 22 ++++++++++--------- server/src/db/migration/index.ts | 6 +---- server/src/db/models/Check.ts | 10 ++------- .../service/infrastructure/statusService.js | 2 +- 4 files changed, 16 insertions(+), 24 deletions(-) diff --git a/server/src/db/migration/0002_convertChecksToTimeSeries.ts b/server/src/db/migration/0002_convertChecksToTimeSeries.ts index 46e6fbc97..a6c503804 100644 --- a/server/src/db/migration/0002_convertChecksToTimeSeries.ts +++ b/server/src/db/migration/0002_convertChecksToTimeSeries.ts @@ -19,10 +19,10 @@ const backupAndDropExistingCollection = async () => { return false; } - await db.collection(CHECKS_COLLECTION).aggregate([ - { $match: {} }, - { $out: BACKUP_COLLECTION }, - ]).toArray(); + await db + .collection(CHECKS_COLLECTION) + .aggregate([{ $match: {} }, { $out: BACKUP_COLLECTION }]) + .toArray(); await db.collection(CHECKS_COLLECTION).drop(); return true; }; @@ -40,12 +40,14 @@ const createTimeSeriesCollection = async () => { }); } - await db.collection(CHECKS_COLLECTION).createIndexes([ - { key: { updatedAt: 1 } }, - { key: { "metadata.monitorId": 1, updatedAt: 1 } }, - { key: { "metadata.monitorId": 1, updatedAt: -1 } }, - { key: { "metadata.teamId": 1, updatedAt: -1 } }, - ]); + await db + .collection(CHECKS_COLLECTION) + .createIndexes([ + { key: { updatedAt: 1 } }, + { key: { "metadata.monitorId": 1, updatedAt: 1 } }, + { key: { "metadata.monitorId": 1, updatedAt: -1 } }, + { key: { "metadata.teamId": 1, updatedAt: -1 } }, + ]); }; const migrateBackupData = async () => { diff --git a/server/src/db/migration/index.ts b/server/src/db/migration/index.ts index de2a1dee7..c8e9e7c75 100644 --- a/server/src/db/migration/index.ts +++ b/server/src/db/migration/index.ts @@ -32,11 +32,7 @@ const runMigrations = async (logger?: { info: Function; error: Function }) => { logger?.info({ message: `Completed ${migration.name}`, service: "Migrations" }); } catch (error) { const err = error as Error; - await MigrationModel.findOneAndUpdate( - { name: migration.name }, - { status: "failed", error: err?.message }, - { upsert: true } - ); + await MigrationModel.findOneAndUpdate({ name: migration.name }, { status: "failed", error: err?.message }, { upsert: true }); throw error; } } diff --git a/server/src/db/models/Check.ts b/server/src/db/models/Check.ts index 3ebacd3f6..fe6f16836 100644 --- a/server/src/db/models/Check.ts +++ b/server/src/db/models/Check.ts @@ -1,8 +1,5 @@ import { Schema, model, Types } from "mongoose"; -import { - MonitorTypes, - type MonitorType, -} from "@/types/monitor.js"; +import { MonitorTypes, type MonitorType } from "@/types/monitor.js"; import type { Check, CheckAudits, @@ -25,10 +22,7 @@ type CheckMetadataDocument = Omit & { type: MonitorType; }; -type CheckDocumentBase = Omit< - Check, - "id" | "metadata" | "expiry" | "ackAt" | "createdAt" | "updatedAt" -> & { +type CheckDocumentBase = Omit & { metadata: CheckMetadataDocument; expiry: Date; ackAt?: Date | null; diff --git a/server/src/service/infrastructure/statusService.js b/server/src/service/infrastructure/statusService.js index 5b64c7419..3aa1ced52 100755 --- a/server/src/service/infrastructure/statusService.js +++ b/server/src/service/infrastructure/statusService.js @@ -374,7 +374,7 @@ class StatusService { return { id: audit.id, title: audit.title, - score: typeof audit.score === "number" ? audit.score : audit.score ?? null, + score: typeof audit.score === "number" ? audit.score : (audit.score ?? null), displayValue: audit.displayValue, numericValue: typeof audit.numericValue === "number" ? audit.numericValue : undefined, numericUnit: audit.numericUnit, From f763126b38daad0977b20383e2b4add98a8585ee Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 13 Jan 2026 20:20:50 +0000 Subject: [PATCH 11/24] format' --- .../src/Pages/Incidents/Components/IncidentTable/index.jsx | 6 +++++- client/src/Pages/Incidents/index.jsx | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/client/src/Pages/Incidents/Components/IncidentTable/index.jsx b/client/src/Pages/Incidents/Components/IncidentTable/index.jsx index 78d6d26be..e066049e6 100644 --- a/client/src/Pages/Incidents/Components/IncidentTable/index.jsx +++ b/client/src/Pages/Incidents/Components/IncidentTable/index.jsx @@ -18,7 +18,11 @@ import { useSelector } from "react-redux"; import { useState } from "react"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; -import { useFetchChecksTeam, useFetchChecksByMonitor, useResolveIncident } from "@/Hooks/checkHooks.js"; +import { + useFetchChecksTeam, + useFetchChecksByMonitor, + useResolveIncident, +} from "@/Hooks/checkHooks.js"; import { Button, Typography, useTheme } from "@mui/material"; import { lighten } from "@mui/material/styles"; diff --git a/client/src/Pages/Incidents/index.jsx b/client/src/Pages/Incidents/index.jsx index c8462f649..698fc4a2f 100644 --- a/client/src/Pages/Incidents/index.jsx +++ b/client/src/Pages/Incidents/index.jsx @@ -10,7 +10,10 @@ import { Box, Button } from "@mui/material"; //Utils import { useTheme } from "@emotion/react"; import { useFetchMonitorsByTeamId } from "@/Hooks/monitorHooks.js"; -import { useFetchChecksSummaryByTeamId, useAcknowledgeChecks } from "@/Hooks/checkHooks.js"; +import { + useFetchChecksSummaryByTeamId, + useAcknowledgeChecks, +} from "@/Hooks/checkHooks.js"; import { useState, useEffect } from "react"; import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx"; import { useTranslation } from "react-i18next"; From 0a60cc0363b06b55314308ba2c0f4df513493e21 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 13 Jan 2026 21:44:16 +0000 Subject: [PATCH 12/24] check repo fix --- .../checks/MongoChecksRepistory.ts | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/server/src/repositories/checks/MongoChecksRepistory.ts b/server/src/repositories/checks/MongoChecksRepistory.ts index d04118da0..f5c398fc8 100644 --- a/server/src/repositories/checks/MongoChecksRepistory.ts +++ b/server/src/repositories/checks/MongoChecksRepistory.ts @@ -173,28 +173,33 @@ class MongoChecksRepistory implements IChecksRepository { }; findLatestChecksByMonitorIds = async (monitorIds: string[]): Promise => { + if (monitorIds.length === 0) { + return {}; + } const mongoIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id)); - const checkMap = await CheckModel.aggregate([ + const limitPerMonitor = 25; + const checkGroups = await CheckModel.aggregate([ { $match: { "metadata.monitorId": { $in: mongoIds }, }, }, - { $sort: { createdAt: -1 } }, { $group: { _id: "$metadata.monitorId", - latestChecks: { $push: "$$ROOT" }, - }, - }, - { - $project: { - latestChecks: { $slice: [{ $ifNull: ["$latestChecks", []] }, 25] }, + latestChecks: { + $topN: { + n: limitPerMonitor, + sortBy: { createdAt: -1 }, + output: "$$ROOT", + }, + }, }, }, ]); - return checkMap.reduce((acc, cm) => { - acc[cm._id.toString()] = cm.latestChecks.map((c: CheckDocument) => this.toEntity(c)); + return checkGroups.reduce((acc, group) => { + const monitorId = group._id.toString(); + acc[monitorId] = (group.latestChecks ?? []).map((doc: CheckDocument) => this.toEntity(doc)); return acc; }, {}); }; From 8de06df8c41089d3c3a0953f508fc3f3b696df28 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 13 Jan 2026 14:45:54 -0800 Subject: [PATCH 13/24] add index and cutof --- server/src/db/models/Check.ts | 2 ++ server/src/repositories/checks/MongoChecksRepistory.ts | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/server/src/db/models/Check.ts b/server/src/db/models/Check.ts index fe6f16836..c176aa3bb 100644 --- a/server/src/db/models/Check.ts +++ b/server/src/db/models/Check.ts @@ -288,7 +288,9 @@ const CheckSchema = new Schema( CheckSchema.index({ updatedAt: 1 }); CheckSchema.index({ "metadata.monitorId": 1, updatedAt: 1 }); CheckSchema.index({ "metadata.monitorId": 1, updatedAt: -1 }); +CheckSchema.index({ "metadata.monitorId": 1, createdAt: -1 }); CheckSchema.index({ "metadata.teamId": 1, updatedAt: -1 }); +CheckSchema.index({ "metadata.teamId": 1, createdAt: -1 }); const CheckModel = model("Check", CheckSchema); diff --git a/server/src/repositories/checks/MongoChecksRepistory.ts b/server/src/repositories/checks/MongoChecksRepistory.ts index f5c398fc8..de26efff4 100644 --- a/server/src/repositories/checks/MongoChecksRepistory.ts +++ b/server/src/repositories/checks/MongoChecksRepistory.ts @@ -178,10 +178,15 @@ class MongoChecksRepistory implements IChecksRepository { } const mongoIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id)); const limitPerMonitor = 25; + const maxIntervalMs = Number(process.env.MONITOR_MAX_INTERVAL_MS ?? 10 * 60 * 1000); + const bufferMs = Number(process.env.MONITOR_INTERVAL_BUFFER_MS ?? maxIntervalMs); + const lookbackMs = 25 * maxIntervalMs + bufferMs; + const cutoffDate = new Date(Date.now() - lookbackMs); const checkGroups = await CheckModel.aggregate([ { $match: { "metadata.monitorId": { $in: mongoIds }, + createdAt: { $gte: cutoffDate }, }, }, { From 8a986d2e58e89e5b763123105a2387d3d4544ab5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 13 Jan 2026 22:56:51 +0000 Subject: [PATCH 14/24] add indexes --- server/src/controllers/monitorController.ts | 2 +- server/src/db/models/Check.ts | 2 ++ .../checks/MongoChecksRepistory.ts | 6 +++++ .../monitors/IMonitorsRepository.ts | 1 + .../monitors/MongoMonitorsRepository.ts | 6 +++++ server/src/service/business/monitorService.ts | 22 ++++++++++--------- 6 files changed, 28 insertions(+), 11 deletions(-) diff --git a/server/src/controllers/monitorController.ts b/server/src/controllers/monitorController.ts index ba0a237bf..9c117e9af 100644 --- a/server/src/controllers/monitorController.ts +++ b/server/src/controllers/monitorController.ts @@ -231,7 +231,7 @@ class MonitorController { throw new AppError({ message: "Cannot get file from buffer", status: 400 }); } - const monitors = await this.monitorService.createBulkMonitors({ fileData, userId, teamId }); + const monitors = await this.monitorService.createBulkMonitors(fileData, userId, teamId); return res.status(200).json({ success: true, diff --git a/server/src/db/models/Check.ts b/server/src/db/models/Check.ts index fe6f16836..c176aa3bb 100644 --- a/server/src/db/models/Check.ts +++ b/server/src/db/models/Check.ts @@ -288,7 +288,9 @@ const CheckSchema = new Schema( CheckSchema.index({ updatedAt: 1 }); CheckSchema.index({ "metadata.monitorId": 1, updatedAt: 1 }); CheckSchema.index({ "metadata.monitorId": 1, updatedAt: -1 }); +CheckSchema.index({ "metadata.monitorId": 1, createdAt: -1 }); CheckSchema.index({ "metadata.teamId": 1, updatedAt: -1 }); +CheckSchema.index({ "metadata.teamId": 1, createdAt: -1 }); const CheckModel = model("Check", CheckSchema); diff --git a/server/src/repositories/checks/MongoChecksRepistory.ts b/server/src/repositories/checks/MongoChecksRepistory.ts index f5c398fc8..10b482fb2 100644 --- a/server/src/repositories/checks/MongoChecksRepistory.ts +++ b/server/src/repositories/checks/MongoChecksRepistory.ts @@ -178,10 +178,15 @@ class MongoChecksRepistory implements IChecksRepository { } const mongoIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id)); const limitPerMonitor = 25; + const maxIntervalMs = Number(10 * 60 * 1000); + const bufferMs = Number(maxIntervalMs); + const lookbackMs = 25 * maxIntervalMs + bufferMs; + const cutoffDate = new Date(Date.now() - lookbackMs); const checkGroups = await CheckModel.aggregate([ { $match: { "metadata.monitorId": { $in: mongoIds }, + createdAt: { $gte: cutoffDate }, }, }, { @@ -197,6 +202,7 @@ class MongoChecksRepistory implements IChecksRepository { }, }, ]); + return checkGroups.reduce((acc, group) => { const monitorId = group._id.toString(); acc[monitorId] = (group.latestChecks ?? []).map((doc: CheckDocument) => this.toEntity(doc)); diff --git a/server/src/repositories/monitors/IMonitorsRepository.ts b/server/src/repositories/monitors/IMonitorsRepository.ts index 99131cb4b..fc69d5740 100644 --- a/server/src/repositories/monitors/IMonitorsRepository.ts +++ b/server/src/repositories/monitors/IMonitorsRepository.ts @@ -13,6 +13,7 @@ export interface TeamQueryConfig { export interface IMonitorsRepository { // create create(monitor: Monitor, teamId: string, userId: string): Promise; + createBulkMonitors(monitors: Monitor[]): Promise; // single fetch findById(monitorId: string): Promise; diff --git a/server/src/repositories/monitors/MongoMonitorsRepository.ts b/server/src/repositories/monitors/MongoMonitorsRepository.ts index 49687c339..383442ac1 100644 --- a/server/src/repositories/monitors/MongoMonitorsRepository.ts +++ b/server/src/repositories/monitors/MongoMonitorsRepository.ts @@ -12,6 +12,12 @@ class MongoMonitorsRepository implements IMonitorsRepository { return this.toEntity(saved); }; + createBulkMonitors = async (monitors: Monitor[]): Promise => { + const newMonitors = monitors.map((monitor) => new MonitorModel({ ...monitor, notifications: undefined })); + await MonitorModel.bulkSave(newMonitors); + return monitors; + }; + findById = async (MonitorModelId: string): Promise => { const monitor = await MonitorModel.findById(MonitorModelId); if (!monitor) { diff --git a/server/src/service/business/monitorService.ts b/server/src/service/business/monitorService.ts index 45a2fc235..0f027b5b8 100644 --- a/server/src/service/business/monitorService.ts +++ b/server/src/service/business/monitorService.ts @@ -1,8 +1,10 @@ import { createMonitorsBodyValidation } from "@/validation/joi.js"; import { NormalizeData } from "@/utils/dataUtils.js"; import { type Monitor } from "@/types/index.js"; +import type { MonitorType } from "@/types/monitor.js"; import type { IMonitorsRepository } from "@/repositories/index.js"; import { AppError } from "../infrastructure/errorService.js"; + const SERVICE_NAME = "MonitorService"; export interface IMonitorService { @@ -11,7 +13,7 @@ export interface IMonitorService { // create createMonitor(teamId: string, userId: string, body: Monitor): Promise; - createBulkMonitors(args: { fileData: string; userId: string; teamId: string }): Promise; + createBulkMonitors(fileData: string, userId: string, teamId: string): Promise; addDemoMonitors(args: { userId: string; teamId: string }): Promise; // read @@ -31,7 +33,7 @@ export interface IMonitorService { getMonitorsByTeamId(args: { teamId: string; limit?: number; - type?: string | string[]; + type?: MonitorType | MonitorType[]; page?: number; rowsPerPage?: number; filter?: string; @@ -42,7 +44,7 @@ export interface IMonitorService { getMonitorsWithChecksByTeamId(args: { teamId: string; limit?: number; - type?: string | string[]; + type?: MonitorType | MonitorType[]; page?: number; rowsPerPage?: number; filter?: string; @@ -136,7 +138,7 @@ export class MonitorService implements IMonitorService { this.jobQueue.addJob(monitor.id, monitor); }; - createBulkMonitors = async ({ fileData, userId, teamId }: { fileData: string; userId: string; teamId: string }): Promise => { + createBulkMonitors = async (fileData: string, userId: string, teamId: string): Promise => { const { parse } = this.papaparse; return new Promise((resolve, reject) => { @@ -167,10 +169,10 @@ export class MonitorService implements IMonitorService { throw this.errorService.createServerError("CSV file contains no data rows"); } - const enrichedData = data.map((monitor: any) => ({ + const enrichedData = data.map((monitor: Monitor) => ({ + ...monitor, userId, teamId, - ...monitor, description: monitor.description || monitor.name || monitor.url, name: monitor.name || monitor.url, type: monitor.type || "http", @@ -178,11 +180,11 @@ export class MonitorService implements IMonitorService { await createMonitorsBodyValidation.validateAsync(enrichedData); - const monitors = await this.db.monitorModule.createBulkMonitors(enrichedData); + const monitors = await this.monitorsRepository.createBulkMonitors(enrichedData); await Promise.all( - monitors.map(async (monitor: any) => { - this.jobQueue.addJob(monitor._id, monitor); + monitors.map(async (monitor: Monitor) => { + this.jobQueue.addJob(monitor.id, monitor); }) ); @@ -334,7 +336,7 @@ export class MonitorService implements IMonitorService { }: { teamId: string; limit?: number; - type?: string | string[]; + type?: MonitorType | MonitorType[]; page?: number; rowsPerPage?: number; filter?: string; From 77764c51eecccb765c9cbfb8cc0165b6feeb8efa Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 13 Jan 2026 23:22:34 +0000 Subject: [PATCH 15/24] addDemo --- .../monitors/IMonitorsRepository.ts | 1 + .../monitors/MongoMonitorsRepository.ts | 16 ++++++-- server/src/service/business/monitorService.ts | 37 +++++++++++++++---- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/server/src/repositories/monitors/IMonitorsRepository.ts b/server/src/repositories/monitors/IMonitorsRepository.ts index fc69d5740..4d76d49db 100644 --- a/server/src/repositories/monitors/IMonitorsRepository.ts +++ b/server/src/repositories/monitors/IMonitorsRepository.ts @@ -23,6 +23,7 @@ export interface IMonitorsRepository { // update update(monitorId: string, updates: Partial): Promise; // delete + deleteByTeamId(teamId: string): Promise<{ monitors: Monitor[]; deletedCount: number }>; // counts findMonitorCountByTeamIdAndType(teamId: string, config: TeamQueryConfig): Promise; diff --git a/server/src/repositories/monitors/MongoMonitorsRepository.ts b/server/src/repositories/monitors/MongoMonitorsRepository.ts index 383442ac1..96795569b 100644 --- a/server/src/repositories/monitors/MongoMonitorsRepository.ts +++ b/server/src/repositories/monitors/MongoMonitorsRepository.ts @@ -13,9 +13,12 @@ class MongoMonitorsRepository implements IMonitorsRepository { }; createBulkMonitors = async (monitors: Monitor[]): Promise => { - const newMonitors = monitors.map((monitor) => new MonitorModel({ ...monitor, notifications: undefined })); - await MonitorModel.bulkSave(newMonitors); - return monitors; + if (!monitors.length) { + return []; + } + const payload = monitors.map((monitor) => ({ ...monitor, notifications: undefined })); + const inserted = await MonitorModel.insertMany(payload, { ordered: false }); + return this.mapDocuments(inserted); }; findById = async (MonitorModelId: string): Promise => { @@ -106,6 +109,13 @@ class MongoMonitorsRepository implements IMonitorsRepository { return this.toEntity(updatedMonitor); }; + deleteByTeamId = async (teamId: string) => { + const monitors = await MonitorModel.find({ teamId }); + const { deletedCount } = await MonitorModel.deleteMany({ teamId }); + + return { monitors: this.mapDocuments(monitors), deletedCount }; + }; + private mapDocuments = (documents: MonitorDocument[]): Monitor[] => { if (!documents?.length) { return []; diff --git a/server/src/service/business/monitorService.ts b/server/src/service/business/monitorService.ts index 0f027b5b8..004b9f25f 100644 --- a/server/src/service/business/monitorService.ts +++ b/server/src/service/business/monitorService.ts @@ -3,6 +3,10 @@ import { NormalizeData } from "@/utils/dataUtils.js"; import { type Monitor } from "@/types/index.js"; import type { MonitorType } from "@/types/monitor.js"; import type { IMonitorsRepository } from "@/repositories/index.js"; +import fs from "fs"; +import { fileURLToPath } from "url"; +import path from "path"; + import { AppError } from "../infrastructure/errorService.js"; const SERVICE_NAME = "MonitorService"; @@ -82,6 +86,7 @@ export class MonitorService implements IMonitorService { private games: any; private monitorsRepository: IMonitorsRepository; private checksRepository: any; + private fs: any; constructor({ db, @@ -198,9 +203,25 @@ export class MonitorService implements IMonitorService { }; addDemoMonitors = async ({ userId, teamId }: { userId: string; teamId: string }): Promise => { - const demoMonitors = await this.db.monitorModule.addDemoMonitors(userId, teamId); + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); + const demoMonitorsPath = path.resolve(__dirname, "../../utils/demoMonitors.json"); - await Promise.all(demoMonitors.map((monitor: any) => this.jobQueue.addJob(monitor._id, monitor))); + const demoData = JSON.parse(fs.readFileSync(demoMonitorsPath, "utf8")); + const monitors: Monitor[] = demoData.map((monitor: Monitor) => { + return { + userId, + teamId, + name: monitor.name, + description: monitor.name, + type: "http", + url: monitor.url, + interval: 60000, + }; + }); + const demoMonitors = await this.monitorsRepository.createBulkMonitors(monitors); + + await Promise.all(demoMonitors.map((monitor) => this.jobQueue.addJob(monitor.id, monitor))); return demoMonitors; }; @@ -399,17 +420,17 @@ export class MonitorService implements IMonitorService { }; deleteAllMonitors = async ({ teamId }: { teamId: string }): Promise => { - const { monitors, deletedCount } = await this.db.monitorModule.deleteAllMonitors(teamId); + const { monitors, deletedCount } = await this.monitorsRepository.deleteByTeamId(teamId); await Promise.all( - monitors.map(async (monitor: any) => { + monitors.map(async (monitor) => { try { await this.jobQueue.deleteJob(monitor); - await this.db.checkModule.deleteChecks(monitor._id); - await this.db.pageSpeedCheckModule.deletePageSpeedChecksByMonitorId(monitor._id); - await this.db.notificationsModule.deleteNotificationsByMonitorId(monitor._id); + await this.db.checkModule.deleteChecks(monitor.id); + await this.db.pageSpeedCheckModule.deletePageSpeedChecksByMonitorId(monitor.id); + await this.db.notificationsModule.deleteNotificationsByMonitorId(monitor.id); } catch (error: any) { this.logger.warn({ - message: `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`, + message: `Error deleting associated records for monitor ${monitor.id} with name ${monitor.name}`, service: SERVICE_NAME, method: "deleteAllMonitors", stack: error.stack, From 76fa99f7bd7ade0f4691fe8c13b6d0fb498ff143 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 13 Jan 2026 23:59:09 +0000 Subject: [PATCH 16/24] getUptimeDetails --- server/src/controllers/monitorController.ts | 13 --- server/src/db/modules/monitorModule.js | 11 -- .../repositories/checks/IChecksRepository.ts | 17 ++- .../checks/MongoChecksRepistory.ts | 100 ++++++++++++++++++ .../monitors/IMonitorsRepository.ts | 2 +- .../monitors/MongoMonitorsRepository.ts | 10 +- server/src/routes/v1/monitorRoute.ts | 1 - server/src/service/business/monitorService.ts | 69 +++++++++--- 8 files changed, 175 insertions(+), 48 deletions(-) diff --git a/server/src/controllers/monitorController.ts b/server/src/controllers/monitorController.ts index 9c117e9af..527fd334d 100644 --- a/server/src/controllers/monitorController.ts +++ b/server/src/controllers/monitorController.ts @@ -39,19 +39,6 @@ class MonitorController { } } - getAllMonitors = async (req: Request, res: Response, next: NextFunction) => { - try { - const monitors = await this.monitorService.getAllMonitors(); - return res.status(200).json({ - success: true, - msg: "Retrieved all monitors successfully", - data: monitors, - }); - } catch (error) { - next(error); - } - }; - getMonitorCertificate = async (req: Request, res: Response, next: NextFunction) => { try { await getCertificateParamValidation.validateAsync(req.params); diff --git a/server/src/db/modules/monitorModule.js b/server/src/db/modules/monitorModule.js index 9c8ca7e32..08b8e1371 100755 --- a/server/src/db/modules/monitorModule.js +++ b/server/src/db/modules/monitorModule.js @@ -194,17 +194,6 @@ class MonitorModule { }; }; - getAllMonitors = async () => { - try { - const monitors = await this.Monitor.find(); - return monitors; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getAllMonitors"; - throw error; - } - }; - getMonitorById = async (monitorId) => { try { const monitor = await this.Monitor.findById(monitorId); diff --git a/server/src/repositories/checks/IChecksRepository.ts b/server/src/repositories/checks/IChecksRepository.ts index df69cf18a..7741c071b 100644 --- a/server/src/repositories/checks/IChecksRepository.ts +++ b/server/src/repositories/checks/IChecksRepository.ts @@ -1,10 +1,17 @@ import type { LatestChecksMap } from "@/repositories/checks/MongoChecksRepistory.js"; export interface IChecksRepository { - // create - // single fetch - // collection fetch findLatestChecksByMonitorIds(monitorIds: string[]): Promise; - // update - // delete + findDateRangeChecksByMonitor( + monitorId: string, + startDate: Date, + endDate: Date, + dateString: string + ): Promise<{ + groupedChecks: Array<{ _id: string; avgResponseTime: number; totalChecks: number }>; + groupedUpChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>; + groupedDownChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>; + uptimePercentage: number; + avgResponseTime: number; + }>; } diff --git a/server/src/repositories/checks/MongoChecksRepistory.ts b/server/src/repositories/checks/MongoChecksRepistory.ts index 10b482fb2..76d3658cd 100644 --- a/server/src/repositories/checks/MongoChecksRepistory.ts +++ b/server/src/repositories/checks/MongoChecksRepistory.ts @@ -209,6 +209,106 @@ class MongoChecksRepistory implements IChecksRepository { return acc; }, {}); }; + + findDateRangeChecksByMonitor = async ( + monitorId: string, + startDate: Date, + endDate: Date, + dateString: string + ): Promise<{ + groupedChecks: Array<{ _id: string; avgResponseTime: number; totalChecks: number }>; + groupedUpChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>; + groupedDownChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>; + uptimePercentage: number; + avgResponseTime: number; + }> => { + const matchStage = { + "metadata.monitorId": new mongoose.Types.ObjectId(monitorId), + updatedAt: { $gte: startDate, $lte: endDate }, + }; + const [result] = await CheckModel.aggregate([ + { $match: matchStage }, + { $sort: { updatedAt: 1 } }, + { + $facet: { + uptimePercentage: [ + { + $group: { + _id: null, + upChecks: { $sum: { $cond: [{ $eq: ["$status", true] }, 1, 0] } }, + totalChecks: { $sum: 1 }, + }, + }, + { + $project: { + _id: 0, + percentage: { + $cond: [{ $eq: ["$totalChecks", 0] }, 0, { $divide: ["$upChecks", "$totalChecks"] }], + }, + }, + }, + ], + groupedAvgResponseTime: [ + { + $group: { + _id: null, + avgResponseTime: { $avg: "$responseTime" }, + }, + }, + ], + groupedChecks: [ + { + $group: { + _id: { + $dateToString: { format: dateString, date: "$createdAt" }, + }, + avgResponseTime: { $avg: "$responseTime" }, + totalChecks: { $sum: 1 }, + }, + }, + { $sort: { _id: 1 } }, + ], + groupedUpChecks: [ + { $match: { status: true } }, + { + $group: { + _id: { + $dateToString: { format: dateString, date: "$createdAt" }, + }, + totalChecks: { $sum: 1 }, + avgResponseTime: { $avg: "$responseTime" }, + }, + }, + { $sort: { _id: 1 } }, + ], + groupedDownChecks: [ + { $match: { status: false } }, + { + $group: { + _id: { + $dateToString: { format: dateString, date: "$createdAt" }, + }, + totalChecks: { $sum: 1 }, + avgResponseTime: { $avg: "$responseTime" }, + }, + }, + { $sort: { _id: 1 } }, + ], + }, + }, + ]).exec(); + + const uptimePercentage = result?.uptimePercentage?.[0]?.percentage ?? 0; + const avgResponseTime = result?.groupedAvgResponseTime?.[0]?.avgResponseTime ?? 0; + + return { + groupedChecks: result?.groupedChecks ?? [], + groupedUpChecks: result?.groupedUpChecks ?? [], + groupedDownChecks: result?.groupedDownChecks ?? [], + uptimePercentage, + avgResponseTime, + }; + }; } export default MongoChecksRepistory; diff --git a/server/src/repositories/monitors/IMonitorsRepository.ts b/server/src/repositories/monitors/IMonitorsRepository.ts index 4d76d49db..0da9c7279 100644 --- a/server/src/repositories/monitors/IMonitorsRepository.ts +++ b/server/src/repositories/monitors/IMonitorsRepository.ts @@ -15,7 +15,7 @@ export interface IMonitorsRepository { create(monitor: Monitor, teamId: string, userId: string): Promise; createBulkMonitors(monitors: Monitor[]): Promise; // single fetch - findById(monitorId: string): Promise; + findById(monitorId: string, teamId?: string): Promise; // collection fetch findAll(): Promise; diff --git a/server/src/repositories/monitors/MongoMonitorsRepository.ts b/server/src/repositories/monitors/MongoMonitorsRepository.ts index 96795569b..c9af6668d 100644 --- a/server/src/repositories/monitors/MongoMonitorsRepository.ts +++ b/server/src/repositories/monitors/MongoMonitorsRepository.ts @@ -21,12 +21,16 @@ class MongoMonitorsRepository implements IMonitorsRepository { return this.mapDocuments(inserted); }; - findById = async (MonitorModelId: string): Promise => { - const monitor = await MonitorModel.findById(MonitorModelId); + findById = async (monitorId: string, teamId?: string): Promise => { + const match: { _id: string; teamId?: string } = { _id: monitorId }; + if (teamId) { + match.teamId = teamId; + } + const monitor = await MonitorModel.findOne(match); if (!monitor) { if (monitor === null || monitor === undefined) { throw new AppError({ - message: `Monitor with ID ${MonitorModelId} not found.`, + message: `Monitor with ID ${monitorId} not found.`, status: 404, }); } diff --git a/server/src/routes/v1/monitorRoute.ts b/server/src/routes/v1/monitorRoute.ts index e68e0019f..a355efbb9 100755 --- a/server/src/routes/v1/monitorRoute.ts +++ b/server/src/routes/v1/monitorRoute.ts @@ -38,7 +38,6 @@ class MonitorRoutes { }); // General monitor CRUD routes - this.router.get("/", this.monitorController.getAllMonitors); this.router.post("/", isAllowed(["admin", "superadmin"]), this.monitorController.createMonitor); this.router.delete("/", isAllowed(["superadmin"]), this.monitorController.deleteAllMonitors); diff --git a/server/src/service/business/monitorService.ts b/server/src/service/business/monitorService.ts index 004b9f25f..b189f9077 100644 --- a/server/src/service/business/monitorService.ts +++ b/server/src/service/business/monitorService.ts @@ -1,8 +1,8 @@ import { createMonitorsBodyValidation } from "@/validation/joi.js"; -import { NormalizeData } from "@/utils/dataUtils.js"; +import { NormalizeData, NormalizeDataUptimeDetails } from "@/utils/dataUtils.js"; import { type Monitor } from "@/types/index.js"; import type { MonitorType } from "@/types/monitor.js"; -import type { IMonitorsRepository } from "@/repositories/index.js"; +import type { IChecksRepository, IMonitorsRepository } from "@/repositories/index.js"; import fs from "fs"; import { fileURLToPath } from "url"; import path from "path"; @@ -10,6 +10,7 @@ import path from "path"; import { AppError } from "../infrastructure/errorService.js"; const SERVICE_NAME = "MonitorService"; +type DateRangeKey = "recent" | "day" | "week" | "month" | "all"; export interface IMonitorService { readonly serviceName: string; @@ -21,7 +22,6 @@ export interface IMonitorService { addDemoMonitors(args: { userId: string; teamId: string }): Promise; // read - getAllMonitors(): Promise; getUptimeDetailsById(args: { teamId: string; monitorId: string; dateRange: string; normalize?: boolean }): Promise; getMonitorStatsById(args: { teamId: string; @@ -85,7 +85,7 @@ export class MonitorService implements IMonitorService { private errorService: any; private games: any; private monitorsRepository: IMonitorsRepository; - private checksRepository: any; + private checksRepository: IChecksRepository; private fs: any; constructor({ @@ -109,7 +109,7 @@ export class MonitorService implements IMonitorService { errorService: any; games: any; monitorsRepository: IMonitorsRepository; - checksRepository: any; + checksRepository: IChecksRepository; }) { this.db = db; this.jobQueue = jobQueue; @@ -127,6 +127,31 @@ export class MonitorService implements IMonitorService { return MonitorService.SERVICE_NAME; } + private getDateRange = (dateRange: DateRangeKey) => { + const startDates = { + recent: new Date(new Date().setHours(new Date().getHours() - 2)), + day: new Date(new Date().setDate(new Date().getDate() - 1)), + week: new Date(new Date().setDate(new Date().getDate() - 7)), + month: new Date(new Date().setMonth(new Date().getMonth() - 1)), + all: new Date(0), + }; + return { + start: startDates[dateRange], + end: new Date(), + }; + }; + + private getDateFormat = (dateRange: DateRangeKey): string => { + const formatLookup = { + recent: "%Y-%m-%dT%H:%M:00Z", + day: "%Y-%m-%dT%H:00:00Z", + week: "%Y-%m-%dT00:00:00Z", + month: "%Y-%m-%dT00:00:00Z", + all: "%Y-%m-%dT00:00:00Z", + }; + return formatLookup[dateRange]; + }; + verifyTeamAccess = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise => { const monitor = await this.db.monitorModule.getMonitorById(monitorId); if (!monitor?.teamId?.equals(teamId)) { @@ -225,11 +250,6 @@ export class MonitorService implements IMonitorService { return demoMonitors; }; - getAllMonitors = async (): Promise => { - const monitors = await this.db.monitorModule.getAllMonitors(); - return monitors; - }; - getUptimeDetailsById = async ({ teamId, monitorId, @@ -242,13 +262,34 @@ export class MonitorService implements IMonitorService { normalize?: boolean; }): Promise => { await this.verifyTeamAccess({ teamId, monitorId }); - const data = await this.db.monitorModule.getUptimeDetailsById({ + + const monitor = await this.monitorsRepository.findById(monitorId, teamId); + if (!monitor) { + throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 }); + } + const rangeKey = (dateRange as DateRangeKey) ?? "recent"; + const { start, end } = this.getDateRange(rangeKey); + const checksData = await this.checksRepository.findDateRangeChecksByMonitor( + monitorId: monitor.id, + startDate: start, + endDate: end, + dateString: this.getDateFormat(rangeKey), + ); + const monitorStats = await this.db.monitorModule.getMonitorStatsById({ monitorId, - dateRange, - normalize, }); - return data; + return { + monitorData: { + monitor, + groupedChecks: NormalizeDataUptimeDetails(checksData.groupedChecks, 10, 100), + groupedUpChecks: NormalizeDataUptimeDetails(checksData.groupedUpChecks, 10, 100), + groupedDownChecks: NormalizeDataUptimeDetails(checksData.groupedDownChecks, 10, 100), + groupedAvgResponseTime: checksData.avgResponseTime, + groupedUptimePercentage: checksData.uptimePercentage, + }, + monitorStats, + }; }; getMonitorStatsById = async ({ From d1faeab042d3fc9ab33d853616284fa9d667a077 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 14 Jan 2026 00:04:22 +0000 Subject: [PATCH 17/24] fix params --- server/src/service/business/monitorService.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/server/src/service/business/monitorService.ts b/server/src/service/business/monitorService.ts index b189f9077..3e4d39ade 100644 --- a/server/src/service/business/monitorService.ts +++ b/server/src/service/business/monitorService.ts @@ -269,12 +269,7 @@ export class MonitorService implements IMonitorService { } const rangeKey = (dateRange as DateRangeKey) ?? "recent"; const { start, end } = this.getDateRange(rangeKey); - const checksData = await this.checksRepository.findDateRangeChecksByMonitor( - monitorId: monitor.id, - startDate: start, - endDate: end, - dateString: this.getDateFormat(rangeKey), - ); + const checksData = await this.checksRepository.findDateRangeChecksByMonitor(monitor.id, start, end, this.getDateFormat(rangeKey)); const monitorStats = await this.db.monitorModule.getMonitorStatsById({ monitorId, }); From 665eb9a67fce1fce0ce2cc8eb2c9813a468992e8 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 14 Jan 2026 19:28:17 +0000 Subject: [PATCH 18/24] hardware details --- server/jest.config.ts | 20 + server/package-lock.json | 3663 +++++++++++++++-- server/package.json | 14 +- server/scripts/generate-checks.js | 176 + .../src/config/{services.js => services.ts} | 14 +- server/src/controllers/monitorController.ts | 35 - server/src/db/MongoDB.js | 6 - server/src/db/models/MonitorStats.js | 49 - server/src/db/models/MonitorStats.ts | 63 + server/src/db/models/index.ts | 3 + server/src/db/modules/monitorModule.js | 42 - .../repositories/checks/IChecksRepository.ts | 58 +- .../checks/MongoChecksRepistory.ts | 85 +- server/src/repositories/index.ts | 3 + .../monitor-stats/IMonitorStatsRepository.ts | 9 + .../MongoMonitorStatsRepository.ts | 44 + server/src/routes/v1/monitorRoute.ts | 1 - server/src/service/business/monitorService.ts | 90 +- .../service/infrastructure/statusService.js | 6 +- server/src/types/index.ts | 1 + server/src/types/monitorStats.ts | 14 + server/src/validation/joi.js | 14 - server/test/monitorService.test.ts | 218 + server/tsconfig.jest.json | 9 + server/tsconfig.json | 3 +- 25 files changed, 4050 insertions(+), 590 deletions(-) create mode 100644 server/jest.config.ts create mode 100644 server/scripts/generate-checks.js rename server/src/config/{services.js => services.ts} (94%) delete mode 100755 server/src/db/models/MonitorStats.js create mode 100755 server/src/db/models/MonitorStats.ts create mode 100644 server/src/repositories/monitor-stats/IMonitorStatsRepository.ts create mode 100644 server/src/repositories/monitor-stats/MongoMonitorStatsRepository.ts create mode 100644 server/src/types/monitorStats.ts create mode 100644 server/test/monitorService.test.ts create mode 100644 server/tsconfig.jest.json diff --git a/server/jest.config.ts b/server/jest.config.ts new file mode 100644 index 000000000..2bcfcbf21 --- /dev/null +++ b/server/jest.config.ts @@ -0,0 +1,20 @@ +import type { Config } from "jest"; + +const config: Config = { + rootDir: ".", + testEnvironment: "node", + extensionsToTreatAsEsm: [".ts"], + transform: { + "^.+\\.(t|j)sx?$": ["ts-jest", { useESM: true, tsconfig: "./tsconfig.jest.json" }], + }, + moduleNameMapper: { + "^@/(.*)$": "/src/$1", + }, + testMatch: ["/test/**/*.test.ts"], + setupFilesAfterEnv: [], + collectCoverageFrom: ["src/**/*.ts"], + coveragePathIgnorePatterns: ["/node_modules/", "/test/"], + clearMocks: true, +}; + +export default config; diff --git a/server/package-lock.json b/server/package-lock.json index d254da407..11e1829b4 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -46,21 +46,27 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/dockerode": "^4.0.0", "@types/express": "5.0.3", + "@types/gamedig": "^5.0.3", + "@types/jest": "^30.0.0", + "@types/jmespath": "^0.15.2", "@types/jsonwebtoken": "9.0.10", + "@types/mjml": "^4.7.4", "@types/multer": "^2.0.0", "@types/nodemailer": "7.0.1", + "@types/papaparse": "^5.5.2", "@types/ping": "0.4.4", "c8": "10.1.3", - "chai": "5.2.0", "eslint": "^9.17.0", "eslint-plugin-mocha": "^10.5.0", "esm": "3.2.25", "globals": "^15.14.0", - "mocha": "11.1.0", + "jest": "^30.2.0", "nodemon": "^3.1.11", "prettier": "^3.3.3", - "sinon": "19.0.2", + "ts-jest": "^29.4.6", + "ts-node": "^10.9.2", "tsc-alias": "1.8.16", "tsx": "4.20.5", "typescript": "5.9.2" @@ -834,12 +840,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -847,6 +853,174 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/compat-data": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", @@ -856,6 +1030,285 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/runtime": { "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", @@ -865,6 +1318,54 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@balena/dockerignore": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", @@ -890,6 +1391,30 @@ "node": ">=0.1.90" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", @@ -1035,6 +1560,18 @@ "kuler": "^2.0.0" } }, + "node_modules/@emnapi/core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/runtime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.6.0.tgz", @@ -1045,6 +1582,17 @@ "tslib": "^2.4.0" } }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.11", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", @@ -2144,6 +2692,113 @@ "node": ">=12" } }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -2154,6 +2809,377 @@ "node": ">=8" } }, + "node_modules/@jest/console": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", + "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", + "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.2.0", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.2.0", + "jest-config": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-resolve-dependencies": "30.2.0", + "jest-runner": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "jest-watcher": "30.2.0", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", + "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-mock": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "30.2.0", + "jest-snapshot": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", + "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", + "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@sinonjs/fake-timers": "^13.0.0", + "@types/node": "*", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", + "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/types": "30.2.0", + "jest-mock": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", + "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@jridgewell/trace-mapping": "^0.3.25", + "@types/node": "*", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^5.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", + "slash": "^3.0.0", + "string-length": "^4.0.2", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", + "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", + "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.2.0", + "@jest/types": "30.2.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", + "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.2.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", + "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.2.0", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", + "chalk": "^4.1.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.2.0", + "micromatch": "^4.0.8", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", + "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -2279,6 +3305,19 @@ "win32" ] }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2327,6 +3366,19 @@ "node": ">=14" } }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -2439,6 +3491,13 @@ "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", "license": "BSD-3-Clause" }, + "node_modules/@sinclair/typebox": { + "version": "0.34.47", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.47.tgz", + "integrity": "sha512-ZGIBQ+XDvO5JQku9wmwtabcVTHJsgSWAHYtVuM9pBNNR5E88v6Jcj/llpmsjivig5X8A8HHOb4/mbEKPS5EvAw==", + "dev": true, + "license": "MIT" + }, "node_modules/@sindresorhus/is": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.1.0.tgz", @@ -2471,34 +3530,6 @@ "@sinonjs/commons": "^3.0.1" } }, - "node_modules/@sinonjs/samsam": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz", - "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "type-detect": "^4.1.0" - } - }, - "node_modules/@sinonjs/samsam/node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", - "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", - "dev": true, - "license": "(Unlicense OR Apache-2.0)" - }, "node_modules/@smithy/abort-controller": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.3.tgz", @@ -3197,6 +4228,90 @@ "node": ">=10.13.0" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, "node_modules/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", @@ -3218,6 +4333,29 @@ "@types/node": "*" } }, + "node_modules/@types/docker-modem": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz", + "integrity": "sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/ssh2": "*" + } + }, + "node_modules/@types/dockerode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-4.0.0.tgz", + "integrity": "sha512-NBm/UIL1KYWivh9ihJELK4Jv0dzOQCleS0OT5JklErra8mZ/t0UQpHfTkMmPrAqtra7OyFius1EFLVs0DhiHQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/docker-modem": "*", + "@types/node": "*", + "@types/ssh2": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -3250,6 +4388,13 @@ "@types/send": "*" } }, + "node_modules/@types/gamedig": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/gamedig/-/gamedig-5.0.3.tgz", + "integrity": "sha512-VLrOVRf72wVE6P0DhFtRVodiONwL51uojn6dNpP/Ks/Vdm3CDH6dZTAw74WmCDLuyvpytNy8auKzlTKfBlO8jg==", + "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", @@ -3270,6 +4415,44 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, + "node_modules/@types/jmespath": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@types/jmespath/-/jmespath-0.15.2.tgz", + "integrity": "sha512-pegh49FtNsC389Flyo9y8AfkVIZn9MMPE9yJrO9svhq6Fks2MwymULWjZqySuxmctd3ZH4/n7Mr98D+1Qo5vGA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -3295,6 +4478,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mjml": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/@types/mjml/-/mjml-4.7.4.tgz", + "integrity": "sha512-vyi1vzWgMzFMwZY7GSZYX0GU0dmtC8vLHwpgk+NWmwbwRSrlieVyJ9sn5elodwUfklJM7yGl0zQeet1brKTWaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mjml-core": "*" + } + }, + "node_modules/@types/mjml-core": { + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/@types/mjml-core/-/mjml-core-4.15.2.tgz", + "integrity": "sha512-Q7SxFXgoX979HP57DEVsRI50TV8x1V4lfCA4Up9AvfINDM5oD/X9ARgfoyX1qS987JCnDLv85JjkqAjt3hZSiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -3317,6 +4517,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz", "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -3332,6 +4533,16 @@ "@types/node": "*" } }, + "node_modules/@types/papaparse": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.2.tgz", + "integrity": "sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/ping": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/@types/ping/-/ping-0.4.4.tgz", @@ -3392,6 +4603,40 @@ "@types/node": "*" } }, + "node_modules/@types/ssh2": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.5.tgz", + "integrity": "sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^18.11.18" + } + }, + "node_modules/@types/ssh2/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/ssh2/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", @@ -3420,6 +4665,299 @@ "@types/webidl-conversions": "*" } }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -3466,6 +5004,19 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -3501,6 +5052,35 @@ "node": ">=6" } }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -3553,6 +5133,13 @@ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", "license": "MIT" }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3584,16 +5171,6 @@ "safer-buffer": "~2.1.0" } }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -3617,6 +5194,142 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/babel-jest": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", + "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "30.2.0", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.2.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", + "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/babel__core": "^7.20.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", + "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3833,13 +5546,6 @@ "node": ">=8" } }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true, - "license": "ISC" - }, "node_modules/browserslist": { "version": "4.27.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", @@ -3874,6 +5580,29 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, "node_modules/bson": { "version": "6.10.4", "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", @@ -4063,16 +5792,13 @@ } }, "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, "node_modules/caniuse-api": { @@ -4107,23 +5833,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/chai": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", - "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -4141,14 +5850,14 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 16" + "node": ">=10" } }, "node_modules/cheerio": { @@ -4231,6 +5940,29 @@ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "license": "ISC" }, + "node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -4312,6 +6044,24 @@ "node": ">=0.10.0" } }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -4598,6 +6348,13 @@ "node": ">=10.0.0" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cron-parser": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", @@ -4875,19 +6632,6 @@ } } }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "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", @@ -4921,14 +6665,19 @@ "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", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "node_modules/dedent": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", + "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6" + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } } }, "node_modules/deep-is": { @@ -4938,6 +6687,16 @@ "dev": true, "license": "MIT" }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/defer-to-connect": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", @@ -4993,6 +6752,16 @@ "node": ">=8" } }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", @@ -5000,9 +6769,9 @@ "license": "MIT" }, "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -5193,6 +6962,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -5600,6 +7382,20 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -5662,6 +7458,91 @@ "deprecated": "Use promise-toolbox/fromEvent instead", "license": "MIT" }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/express": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", @@ -5827,6 +7708,16 @@ "reusify": "^1.0.4" } }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, "node_modules/fecha": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", @@ -5931,16 +7822,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -6065,6 +7946,13 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "license": "MIT" }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -6255,6 +8143,16 @@ "node": ">=0.10" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -6288,6 +8186,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -6458,6 +8366,13 @@ "url": "https://github.com/sindresorhus/got?sponsor=1" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, "node_modules/handlebars": { "version": "4.7.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", @@ -6528,16 +8443,6 @@ "node": ">= 0.4" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, "node_modules/helmet": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", @@ -6702,6 +8607,16 @@ "numbered": "^1.1.0" } }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -6767,6 +8682,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -6777,6 +8712,18 @@ "node": ">=0.8.19" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -6861,6 +8808,16 @@ "node": ">=8" } }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -6888,16 +8845,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "license": "MIT", - "engines": { - "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", @@ -6916,19 +8863,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -7168,6 +9102,23 @@ "node": ">=8" } }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -7183,6 +9134,21 @@ "node": ">=10" } }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/istanbul-reports": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", @@ -7212,6 +9178,600 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jest": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", + "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/core": "30.2.0", + "@jest/types": "30.2.0", + "import-local": "^3.2.0", + "jest-cli": "30.2.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", + "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.1.1", + "jest-util": "30.2.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", + "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "co": "^4.6.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "p-limit": "^3.1.0", + "pretty-format": "30.2.0", + "pure-rand": "^7.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-cli": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", + "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "yargs": "^17.7.2" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", + "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.2.0", + "@jest/types": "30.2.0", + "babel-jest": "30.2.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-circus": "30.2.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-runner": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "micromatch": "^4.0.8", + "parse-json": "^5.2.0", + "pretty-format": "30.2.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "esbuild-register": ">=3.4.0", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild-register": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", + "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", + "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "jest-util": "30.2.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", + "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-mock": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", + "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", + "micromatch": "^4.0.8", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/jest-leak-detector": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", + "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", + "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.2.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", + "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.2.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", + "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", + "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", + "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", + "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.2.0", + "@jest/environment": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-leak-detector": "30.2.0", + "jest-message-util": "30.2.0", + "jest-resolve": "30.2.0", + "jest-runtime": "30.2.0", + "jest-util": "30.2.0", + "jest-watcher": "30.2.0", + "jest-worker": "30.2.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", + "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/globals": "30.2.0", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", + "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.2.0", + "graceful-fs": "^4.2.11", + "jest-diff": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "pretty-format": "30.2.0", + "semver": "^7.7.2", + "synckit": "^0.11.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", + "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-validate": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", + "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.2.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", + "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "jest-util": "30.2.0", + "string-length": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", + "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.2.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/jmespath": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", @@ -7291,6 +9851,19 @@ } } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -7317,6 +9890,19 @@ "dev": true, "license": "MIT" }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -7427,13 +10013,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/just-extend": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", - "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", - "dev": true, - "license": "MIT" - }, "node_modules/jwa": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", @@ -7479,6 +10058,16 @@ "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", "license": "MIT" }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -7612,23 +10201,6 @@ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "license": "MIT" }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/logform": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", @@ -7652,13 +10224,6 @@ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "license": "Apache-2.0" }, - "node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "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", @@ -7713,6 +10278,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -7758,6 +10340,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -7833,6 +10422,16 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/mimic-response": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", @@ -8323,81 +10922,6 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "license": "MIT" }, - "node_modules/mocha": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz", - "integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^10.4.5", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "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", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -8613,6 +11137,22 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -8641,31 +11181,6 @@ "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", - "integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^13.0.1", - "@sinonjs/text-encoding": "^0.7.3", - "just-extend": "^6.2.0", - "path-to-regexp": "^8.1.0" - } - }, - "node_modules/nise/node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", @@ -8749,6 +11264,13 @@ "node-gyp-build-optional-packages-test": "build-test.js" } }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, "node_modules/node-releases": { "version": "2.0.26", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz", @@ -8837,6 +11359,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -8921,6 +11456,22 @@ "fn.name": "1.x.x" } }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -8980,6 +11531,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -9090,6 +11651,16 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -9131,16 +11702,6 @@ "node": ">=8" } }, - "node_modules/pathval": { - "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": { - "node": ">= 14.16" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -9168,6 +11729,85 @@ "node": ">=4.0.0" } }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/plimit-lit": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz", @@ -9819,6 +12459,34 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-format": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", + "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -9894,6 +12562,23 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/qs": { "version": "6.14.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", @@ -9959,16 +12644,6 @@ "dev": true, "license": "MIT" }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -10022,6 +12697,13 @@ "node": ">= 0.8" } }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -10093,6 +12775,29 @@ "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", "license": "MIT" }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -10306,16 +13011,6 @@ "node": ">= 0.8" } }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", @@ -10515,35 +13210,6 @@ "node": ">=10" } }, - "node_modules/sinon": { - "version": "19.0.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.2.tgz", - "integrity": "sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^13.0.2", - "@sinonjs/samsam": "^8.0.1", - "diff": "^7.0.0", - "nise": "^6.1.1", - "supports-color": "^7.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, - "node_modules/sinon/node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -10581,6 +13247,17 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", @@ -10596,6 +13273,13 @@ "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==", "license": "ISC" }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/ssh2": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz", @@ -10628,6 +13312,29 @@ "node": "*" } }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/standard-as-callback": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", @@ -10669,6 +13376,43 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string-to-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string-to-stream/-/string-to-stream-1.1.1.tgz", @@ -10811,6 +13555,26 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -11009,6 +13773,22 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "license": "MIT" }, + "node_modules/synckit": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, "node_modules/tar-fs": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", @@ -11116,6 +13896,13 @@ "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", "license": "MIT" }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -11180,6 +13967,104 @@ "node": ">= 14.0.0" } }, + "node_modules/ts-jest": { + "version": "29.4.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", + "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.3", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tsc-alias": { "version": "1.8.16", "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.16.tgz", @@ -11366,6 +14251,41 @@ "node": ">= 0.8" } }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", @@ -11434,6 +14354,13 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -11485,6 +14412,16 @@ "node": ">=18" } }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, "node_modules/web-resource-inliner": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-7.0.0.tgz", @@ -11753,13 +14690,6 @@ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "license": "MIT" }, - "node_modules/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -11854,6 +14784,20 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", @@ -11937,6 +14881,13 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -11964,22 +14915,6 @@ "node": ">=12" } }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/yargs/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -12021,6 +14956,16 @@ "node": ">=8" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/server/package.json b/server/package.json index 3547810ce..91ba82988 100755 --- a/server/package.json +++ b/server/package.json @@ -5,7 +5,7 @@ "main": "index.js", "type": "module", "scripts": { - "test": "c8 mocha", + "test": "NODE_OPTIONS=--experimental-vm-modules c8 jest --runInBand", "dev": "nodemon --exec tsx src/index.js", "start": "node --watch ./dist/index.js", "build": "tsc && tsc-alias", @@ -58,21 +58,27 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/dockerode": "^4.0.0", "@types/express": "5.0.3", + "@types/gamedig": "^5.0.3", + "@types/jest": "^30.0.0", + "@types/jmespath": "^0.15.2", "@types/jsonwebtoken": "9.0.10", + "@types/mjml": "^4.7.4", "@types/multer": "^2.0.0", "@types/nodemailer": "7.0.1", + "@types/papaparse": "^5.5.2", "@types/ping": "0.4.4", "c8": "10.1.3", - "chai": "5.2.0", "eslint": "^9.17.0", "eslint-plugin-mocha": "^10.5.0", "esm": "3.2.25", "globals": "^15.14.0", - "mocha": "11.1.0", + "jest": "^30.2.0", "nodemon": "^3.1.11", "prettier": "^3.3.3", - "sinon": "19.0.2", + "ts-jest": "^29.4.6", + "ts-node": "^10.9.2", "tsc-alias": "1.8.16", "tsx": "4.20.5", "typescript": "5.9.2" diff --git a/server/scripts/generate-checks.js b/server/scripts/generate-checks.js new file mode 100644 index 000000000..d3a33b033 --- /dev/null +++ b/server/scripts/generate-checks.js @@ -0,0 +1,176 @@ +import mongoose from "mongoose"; +import { MonitorModel } from "../dist/db/models/Monitor.js"; +import { CheckModel } from "../dist/db/models/Check.js"; + +const DEFAULT_MONITOR_ID = "000000000000000000000001"; +const DEFAULT_TEAM_ID = "0000000000000000000000aa"; +const DEFAULT_USER_ID = "0000000000000000000000bb"; +const DEFAULT_MONITOR_TYPE = "http"; +const DEFAULT_TOTAL = 1_000_000; +const DEFAULT_BATCH_SIZE = 5_000; + +const parseObjectId = (value, fallback) => { + try { + return new mongoose.Types.ObjectId(value || fallback); + } catch (error) { + console.warn(`Invalid ObjectId '${value}', falling back to '${fallback}'.`); + return new mongoose.Types.ObjectId(fallback); + } +}; + +async function ensureMonitor({ monitorId, teamId, userId, type }) { + const existing = await MonitorModel.findById(monitorId); + if (existing) { + return existing; + } + + console.log(`Monitor ${monitorId.toString()} not found, creating it.`); + const monitor = new MonitorModel({ + _id: monitorId, + userId, + teamId, + name: `Seed Monitor ${monitorId.toString()}`, + description: "Synthetic monitor for performance testing", + statusWindow: [], + statusWindowSize: 5, + statusWindowThreshold: 60, + type, + ignoreTlsErrors: false, + url: "https://example.com", + isActive: true, + interval: 60000, + alertThreshold: 5, + cpuAlertThreshold: 5, + memoryAlertThreshold: 5, + diskAlertThreshold: 5, + tempAlertThreshold: 5, + selectedDisks: [], + }); + + await monitor.save(); + return monitor; +} + +async function run() { + const mongoUri = process.env.MONGO_URI ?? "mongodb://localhost:27017/uptime_db"; + const monitorId = parseObjectId(process.env.MONITOR_ID ?? DEFAULT_MONITOR_ID, DEFAULT_MONITOR_ID); + const teamId = parseObjectId("69648b0578209af45f9ffe30"); + const userId = parseObjectId("69648b0678209af45f9ffe32"); + const monitorType = process.env.MONITOR_TYPE ?? DEFAULT_MONITOR_TYPE; + const total = Number(process.env.CHECK_TOTAL ?? DEFAULT_TOTAL); + const batchSize = Number(process.env.CHECK_BATCH_SIZE ?? DEFAULT_BATCH_SIZE); + + console.log(`Connecting to MongoDB at ${mongoUri}`); + await mongoose.connect(mongoUri); + + await ensureMonitor({ monitorId, teamId, userId, type: monitorType }); + + console.log(`Seeding ${total} checks for monitor ${monitorId.toString()} (team ${teamId.toString()}) in batches of ${batchSize}.`); + + const docs = []; + const startTime = Date.now(); + + for (let i = 0; i < total; i += 1) { + const baseTime = Date.now() - (total - i) * 1000; + const createdAt = new Date(baseTime); + docs.push({ + metadata: { + monitorId, + teamId, + type: monitorType, + }, + status: i % 50 !== 0, + statusCode: i % 50 !== 0 ? 200 : 500, + responseTime: Math.floor(Math.random() * 1000), + message: i % 50 !== 0 ? "OK" : "Error", + expiry: createdAt, + createdAt, + updatedAt: createdAt, + timings: { + start: baseTime, + socket: baseTime, + lookup: baseTime, + connect: baseTime, + secureConnect: baseTime, + upload: baseTime, + response: baseTime + 40, + end: baseTime + 45, + phases: { + wait: 0, + dns: 1, + tcp: 2, + tls: 4, + request: 0, + firstByte: 30, + download: 5, + total: 45, + }, + }, + cpu: { + physical_core: 8, + logical_core: 16, + frequency: 3600, + temperature: [50 + Math.random() * 10], + free_percent: 40, + usage_percent: Math.random() * 100, + }, + memory: { + total_bytes: 32 * 1024 ** 3, + available_bytes: 16 * 1024 ** 3, + used_bytes: 16 * 1024 ** 3, + usage_percent: Math.random() * 100, + }, + disk: [ + { + device: "/dev/sda1", + mountpoint: "/", + read_speed_bytes: Math.random() * 10_000_000, + write_speed_bytes: Math.random() * 10_000_000, + total_bytes: 512 * 1024 ** 3, + free_bytes: 128 * 1024 ** 3, + usage_percent: Math.random() * 100, + }, + ], + host: { + os: "linux", + platform: "ubuntu", + kernel_version: "5.15.0", + }, + net: [ + { + name: "eth0", + bytes_sent: Math.random() * 10_000_000, + bytes_recv: Math.random() * 10_000_000, + packets_sent: Math.random() * 1_000_000, + packets_recv: Math.random() * 1_000_000, + err_in: 0, + err_out: 0, + drop_in: 0, + drop_out: 0, + fifo_in: 0, + fifo_out: 0, + }, + ], + errors: i % 50 === 0 ? [{ metric: ["uptime"], err: "500" }] : [], + }); + + if (docs.length === batchSize) { + await CheckModel.insertMany(docs, { ordered: false }); + console.log(`Inserted ${i + 1} / ${total}`); + docs.length = 0; + } + } + + if (docs.length > 0) { + await CheckModel.insertMany(docs, { ordered: false }); + } + + await mongoose.disconnect(); + const duration = ((Date.now() - startTime) / 1000).toFixed(2); + console.log(`Finished inserting ${total} checks in ${duration}s`); +} + +run().catch((error) => { + console.error("Failed to seed checks", error); + process.exit(1); +}); diff --git a/server/src/config/services.js b/server/src/config/services.ts similarity index 94% rename from server/src/config/services.js rename to server/src/config/services.ts index 6878026d6..74c68c86e 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.ts @@ -34,9 +34,8 @@ const { compile } = pkg; import mjml2html from "mjml"; import jwt from "jsonwebtoken"; import crypto from "crypto"; -import { games } from "gamedig"; +import { games, GameDig } from "gamedig"; import jmespath from "jmespath"; -import { GameDig } from "gamedig"; import { fileURLToPath } from "url"; import { ObjectId } from "mongodb"; @@ -47,7 +46,6 @@ import { GenerateAvatarImage } from "../utils/imageProcessing.js"; import { ParseBoolean } from "../utils/utils.js"; // Models -import { CheckModel } from "@/db/models/index.js"; import Monitor from "../db/models/Monitor.js"; import User from "../db/models/User.js"; import InviteToken from "../db/models/InviteToken.js"; @@ -72,11 +70,11 @@ import SettingsModule from "../db/modules/settingsModule.js"; import IncidentModule from "../db/modules/incidentModule.js"; // repositories -import { MongoMonitorsRepository, MongoChecksRepository } from "@/repositories/index.js"; +import { MongoMonitorsRepository, MongoChecksRepository, MongoMonitorStatsRepository } from "@/repositories/index.js"; -export const initializeServices = async ({ logger, envSettings, settingsService }) => { +export const initializeServices = async ({ logger, envSettings, settingsService }: { logger: any; envSettings: any; settingsService: any }) => { const serviceRegistry = new ServiceRegistry({ logger }); - ServiceRegistry.instance = serviceRegistry; + (ServiceRegistry as any).instance = serviceRegistry; const translationService = new TranslationService(logger); await translationService.initialize(); @@ -84,7 +82,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService const stringService = new StringService(translationService); // Create DB - const checkModule = new CheckModule({ logger, CheckModel, Monitor, User }); + const checkModule = new CheckModule({ logger, Monitor, User }); const inviteModule = new InviteModule({ InviteToken, crypto, stringService }); const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService }); const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean, stringService }); @@ -125,6 +123,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService // Repositories const monitorsRepository = new MongoMonitorsRepository(); const checksRepository = new MongoChecksRepository(); + const monitorStatsRepository = new MongoMonitorStatsRepository(); const networkService = new NetworkService({ axios, @@ -228,6 +227,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService games, monitorsRepository, checksRepository, + monitorStatsRepository, }); const services = { diff --git a/server/src/controllers/monitorController.ts b/server/src/controllers/monitorController.ts index 527fd334d..ce0347512 100644 --- a/server/src/controllers/monitorController.ts +++ b/server/src/controllers/monitorController.ts @@ -8,8 +8,6 @@ import { createMonitorBodyValidation, editMonitorBodyValidation, pauseMonitorParamValidation, - getMonitorStatsByIdParamValidation, - getMonitorStatsByIdQueryValidation, getCertificateParamValidation, getHardwareDetailsByIdParamValidation, getHardwareDetailsByIdQueryValidation, @@ -90,39 +88,6 @@ class MonitorController { } }; - getMonitorStatsById = async (req: Request, res: Response, next: NextFunction) => { - try { - await getMonitorStatsByIdParamValidation.validateAsync(req.params); - await getMonitorStatsByIdQueryValidation.validateAsync(req.query); - - let { limit, sortOrder, dateRange, numToDisplay, normalize } = req.query; - const monitorId = req?.params?.monitorId; - - const teamId = req?.user?.teamId; - if (!teamId) { - throw new AppError({ message: "Team ID is required", status: 400 }); - } - - const monitorStats = await this.monitorService.getMonitorStatsById({ - teamId, - monitorId, - limit, - sortOrder, - dateRange, - numToDisplay, - normalize, - }); - - return res.status(200).json({ - success: true, - msg: "Monitor stats retrieved successfully", - data: monitorStats, - }); - } catch (error) { - next(error); - } - }; - getHardwareDetailsById = async (req: Request, res: Response, next: NextFunction) => { try { await getHardwareDetailsByIdParamValidation.validateAsync(req.params); diff --git a/server/src/db/MongoDB.js b/server/src/db/MongoDB.js index 115c7e9ee..3db17c78f 100755 --- a/server/src/db/MongoDB.js +++ b/server/src/db/MongoDB.js @@ -11,12 +11,9 @@ class MongoDB { inviteModule, statusPageModule, userModule, - hardwareCheckModule, maintenanceWindowModule, monitorModule, - networkCheckModule, notificationModule, - pageSpeedCheckModule, recoveryModule, settingsModule, incidentModule, @@ -26,15 +23,12 @@ class MongoDB { this.userModule = userModule; this.inviteModule = inviteModule; this.recoveryModule = recoveryModule; - this.pageSpeedCheckModule = pageSpeedCheckModule; - this.hardwareCheckModule = hardwareCheckModule; this.checkModule = checkModule; this.maintenanceWindowModule = maintenanceWindowModule; this.monitorModule = monitorModule; this.notificationModule = notificationModule; this.settingsModule = settingsModule; this.statusPageModule = statusPageModule; - this.networkCheckModule = networkCheckModule; this.incidentModule = incidentModule; } diff --git a/server/src/db/models/MonitorStats.js b/server/src/db/models/MonitorStats.js deleted file mode 100755 index c081c8a4f..000000000 --- a/server/src/db/models/MonitorStats.js +++ /dev/null @@ -1,49 +0,0 @@ -import mongoose from "mongoose"; - -const MonitorStatsSchema = new mongoose.Schema( - { - monitorId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Monitor", - immutable: true, - index: true, - }, - avgResponseTime: { - type: Number, - default: 0, - }, - totalChecks: { - type: Number, - default: 0, - }, - totalUpChecks: { - type: Number, - default: 0, - }, - totalDownChecks: { - type: Number, - default: 0, - }, - uptimePercentage: { - type: Number, - default: 0, - }, - lastCheckTimestamp: { - type: Number, - default: 0, - }, - lastResponseTime: { - type: Number, - default: 0, - }, - timeOfLastFailure: { - type: Number, - default: 0, - }, - }, - { timestamps: true } -); - -const MonitorStats = mongoose.model("MonitorStats", MonitorStatsSchema); - -export default MonitorStats; diff --git a/server/src/db/models/MonitorStats.ts b/server/src/db/models/MonitorStats.ts new file mode 100755 index 000000000..d1d49ca9f --- /dev/null +++ b/server/src/db/models/MonitorStats.ts @@ -0,0 +1,63 @@ +import { Schema, model, type Types } from "mongoose"; +import type { MonitorStats as MonitorStatsEntity } from "@/types/monitorStats.js"; + +type MonitorStatsDocumentBase = Omit & { + monitorId: Types.ObjectId; +}; + +interface MonitorStatsDocument extends MonitorStatsDocumentBase { + _id: Types.ObjectId; + createdAt: Date; + updatedAt: Date; +} + +const MonitorStatsSchema = new Schema( + { + monitorId: { + type: Schema.Types.ObjectId, + ref: "Monitor", + immutable: true, + index: true, + required: true, + }, + avgResponseTime: { + type: Number, + default: 0, + }, + totalChecks: { + type: Number, + default: 0, + }, + totalUpChecks: { + type: Number, + default: 0, + }, + totalDownChecks: { + type: Number, + default: 0, + }, + uptimePercentage: { + type: Number, + default: 0, + }, + lastCheckTimestamp: { + type: Number, + default: 0, + }, + lastResponseTime: { + type: Number, + default: 0, + }, + timeOfLastFailure: { + type: Number, + default: undefined, + }, + }, + { timestamps: true } +); + +const MonitorStatsModel = model("MonitorStats", MonitorStatsSchema); + +export type { MonitorStatsDocument }; +export { MonitorStatsModel }; +export default MonitorStatsModel; diff --git a/server/src/db/models/index.ts b/server/src/db/models/index.ts index 473940874..29f3539c9 100644 --- a/server/src/db/models/index.ts +++ b/server/src/db/models/index.ts @@ -3,3 +3,6 @@ export { default as MonitorModel } from "@/db/models/Monitor.js"; export * from "@/db/models/Check.js"; export { default as CheckModel } from "@/db/models/Check.js"; + +export * from "@/db/models/MonitorStats.js"; +export { default as MonitorStatsModel } from "@/db/models/MonitorStats.js"; \ No newline at end of file diff --git a/server/src/db/modules/monitorModule.js b/server/src/db/modules/monitorModule.js index 08b8e1371..87237632a 100755 --- a/server/src/db/modules/monitorModule.js +++ b/server/src/db/modules/monitorModule.js @@ -253,48 +253,6 @@ class MonitorModule { } }; - getMonitorStatsById = async ({ monitorId, sortOrder, dateRange, numToDisplay, normalize }) => { - try { - // Get monitor, if we can't find it, abort with error - const monitor = await this.Monitor.findById(monitorId); - if (monitor === null || monitor === undefined) { - throw new Error(this.stringService.getDbFindMonitorById(monitorId)); - } - - // Get query params - const sort = sortOrder === "asc" ? 1 : -1; - - // Get Checks for monitor in date range requested - const dates = this.getDateRange(dateRange); - const { checksAll, checksForDateRange } = await this.getMonitorChecks(monitorId, dates, sort); - - // Build monitor stats - const monitorStats = { - ...monitor.toObject(), - uptimeDuration: this.calculateUptimeDuration(checksAll), - lastChecked: this.getLastChecked(checksAll), - latestResponseTime: this.getLatestResponseTime(checksAll), - periodIncidents: this.getIncidents(checksForDateRange), - periodTotalChecks: checksForDateRange.length, - checks: this.processChecksForDisplay(this.NormalizeData, checksForDateRange, numToDisplay, normalize), - }; - - 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); - const groupedChecks = this.groupChecksByTime(checksForDateRange, dateRange); - monitorStats.aggregateData = Object.values(groupedChecks).map(this.calculateGroupStats); - } - - return monitorStats; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorStatsById"; - throw error; - } - }; - getHardwareDetailsById = async ({ monitorId, dateRange }) => { try { const monitor = await this.Monitor.findById(monitorId); diff --git a/server/src/repositories/checks/IChecksRepository.ts b/server/src/repositories/checks/IChecksRepository.ts index 7741c071b..9451e63af 100644 --- a/server/src/repositories/checks/IChecksRepository.ts +++ b/server/src/repositories/checks/IChecksRepository.ts @@ -1,3 +1,4 @@ +import type { Check, MonitorType } from "@/types/index.js"; import type { LatestChecksMap } from "@/repositories/checks/MongoChecksRepistory.js"; export interface IChecksRepository { @@ -6,12 +7,53 @@ export interface IChecksRepository { monitorId: string, startDate: Date, endDate: Date, - dateString: string - ): Promise<{ - groupedChecks: Array<{ _id: string; avgResponseTime: number; totalChecks: number }>; - groupedUpChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>; - groupedDownChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>; - uptimePercentage: number; - avgResponseTime: number; - }>; + dateString: string, + options?: { type?: MonitorType } + ): Promise< + | { + monitorType: "uptime"; + groupedChecks: Array<{ _id: string; avgResponseTime: number; totalChecks: number }>; + groupedUpChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>; + groupedDownChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>; + uptimePercentage: number; + avgResponseTime: number; + } + | { + monitorType: "hardware"; + aggregateData: { + latestCheck: Check | null; + totalChecks: number; + }; + upChecks: { + totalChecks: number; + }; + checks: Array<{ + _id: string; + avgCpuUsage: number; + avgMemoryUsage: number; + avgTemperature: number[]; + disks: Array<{ + name: string; + readSpeed: number; + writeSpeed: number; + totalBytes: number; + freeBytes: number; + usagePercent: number; + }>; + net: Array<{ + name: string; + bytesSentPerSecond: number; + deltaBytesRecv: number; + deltaPacketsSent: number; + deltaPacketsRecv: number; + deltaErrIn: number; + deltaErrOut: number; + deltaDropIn: number; + deltaDropOut: number; + deltaFifoIn: number; + deltaFifoOut: number; + }>; + }>; + } + >; } diff --git a/server/src/repositories/checks/MongoChecksRepistory.ts b/server/src/repositories/checks/MongoChecksRepistory.ts index 76d3658cd..23c7071be 100644 --- a/server/src/repositories/checks/MongoChecksRepistory.ts +++ b/server/src/repositories/checks/MongoChecksRepistory.ts @@ -14,6 +14,11 @@ import type { } from "@/types/index.js"; import { CheckModel, type CheckDocument } from "@/db/models/index.js"; import mongoose from "mongoose"; +import { + getAggregateData as getHardwareAggregateData, + getHardwareStats, + getUpChecks as getHardwareUpChecks, +} from "@/db/modules/monitorModuleQueries.js"; export type LatestChecksMap = Record; @@ -210,20 +215,17 @@ class MongoChecksRepistory implements IChecksRepository { }, {}); }; - findDateRangeChecksByMonitor = async ( - monitorId: string, - startDate: Date, - endDate: Date, - dateString: string - ): Promise<{ - groupedChecks: Array<{ _id: string; avgResponseTime: number; totalChecks: number }>; - groupedUpChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>; - groupedDownChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>; - uptimePercentage: number; - avgResponseTime: number; - }> => { + findDateRangeChecksByMonitor = async (monitorId: string, startDate: Date, endDate: Date, dateString: string, options?: { type?: string }) => { + const monitorObjectId = new mongoose.Types.ObjectId(monitorId); + if (options?.type === "hardware") { + return this.findHardwareDateRangeChecks(monitorObjectId, startDate, endDate, dateString); + } + return this.findUptimeDateRangeChecks(monitorObjectId, startDate, endDate, dateString); + }; + + private findUptimeDateRangeChecks = async (monitorObjectId: mongoose.Types.ObjectId, startDate: Date, endDate: Date, dateString: string) => { const matchStage = { - "metadata.monitorId": new mongoose.Types.ObjectId(monitorId), + "metadata.monitorId": monitorObjectId, updatedAt: { $gte: startDate, $lte: endDate }, }; const [result] = await CheckModel.aggregate([ @@ -296,12 +298,13 @@ class MongoChecksRepistory implements IChecksRepository { ], }, }, - ]).exec(); + ]); const uptimePercentage = result?.uptimePercentage?.[0]?.percentage ?? 0; const avgResponseTime = result?.groupedAvgResponseTime?.[0]?.avgResponseTime ?? 0; return { + monitorType: "uptime" as const, groupedChecks: result?.groupedChecks ?? [], groupedUpChecks: result?.groupedUpChecks ?? [], groupedDownChecks: result?.groupedDownChecks ?? [], @@ -309,6 +312,60 @@ class MongoChecksRepistory implements IChecksRepository { avgResponseTime, }; }; + + private findHardwareDateRangeChecks = async (monitorObjectId: mongoose.Types.ObjectId, startDate: Date, endDate: Date, dateString: string) => { + const monitorId = monitorObjectId.toHexString(); + const dates = { start: startDate, end: endDate }; + const [aggregateDataDoc, upChecksDoc, hardwareMetrics] = await Promise.all([ + getHardwareAggregateData(monitorId, dates), + getHardwareUpChecks(monitorId, dates), + getHardwareStats(monitorId, dates, dateString), + ]); + + const aggregateData = { + latestCheck: aggregateDataDoc?.latestCheck ? this.toEntity(aggregateDataDoc.latestCheck as CheckDocument) : null, + totalChecks: aggregateDataDoc?.totalChecks ?? 0, + }; + + const upChecks = { + totalChecks: upChecksDoc?.totalChecks ?? 0, + }; + + const checks = (hardwareMetrics ?? []).map((metric) => ({ + _id: metric._id, + avgCpuUsage: metric.avgCpuUsage ?? 0, + avgMemoryUsage: metric.avgMemoryUsage ?? 0, + avgTemperature: metric.avgTemperature ?? [], + disks: (metric.disks ?? []).map((disk: { [key: string]: number | string | undefined }) => ({ + name: disk?.name ?? "", + readSpeed: disk?.readSpeed ?? 0, + writeSpeed: disk?.writeSpeed ?? 0, + totalBytes: disk?.totalBytes ?? 0, + freeBytes: disk?.freeBytes ?? 0, + usagePercent: disk?.usagePercent ?? 0, + })), + net: (metric.net ?? []).map((iface: { [key: string]: number | string | undefined }) => ({ + name: iface?.name ?? "", + bytesSentPerSecond: iface?.bytesSentPerSecond ?? 0, + deltaBytesRecv: iface?.deltaBytesRecv ?? 0, + deltaPacketsSent: iface?.deltaPacketsSent ?? 0, + deltaPacketsRecv: iface?.deltaPacketsRecv ?? 0, + deltaErrIn: iface?.deltaErrIn ?? 0, + deltaErrOut: iface?.deltaErrOut ?? 0, + deltaDropIn: iface?.deltaDropIn ?? 0, + deltaDropOut: iface?.deltaDropOut ?? 0, + deltaFifoIn: iface?.deltaFifoIn ?? 0, + deltaFifoOut: iface?.deltaFifoOut ?? 0, + })), + })); + + return { + monitorType: "hardware" as const, + aggregateData, + upChecks, + checks, + }; + }; } export default MongoChecksRepistory; diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts index c2bae6939..44d64f5bb 100644 --- a/server/src/repositories/index.ts +++ b/server/src/repositories/index.ts @@ -3,3 +3,6 @@ export { default as MongoMonitorsRepository } from "@/repositories/monitors/Mong export * from "@/repositories/checks/IChecksRepository.js"; export { default as MongoChecksRepository } from "@/repositories/checks/MongoChecksRepistory.js"; + +export * from "@/repositories/monitor-stats/IMonitorStatsRepository.js"; +export { default as MongoMonitorStatsRepository } from "@/repositories/monitor-stats/MongoMonitorStatsRepository.js"; diff --git a/server/src/repositories/monitor-stats/IMonitorStatsRepository.ts b/server/src/repositories/monitor-stats/IMonitorStatsRepository.ts new file mode 100644 index 000000000..a0720619f --- /dev/null +++ b/server/src/repositories/monitor-stats/IMonitorStatsRepository.ts @@ -0,0 +1,9 @@ +import type { MonitorStats } from "@/types/index.js"; +export interface IMonitorStatsRepository { + // create + // single fetch + findByMonitorId(monitorId: string): Promise; + // update + // delete + // other +} diff --git a/server/src/repositories/monitor-stats/MongoMonitorStatsRepository.ts b/server/src/repositories/monitor-stats/MongoMonitorStatsRepository.ts new file mode 100644 index 000000000..d1d6a4825 --- /dev/null +++ b/server/src/repositories/monitor-stats/MongoMonitorStatsRepository.ts @@ -0,0 +1,44 @@ +import { type MonitorStatsDocument, MonitorStatsModel } from "@/db/models/index.js"; +import type { MonitorStats } from "@/types/index.js"; +import { IMonitorStatsRepository } from "@/repositories/index.js"; +import mongoose from "mongoose"; +import { AppError } from "@/utils/AppError.js"; +class MongoMonitorStatsRepository implements IMonitorStatsRepository { + private toEntity = (doc: MonitorStatsDocument): MonitorStats => { + const toStringId = (value: unknown): string => { + if (value instanceof mongoose.Types.ObjectId) { + return value.toString(); + } + return value?.toString() ?? ""; + }; + + const toDateString = (value: Date | string): string => { + return value instanceof Date ? value.toISOString() : value; + }; + + return { + id: toStringId(doc._id), + monitorId: toStringId(doc.monitorId), + avgResponseTime: doc.avgResponseTime, + totalChecks: doc.totalChecks, + totalUpChecks: doc.totalUpChecks, + totalDownChecks: doc.totalDownChecks, + uptimePercentage: doc.uptimePercentage, + lastCheckTimestamp: doc.lastCheckTimestamp, + lastResponseTime: doc.lastResponseTime, + timeOfLastFailure: doc.timeOfLastFailure, + createdAt: toDateString(doc.createdAt), + updatedAt: toDateString(doc.updatedAt), + }; + }; + + findByMonitorId = async (monitorId: string): Promise => { + const monitorStats = await MonitorStatsModel.findOne({ monitorId: new mongoose.Types.ObjectId(monitorId) }); + if (!monitorStats) { + throw new AppError({ message: "Monitor stats not found", status: 404 }); + } + return this.toEntity(monitorStats); + }; +} + +export default MongoMonitorStatsRepository; diff --git a/server/src/routes/v1/monitorRoute.ts b/server/src/routes/v1/monitorRoute.ts index a355efbb9..bcbf5a1c6 100755 --- a/server/src/routes/v1/monitorRoute.ts +++ b/server/src/routes/v1/monitorRoute.ts @@ -30,7 +30,6 @@ class MonitorRoutes { // General monitor routes this.router.post("/pause/:monitorId", isAllowed(["admin", "superadmin"]), this.monitorController.pauseMonitor); - this.router.get("/stats/:monitorId", this.monitorController.getMonitorStatsById); // Util routes this.router.get("/certificate/:monitorId", (req, res, next) => { diff --git a/server/src/service/business/monitorService.ts b/server/src/service/business/monitorService.ts index 3e4d39ade..4a2a2421c 100644 --- a/server/src/service/business/monitorService.ts +++ b/server/src/service/business/monitorService.ts @@ -2,7 +2,7 @@ import { createMonitorsBodyValidation } from "@/validation/joi.js"; import { NormalizeData, NormalizeDataUptimeDetails } from "@/utils/dataUtils.js"; import { type Monitor } from "@/types/index.js"; import type { MonitorType } from "@/types/monitor.js"; -import type { IChecksRepository, IMonitorsRepository } from "@/repositories/index.js"; +import type { IChecksRepository, IMonitorsRepository, IMonitorStatsRepository } from "@/repositories/index.js"; import fs from "fs"; import { fileURLToPath } from "url"; import path from "path"; @@ -23,17 +23,8 @@ export interface IMonitorService { // read getUptimeDetailsById(args: { teamId: string; monitorId: string; dateRange: string; normalize?: boolean }): Promise; - getMonitorStatsById(args: { - teamId: string; - monitorId: string; - limit?: number; - sortOrder?: 1 | -1; - dateRange?: string; - numToDisplay?: number; - normalize?: boolean; - }): Promise; getHardwareDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise; - getMonitorById(args: { teamId: string; monitorId: string }): Promise; + getMonitorById(args: { teamId: string; monitorId: string }): Promise; getMonitorsByTeamId(args: { teamId: string; limit?: number; @@ -86,7 +77,7 @@ export class MonitorService implements IMonitorService { private games: any; private monitorsRepository: IMonitorsRepository; private checksRepository: IChecksRepository; - private fs: any; + private monitorStatsRepository: IMonitorStatsRepository; constructor({ db, @@ -99,6 +90,7 @@ export class MonitorService implements IMonitorService { games, monitorsRepository, checksRepository, + monitorStatsRepository, }: { db: any; jobQueue: any; @@ -110,6 +102,7 @@ export class MonitorService implements IMonitorService { games: any; monitorsRepository: IMonitorsRepository; checksRepository: IChecksRepository; + monitorStatsRepository: IMonitorStatsRepository; }) { this.db = db; this.jobQueue = jobQueue; @@ -121,6 +114,7 @@ export class MonitorService implements IMonitorService { this.games = games; this.monitorsRepository = monitorsRepository; this.checksRepository = checksRepository; + this.monitorStatsRepository = monitorStatsRepository; } get serviceName(): string { @@ -269,10 +263,14 @@ export class MonitorService implements IMonitorService { } const rangeKey = (dateRange as DateRangeKey) ?? "recent"; const { start, end } = this.getDateRange(rangeKey); - const checksData = await this.checksRepository.findDateRangeChecksByMonitor(monitor.id, start, end, this.getDateFormat(rangeKey)); - const monitorStats = await this.db.monitorModule.getMonitorStatsById({ - monitorId, + const checksData = await this.checksRepository.findDateRangeChecksByMonitor(monitor.id, start, end, this.getDateFormat(rangeKey), { + type: monitor.type, }); + const monitorStats = await this.monitorStatsRepository.findByMonitorId(monitor.id); + + if (checksData.monitorType !== "uptime") { + throw new AppError({ message: `${monitor.type} monitors are not supported for uptime details`, status: 400 }); + } return { monitorData: { @@ -287,47 +285,41 @@ export class MonitorService implements IMonitorService { }; }; - getMonitorStatsById = async ({ - teamId, - monitorId, - limit, - sortOrder, - dateRange, - numToDisplay, - normalize, - }: { - teamId: string; - monitorId: string; - limit?: number; - sortOrder?: 1 | -1; - dateRange?: string; - numToDisplay?: number; - normalize?: boolean; - }): Promise => { - await this.verifyTeamAccess({ teamId, monitorId }); - const monitorStats = await this.db.monitorModule.getMonitorStatsById({ - monitorId, - limit, - sortOrder, - dateRange, - numToDisplay, - normalize, - }); - - return monitorStats; - }; - getHardwareDetailsById = async ({ teamId, monitorId, dateRange }: { teamId: string; monitorId: string; dateRange: string }): Promise => { await this.verifyTeamAccess({ teamId, monitorId }); - const monitor = await this.db.monitorModule.getHardwareDetailsById({ monitorId, dateRange }); + const monitor = await this.monitorsRepository.findById(monitorId, teamId); + if (!monitor) { + throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 }); + } + if (monitor.type !== "hardware") { + throw new AppError({ message: `${monitor.type} monitors are not supported for hardware details`, status: 400 }); + } - return monitor; + const rangeKey = (dateRange as DateRangeKey) ?? "recent"; + const { start, end } = this.getDateRange(rangeKey); + const checksData = await this.checksRepository.findDateRangeChecksByMonitor(monitor.id, start, end, this.getDateFormat(rangeKey), { + type: monitor.type, + }); + + if (checksData.monitorType !== "hardware") { + throw new AppError({ message: "Unable to load hardware stats for this monitor", status: 500 }); + } + + const stats = { + aggregateData: checksData.aggregateData, + upChecks: checksData.upChecks, + checks: checksData.checks, + }; + + return { + ...monitor, + stats, + }; }; getMonitorById = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise => { await this.verifyTeamAccess({ teamId, monitorId }); - const monitor = await this.db.monitorModule.getMonitorById(monitorId); - + const monitor = await this.monitorsRepository.findById(monitorId, teamId); return monitor; }; diff --git a/server/src/service/infrastructure/statusService.js b/server/src/service/infrastructure/statusService.js index 30a9fc12c..e849bbb83 100755 --- a/server/src/service/infrastructure/statusService.js +++ b/server/src/service/infrastructure/statusService.js @@ -7,10 +7,14 @@ class StatusService { /** * @param {{ + * db: any + * logger: any * buffer: import("./bufferService.js").BufferService * incidentService: import("../business/incidentService.js").IncidentService + * monitorsRepository: any * }} - */ constructor({ db, logger, buffer, incidentService, monitorsRepository }) { + */ + constructor({ db, logger, buffer, incidentService, monitorsRepository }) { this.db = db; this.logger = logger; this.buffer = buffer; diff --git a/server/src/types/index.ts b/server/src/types/index.ts index 92bcb69b0..2367f1c1c 100644 --- a/server/src/types/index.ts +++ b/server/src/types/index.ts @@ -1,2 +1,3 @@ export * from "@/types/check.js"; export * from "@/types/monitor.js"; +export * from "@/types/monitorStats.js"; diff --git a/server/src/types/monitorStats.ts b/server/src/types/monitorStats.ts new file mode 100644 index 000000000..8f9603529 --- /dev/null +++ b/server/src/types/monitorStats.ts @@ -0,0 +1,14 @@ +export interface MonitorStats { + id: string; + monitorId: string; + avgResponseTime: number; + totalChecks: number; + totalUpChecks: number; + totalDownChecks: number; + uptimePercentage: number; + lastCheckTimestamp: number; + lastResponseTime: number; + timeOfLastFailure?: number; + createdAt: string; + updatedAt: string; +} diff --git a/server/src/validation/joi.js b/server/src/validation/joi.js index b3342de83..e1d04047d 100755 --- a/server/src/validation/joi.js +++ b/server/src/validation/joi.js @@ -134,18 +134,6 @@ const getMonitorsByTeamIdQueryValidation = joi.object({ order: joi.string().valid("asc", "desc"), }); -const getMonitorStatsByIdParamValidation = joi.object({ - monitorId: joi.string().required(), -}); -const getMonitorStatsByIdQueryValidation = joi.object({ - status: joi.string(), - limit: joi.number(), - sortOrder: joi.string().valid("asc", "desc"), - dateRange: joi.string().valid("hour", "day", "week", "month", "all"), - numToDisplay: joi.number(), - normalize: joi.boolean(), -}); - const getCertificateParamValidation = joi.object({ monitorId: joi.string().required(), }); @@ -725,8 +713,6 @@ export { getMonitorByIdQueryValidation, getMonitorsByTeamIdParamValidation, getMonitorsByTeamIdQueryValidation, - getMonitorStatsByIdParamValidation, - getMonitorStatsByIdQueryValidation, getHardwareDetailsByIdParamValidation, getHardwareDetailsByIdQueryValidation, getCertificateParamValidation, diff --git a/server/test/monitorService.test.ts b/server/test/monitorService.test.ts new file mode 100644 index 000000000..4fdcbeb49 --- /dev/null +++ b/server/test/monitorService.test.ts @@ -0,0 +1,218 @@ +import { jest } from "@jest/globals"; +import { MonitorService } from "../src/service/business/monitorService.ts"; +import type { IMonitorsRepository, IChecksRepository } from "../src/repositories/index.ts"; + +const createMonitorsRepositoryMock = () => + ({ + findMonitorCountByTeamIdAndType: jest.fn(), + findByTeamId: jest.fn(), + findById: jest.fn(), + create: jest.fn(), + createBulkMonitors: jest.fn(), + deleteByTeamId: jest.fn(), + }) as unknown as IMonitorsRepository; + +const createChecksRepositoryMock = () => + ({ + findLatestChecksByMonitorIds: jest.fn(), + findDateRangeChecksByMonitor: jest.fn(), + }) as unknown as IChecksRepository; + +const createService = ({ + monitorsRepository = createMonitorsRepositoryMock(), + checksRepository = createChecksRepositoryMock(), + monitorStatsRepository = { findByMonitorId: jest.fn() }, + monitorModuleOverrides = {}, +}: { + monitorsRepository?: IMonitorsRepository; + checksRepository?: IChecksRepository; + monitorStatsRepository?: { findByMonitorId: jest.Mock }; + monitorModuleOverrides?: Record; +} = {}) => { + const monitorModule = { + getMonitorById: jest.fn().mockResolvedValue({ teamId: { equals: () => true } }), + getMonitorStatsById: jest.fn().mockResolvedValue({ latest: {} }), + getMonitorsByTeamId: jest.fn().mockResolvedValue([]), + getMonitorsAndSummaryByTeamId: jest.fn().mockResolvedValue({ monitors: [], summary: {} }), + ...monitorModuleOverrides, + }; + + return new MonitorService({ + db: { + monitorModule, + statusPageModule: { deleteStatusPagesByMonitorId: jest.fn() }, + checkModule: { deleteChecks: jest.fn() }, + pageSpeedCheckModule: { deletePageSpeedChecksByMonitorId: jest.fn() }, + notificationsModule: { deleteNotificationsByMonitorId: jest.fn() }, + }, + jobQueue: { + addJob: jest.fn(), + updateJob: jest.fn(), + resumeJob: jest.fn(), + pauseJob: jest.fn(), + deleteJob: jest.fn(), + }, + stringService: {}, + emailService: { buildEmail: jest.fn(), sendEmail: jest.fn() }, + papaparse: { parse: jest.fn(), unparse: jest.fn() }, + logger: { info: jest.fn(), error: jest.fn(), warn: jest.fn() }, + errorService: { + createAuthorizationError: jest.fn(() => new Error("unauthorized")), + createServerError: jest.fn(() => new Error("server")), + createBadRequestError: jest.fn(() => new Error("bad request")), + createNotFoundError: jest.fn(() => new Error("not found")), + }, + games: [], + monitorsRepository, + checksRepository, + monitorStatsRepository, + }); +}; + +describe("MonitorService", () => { + describe("getMonitorsWithChecksByTeamId", () => { + it("returns monitors enriched with normalized checks", async () => { + const monitorsRepository = createMonitorsRepositoryMock(); + (monitorsRepository.findMonitorCountByTeamIdAndType as jest.Mock).mockResolvedValue(2); + (monitorsRepository.findByTeamId as jest.Mock).mockResolvedValue([ + { id: "m1", name: "Monitor 1", interval: 60000 }, + { id: "m2", name: "Monitor 2", interval: 60000 }, + ]); + + const checksRepository = createChecksRepositoryMock(); + (checksRepository.findLatestChecksByMonitorIds as jest.Mock).mockResolvedValue({ + m1: [ + { responseTime: 10, status: true, message: "OK" }, + { responseTime: 20, status: true, message: "OK" }, + ], + m2: [{ responseTime: 50, status: true, message: "OK" }], + }); + + const service = createService({ monitorsRepository, checksRepository }); + const result = await service.getMonitorsWithChecksByTeamId({ teamId: "team" }); + + expect(result).toMatchObject({ count: 2 }); + expect(result.monitors).toHaveLength(2); + expect(result.monitors[0]).toHaveProperty("checks"); + expect(result.monitors[0].checks.length).toBeGreaterThan(0); + expect(result.monitors[0].checks[0]).toEqual( + expect.objectContaining({ + responseTime: expect.any(Number), + status: expect.any(Boolean), + message: expect.any(String), + }) + ); + }); + }); + + describe("getMonitorsByTeamId", () => { + it("returns monitors array from db module", async () => { + const monitorsPayload = [ + { id: "m1", name: "Monitor 1" }, + { id: "m2", name: "Monitor 2" }, + ]; + const monitorModuleOverrides = { + getMonitorsByTeamId: jest.fn().mockResolvedValue(monitorsPayload), + }; + const service = createService({ monitorModuleOverrides }); + const result = await service.getMonitorsByTeamId({ teamId: "team" } as any); + expect(result).toHaveLength(2); + expect(result[0]).toHaveProperty("id", "m1"); + }); + }); + + describe("getMonitorsAndSummaryByTeamId", () => { + it("returns monitors with summary block", async () => { + const monitorModuleOverrides = { + getMonitorsAndSummaryByTeamId: jest.fn().mockResolvedValue({ monitors: [{ id: "m1" }], summary: { total: 1, uptime: 0.99 } }), + }; + const service = createService({ monitorModuleOverrides }); + const result = await service.getMonitorsAndSummaryByTeamId({ teamId: "team" }); + expect(result).toEqual({ monitors: [{ id: "m1" }], summary: { total: 1, uptime: 0.99 } }); + }); + }); + + describe("getUptimeDetailsById", () => { + it("returns monitorData and monitorStats with expected shape", async () => { + const TEAM_ID = "team"; + const monitor = { + id: "monitor-1", + teamId: TEAM_ID, + name: "Hardware monitor", + interval: 60000, + statusWindow: [], + statusWindowSize: 5, + statusWindowThreshold: 60, + type: "http", + ignoreTlsErrors: false, + url: "https://example.com", + isActive: true, + alertThreshold: 5, + cpuAlertThreshold: 5, + memoryAlertThreshold: 5, + diskAlertThreshold: 5, + tempAlertThreshold: 5, + selectedDisks: [], + notifications: [], + group: null, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + + const monitorsRepository = createMonitorsRepositoryMock(); + (monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor); + const checksRepository = createChecksRepositoryMock(); + (checksRepository.findDateRangeChecksByMonitor as jest.Mock).mockResolvedValue({ + monitorType: "uptime", + groupedChecks: [{ _id: "2024-01-01", avgResponseTime: 100, totalChecks: 2 }], + groupedUpChecks: [{ _id: "2024-01-01", totalChecks: 2, avgResponseTime: 90 }], + groupedDownChecks: [{ _id: "2024-01-01", totalChecks: 0, avgResponseTime: 0 }], + uptimePercentage: 0.99, + avgResponseTime: 95, + }); + + const monitorStatsRepository = { + findByMonitorId: jest.fn().mockResolvedValue({ + id: "stats-1", + monitorId: monitor.id, + avgResponseTime: 90, + totalChecks: 10, + totalUpChecks: 9, + totalDownChecks: 1, + uptimePercentage: 0.9, + lastCheckTimestamp: 123456789, + lastResponseTime: 80, + timeOfLastFailure: 123456700, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }), + } as any; + const monitorModuleOverrides = { + getMonitorById: jest.fn().mockResolvedValue({ teamId: { equals: (value: string) => value === TEAM_ID } }), + }; + + const service = createService({ monitorsRepository, checksRepository, monitorModuleOverrides, monitorStatsRepository }); + const result = await service.getUptimeDetailsById({ teamId: TEAM_ID, monitorId: "monitor-1", dateRange: "recent" }); + + expect(result).toHaveProperty("monitorData"); + expect(result.monitorData.monitor).toMatchObject({ id: monitor.id, name: monitor.name }); + expect(result.monitorData.groupedChecks[0]).toEqual( + expect.objectContaining({ + _id: expect.any(String), + avgResponseTime: expect.any(Number), + totalChecks: expect.any(Number), + }) + ); + expect(result.monitorStats).toEqual( + expect.objectContaining({ + monitorId: monitor.id, + avgResponseTime: 90, + totalChecks: 10, + totalUpChecks: 9, + totalDownChecks: 1, + }) + ); + }); + + }); +}); diff --git a/server/tsconfig.jest.json b/server/tsconfig.jest.json new file mode 100644 index 000000000..928b0977c --- /dev/null +++ b/server/tsconfig.jest.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "types": ["jest"], + "noEmit": true + }, + "include": ["src", "test"] +} diff --git a/server/tsconfig.json b/server/tsconfig.json index f0b8e7223..cbb874656 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -12,7 +12,8 @@ "module": "nodenext", "moduleResolution": "nodenext", "skipLibCheck": true, - "noUncheckedIndexedAccess": true + "noUncheckedIndexedAccess": true, + "isolatedModules": true }, "include": ["src"], "exclude": ["tests", "dist", "node_modules"] From 4ad09fad4f73c00151084427c2e31f878036b12c Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 14 Jan 2026 20:06:37 +0000 Subject: [PATCH 19/24] snapshots --- .../repositories/checks/IChecksRepository.ts | 2 +- .../checks/MongoChecksRepistory.ts | 9 ++++++--- server/src/service/business/monitorService.ts | 20 +++++++++++++++---- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/server/src/repositories/checks/IChecksRepository.ts b/server/src/repositories/checks/IChecksRepository.ts index 9451e63af..507389e6a 100644 --- a/server/src/repositories/checks/IChecksRepository.ts +++ b/server/src/repositories/checks/IChecksRepository.ts @@ -2,7 +2,7 @@ import type { Check, MonitorType } from "@/types/index.js"; import type { LatestChecksMap } from "@/repositories/checks/MongoChecksRepistory.js"; export interface IChecksRepository { - findLatestChecksByMonitorIds(monitorIds: string[]): Promise; + findLatestChecksByMonitorIds(monitorIds: string[], options?: { limitPerMonitor?: number }): Promise; findDateRangeChecksByMonitor( monitorId: string, startDate: Date, diff --git a/server/src/repositories/checks/MongoChecksRepistory.ts b/server/src/repositories/checks/MongoChecksRepistory.ts index 23c7071be..2aeb32b2d 100644 --- a/server/src/repositories/checks/MongoChecksRepistory.ts +++ b/server/src/repositories/checks/MongoChecksRepistory.ts @@ -177,15 +177,18 @@ class MongoChecksRepistory implements IChecksRepository { }; }; - findLatestChecksByMonitorIds = async (monitorIds: string[]): Promise => { + findLatestChecksByMonitorIds = async ( + monitorIds: string[], + options?: { limitPerMonitor?: number } + ): Promise => { if (monitorIds.length === 0) { return {}; } const mongoIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id)); - const limitPerMonitor = 25; + const limitPerMonitor = options?.limitPerMonitor ?? 25; const maxIntervalMs = Number(10 * 60 * 1000); const bufferMs = Number(maxIntervalMs); - const lookbackMs = 25 * maxIntervalMs + bufferMs; + const lookbackMs = limitPerMonitor * maxIntervalMs + bufferMs; const cutoffDate = new Date(Date.now() - lookbackMs); const checkGroups = await CheckModel.aggregate([ { diff --git a/server/src/service/business/monitorService.ts b/server/src/service/business/monitorService.ts index 4a2a2421c..0296ce9ca 100644 --- a/server/src/service/business/monitorService.ts +++ b/server/src/service/business/monitorService.ts @@ -404,10 +404,22 @@ export class MonitorService implements IMonitorService { order, }); - const monitorIds = monitors?.map((m: any) => m.id) ?? []; - const checksMap = await this.checksRepository.findLatestChecksByMonitorIds(monitorIds); - const monitorsWithChecks = (monitors ?? []).map((monitor: any) => { - const checks = NormalizeData(checksMap[monitor.id] ?? [], 10, 100); + const monitorsList = (monitors ?? []) as Monitor[]; + const snapshotTypes: MonitorType[] = ["hardware", "pagespeed"]; + const requestedTypes = Array.isArray(type) ? type : type ? [type] : []; + const snapshotOnlyRequest = + requestedTypes.length > 0 && requestedTypes.every((requestedType) => snapshotTypes.includes(requestedType as MonitorType)); + + const limitPerMonitor = snapshotOnlyRequest ? 1 : 25; + const checksMap = await this.checksRepository.findLatestChecksByMonitorIds( + monitorsList.map((monitor) => monitor.id), + { limitPerMonitor } + ); + + const monitorsWithChecks = monitorsList.map((monitor: Monitor) => { + const rawChecks = checksMap[monitor.id] ?? []; + const isSnapshotType = snapshotOnlyRequest || snapshotTypes.includes(monitor.type); + const checks = isSnapshotType ? rawChecks.slice(0, 1) : NormalizeData(rawChecks, 10, 100); return { ...monitor, checks, From 2b0c52304797018eb7e471115b92faeafe2e81c8 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 14 Jan 2026 20:11:05 +0000 Subject: [PATCH 20/24] unify requests --- .../src/Pages/Infrastructure/Monitors/index.jsx | 5 +---- client/src/Pages/PageSpeed/Monitors/index.jsx | 15 +-------------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/client/src/Pages/Infrastructure/Monitors/index.jsx b/client/src/Pages/Infrastructure/Monitors/index.jsx index 4d739417c..b25636fa9 100644 --- a/client/src/Pages/Infrastructure/Monitors/index.jsx +++ b/client/src/Pages/Infrastructure/Monitors/index.jsx @@ -13,10 +13,7 @@ import { useTheme } from "@emotion/react"; import { useEffect, useState } from "react"; import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; import { useTranslation } from "react-i18next"; -import { - useFetchMonitorsByTeamId, - useFetchMonitorsWithChecks, -} from "@/Hooks/monitorHooks.js"; +import { useFetchMonitorsWithChecks } from "@/Hooks/monitorHooks.js"; import { useDispatch, useSelector } from "react-redux"; import { setRowsPerPage } from "../../../Features/UI/uiSlice.js"; diff --git a/client/src/Pages/PageSpeed/Monitors/index.jsx b/client/src/Pages/PageSpeed/Monitors/index.jsx index 548cd3491..4530a41e9 100644 --- a/client/src/Pages/PageSpeed/Monitors/index.jsx +++ b/client/src/Pages/PageSpeed/Monitors/index.jsx @@ -11,10 +11,7 @@ import FallbackPageSpeedWarning from "@/Components/v1/Fallback/FallbackPageSpeed // Utils import { useTheme } from "@emotion/react"; import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; -import { - useFetchMonitorsByTeamId, - useFetchMonitorsWithChecks, -} from "@/Hooks/monitorHooks.js"; +import { useFetchMonitorsWithChecks } from "@/Hooks/monitorHooks.js"; import { useFetchSettings } from "@/Hooks/settingsHooks.js"; // Constants const BREADCRUMBS = [{ name: `pagespeed`, path: "/pagespeed" }]; @@ -23,16 +20,6 @@ const PageSpeed = () => { const theme = useTheme(); const isAdmin = useIsAdmin(); - // const [monitors, monitorsSummary, isLoading, networkError] = useFetchMonitorsByTeamId({ - // limit: 10, - // types: TYPES, - // page: null, - // rowsPerPage: null, - // filter: null, - // field: null, - // order: null, - // }); - const [ monitorsWithChecks, monitorsWithChecksCount, From 940247b7bf86d12a7776bcac0b98f3f989a4eb38 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 14 Jan 2026 21:47:37 +0000 Subject: [PATCH 21/24] teamid --- .../src/Components/v1/Inputs/Search/index.jsx | 8 +-- .../src/Components/v1/Inputs/Select/index.jsx | 37 +++++++---- client/src/Hooks/monitorHooks.js | 38 ++--------- client/src/Pages/Incidents2/index.jsx | 2 +- .../Components/MonitorList/index.jsx | 8 +-- .../hooks/useMaintenanceActions.jsx | 8 ++- .../hooks/useMaintenanceData.jsx | 4 +- .../Maintenance/CreateMaintenance/index.jsx | 1 + .../Create/Components/MonitorList/index.jsx | 6 +- .../Create/Components/Tabs/Content.jsx | 2 +- .../Create/Hooks/useMonitorsFetch.jsx | 2 +- client/src/Pages/StatusPage/Create/index.jsx | 5 +- .../Status/Components/MonitorsList/index.jsx | 4 +- client/src/Utils/NetworkService.js | 9 +-- server/src/controllers/monitorController.ts | 7 +- server/src/db/modules/monitorModule.js | 66 ++++++++----------- server/src/service/business/monitorService.ts | 28 +------- server/src/validation/joi.js | 25 +++++-- 18 files changed, 109 insertions(+), 151 deletions(-) diff --git a/client/src/Components/v1/Inputs/Search/index.jsx b/client/src/Components/v1/Inputs/Search/index.jsx index 7a3771d47..347223bf3 100644 --- a/client/src/Components/v1/Inputs/Search/index.jsx +++ b/client/src/Components/v1/Inputs/Search/index.jsx @@ -85,7 +85,7 @@ const Search = ({ const enhancedOptions = React.useMemo(() => { return multiple && isAdorned ? [ - { [filteredBy]: t("selectAll"), isSelectAll: true, _id: "select_all" }, + { [filteredBy]: t("selectAll"), isSelectAll: true, _id: "select_all", id: "select_all" }, ...options, ] : options; @@ -93,7 +93,7 @@ const Search = ({ const isOptionSelected = (option) => { if (!multiple && !isAdorned) return false; if (Array.isArray(value)) { - return value.some((item) => item._id === option._id); + return value.some((item) => (item._id ?? item.id) === (option._id ?? option.id)); } return false; }; @@ -145,7 +145,7 @@ const Search = ({ disableClearable options={enhancedOptions} getOptionLabel={(option) => option[filteredBy]} - isOptionEqualToValue={(option, value) => option._id === value._id} // Compare by unique identifier + isOptionEqualToValue={(option, value) => (option._id ?? option.id) === (value?._id ?? value?.id)} // Compare by unique identifier renderInput={(params) => ( { - return option._id; + return option._id ?? option.id; }} renderOption={(props, option) => { const { key, ...optionProps } = props; diff --git a/client/src/Components/v1/Inputs/Select/index.jsx b/client/src/Components/v1/Inputs/Select/index.jsx index 37ce30dc1..f952b6818 100644 --- a/client/src/Components/v1/Inputs/Select/index.jsx +++ b/client/src/Components/v1/Inputs/Select/index.jsx @@ -62,6 +62,7 @@ const Select = ({ fieldWrapperSx = {}, }) => { const theme = useTheme(); + const getItemValue = (item) => item?._id ?? item?.id; const itemStyles = { fontSize: "var(--env-var-font-size-medium)", color: theme.palette.primary.contrastTextTertiary, @@ -123,7 +124,7 @@ const Select = ({ ...sx, }} renderValue={(selected) => { - const selectedItem = items.find((item) => item._id === selected); + const selectedItem = items.find((item) => getItemValue(item) === selected); const displayName = selectedItem ? selectedItem.name : placeholder; return ( )} - {items.map((item) => ( - - {item.name} - - ))} + {items + .map((item) => { + const itemValue = getItemValue(item); + if (itemValue === undefined || itemValue === null) { + return null; + } + return ( + + {item.name} + + ); + }) + .filter(Boolean)} ); @@ -179,8 +188,8 @@ Select.propTypes = { .isRequired, items: PropTypes.arrayOf( PropTypes.shape({ - _id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]) - .isRequired, + _id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]), + id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]), name: PropTypes.string.isRequired, }) diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index ffc356320..197779b53 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -100,22 +100,9 @@ const useFetchMonitorsWithChecks = ({ return [monitors, count, isLoading, networkError]; }; -const useFetchMonitorsByTeamId = ({ - types, - limit, - page, - rowsPerPage, - filter, - field, - order, - checkOrder, - normalize, - status, - updateTrigger, -}) => { +const useFetchMonitorsByTeamId = ({ types, filter, updateTrigger }) => { const [isLoading, setIsLoading] = useState(false); const [monitors, setMonitors] = useState(undefined); - const [summary, setSummary] = useState(undefined); const [networkError, setNetworkError] = useState(false); useEffect(() => { @@ -123,20 +110,11 @@ const useFetchMonitorsByTeamId = ({ try { setIsLoading(true); const res = await networkService.getMonitorsByTeamId({ - limit, types, - page, - rowsPerPage, filter, - field, - order, - checkOrder, - status, - normalize, }); - if (res?.data?.data?.filteredMonitors) { - setMonitors(res.data.data.filteredMonitors); - setSummary(res.data.data.summary); + if (res?.data?.data) { + setMonitors(res.data.data); } } catch (error) { setNetworkError(true); @@ -150,18 +128,10 @@ const useFetchMonitorsByTeamId = ({ fetchMonitors(); }, [ types, - limit, - page, - rowsPerPage, filter, - field, - order, updateTrigger, - checkOrder, - normalize, - status, ]); - return [monitors, summary, isLoading, networkError]; + return [monitors, isLoading, networkError]; }; const useFetchStatsByMonitorId = ({ diff --git a/client/src/Pages/Incidents2/index.jsx b/client/src/Pages/Incidents2/index.jsx index eda68d976..966388b51 100644 --- a/client/src/Pages/Incidents2/index.jsx +++ b/client/src/Pages/Incidents2/index.jsx @@ -34,7 +34,7 @@ const Incidents2 = () => { setUpdateTrigger((prev) => !prev); }; - const [monitors, , isLoadingMonitors, monitorsNetworkError] = useFetchMonitorsByTeamId( + const [monitors, isLoadingMonitors, monitorsNetworkError] = useFetchMonitorsByTeamId( {} ); diff --git a/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx index c3a4681a1..c70912582 100644 --- a/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx +++ b/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx @@ -24,7 +24,7 @@ const MonitorListItem = ({ monitor, onDelete }) => { MonitorListItem.propTypes = { monitor: PropTypes.shape({ - _id: PropTypes.string.isRequired, + id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, }).isRequired, onDelete: PropTypes.func.isRequired, @@ -33,7 +33,7 @@ MonitorListItem.propTypes = { const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => { const onDelete = (monitorToDelete) => { const newMonitors = selectedMonitors.filter( - (monitor) => monitor._id !== monitorToDelete._id + (monitor) => monitor.id !== monitorToDelete.id ); setSelectedMonitors(newMonitors); }; @@ -47,7 +47,7 @@ const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => { > {selectedMonitors?.map((monitor) => ( @@ -59,7 +59,7 @@ const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => { MonitorList.propTypes = { selectedMonitors: PropTypes.arrayOf( PropTypes.shape({ - _id: PropTypes.string.isRequired, + id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, }) ).isRequired, diff --git a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx b/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx index 6f180c880..22a61084d 100644 --- a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx +++ b/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx @@ -22,6 +22,12 @@ const useMaintenanceActions = () => { weekly: MS_PER_DAY * 7, }; const handleSubmitForm = async (maintenanceWindowId, form) => { + const toStringId = (monitor) => { + if (!monitor) { + return ""; + } + return monitor._id ?? monitor.id ?? ""; + }; const start = dayjs(form.startDate) .set("hour", form.startTime.hour()) .set("minute", form.startTime.minute()); @@ -33,7 +39,7 @@ const useMaintenanceActions = () => { const repeat = REPEAT_LOOKUP[form.repeat]; const submit = { - monitors: form.monitors.map((monitor) => monitor._id), + monitors: form.monitors.map((monitor) => toStringId(monitor)).filter(Boolean), name: form.name, start: start.toISOString(), end: end.toISOString(), diff --git a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx b/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx index c3be0091a..96f37fd61 100644 --- a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx +++ b/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx @@ -41,7 +41,7 @@ const useMaintenanceData = () => { limit: null, types: ["http", "ping", "pagespeed", "port"], }); - const fetchedMonitors = response.data.data.monitors; + const fetchedMonitors = response.data.data; return fetchedMonitors; }; @@ -55,7 +55,7 @@ const useMaintenanceData = () => { const endTime = dayjs(end); const durationInMs = endTime.diff(startTime, "milliseconds").toString(); const { duration, durationUnit } = getDurationAndUnit(durationInMs); - const monitor = monitorList.find((monitor) => monitor._id === monitorId); + const monitor = monitorList.find((monitor) => (monitor._id ?? monitor.id) === monitorId); const maintenanceWindowInformation = { name, repeat: REVERSE_REPEAT_LOOKUP[repeat], diff --git a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx index 384805f3a..759679a2d 100644 --- a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx +++ b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx @@ -108,6 +108,7 @@ const CreateMaintenance = () => { const handleSubmit = async () => { if (hasValidationErrors(form, maintenanceWindowValidation, setErrors)) return; + const request = handleSubmitForm(maintenanceWindowId, form); try { setIsLoading(true); diff --git a/client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx b/client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx index 5484202d5..d2a13b2eb 100644 --- a/client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx +++ b/client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx @@ -43,7 +43,7 @@ const MonitorListItem = ({ const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => { const onDelete = (monitorToDelete) => { const newMonitors = selectedMonitors.filter( - (monitor) => monitor._id !== monitorToDelete._id + (monitor) => monitor.id !== monitorToDelete.id ); setSelectedMonitors(newMonitors); }; @@ -80,8 +80,8 @@ const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => { > {selectedMonitors?.map((monitor, index) => ( {(provided, snapshot) => ( diff --git a/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx index d11825e69..e84de16dc 100644 --- a/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx +++ b/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx @@ -24,7 +24,7 @@ const Content = ({ // Handlers const handleMonitorsChange = (selectedMonitors) => { handleFormChange({ - target: { name: "monitors", value: selectedMonitors.map((monitor) => monitor._id) }, + target: { name: "monitors", value: selectedMonitors.map((monitor) => monitor.id) }, }); setSelectedMonitors(selectedMonitors); }; diff --git a/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx b/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx index 95ce2b8c0..03ebb07bf 100644 --- a/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx +++ b/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx @@ -16,7 +16,7 @@ const useMonitorsFetch = () => { limit: null, // donot return any checks for the monitors types: ["http", "ping", "port", "game"], // include game servers in status page monitor selection }); - setMonitors(response.data.data.monitors); + setMonitors(response.data.data); } catch (error) { setNetworkError(true); createToast({ body: error.message }); diff --git a/client/src/Pages/StatusPage/Create/index.jsx b/client/src/Pages/StatusPage/Create/index.jsx index 7fbe6a8dc..f8fcf0e91 100644 --- a/client/src/Pages/StatusPage/Create/index.jsx +++ b/client/src/Pages/StatusPage/Create/index.jsx @@ -62,7 +62,6 @@ const CreateStatusPage = () => { useStatusPageFetch(isCreate, url); const [deleteStatusPage, isDeleting] = useStatusPageDelete(fetchStatusPage, url); - console.log(JSON.stringify(form, null, 2)); // Handlers const handleFormChange = (e) => { let { type, name, value, checked } = e.target; @@ -144,6 +143,8 @@ const CreateStatusPage = () => { ...form, logo: { type: form.logo?.type ?? null, size: form.logo?.size ?? null }, }; + + console.log(toSubmit); const { error } = statusPageValidation.validate(toSubmit, { abortEarly: false, }); @@ -205,7 +206,7 @@ const CreateStatusPage = () => { companyName: statusPage?.companyName, isPublished: statusPage?.isPublished, timezone: statusPage?.timezone, - monitors: statusPageMonitors.map((monitor) => monitor._id), + monitors: statusPageMonitors.map((monitor) => monitor.id), color: statusPage?.color, logo: newLogo, showCharts: statusPage?.showCharts ?? true, diff --git a/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx b/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx index 89e32e5d5..33f771233 100644 --- a/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx +++ b/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx @@ -28,14 +28,14 @@ const MonitorsList = ({ const status = determineState(monitor); return ( { params.append("type", type); }); } - if (page) params.append("page", page); - if (rowsPerPage) params.append("rowsPerPage", rowsPerPage); - if (filter) params.append("filter", filter); - if (field) params.append("field", field); - if (order) params.append("order", order); + if (filter !== undefined && filter !== null && filter !== "") params.append("filter", filter); return this.axiosInstance.get(`/monitors/team?${params.toString()}`, { headers: { diff --git a/server/src/controllers/monitorController.ts b/server/src/controllers/monitorController.ts index ce0347512..9124abd06 100644 --- a/server/src/controllers/monitorController.ts +++ b/server/src/controllers/monitorController.ts @@ -5,6 +5,7 @@ import { getMonitorByIdQueryValidation, getMonitorsByTeamIdParamValidation, getMonitorsByTeamIdQueryValidation, + getMonitorsWithChecksQueryValidation, createMonitorBodyValidation, editMonitorBodyValidation, pauseMonitorParamValidation, @@ -317,10 +318,10 @@ class MonitorController { await getMonitorsByTeamIdParamValidation.validateAsync(req.params); await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); - let { limit, type, page, rowsPerPage, filter, field, order } = req.query; + const { type, filter } = req.query; const teamId = req?.user?.teamId; - const monitors = await this.monitorService.getMonitorsByTeamId({ teamId, limit, type, page, rowsPerPage, filter, field, order }); + const monitors = await this.monitorService.getMonitorsByTeamId({ teamId, type, filter }); return res.status(200).json({ success: true, @@ -358,7 +359,7 @@ class MonitorController { getMonitorsWithChecksByTeamId = async (req: Request, res: Response, next: NextFunction) => { try { await getMonitorsByTeamIdParamValidation.validateAsync(req.params); - await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); + await getMonitorsWithChecksQueryValidation.validateAsync(req.query); const explain = req?.query?.explain; let { limit, type, page, rowsPerPage, filter, field, order } = req.query; diff --git a/server/src/db/modules/monitorModule.js b/server/src/db/modules/monitorModule.js index 87237632a..84ff86996 100755 --- a/server/src/db/modules/monitorModule.js +++ b/server/src/db/modules/monitorModule.js @@ -289,47 +289,33 @@ class MonitorModule { } }; - getMonitorsByTeamId = async ({ limit, type, page, rowsPerPage, filter, field, order, teamId }) => { - limit = parseInt(limit); - page = parseInt(page); - rowsPerPage = parseInt(rowsPerPage); - if (field === undefined) { - field = "name"; - order = "asc"; - } - // Build match stage - const matchStage = { teamId: new this.ObjectId(teamId) }; - if (type !== undefined) { - matchStage.type = Array.isArray(type) ? { $in: type } : type; - } - - const summaryResult = await this.Monitor.aggregate(buildMonitorSummaryByTeamIdPipeline({ matchStage })); - const summary = summaryResult[0]; - - const monitors = await this.Monitor.aggregate(buildMonitorsByTeamIdPipeline({ matchStage, field, order })); - - const filteredMonitors = await this.Monitor.aggregate( - buildFilteredMonitorsByTeamIdPipeline({ - matchStage, - filter, - page, - rowsPerPage, - field, - order, - limit, - type, - }) - ); - - const normalizedFilteredMonitors = filteredMonitors.map((monitor) => { - if (!monitor.checks) { - return monitor; + getMonitorsByTeamId = async ({ teamId, type, filter }) => { + try { + const matchStage = { teamId: new this.ObjectId(teamId) }; + if (type !== undefined) { + matchStage.type = Array.isArray(type) ? { $in: type } : type; } - monitor.checks = this.NormalizeData(monitor.checks, 10, 100); - return monitor; - }); - - return { summary, monitors, filteredMonitors: normalizedFilteredMonitors }; + if (filter !== undefined && filter !== null && filter !== "") { + matchStage.$or = [{ name: { $regex: filter, $options: "i" } }, { url: { $regex: filter, $options: "i" } }]; + } + const monitors = await this.Monitor.find(matchStage) + .sort({ name: 1 }) + .select({ + _id: 1, + name: 1, + type: 1, + url: 1, + status: 1, + isActive: 1, + teamId: 1, + }) + .lean(); + return monitors; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorsByTeamId"; + throw error; + } }; getMonitorsAndSummaryByTeamId = async ({ type, explain, teamId }) => { diff --git a/server/src/service/business/monitorService.ts b/server/src/service/business/monitorService.ts index 0296ce9ca..46731446f 100644 --- a/server/src/service/business/monitorService.ts +++ b/server/src/service/business/monitorService.ts @@ -323,34 +323,10 @@ export class MonitorService implements IMonitorService { return monitor; }; - getMonitorsByTeamId = async ({ - teamId, - limit, - type, - page, - rowsPerPage, - filter, - field, - order, - }: { - teamId: string; - limit?: number; - type?: string | string[]; - page?: number; - rowsPerPage?: number; - filter?: string; - field?: string; - order?: "asc" | "desc"; - }): Promise => { - const monitors = await this.db.monitorModule.getMonitorsByTeamId({ - limit, + getMonitorsByTeamId = async ({ teamId, type, filter }: { teamId: string; type?: MonitorType | MonitorType[]; filter?: string }): Promise => { + const monitors = await this.monitorsRepository.findByTeamId(teamId, { type, - page, - rowsPerPage, filter, - field, - order, - teamId, }); return monitors; }; diff --git a/server/src/validation/joi.js b/server/src/validation/joi.js index e1d04047d..c09ac88b9 100755 --- a/server/src/validation/joi.js +++ b/server/src/validation/joi.js @@ -120,18 +120,30 @@ const getMonitorByIdQueryValidation = joi.object({ const getMonitorsByTeamIdParamValidation = joi.object({}); const getMonitorsByTeamIdQueryValidation = joi.object({ - limit: joi.number(), type: joi .alternatives() .try( 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(), - filter: joi.string(), - field: joi.string(), - order: joi.string().valid("asc", "desc"), + filter: joi.string().allow("", null), +}); + +const getMonitorsWithChecksQueryValidation = joi.object({ + limit: joi.number().integer().min(1).max(100).optional(), + page: joi.number().integer().min(0).optional(), + rowsPerPage: joi.number().integer().min(1).max(100).optional(), + filter: joi.string().allow("", null).optional(), + field: joi.string().optional(), + order: joi.string().valid("asc", "desc").optional(), + type: joi + .alternatives() + .try( + joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port", "game"), + joi.array().items(joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port", "game")) + ) + .optional(), + explain: joi.boolean().optional(), }); const getCertificateParamValidation = joi.object({ @@ -713,6 +725,7 @@ export { getMonitorByIdQueryValidation, getMonitorsByTeamIdParamValidation, getMonitorsByTeamIdQueryValidation, + getMonitorsWithChecksQueryValidation, getHardwareDetailsByIdParamValidation, getHardwareDetailsByIdQueryValidation, getCertificateParamValidation, From d4af86f26c3466b7616f8a43c7d7ee6690fb7f48 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 14 Jan 2026 22:44:23 +0000 Subject: [PATCH 22/24] pagespeed details --- client/src/Hooks/monitorHooks.js | 92 +++++------ client/src/Pages/PageSpeed/Details/index.jsx | 11 +- client/src/Utils/NetworkService.js | 11 +- server/src/controllers/controllerUtils.ts | 101 +++++++++++- server/src/controllers/monitorController.ts | 152 +++++++++--------- .../repositories/checks/IChecksRepository.ts | 102 ++++++------ .../checks/MongoChecksRepistory.ts | 36 ++++- server/src/routes/v1/monitorRoute.ts | 2 + server/src/service/business/monitorService.ts | 33 +++- 9 files changed, 354 insertions(+), 186 deletions(-) diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index 197779b53..bc391ebf6 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -6,7 +6,7 @@ import { useMonitorUtils } from "./useMonitorUtils.js"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; -const useFetchMonitorsWithSummary = ({ types, monitorUpdateTrigger }) => { +export const useFetchMonitorsWithSummary = ({ types, monitorUpdateTrigger }) => { const [isLoading, setIsLoading] = useState(false); const [monitors, setMonitors] = useState(undefined); const [monitorsSummary, setMonitorsSummary] = useState(undefined); @@ -37,7 +37,7 @@ const useFetchMonitorsWithSummary = ({ types, monitorUpdateTrigger }) => { return [monitors, monitorsSummary, isLoading, networkError]; }; -const useFetchMonitorsWithChecks = ({ +export const useFetchMonitorsWithChecks = ({ types, limit, page, @@ -100,7 +100,7 @@ const useFetchMonitorsWithChecks = ({ return [monitors, count, isLoading, networkError]; }; -const useFetchMonitorsByTeamId = ({ types, filter, updateTrigger }) => { +export const useFetchMonitorsByTeamId = ({ types, filter, updateTrigger }) => { const [isLoading, setIsLoading] = useState(false); const [monitors, setMonitors] = useState(undefined); const [networkError, setNetworkError] = useState(false); @@ -126,15 +126,11 @@ const useFetchMonitorsByTeamId = ({ types, filter, updateTrigger }) => { } }; fetchMonitors(); - }, [ - types, - filter, - updateTrigger, - ]); + }, [types, filter, updateTrigger]); return [monitors, isLoading, networkError]; }; -const useFetchStatsByMonitorId = ({ +export const useFetchStatsByMonitorId = ({ monitorId, sortOrder, limit, @@ -173,7 +169,7 @@ const useFetchStatsByMonitorId = ({ return [monitor, audits, isLoading, networkError]; }; -const useFetchMonitorGames = ({ setGames, updateTrigger }) => { +export const useFetchMonitorGames = ({ setGames, updateTrigger }) => { const [isLoading, setIsLoading] = useState(true); useEffect(() => { const fetchGames = async () => { @@ -192,7 +188,7 @@ const useFetchMonitorGames = ({ setGames, updateTrigger }) => { return [isLoading]; }; -const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => { +export const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => { const [isLoading, setIsLoading] = useState(true); useEffect(() => { if (typeof monitorId === "undefined") { @@ -215,7 +211,7 @@ const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => { return [isLoading]; }; -const useFetchHardwareMonitorById = ({ monitorId, dateRange, updateTrigger }) => { +export const useFetchHardwareMonitorById = ({ monitorId, dateRange, updateTrigger }) => { const [isLoading, setIsLoading] = useState(true); const [networkError, setNetworkError] = useState(false); const [monitor, setMonitor] = useState(undefined); @@ -241,8 +237,34 @@ const useFetchHardwareMonitorById = ({ monitorId, dateRange, updateTrigger }) => }, [monitorId, dateRange, updateTrigger]); return [monitor, isLoading, networkError]; }; +export const useFetchPageSpeedMonitorById = ({ monitorId, dateRange, updateTrigger }) => { + const [isLoading, setIsLoading] = useState(true); + const [networkError, setNetworkError] = useState(false); + const [monitor, setMonitor] = useState(undefined); -const useFetchUptimeMonitorById = ({ monitorId, dateRange, trigger }) => { + useEffect(() => { + const fetchMonitor = async () => { + try { + if (!monitorId) { + return { monitor: undefined, isLoading: false, networkError: undefined }; + } + const response = await networkService.getPageSpeedDetailsByMonitorId({ + monitorId: monitorId, + dateRange: dateRange, + }); + setMonitor(response.data.data); + } catch (error) { + setNetworkError(true); + } finally { + setIsLoading(false); + } + }; + fetchMonitor(); + }, [monitorId, dateRange, updateTrigger]); + return [monitor, isLoading, networkError]; +}; + +export const useFetchUptimeMonitorById = ({ monitorId, dateRange, trigger }) => { const [networkError, setNetworkError] = useState(false); const [isLoading, setIsLoading] = useState(true); const [monitor, setMonitor] = useState(undefined); @@ -270,7 +292,7 @@ const useFetchUptimeMonitorById = ({ monitorId, dateRange, trigger }) => { return [monitor, monitorStats, isLoading, networkError]; }; -const useCreateMonitor = () => { +export const useCreateMonitor = () => { const [isLoading, setIsLoading] = useState(false); const navigate = useNavigate(); const createMonitor = async ({ monitor, redirect }) => { @@ -290,7 +312,7 @@ const useCreateMonitor = () => { return [createMonitor, isLoading]; }; -const useFetchGlobalSettings = () => { +export const useFetchGlobalSettings = () => { const [isLoading, setIsLoading] = useState(true); const [globalSettings, setGlobalSettings] = useState(undefined); useEffect(() => { @@ -312,7 +334,7 @@ const useFetchGlobalSettings = () => { return [globalSettings, isLoading]; }; -const useDeleteMonitor = () => { +export const useDeleteMonitor = () => { const [isLoading, setIsLoading] = useState(false); const navigate = useNavigate(); const deleteMonitor = async ({ monitor, redirect }) => { @@ -333,7 +355,7 @@ const useDeleteMonitor = () => { return [deleteMonitor, isLoading]; }; -const useUpdateMonitor = () => { +export const useUpdateMonitor = () => { const [isLoading, setIsLoading] = useState(false); const navigate = useNavigate(); const updateMonitor = async ({ monitor, redirect }) => { @@ -380,7 +402,7 @@ const useUpdateMonitor = () => { return [updateMonitor, isLoading]; }; -const usePauseMonitor = () => { +export const usePauseMonitor = () => { const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(undefined); const pauseMonitor = async ({ monitorId, triggerUpdate }) => { @@ -403,7 +425,7 @@ const usePauseMonitor = () => { return [pauseMonitor, isLoading, error]; }; -const useAddDemoMonitors = () => { +export const useAddDemoMonitors = () => { const [isLoading, setIsLoading] = useState(false); const { t } = useTranslation(); const addDemoMonitors = async () => { @@ -420,7 +442,7 @@ const useAddDemoMonitors = () => { return [addDemoMonitors, isLoading]; }; -const useDeleteAllMonitors = () => { +export const useDeleteAllMonitors = () => { const [isLoading, setIsLoading] = useState(false); const { t } = useTranslation(); const deleteAllMonitors = async () => { @@ -437,7 +459,7 @@ const useDeleteAllMonitors = () => { return [deleteAllMonitors, isLoading]; }; -const useDeleteMonitorStats = () => { +export const useDeleteMonitorStats = () => { const { t } = useTranslation(); const [isLoading, setIsLoading] = useState(false); const deleteMonitorStats = async () => { @@ -455,7 +477,7 @@ const useDeleteMonitorStats = () => { return [deleteMonitorStats, isLoading]; }; -const useCreateBulkMonitors = () => { +export const useCreateBulkMonitors = () => { const [isLoading, setIsLoading] = useState(false); const createBulkMonitors = async (file, user) => { @@ -478,7 +500,7 @@ const useCreateBulkMonitors = () => { return [createBulkMonitors, isLoading]; }; -const useExportMonitors = () => { +export const useExportMonitors = () => { const [isLoading, setIsLoading] = useState(false); const { t } = useTranslation(); @@ -511,7 +533,7 @@ const useExportMonitors = () => { return [exportMonitors, isLoading]; }; -const useFetchJson = () => { +export const useFetchJson = () => { const [isLoading, setIsLoading] = useState(false); const fetchJson = async () => { try { @@ -527,25 +549,3 @@ const useFetchJson = () => { }; return [fetchJson, isLoading]; }; - -export { - useFetchMonitorsWithSummary, - useFetchMonitorsWithChecks, - useFetchMonitorsByTeamId, - useFetchStatsByMonitorId, - useFetchMonitorById, - useFetchUptimeMonitorById, - useFetchHardwareMonitorById, - useCreateMonitor, - useFetchGlobalSettings, - useDeleteMonitor, - useUpdateMonitor, - usePauseMonitor, - useAddDemoMonitors, - useDeleteAllMonitors, - useDeleteMonitorStats, - useCreateBulkMonitors, - useExportMonitors, - useFetchMonitorGames, - useFetchJson, -}; diff --git a/client/src/Pages/PageSpeed/Details/index.jsx b/client/src/Pages/PageSpeed/Details/index.jsx index 8a87133df..df411048a 100644 --- a/client/src/Pages/PageSpeed/Details/index.jsx +++ b/client/src/Pages/PageSpeed/Details/index.jsx @@ -11,14 +11,13 @@ import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; import { useTheme } from "@emotion/react"; import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; import { useParams } from "react-router-dom"; -import { useFetchStatsByMonitorId } from "../../../Hooks/monitorHooks.js"; +import { useFetchPageSpeedMonitorById } from "../../../Hooks/monitorHooks.js"; import { useState } from "react"; import { useTranslation } from "react-i18next"; // Constants const BREADCRUMBS = [ { name: "pagespeed", path: "/pagespeed" }, { name: "details", path: `` }, - // { name: "details", path: `/pagespeed/${monitorId}` }, // Not needed? ]; const PageSpeedDetails = () => { @@ -36,13 +35,9 @@ const PageSpeedDetails = () => { }); const [trigger, setTrigger] = useState(false); // Network - const [monitor, audits, isLoading, networkError] = useFetchStatsByMonitorId({ + const [monitor, isLoading, networkError] = useFetchPageSpeedMonitorById({ monitorId, - sortOrder: "desc", - limit: 50, dateRange: "day", - numToDisplay: null, - normalize: null, updateTrigger: trigger, }); @@ -116,7 +111,7 @@ const PageSpeedDetails = () => { /> ); diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js index a736a1c3b..bd0e37b30 100644 --- a/client/src/Utils/NetworkService.js +++ b/client/src/Utils/NetworkService.js @@ -152,7 +152,8 @@ class NetworkService { params.append("type", type); }); } - if (filter !== undefined && filter !== null && filter !== "") params.append("filter", filter); + if (filter !== undefined && filter !== null && filter !== "") + params.append("filter", filter); return this.axiosInstance.get(`/monitors/team?${params.toString()}`, { headers: { @@ -197,6 +198,14 @@ class NetworkService { `/monitors/hardware/details/${config.monitorId}?${params.toString()}` ); } + async getPageSpeedDetailsByMonitorId(config) { + const params = new URLSearchParams(); + if (config.dateRange) params.append("dateRange", config.dateRange); + + return this.axiosInstance.get( + `/monitors/pagespeed/details/${config.monitorId}?${params.toString()}` + ); + } async getUptimeDetailsById(config) { const params = new URLSearchParams(); if (config.dateRange) params.append("dateRange", config.dateRange); diff --git a/server/src/controllers/controllerUtils.ts b/server/src/controllers/controllerUtils.ts index 4d9f9a279..af3eddd44 100755 --- a/server/src/controllers/controllerUtils.ts +++ b/server/src/controllers/controllerUtils.ts @@ -1,3 +1,6 @@ +import { AppError } from "@/utils/AppError.js"; +import { type MonitorType, MonitorTypes } from "@/types/index.js"; + const fetchMonitorCertificate = async (sslChecker: any, monitor: any): Promise => { const monitorUrl = new URL(monitor.url); const hostname = monitorUrl.hostname; @@ -8,5 +11,101 @@ const fetchMonitorCertificate = async (sslChecker: any, monitor: any): Promise { + if (typeof value === "string" && value.trim().length > 0) { + return value; + } + throw new AppError({ message: `${fieldName} is required`, status: 400 }); +}; -export { fetchMonitorCertificate }; +const optionalString = (value: unknown, fieldName: string): string | undefined => { + if (value === undefined) { + return undefined; + } + if (typeof value === "string") { + return value; + } + throw new AppError({ message: `${fieldName} must be a string`, status: 400 }); +}; + +const optionalNumber = (value: unknown, fieldName: string): number | undefined => { + if (value === undefined) { + return undefined; + } + if (typeof value === "number" && Number.isFinite(value)) { + return value; + } + if (typeof value === "string" && value.trim() !== "") { + const parsed = Number(value); + if (!Number.isNaN(parsed)) { + return parsed; + } + } + throw new AppError({ message: `${fieldName} must be a number`, status: 400 }); +}; + +const optionalBoolean = (value: unknown, fieldName: string): boolean | undefined => { + if (value === undefined) { + return undefined; + } + if (typeof value === "boolean") { + return value; + } + if (typeof value === "string") { + if (value === "true") { + return true; + } + if (value === "false") { + return false; + } + } + throw new AppError({ message: `${fieldName} must be a boolean`, status: 400 }); +}; + +const parseMonitorTypeFilter = (value: unknown): MonitorType | MonitorType[] | undefined => { + const parseSingle = (input: unknown): MonitorType => { + if (typeof input !== "string") { + throw new AppError({ message: "Monitor type must be a string", status: 400 }); + } + if (!MonitorTypes.includes(input as MonitorType)) { + throw new AppError({ message: `Invalid monitor type: ${input}`, status: 400 }); + } + return input as MonitorType; + }; + + if (value === undefined) { + return undefined; + } + if (Array.isArray(value)) { + return value.map((entry) => parseSingle(entry)); + } + return parseSingle(value); +}; + +const parseSortOrder = (value: unknown): "asc" | "desc" | undefined => { + if (value === undefined) { + return undefined; + } + if (value === "asc" || value === "desc") { + return value; + } + throw new AppError({ message: "order must be either 'asc' or 'desc'", status: 400 }); +}; + +const requireTeamId = (teamId?: string): string => { + if (!teamId) { + throw new AppError({ message: "Team ID is required", status: 400 }); + } + return teamId; +}; + +export { + fetchMonitorCertificate, + requireString, + optionalString, + optionalNumber, + optionalBoolean, + parseMonitorTypeFilter, + parseSortOrder, + requireTeamId, +}; diff --git a/server/src/controllers/monitorController.ts b/server/src/controllers/monitorController.ts index 9124abd06..90ab5e7a5 100644 --- a/server/src/controllers/monitorController.ts +++ b/server/src/controllers/monitorController.ts @@ -14,16 +14,26 @@ import { getHardwareDetailsByIdQueryValidation, } from "@/validation/joi.js"; import sslChecker from "ssl-checker"; -import { fetchMonitorCertificate } from "./controllerUtils.js"; +import { + fetchMonitorCertificate, + requireString, + optionalString, + optionalNumber, + optionalBoolean, + parseMonitorTypeFilter, + parseSortOrder, + requireTeamId, +} from "./controllerUtils.js"; import { AppError } from "@/utils/AppError.js"; +import { IMonitorService } from "@/service/index.js"; const SERVICE_NAME = "monitorController"; class MonitorController { static SERVICE_NAME = SERVICE_NAME; - private monitorService: any; + private monitorService: IMonitorService; - constructor(monitorService: any) { + constructor(monitorService: IMonitorService) { this.monitorService = monitorService; } @@ -32,8 +42,8 @@ class MonitorController { } async verifyTeamAccess(teamId: string, monitorId: string) { - const monitor = await this.monitorService.getMonitorById(monitorId); - if (!monitor.teamId.equals(teamId)) { + const monitor = await this.monitorService.getMonitorById({ teamId, monitorId }); + if (monitor.teamId !== teamId) { throw new AppError({ message: "Access denied", status: 403 }); } } @@ -41,11 +51,8 @@ class MonitorController { getMonitorCertificate = async (req: Request, res: Response, next: NextFunction) => { try { await getCertificateParamValidation.validateAsync(req.params); - const teamId = req?.user?.teamId; - if (!teamId) { - throw new AppError({ message: "Team ID is required", status: 400 }); - } - const { monitorId } = req.params; + const teamId = requireTeamId(req?.user?.teamId); + const monitorId = requireString(req.params?.monitorId, "Monitor ID"); const monitor = await this.monitorService.getMonitorById({ teamId, monitorId }); const certificate = await fetchMonitorCertificate(sslChecker, monitor); @@ -63,15 +70,10 @@ class MonitorController { getUptimeDetailsById = async (req: Request, res: Response, next: NextFunction) => { try { - const monitorId = req?.params?.monitorId; - const dateRange = req?.query?.dateRange; - const normalize = req?.query?.normalize; - - const teamId = req?.user?.teamId; - - if (!teamId) { - throw new AppError({ message: "Team ID is required", status: 400 }); - } + const monitorId = requireString(req?.params?.monitorId, "Monitor ID"); + const dateRange = requireString(req?.query?.dateRange, "dateRange"); + const normalize = optionalBoolean(req?.query?.normalize, "normalize"); + const teamId = requireTeamId(req?.user?.teamId); const data = await this.monitorService.getUptimeDetailsById({ teamId, @@ -94,12 +96,9 @@ class MonitorController { await getHardwareDetailsByIdParamValidation.validateAsync(req.params); await getHardwareDetailsByIdQueryValidation.validateAsync(req.query); - const monitorId = req?.params?.monitorId; - const dateRange = req?.query?.dateRange; - const teamId = req?.user?.teamId; - if (!teamId) { - throw new AppError({ message: "Team ID is required", status: 400 }); - } + const monitorId = requireString(req?.params?.monitorId, "Monitor ID"); + const dateRange = requireString(req?.query?.dateRange, "dateRange"); + const teamId = requireTeamId(req?.user?.teamId); const monitor = await this.monitorService.getHardwareDetailsById({ teamId, @@ -116,18 +115,40 @@ class MonitorController { next(error); } }; + getPageSpeedDetailsById = async (req: Request, res: Response, next: NextFunction) => { + try { + await getHardwareDetailsByIdParamValidation.validateAsync(req.params); + await getHardwareDetailsByIdQueryValidation.validateAsync(req.query); + + const monitorId = requireString(req?.params?.monitorId, "Monitor ID"); + const dateRange = requireString(req?.query?.dateRange, "dateRange"); + const teamId = requireTeamId(req?.user?.teamId); + + const monitor = await this.monitorService.getPageSpeedDetailsById({ + teamId, + monitorId, + dateRange, + }); + + return res.status(200).json({ + success: true, + msg: "Page speed details retrieved successfully", + data: monitor, + }); + } catch (error) { + next(error); + } + }; getMonitorById = async (req: Request, res: Response, next: NextFunction) => { try { await getMonitorByIdParamValidation.validateAsync(req.params); await getMonitorByIdQueryValidation.validateAsync(req.query); - const teamId = req?.user?.teamId; - if (!teamId) { - throw new AppError({ message: "Team ID is required", status: 400 }); - } + const teamId = requireTeamId(req?.user?.teamId); + const monitorId = requireString(req?.params?.monitorId, "Monitor ID"); - const monitor = await this.monitorService.getMonitorById({ teamId, monitorId: req?.params?.monitorId }); + const monitor = await this.monitorService.getMonitorById({ teamId, monitorId }); return res.status(200).json({ success: true, @@ -143,8 +164,8 @@ class MonitorController { try { await createMonitorBodyValidation.validateAsync(req.body); - const userId = req?.user?._id; - const teamId = req?.user?.teamId; + const userId = requireString(req?.user?._id, "User ID"); + const teamId = requireTeamId(req?.user?.teamId); const monitor = await this.monitorService.createMonitor(teamId, userId, req.body); @@ -172,12 +193,8 @@ class MonitorController { throw new AppError({ message: "File is empty", status: 400 }); } - const userId = req?.user?._id; - const teamId = req?.user?.teamId; - - if (!userId || !teamId) { - throw new AppError({ message: "Missing userId or teamId", status: 400 }); - } + const userId = requireString(req?.user?._id, "User ID"); + const teamId = requireTeamId(req?.user?.teamId); const fileData = req?.file?.buffer?.toString("utf-8"); if (!fileData) { @@ -199,11 +216,8 @@ class MonitorController { deleteMonitor = async (req: Request, res: Response, next: NextFunction) => { try { await getMonitorByIdParamValidation.validateAsync(req.params); - const monitorId = req.params.monitorId; - const teamId = req?.user?.teamId; - if (!teamId) { - throw new AppError({ message: "Team ID is required", status: 400 }); - } + const monitorId = requireString(req?.params?.monitorId, "Monitor ID"); + const teamId = requireTeamId(req?.user?.teamId); const deletedMonitor = await this.monitorService.deleteMonitor({ teamId, monitorId }); @@ -219,10 +233,7 @@ class MonitorController { deleteAllMonitors = async (req: Request, res: Response, next: NextFunction) => { try { - const teamId = req?.user?.teamId; - if (!teamId) { - throw new AppError({ message: "Team ID is required", status: 400 }); - } + const teamId = requireTeamId(req?.user?.teamId); const deletedCount = await this.monitorService.deleteAllMonitors({ teamId }); @@ -239,12 +250,8 @@ class MonitorController { try { await getMonitorByIdParamValidation.validateAsync(req.params); await editMonitorBodyValidation.validateAsync(req.body); - const monitorId = req?.params?.monitorId; - - const teamId = req?.user?.teamId; - if (!teamId) { - throw new AppError({ message: "Team ID is required", status: 400 }); - } + const monitorId = requireString(req?.params?.monitorId, "Monitor ID"); + const teamId = requireTeamId(req?.user?.teamId); const editedMonitor = await this.monitorService.editMonitor({ teamId, monitorId, body: req.body }); @@ -262,11 +269,8 @@ class MonitorController { try { await pauseMonitorParamValidation.validateAsync(req.params); - const monitorId = req.params.monitorId; - const teamId = req?.user?.teamId; - if (!teamId) { - throw new AppError({ message: "Team ID is required", status: 400 }); - } + const monitorId = requireString(req?.params?.monitorId, "Monitor ID"); + const teamId = requireTeamId(req?.user?.teamId); const monitor = await this.monitorService.pauseMonitor({ teamId, monitorId }); @@ -282,7 +286,8 @@ class MonitorController { addDemoMonitors = async (req: Request, res: Response, next: NextFunction) => { try { - const { _id, teamId } = req.user; + const _id = requireString(req?.user?._id, "User ID"); + const teamId = requireTeamId(req?.user?.teamId); const demoMonitors = await this.monitorService.addDemoMonitors({ userId: _id, teamId }); return res.status(200).json({ @@ -318,8 +323,9 @@ class MonitorController { await getMonitorsByTeamIdParamValidation.validateAsync(req.params); await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); - const { type, filter } = req.query; - const teamId = req?.user?.teamId; + const teamId = requireTeamId(req?.user?.teamId); + const type = parseMonitorTypeFilter(req.query?.type); + const filter = optionalString(req.query?.filter, "filter"); const monitors = await this.monitorService.getMonitorsByTeamId({ teamId, type, filter }); @@ -338,12 +344,9 @@ class MonitorController { await getMonitorsByTeamIdParamValidation.validateAsync(req.params); await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); - const explain = req?.query?.explain; - const type = req?.query?.type; - const teamId = req?.user?.teamId; - if (!teamId) { - throw new AppError({ message: "Team ID is required", status: 400 }); - } + const explain = optionalBoolean(req?.query?.explain, "explain"); + const type = parseMonitorTypeFilter(req?.query?.type); + const teamId = requireTeamId(req?.user?.teamId); const result = await this.monitorService.getMonitorsAndSummaryByTeamId({ teamId, type, explain }); @@ -361,12 +364,15 @@ class MonitorController { await getMonitorsByTeamIdParamValidation.validateAsync(req.params); await getMonitorsWithChecksQueryValidation.validateAsync(req.query); - const explain = req?.query?.explain; - let { limit, type, page, rowsPerPage, filter, field, order } = req.query; - const teamId = req?.user?.teamId; - if (!teamId) { - throw new AppError({ message: "Team ID is required", status: 400 }); - } + const explain = optionalBoolean(req?.query?.explain, "explain"); + const limit = optionalNumber(req?.query?.limit, "limit"); + const page = optionalNumber(req?.query?.page, "page"); + const rowsPerPage = optionalNumber(req?.query?.rowsPerPage, "rowsPerPage"); + const filter = optionalString(req?.query?.filter, "filter"); + const field = optionalString(req?.query?.field, "field"); + const order = parseSortOrder(req?.query?.order); + const type = parseMonitorTypeFilter(req?.query?.type); + const teamId = requireTeamId(req?.user?.teamId); const monitors = await this.monitorService.getMonitorsWithChecksByTeamId({ teamId, diff --git a/server/src/repositories/checks/IChecksRepository.ts b/server/src/repositories/checks/IChecksRepository.ts index 507389e6a..cedea8d86 100644 --- a/server/src/repositories/checks/IChecksRepository.ts +++ b/server/src/repositories/checks/IChecksRepository.ts @@ -1,6 +1,58 @@ -import type { Check, MonitorType } from "@/types/index.js"; +import type { Check, CheckAudits, MonitorType } from "@/types/index.js"; import type { LatestChecksMap } from "@/repositories/checks/MongoChecksRepistory.js"; +export interface PageSpeedChecksResult { + monitorType: "pagespeed"; + checks: Check[]; +} + +export interface HardwareChecksResult { + monitorType: "hardware"; + aggregateData: { + latestCheck: Check | null; + totalChecks: number; + }; + upChecks: { + totalChecks: number; + }; + checks: Array<{ + _id: string; + avgCpuUsage: number; + avgMemoryUsage: number; + avgTemperature: number[]; + disks: Array<{ + name: string; + readSpeed: number; + writeSpeed: number; + totalBytes: number; + freeBytes: number; + usagePercent: number; + }>; + net: Array<{ + name: string; + bytesSentPerSecond: number; + deltaBytesRecv: number; + deltaPacketsSent: number; + deltaPacketsRecv: number; + deltaErrIn: number; + deltaErrOut: number; + deltaDropIn: number; + deltaDropOut: number; + deltaFifoIn: number; + deltaFifoOut: number; + }>; + }>; +} + +export interface UptimeChecksResult { + monitorType: Exclude; + groupedChecks: Array<{ _id: string; avgResponseTime: number; totalChecks: number }>; + groupedUpChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>; + groupedDownChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>; + uptimePercentage: number; + avgResponseTime: number; +} + export interface IChecksRepository { findLatestChecksByMonitorIds(monitorIds: string[], options?: { limitPerMonitor?: number }): Promise; findDateRangeChecksByMonitor( @@ -9,51 +61,5 @@ export interface IChecksRepository { endDate: Date, dateString: string, options?: { type?: MonitorType } - ): Promise< - | { - monitorType: "uptime"; - groupedChecks: Array<{ _id: string; avgResponseTime: number; totalChecks: number }>; - groupedUpChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>; - groupedDownChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>; - uptimePercentage: number; - avgResponseTime: number; - } - | { - monitorType: "hardware"; - aggregateData: { - latestCheck: Check | null; - totalChecks: number; - }; - upChecks: { - totalChecks: number; - }; - checks: Array<{ - _id: string; - avgCpuUsage: number; - avgMemoryUsage: number; - avgTemperature: number[]; - disks: Array<{ - name: string; - readSpeed: number; - writeSpeed: number; - totalBytes: number; - freeBytes: number; - usagePercent: number; - }>; - net: Array<{ - name: string; - bytesSentPerSecond: number; - deltaBytesRecv: number; - deltaPacketsSent: number; - deltaPacketsRecv: number; - deltaErrIn: number; - deltaErrOut: number; - deltaDropIn: number; - deltaDropOut: number; - deltaFifoIn: number; - deltaFifoOut: number; - }>; - }>; - } - >; + ): Promise; } diff --git a/server/src/repositories/checks/MongoChecksRepistory.ts b/server/src/repositories/checks/MongoChecksRepistory.ts index 2aeb32b2d..d9466b4d8 100644 --- a/server/src/repositories/checks/MongoChecksRepistory.ts +++ b/server/src/repositories/checks/MongoChecksRepistory.ts @@ -11,6 +11,7 @@ import type { CheckMetadata, CheckNetworkInterfaceInfo, CheckTimings, + MonitorType, } from "@/types/index.js"; import { CheckModel, type CheckDocument } from "@/db/models/index.js"; import mongoose from "mongoose"; @@ -177,10 +178,7 @@ class MongoChecksRepistory implements IChecksRepository { }; }; - findLatestChecksByMonitorIds = async ( - monitorIds: string[], - options?: { limitPerMonitor?: number } - ): Promise => { + findLatestChecksByMonitorIds = async (monitorIds: string[], options?: { limitPerMonitor?: number }): Promise => { if (monitorIds.length === 0) { return {}; } @@ -218,15 +216,24 @@ class MongoChecksRepistory implements IChecksRepository { }, {}); }; - findDateRangeChecksByMonitor = async (monitorId: string, startDate: Date, endDate: Date, dateString: string, options?: { type?: string }) => { + findDateRangeChecksByMonitor = async (monitorId: string, startDate: Date, endDate: Date, dateString: string, options?: { type?: MonitorType }) => { const monitorObjectId = new mongoose.Types.ObjectId(monitorId); if (options?.type === "hardware") { return this.findHardwareDateRangeChecks(monitorObjectId, startDate, endDate, dateString); } - return this.findUptimeDateRangeChecks(monitorObjectId, startDate, endDate, dateString); + if (options?.type === "pagespeed") { + return this.findPageSpeedDateRangeChecks(monitorObjectId, startDate, endDate); + } + return this.findUptimeDateRangeChecks(options?.type ?? "http", monitorObjectId, startDate, endDate, dateString); }; - private findUptimeDateRangeChecks = async (monitorObjectId: mongoose.Types.ObjectId, startDate: Date, endDate: Date, dateString: string) => { + private findUptimeDateRangeChecks = async ( + monitorType: Exclude, + monitorObjectId: mongoose.Types.ObjectId, + startDate: Date, + endDate: Date, + dateString: string + ) => { const matchStage = { "metadata.monitorId": monitorObjectId, updatedAt: { $gte: startDate, $lte: endDate }, @@ -307,7 +314,7 @@ class MongoChecksRepistory implements IChecksRepository { const avgResponseTime = result?.groupedAvgResponseTime?.[0]?.avgResponseTime ?? 0; return { - monitorType: "uptime" as const, + monitorType, groupedChecks: result?.groupedChecks ?? [], groupedUpChecks: result?.groupedUpChecks ?? [], groupedDownChecks: result?.groupedDownChecks ?? [], @@ -369,6 +376,19 @@ class MongoChecksRepistory implements IChecksRepository { checks, }; }; + + private findPageSpeedDateRangeChecks = async (monitorObjectId: mongoose.Types.ObjectId, startDate: Date, endDate: Date) => { + const matchStage = { + "metadata.monitorId": monitorObjectId, + createdAt: { $gte: startDate, $lte: endDate }, + }; + + const checks = await CheckModel.find(matchStage).sort({ createdAt: -1 }).limit(25).lean(); + return { + monitorType: "pagespeed" as const, + checks: checks.map((doc) => this.toEntity(doc)), + }; + }; } export default MongoChecksRepistory; diff --git a/server/src/routes/v1/monitorRoute.ts b/server/src/routes/v1/monitorRoute.ts index bcbf5a1c6..24bc64ec8 100755 --- a/server/src/routes/v1/monitorRoute.ts +++ b/server/src/routes/v1/monitorRoute.ts @@ -27,6 +27,8 @@ class MonitorRoutes { // Hardware routes this.router.get("/hardware/details/:monitorId", this.monitorController.getHardwareDetailsById); + // PageSpeed routes + this.router.get("/pagespeed/details/:monitorId", this.monitorController.getPageSpeedDetailsById); // General monitor routes this.router.post("/pause/:monitorId", isAllowed(["admin", "superadmin"]), this.monitorController.pauseMonitor); diff --git a/server/src/service/business/monitorService.ts b/server/src/service/business/monitorService.ts index 46731446f..3793ac35e 100644 --- a/server/src/service/business/monitorService.ts +++ b/server/src/service/business/monitorService.ts @@ -24,6 +24,7 @@ export interface IMonitorService { // read getUptimeDetailsById(args: { teamId: string; monitorId: string; dateRange: string; normalize?: boolean }): Promise; getHardwareDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise; + getPageSpeedDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise; getMonitorById(args: { teamId: string; monitorId: string }): Promise; getMonitorsByTeamId(args: { teamId: string; @@ -268,7 +269,13 @@ export class MonitorService implements IMonitorService { }); const monitorStats = await this.monitorStatsRepository.findByMonitorId(monitor.id); - if (checksData.monitorType !== "uptime") { + if ( + checksData.monitorType !== "http" && + checksData.monitorType !== "ping" && + checksData.monitorType !== "docker" && + checksData.monitorType !== "port" && + checksData.monitorType !== "game" + ) { throw new AppError({ message: `${monitor.type} monitors are not supported for uptime details`, status: 400 }); } @@ -317,6 +324,30 @@ export class MonitorService implements IMonitorService { }; }; + getPageSpeedDetailsById = async ({ teamId, monitorId, dateRange }: { teamId: string; monitorId: string; dateRange: string }): Promise => { + await this.verifyTeamAccess({ teamId, monitorId }); + const monitor = await this.monitorsRepository.findById(monitorId, teamId); + if (!monitor) { + throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 }); + } + if (monitor.type !== "pagespeed") { + throw new AppError({ message: `${monitor.type} monitors are not supported for pagespeed details`, status: 400 }); + } + + const rangeKey = (dateRange as DateRangeKey) ?? "recent"; + const { start, end } = this.getDateRange(rangeKey); + const checksData = await this.checksRepository.findDateRangeChecksByMonitor(monitor.id, start, end, this.getDateFormat(rangeKey), { + type: monitor.type, + }); + + if (checksData.monitorType !== "pagespeed") { + throw new AppError({ message: "Unable to load pagespeed stats for this monitor", status: 500 }); + } + return { + ...monitor, + checks: checksData.checks, + }; + }; getMonitorById = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise => { await this.verifyTeamAccess({ teamId, monitorId }); const monitor = await this.monitorsRepository.findById(monitorId, teamId); From aea0b3453304a024387fab503810447ed038c212 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 14 Jan 2026 22:45:39 +0000 Subject: [PATCH 23/24] format --- server/src/db/models/index.ts | 2 +- server/test/monitorService.test.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/db/models/index.ts b/server/src/db/models/index.ts index 29f3539c9..3ac99a390 100644 --- a/server/src/db/models/index.ts +++ b/server/src/db/models/index.ts @@ -5,4 +5,4 @@ export * from "@/db/models/Check.js"; export { default as CheckModel } from "@/db/models/Check.js"; export * from "@/db/models/MonitorStats.js"; -export { default as MonitorStatsModel } from "@/db/models/MonitorStats.js"; \ No newline at end of file +export { default as MonitorStatsModel } from "@/db/models/MonitorStats.js"; diff --git a/server/test/monitorService.test.ts b/server/test/monitorService.test.ts index 4fdcbeb49..7a3bbe12f 100644 --- a/server/test/monitorService.test.ts +++ b/server/test/monitorService.test.ts @@ -213,6 +213,5 @@ describe("MonitorService", () => { }) ); }); - }); }); From 1326a6222b347996a42170acbcf58b257a04d3a8 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 14 Jan 2026 22:45:47 +0000 Subject: [PATCH 24/24] format --- client/src/Components/v1/Inputs/Search/index.jsx | 11 +++++++++-- .../CreateMaintenance/hooks/useMaintenanceData.jsx | 4 +++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/client/src/Components/v1/Inputs/Search/index.jsx b/client/src/Components/v1/Inputs/Search/index.jsx index 347223bf3..8138ee6f4 100644 --- a/client/src/Components/v1/Inputs/Search/index.jsx +++ b/client/src/Components/v1/Inputs/Search/index.jsx @@ -85,7 +85,12 @@ const Search = ({ const enhancedOptions = React.useMemo(() => { return multiple && isAdorned ? [ - { [filteredBy]: t("selectAll"), isSelectAll: true, _id: "select_all", id: "select_all" }, + { + [filteredBy]: t("selectAll"), + isSelectAll: true, + _id: "select_all", + id: "select_all", + }, ...options, ] : options; @@ -145,7 +150,9 @@ const Search = ({ disableClearable options={enhancedOptions} getOptionLabel={(option) => option[filteredBy]} - isOptionEqualToValue={(option, value) => (option._id ?? option.id) === (value?._id ?? value?.id)} // Compare by unique identifier + isOptionEqualToValue={(option, value) => + (option._id ?? option.id) === (value?._id ?? value?.id) + } // Compare by unique identifier renderInput={(params) => ( { const endTime = dayjs(end); const durationInMs = endTime.diff(startTime, "milliseconds").toString(); const { duration, durationUnit } = getDurationAndUnit(durationInMs); - const monitor = monitorList.find((monitor) => (monitor._id ?? monitor.id) === monitorId); + const monitor = monitorList.find( + (monitor) => (monitor._id ?? monitor.id) === monitorId + ); const maintenanceWindowInformation = { name, repeat: REVERSE_REPEAT_LOOKUP[repeat],