From d8ed9abebf2c182e99aa309828ade4dc703cebfd Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 22 Sep 2025 12:53:44 -0700 Subject: [PATCH 001/103] add gitignore --- .gitignore | 3 +- server/.gitignore | 3 +- server/package-lock.json | 530 ++++- server/package.json | 7 +- server/src/config/routes.js | 22 +- .../src/routes/{ => v1}/announcementsRoute.js | 0 server/src/routes/{ => v1}/authRoute.js | 6 +- server/src/routes/{ => v1}/checkRoute.js | 7 +- server/src/routes/{ => v1}/diagnosticRoute.js | 4 +- server/src/routes/{ => v1}/inviteRoute.js | 4 +- server/src/routes/{ => v1}/logRoutes.js | 2 +- .../routes/{ => v1}/maintenanceWindowRoute.js | 2 +- server/src/routes/{ => v1}/monitorRoute.js | 4 +- .../src/routes/{ => v1}/notificationRoute.js | 0 server/src/routes/{ => v1}/queueRoute.js | 2 +- server/src/routes/{ => v1}/settingsRoute.js | 2 +- server/src/routes/{ => v1}/statusPageRoute.js | 2 +- .../tests/controllers/authController.test.js | 962 --------- .../tests/controllers/checkController.test.js | 372 ---- .../tests/controllers/controllerUtils.test.js | 157 -- .../controllers/inviteController.test.js | 202 -- .../maintenanceWindowController.test.js | 422 ---- .../controllers/monitorController.test.js | 1116 ---------- .../tests/controllers/queueController.test.js | 176 -- .../controllers/settingsController.test.js | 109 - .../controllers/statusPageController.test.js | 127 -- server/tests/db/checkModule.test.js | 589 ------ server/tests/db/hardwareCheckModule.test.js | 136 -- server/tests/db/inviteModule.test.js | 103 - .../tests/db/maintenanceWindowModule.test.js | 255 --- server/tests/db/monitorModule.test.js | 1865 ----------------- server/tests/db/notificationModule.test.js | 76 - server/tests/db/pageSpeedCheckModule.test.js | 63 - server/tests/db/recoveryModule.test.js | 168 -- server/tests/db/settingsModule.test.js | 56 - server/tests/db/statusPageModule.test.js | 70 - server/tests/db/userModule.test.js | 294 --- server/tests/services/emailService.test.js | 168 -- server/tests/services/jobQueue.test.js | 813 ------- server/tests/services/networkService.test.js | 439 ---- .../services/notificationService.test.js | 285 --- server/tests/services/settingsService.test.js | 140 -- server/tests/services/statusService.test.js | 255 --- server/tests/utils/dataUtils.test.js | 61 - server/tests/utils/imageProcessing.test.js | 53 - server/tests/utils/logger.test.js | 139 -- server/tests/utils/messages.test.js | 23 - server/tests/utils/utils.test.js | 50 - server/tsconfig.json | 26 + 49 files changed, 590 insertions(+), 9780 deletions(-) rename server/src/routes/{ => v1}/announcementsRoute.js (100%) rename server/src/routes/{ => v1}/authRoute.js (85%) rename server/src/routes/{ => v1}/checkRoute.js (76%) rename server/src/routes/{ => v1}/diagnosticRoute.js (77%) rename server/src/routes/{ => v1}/inviteRoute.js (82%) rename server/src/routes/{ => v1}/logRoutes.js (85%) rename server/src/routes/{ => v1}/maintenanceWindowRoute.js (91%) rename server/src/routes/{ => v1}/monitorRoute.js (94%) rename server/src/routes/{ => v1}/notificationRoute.js (100%) rename server/src/routes/{ => v1}/queueRoute.js (93%) rename server/src/routes/{ => v1}/settingsRoute.js (90%) rename server/src/routes/{ => v1}/statusPageRoute.js (93%) delete mode 100755 server/tests/controllers/authController.test.js delete mode 100755 server/tests/controllers/checkController.test.js delete mode 100755 server/tests/controllers/controllerUtils.test.js delete mode 100755 server/tests/controllers/inviteController.test.js delete mode 100755 server/tests/controllers/maintenanceWindowController.test.js delete mode 100755 server/tests/controllers/monitorController.test.js delete mode 100755 server/tests/controllers/queueController.test.js delete mode 100755 server/tests/controllers/settingsController.test.js delete mode 100755 server/tests/controllers/statusPageController.test.js delete mode 100755 server/tests/db/checkModule.test.js delete mode 100755 server/tests/db/hardwareCheckModule.test.js delete mode 100755 server/tests/db/inviteModule.test.js delete mode 100755 server/tests/db/maintenanceWindowModule.test.js delete mode 100755 server/tests/db/monitorModule.test.js delete mode 100755 server/tests/db/notificationModule.test.js delete mode 100755 server/tests/db/pageSpeedCheckModule.test.js delete mode 100755 server/tests/db/recoveryModule.test.js delete mode 100755 server/tests/db/settingsModule.test.js delete mode 100755 server/tests/db/statusPageModule.test.js delete mode 100755 server/tests/db/userModule.test.js delete mode 100755 server/tests/services/emailService.test.js delete mode 100755 server/tests/services/jobQueue.test.js delete mode 100755 server/tests/services/networkService.test.js delete mode 100755 server/tests/services/notificationService.test.js delete mode 100755 server/tests/services/settingsService.test.js delete mode 100755 server/tests/services/statusService.test.js delete mode 100755 server/tests/utils/dataUtils.test.js delete mode 100755 server/tests/utils/imageProcessing.test.js delete mode 100755 server/tests/utils/logger.test.js delete mode 100755 server/tests/utils/messages.test.js delete mode 100755 server/tests/utils/utils.test.js create mode 100644 server/tsconfig.json diff --git a/.gitignore b/.gitignore index 600d2d33b..172702b40 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.vscode \ No newline at end of file +.vscode +.VSCodeCounter \ No newline at end of file diff --git a/server/.gitignore b/server/.gitignore index 4bc621f83..9ae4e58ed 100755 --- a/server/.gitignore +++ b/server/.gitignore @@ -8,4 +8,5 @@ coverage .clinic node_modules .vscode/* -public \ No newline at end of file +public +dist \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json index 3e1130dc4..e2affbca0 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -54,7 +54,8 @@ "mocha": "11.1.0", "nodemon": "3.1.9", "prettier": "^3.3.3", - "sinon": "19.0.2" + "sinon": "19.0.2", + "tsx": "4.20.5" } }, "node_modules/@asamuzakjp/css-color": { @@ -258,6 +259,448 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", + "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", + "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", + "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", + "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", + "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", + "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", + "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", + "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", + "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", + "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", + "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", + "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", + "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", + "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", + "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", + "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", + "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", + "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", + "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", + "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", + "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", + "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", + "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", + "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", + "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", + "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.8.0.tgz", @@ -3163,6 +3606,48 @@ "node": ">= 0.4" } }, + "node_modules/esbuild": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", + "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.10", + "@esbuild/android-arm": "0.25.10", + "@esbuild/android-arm64": "0.25.10", + "@esbuild/android-x64": "0.25.10", + "@esbuild/darwin-arm64": "0.25.10", + "@esbuild/darwin-x64": "0.25.10", + "@esbuild/freebsd-arm64": "0.25.10", + "@esbuild/freebsd-x64": "0.25.10", + "@esbuild/linux-arm": "0.25.10", + "@esbuild/linux-arm64": "0.25.10", + "@esbuild/linux-ia32": "0.25.10", + "@esbuild/linux-loong64": "0.25.10", + "@esbuild/linux-mips64el": "0.25.10", + "@esbuild/linux-ppc64": "0.25.10", + "@esbuild/linux-riscv64": "0.25.10", + "@esbuild/linux-s390x": "0.25.10", + "@esbuild/linux-x64": "0.25.10", + "@esbuild/netbsd-arm64": "0.25.10", + "@esbuild/netbsd-x64": "0.25.10", + "@esbuild/openbsd-arm64": "0.25.10", + "@esbuild/openbsd-x64": "0.25.10", + "@esbuild/openharmony-arm64": "0.25.10", + "@esbuild/sunos-x64": "0.25.10", + "@esbuild/win32-arm64": "0.25.10", + "@esbuild/win32-ia32": "0.25.10", + "@esbuild/win32-x64": "0.25.10" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -4079,6 +4564,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -7578,6 +8076,16 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/responselike": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", @@ -8551,6 +9059,26 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.20.5", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.5.tgz", + "integrity": "sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", diff --git a/server/package.json b/server/package.json index d879776a8..c57c3a4ce 100755 --- a/server/package.json +++ b/server/package.json @@ -6,7 +6,9 @@ "type": "module", "scripts": { "test": "c8 mocha", - "dev": "nodemon src/index.js", + "dev": "nodemon --exec tsx src/index.js", + "start": "nodemon ./dist/index.js", + "build": "tsc", "lint": "eslint .", "lint-fix": "eslint --fix .", "format": "prettier --write .", @@ -61,6 +63,7 @@ "mocha": "11.1.0", "nodemon": "3.1.9", "prettier": "^3.3.3", - "sinon": "19.0.2" + "sinon": "19.0.2", + "tsx": "4.20.5" } } diff --git a/server/src/config/routes.js b/server/src/config/routes.js index 7056393f9..2ec93a2dc 100644 --- a/server/src/config/routes.js +++ b/server/src/config/routes.js @@ -1,17 +1,17 @@ import { verifyJWT } from "../middleware/verifyJWT.js"; import { authApiLimiter } from "../middleware/rateLimiter.js"; -import AuthRoutes from "../routes/authRoute.js"; -import InviteRoutes from "../routes/inviteRoute.js"; -import MonitorRoutes from "../routes/monitorRoute.js"; -import CheckRoutes from "../routes/checkRoute.js"; -import SettingsRoutes from "../routes/settingsRoute.js"; -import MaintenanceWindowRoutes from "../routes/maintenanceWindowRoute.js"; -import StatusPageRoutes from "../routes/statusPageRoute.js"; -import QueueRoutes from "../routes/queueRoute.js"; -import LogRoutes from "../routes/logRoutes.js"; -import DiagnosticRoutes from "../routes/diagnosticRoute.js"; -import NotificationRoutes from "../routes/notificationRoute.js"; +import AuthRoutes from "../routes/v1/authRoute.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"; +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 NotificationRoutes from "../routes/v1/notificationRoute.js"; export const setupRoutes = (app, controllers) => { const authRoutes = new AuthRoutes(controllers.authController); diff --git a/server/src/routes/announcementsRoute.js b/server/src/routes/v1/announcementsRoute.js similarity index 100% rename from server/src/routes/announcementsRoute.js rename to server/src/routes/v1/announcementsRoute.js diff --git a/server/src/routes/authRoute.js b/server/src/routes/v1/authRoute.js similarity index 85% rename from server/src/routes/authRoute.js rename to server/src/routes/v1/authRoute.js index e790c60b0..f69f94f43 100755 --- a/server/src/routes/authRoute.js +++ b/server/src/routes/v1/authRoute.js @@ -1,9 +1,7 @@ import { Router } from "express"; -import { verifyJWT } from "../middleware/verifyJWT.js"; -import { verifyOwnership } from "../middleware/verifyOwnership.js"; -import { isAllowed } from "../middleware/isAllowed.js"; +import { verifyJWT } from "../../middleware/verifyJWT.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; import multer from "multer"; -import User from "../db/models/User.js"; const upload = multer(); diff --git a/server/src/routes/checkRoute.js b/server/src/routes/v1/checkRoute.js similarity index 76% rename from server/src/routes/checkRoute.js rename to server/src/routes/v1/checkRoute.js index 8bdb2d4fb..673fe51e0 100755 --- a/server/src/routes/checkRoute.js +++ b/server/src/routes/v1/checkRoute.js @@ -1,9 +1,6 @@ import { Router } from "express"; -import { verifyOwnership } from "../middleware/verifyOwnership.js"; -import { verifyTeamAccess } from "../middleware/verifyTeamAccess.js"; -import { isAllowed } from "../middleware/isAllowed.js"; -import Monitor from "../db/models/Monitor.js"; -import Check from "../db/models/Check.js"; + +import { isAllowed } from "../../middleware/isAllowed.js"; class CheckRoutes { constructor(checkController) { diff --git a/server/src/routes/diagnosticRoute.js b/server/src/routes/v1/diagnosticRoute.js similarity index 77% rename from server/src/routes/diagnosticRoute.js rename to server/src/routes/v1/diagnosticRoute.js index a15f5a038..1b7cc06be 100755 --- a/server/src/routes/diagnosticRoute.js +++ b/server/src/routes/v1/diagnosticRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { verifyJWT } from "../middleware/verifyJWT.js"; -import { isAllowed } from "../middleware/isAllowed.js"; +import { verifyJWT } from "../../middleware/verifyJWT.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class DiagnosticRoutes { constructor(diagnosticController) { diff --git a/server/src/routes/inviteRoute.js b/server/src/routes/v1/inviteRoute.js similarity index 82% rename from server/src/routes/inviteRoute.js rename to server/src/routes/v1/inviteRoute.js index d495c5476..74fcfeb91 100755 --- a/server/src/routes/inviteRoute.js +++ b/server/src/routes/v1/inviteRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { verifyJWT } from "../middleware/verifyJWT.js"; -import { isAllowed } from "../middleware/isAllowed.js"; +import { verifyJWT } from "../../middleware/verifyJWT.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class InviteRoutes { constructor(inviteController) { diff --git a/server/src/routes/logRoutes.js b/server/src/routes/v1/logRoutes.js similarity index 85% rename from server/src/routes/logRoutes.js rename to server/src/routes/v1/logRoutes.js index 867826dde..feabe669a 100755 --- a/server/src/routes/logRoutes.js +++ b/server/src/routes/v1/logRoutes.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class LogRoutes { constructor(logController) { this.router = Router(); diff --git a/server/src/routes/maintenanceWindowRoute.js b/server/src/routes/v1/maintenanceWindowRoute.js similarity index 91% rename from server/src/routes/maintenanceWindowRoute.js rename to server/src/routes/v1/maintenanceWindowRoute.js index 774434ed6..622e98f88 100755 --- a/server/src/routes/maintenanceWindowRoute.js +++ b/server/src/routes/v1/maintenanceWindowRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import MaintenanceWindow from "../db/models/MaintenanceWindow.js"; +import MaintenanceWindow from "../../db/models/MaintenanceWindow.js"; class MaintenanceWindowRoutes { constructor(maintenanceWindowController) { this.router = Router(); diff --git a/server/src/routes/monitorRoute.js b/server/src/routes/v1/monitorRoute.js similarity index 94% rename from server/src/routes/monitorRoute.js rename to server/src/routes/v1/monitorRoute.js index 043d361d1..f8f219b83 100755 --- a/server/src/routes/monitorRoute.js +++ b/server/src/routes/v1/monitorRoute.js @@ -1,7 +1,7 @@ import { Router } from "express"; -import { isAllowed } from "../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; import multer from "multer"; -import { fetchMonitorCertificate } from "../controllers/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/routes/notificationRoute.js b/server/src/routes/v1/notificationRoute.js similarity index 100% rename from server/src/routes/notificationRoute.js rename to server/src/routes/v1/notificationRoute.js diff --git a/server/src/routes/queueRoute.js b/server/src/routes/v1/queueRoute.js similarity index 93% rename from server/src/routes/queueRoute.js rename to server/src/routes/v1/queueRoute.js index 55e829b91..59e1cb744 100755 --- a/server/src/routes/queueRoute.js +++ b/server/src/routes/v1/queueRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class QueueRoutes { constructor(queueController) { this.router = Router(); diff --git a/server/src/routes/settingsRoute.js b/server/src/routes/v1/settingsRoute.js similarity index 90% rename from server/src/routes/settingsRoute.js rename to server/src/routes/v1/settingsRoute.js index 88eb67e65..76556e923 100755 --- a/server/src/routes/settingsRoute.js +++ b/server/src/routes/v1/settingsRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class SettingsRoutes { constructor(settingsController) { diff --git a/server/src/routes/statusPageRoute.js b/server/src/routes/v1/statusPageRoute.js similarity index 93% rename from server/src/routes/statusPageRoute.js rename to server/src/routes/v1/statusPageRoute.js index 53eb21fba..8300066a8 100755 --- a/server/src/routes/statusPageRoute.js +++ b/server/src/routes/v1/statusPageRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { verifyJWT } from "../middleware/verifyJWT.js"; +import { verifyJWT } from "../../middleware/verifyJWT.js"; import multer from "multer"; const upload = multer(); diff --git a/server/tests/controllers/authController.test.js b/server/tests/controllers/authController.test.js deleted file mode 100755 index 341c8eda6..000000000 --- a/server/tests/controllers/authController.test.js +++ /dev/null @@ -1,962 +0,0 @@ -import { - issueToken, - registerUser, - loginUser, - editUser, - checkSuperadminExists, - requestRecovery, - validateRecovery, - resetPassword, - deleteUser, - getAllUsers, -} from "../../controllers/authController.js"; -import jwt from "jsonwebtoken"; -import { errorMessages, successMessages } from "../../utils/messages.js"; -import sinon from "sinon"; -import { tokenType } from "../../utils/utils.js"; -import logger from "../../utils/logger.js"; - -describe("Auth Controller - issueToken", function () { - let stub; - - afterEach(function () { - sinon.restore(); // Restore stubs after each test - }); - - it("should reject with an error if jwt.sign fails", function () { - const error = new Error("jwt.sign error"); - stub = sinon.stub(jwt, "sign").throws(error); - const payload = { id: "123" }; - const appSettings = { jwtSecret: "my_secret" }; - expect(() => issueToken(payload, tokenType.ACCESS_TOKEN, appSettings)).to.throw(error); - }); - - it("should return a token if jwt.sign is successful and appSettings.jwtTTL is not defined", function () { - const payload = { id: "123" }; - const appSettings = { jwtSecret: "my_secret" }; - const expectedToken = "mockToken"; - - stub = sinon.stub(jwt, "sign").returns(expectedToken); - const token = issueToken(payload, tokenType.ACCESS_TOKEN, appSettings); - expect(token).to.equal(expectedToken); - }); - - it("should return a token if jwt.sign is successful and appSettings.jwtTTL is defined", function () { - const payload = { id: "123" }; - const appSettings = { jwtSecret: "my_secret", jwtTTL: "1s" }; - const expectedToken = "mockToken"; - - stub = sinon.stub(jwt, "sign").returns(expectedToken); - const token = issueToken(payload, tokenType.ACCESS_TOKEN, appSettings); - expect(token).to.equal(expectedToken); - }); - - it("should return a refresh token if jwt.sign is successful and appSettings.refreshTokenTTL is not defined", function () { - const payload = {}; - const appSettings = { refreshTokenSecret: "my_refresh_secret" }; - const expectedToken = "mockRefreshToken"; - - stub = sinon.stub(jwt, "sign").returns(expectedToken); - const token = issueToken(payload, tokenType.REFRESH_TOKEN, appSettings); - expect(token).to.equal(expectedToken); - }); - - it("should return a refresh token if jwt.sign is successful and appSettings.refreshTokenTTL is defined", function () { - const payload = {}; - const appSettings = { - refreshTokenSecret: "my_refresh_secret", - refreshTokenTTL: "7d", - }; - const expectedToken = "mockRefreshToken"; - - stub = sinon.stub(jwt, "sign").returns(expectedToken); - const token = issueToken(payload, tokenType.REFRESH_TOKEN, appSettings); - expect(token).to.equal(expectedToken); - }); -}); - -describe("Auth Controller - registerUser", function () { - let req, res, next; - - beforeEach(function () { - req = { - body: { - firstName: "firstname", - lastName: "lastname", - email: "test@test.com", - password: "Uptime1!", - role: ["admin"], - teamId: "123", - inviteToken: "invite", - }, - db: { - checkSuperadmin: sinon.stub(), - getInviteTokenAndDelete: sinon.stub(), - updateAppSettings: sinon.stub(), - insertUser: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub().resolves({ - jwtSecret: "my_secret", - refreshTokenSecret: "my_refresh_secret", - }), - }, - emailService: { - buildAndSendEmail: sinon.stub(), - }, - file: {}, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - sinon.stub(logger, "error"); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if body validation fails", async function () { - req.body = {}; - await registerUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if checkSuperadmin fails", async function () { - req.db.checkSuperadmin.throws(new Error("checkSuperadmin error")); - await registerUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("checkSuperadmin error"); - }); - - it("should reject with an error if getInviteTokenAndDelete fails", async function () { - req.db.checkSuperadmin.returns(true); - req.db.getInviteTokenAndDelete.throws(new Error("getInviteTokenAndDelete error")); - await registerUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getInviteTokenAndDelete error"); - }); - - it("should reject with an error if updateAppSettings fails", async function () { - req.db.checkSuperadmin.returns(false); - req.db.updateAppSettings.throws(new Error("updateAppSettings error")); - await registerUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("updateAppSettings error"); - }); - - it("should reject with an error if insertUser fails", async function () { - req.db.checkSuperadmin.resolves(false); - req.db.updateAppSettings.resolves(); - req.db.insertUser.rejects(new Error("insertUser error")); - await registerUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("insertUser error"); - }); - - it("should reject with an error if settingsService.getSettings fails", async function () { - req.db.checkSuperadmin.resolves(false); - req.db.updateAppSettings.resolves(); - req.db.insertUser.resolves({ _id: "123" }); - req.settingsService.getSettings.rejects(new Error("settingsService.getSettings error")); - await registerUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("settingsService.getSettings error"); - }); - - it("should log an error if emailService.buildAndSendEmail fails", async function () { - req.db.checkSuperadmin.resolves(false); - req.db.updateAppSettings.resolves(); - req.db.insertUser.returns({ _id: "123" }); - req.settingsService.getSettings.returns({ - jwtSecret: "my_secret", - refreshTokenSecret: "my_secret", - }); - req.emailService.buildAndSendEmail.rejects(new Error("emailService error")); - await registerUser(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal("emailService error"); - }); - - it("should return a success message and data if all operations are successful", async function () { - const user = { _id: "123" }; - req.db.checkSuperadmin.resolves(false); - req.db.updateAppSettings.resolves(); - req.db.insertUser.returns(user); - req.settingsService.getSettings.returns({ - jwtSecret: "my_secret", - refreshTokenSecret: "my_secret", - }); - req.emailService.buildAndSendEmail.resolves("message-id"); - await registerUser(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_CREATE_USER, - data: { user, token: sinon.match.string, refreshToken: sinon.match.string }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should return a success message and data if all operations are successful and superAdmin true", async function () { - const user = { _id: "123" }; - req.db.checkSuperadmin.resolves(true); - req.db.updateAppSettings.resolves(); - req.db.insertUser.returns(user); - req.settingsService.getSettings.returns({ - jwtSecret: "my_secret", - refreshTokenSecret: "my_secret", - }); - req.emailService.buildAndSendEmail.resolves("message-id"); - await registerUser(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_CREATE_USER, - data: { user, token: sinon.match.string, refreshToken: sinon.match.string }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); -}); - -describe("Auth Controller - loginUser", function () { - let req, res, next, user; - - beforeEach(function () { - req = { - body: { email: "test@example.com", password: "Password123!" }, - db: { - getUserByEmail: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub().resolves({ - jwtSecret: "my_secret", - refreshTokenSecret: "my_refresh_token", - }), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - user = { - _doc: { - email: "test@example.com", - }, - comparePassword: sinon.stub(), - }; - }); - - it("should reject with an error if validation fails", async function () { - req.body = {}; - await loginUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if getUserByEmail fails", async function () { - req.db.getUserByEmail.rejects(new Error("getUserByEmail error")); - await loginUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getUserByEmail error"); - }); - - it("should login user successfully", async function () { - req.db.getUserByEmail.resolves(user); - user.comparePassword.resolves(true); - await loginUser(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_LOGIN_USER, - data: { - user: { - email: "test@example.com", - avatarImage: undefined, - }, - token: sinon.match.string, - refreshToken: sinon.match.string, - }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should reject a user with an incorrect password", async function () { - req.body = { - email: "test@test.com", - password: "Password123!", - }; - req.db.getUserByEmail.resolves(user); - user.comparePassword.resolves(false); - await loginUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal(errorMessages.AUTH_INCORRECT_PASSWORD); - }); -}); - -describe("Auth Controller - refreshAuthToken", function () { - let req, res, next, issueTokenStub; - - beforeEach(function () { - req = { - headers: { - "x-refresh-token": "valid_refresh_token", - authorization: "Bearer old_auth_token", - }, - settingsService: { - getSettings: sinon.stub().resolves({ - jwtSecret: "my_secret", - refreshTokenSecret: "my_refresh_secret", - }), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - sinon.stub(jwt, "verify"); - - issueTokenStub = sinon.stub().returns("new_auth_token"); - sinon.replace({ issueToken }, "issueToken", issueTokenStub); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject if no refresh token is provided", async function () { - delete req.headers["x-refresh-token"]; - await refreshAuthToken(req, res, next); - - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal(errorMessages.NO_REFRESH_TOKEN); - expect(next.firstCall.args[0].status).to.equal(401); - }); - - it("should reject if the refresh token is invalid", async function () { - jwt.verify.yields(new Error("invalid token")); - await refreshAuthToken(req, res, next); - - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal(errorMessages.INVALID_REFRESH_TOKEN); - expect(next.firstCall.args[0].status).to.equal(401); - }); - - it("should reject if the refresh token is expired", async function () { - const error = new Error("Token expired"); - error.name = "TokenExpiredError"; - jwt.verify.yields(error); - await refreshAuthToken(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal(errorMessages.EXPIRED_REFRESH_TOKEN); - expect(next.firstCall.args[0].status).to.equal(401); - }); - - it("should reject if settingsService.getSettings fails", async function () { - req.settingsService.getSettings.rejects(new Error("settingsService.getSettings error")); - await refreshAuthToken(req, res, next); - - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("settingsService.getSettings error"); - }); - - it("should generate a new auth token if the refresh token is valid", async function () { - const decodedPayload = { expiresIn: "60" }; - jwt.verify.callsFake(() => { - return decodedPayload; - }); - await refreshAuthToken(req, res, next); - - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_TOKEN_REFRESHED, - data: { - user: decodedPayload, - token: sinon.match.string, - refreshToken: "valid_refresh_token", - }, - }) - ).to.be.true; - }); -}); - -describe("Auth Controller - editUser", function () { - let req, res, next, stub, user; - - beforeEach(function () { - req = { - params: { userId: "123" }, - body: { password: "Password1!", newPassword: "Password2!" }, - headers: { authorization: "Bearer token" }, - user: { _id: "123" }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "my_secret" }), - }, - db: { - getUserByEmail: sinon.stub(), - updateUser: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - stub = sinon.stub(jwt, "verify").returns({ email: "test@example.com" }); - }); - - afterEach(function () { - sinon.restore(); - stub.restore(); - }); - - it("should reject with an error if param validation fails", async function () { - req.params = {}; - await editUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if body validation fails", async function () { - req.body = { invalid: 1 }; - await editUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if param.userId !== req.user._id", async function () { - req.params = { userId: "456" }; - await editUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(401); - }); - - it("should reject with an error if !req.body.password and getUserByEmail fails", async function () { - req.db.getUserByEmail.rejects(new Error("getUserByEmail error")); - await editUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getUserByEmail error"); - }); - - it("should reject with an error if user.comparePassword fails", async function () { - req.db.getUserByEmail.returns({ - comparePassword: sinon.stub().rejects(new Error("Bad Password Match")), - }); - await editUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Bad Password Match"); - }); - - it("should reject with an error if user.comparePassword returns false", async function () { - req.db.getUserByEmail.returns({ - comparePassword: sinon.stub().returns(false), - }); - await editUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(401); - expect(next.firstCall.args[0].message).to.equal(errorMessages.AUTH_INCORRECT_PASSWORD); - }); - - it("should edit a user if it receives a proper request", async function () { - const user = { - comparePassword: sinon.stub().resolves(true), - }; - req.db.getUserByEmail.resolves(user); - req.db.updateUser.resolves({ email: "test@example.com" }); - - await editUser(req, res, next); - - expect(req.db.getUserByEmail.calledOnce).to.be.true; - expect(req.db.updateUser.calledOnce).to.be.true; - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_UPDATE_USER, - data: { email: "test@example.com" }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should edit a user if it receives a proper request and both password fields are undefined", async function () { - req.body.password = undefined; - req.body.newPassword = undefined; - req.db.getUserByEmail.resolves(user); - req.db.updateUser.resolves({ email: "test@example.com" }); - - await editUser(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_UPDATE_USER, - data: { email: "test@example.com" }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should reject an edit request if password format is incorrect", async function () { - req.body = { password: "bad_password", newPassword: "bad_password" }; - const user = { - comparePassword: sinon.stub().resolves(true), - }; - req.db.getUserByEmail.resolves(user); - - await editUser(req, res, next); - - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); -}); - -describe("Auth Controller - checkSuperadminExists", function () { - let req, res, next; - - beforeEach(function () { - req = { - db: { - checkSuperadmin: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - it("should reject with an error if checkSuperadmin fails", async function () { - req.db.checkSuperadmin.rejects(new Error("checkSuperadmin error")); - await checkSuperadminExists(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("checkSuperadmin error"); - }); - - it("should return true if a superadmin exists", async function () { - req.db.checkSuperadmin.resolves(true); - await checkSuperadminExists(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_SUPERADMIN_EXISTS, - data: true, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should return false if a superadmin does not exist", async function () { - req.db.checkSuperadmin.resolves(false); - await checkSuperadminExists(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_SUPERADMIN_EXISTS, - data: false, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); -}); - -describe("Auth Controller - requestRecovery", function () { - let req, res, next; - - beforeEach(function () { - req = { - body: { email: "test@test.com" }, - db: { - getUserByEmail: sinon.stub(), - requestRecoveryToken: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub().returns({ clientHost: "http://localhost" }), - }, - emailService: { - buildAndSendEmail: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - it("should reject with an error if validation fails", async function () { - req.body = {}; - await requestRecovery(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if getUserByEmail fails", async function () { - req.db.getUserByEmail.rejects(new Error("getUserByEmail error")); - await requestRecovery(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getUserByEmail error"); - }); - - it("should throw an error if the user is not found", async function () { - req.db.getUserByEmail.resolves(null); - await requestRecovery(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - // expect(next.firstCall.args[0].message).to.equal( - // errorMessages.FRIENDLY_ERROR - // ); - }); - - it("should throw an error if the email is not provided", async function () { - req.body = {}; - await requestRecovery(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should return a success message if the email is provided", async function () { - const user = { firstName: "John" }; - const recoveryToken = { token: "recovery-token" }; - const msgId = "message-id"; - req.db.getUserByEmail.resolves(user); - req.db.requestRecoveryToken.resolves(recoveryToken); - req.emailService.buildAndSendEmail.resolves(msgId); - await requestRecovery(req, res, next); - expect(req.db.getUserByEmail.calledOnceWith("test@test.com")).to.be.true; - expect(req.db.requestRecoveryToken.calledOnceWith(req, res)).to.be.true; - expect( - req.emailService.buildAndSendEmail.calledOnceWith( - "passwordResetTemplate", - { - name: "John", - email: "test@test.com", - url: "http://localhost/set-new-password/recovery-token", - }, - "test@test.com", - "Checkmate Password Reset" - ) - ).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.AUTH_CREATE_RECOVERY_TOKEN, - data: msgId, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); -}); - -describe("Auth Controller - validateRecovery", function () { - let req, res, next; - - beforeEach(function () { - req = { - body: { recoveryToken: "recovery-token" }, - db: { - validateRecoveryToken: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - it("should reject with an error if validation fails", async function () { - req.body = {}; - await validateRecovery(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if validateRecoveryToken fails", async function () { - req.db.validateRecoveryToken.rejects(new Error("validateRecoveryToken error")); - await validateRecovery(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("validateRecoveryToken error"); - }); - - it("should return a success message if the token is valid", async function () { - req.db.validateRecoveryToken.resolves(); - await validateRecovery(req, res, next); - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.AUTH_VERIFY_RECOVERY_TOKEN, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); -}); - -describe("Auth Controller - resetPassword", function () { - let req, res, next, newPasswordValidation, handleValidationError, handleError; - - beforeEach(function () { - req = { - body: { - recoveryToken: "recovery-token", - password: "Password1!", - }, - db: { - resetPassword: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - newPasswordValidation = { - validateAsync: sinon.stub(), - }; - handleValidationError = sinon.stub(); - handleError = sinon.stub(); - }); - - it("should reject with an error if validation fails", async function () { - req.body = { password: "bad_password" }; - await resetPassword(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if resetPassword fails", async function () { - const error = new Error("resetPassword error"); - newPasswordValidation.validateAsync.resolves(); - req.db.resetPassword.rejects(error); - await resetPassword(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("resetPassword error"); - }); - - it("should reset password successfully", async function () { - const user = { _doc: {} }; - const appSettings = { jwtSecret: "my_secret" }; - const token = "token"; - - newPasswordValidation.validateAsync.resolves(); - req.db.resetPassword.resolves(user); - req.settingsService.getSettings.resolves(appSettings); - - await resetPassword(req, res, next); - - expect(req.db.resetPassword.calledOnceWith(req, res)).to.be.true; - expect(req.settingsService.getSettings.calledOnce).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.AUTH_RESET_PASSWORD, - data: { user: sinon.match.object, token: sinon.match.string }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); -}); - -describe("Auth Controller - deleteUser", function () { - let req, res, next, handleError; - - beforeEach(function () { - req = { - headers: { - authorization: "Bearer token", - }, - db: { - getUserByEmail: sinon.stub(), - getMonitorsByTeamId: sinon.stub(), - deleteJob: sinon.stub(), - deleteChecks: sinon.stub(), - deletePageSpeedChecksByMonitorId: sinon.stub(), - deleteNotificationsByMonitorId: sinon.stub(), - deleteTeam: sinon.stub(), - deleteAllOtherUsers: sinon.stub(), - deleteMonitorsByUserId: sinon.stub(), - deleteUser: sinon.stub(), - }, - jobQueue: { - deleteJob: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - - sinon.stub(jwt, "decode"); - - handleError = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should throw an error if user is not found", async function () { - jwt.decode.returns({ email: "test@example.com" }); - req.db.getUserByEmail.throws(new Error(errorMessages.DB_USER_NOT_FOUND)); - - await deleteUser(req, res, next); - - expect(req.db.getUserByEmail.calledOnceWith("test@example.com")).to.be.true; - expect(next.calledOnce).to.be.true; - expect(next.firstCall.args[0].message).to.equal(errorMessages.DB_USER_NOT_FOUND); - expect(res.status.notCalled).to.be.true; - expect(res.json.notCalled).to.be.true; - }); - - it("should delete user and associated data if user is superadmin", async function () { - const user = { - _id: "user_id", - email: "test@example.com", - role: ["superadmin"], - teamId: "team_id", - }; - const monitors = [{ _id: "monitor_id" }]; - - jwt.decode.returns({ email: "test@example.com" }); - req.db.getUserByEmail.resolves(user); - req.db.getMonitorsByTeamId.resolves({ monitors }); - - await deleteUser(req, res, next); - - expect(req.db.getUserByEmail.calledOnceWith("test@example.com")).to.be.true; - expect( - req.db.getMonitorsByTeamId.calledOnceWith({ - params: { teamId: "team_id" }, - }) - ).to.be.true; - expect(req.jobQueue.deleteJob.calledOnceWith(monitors[0])).to.be.true; - expect(req.db.deleteChecks.calledOnceWith("monitor_id")).to.be.true; - expect(req.db.deletePageSpeedChecksByMonitorId.calledOnceWith("monitor_id")).to.be.true; - expect(req.db.deleteNotificationsByMonitorId.calledOnceWith("monitor_id")).to.be.true; - expect(req.db.deleteTeam.calledOnceWith("team_id")).to.be.true; - expect(req.db.deleteAllOtherUsers.calledOnce).to.be.true; - expect(req.db.deleteMonitorsByUserId.calledOnceWith("user_id")).to.be.true; - expect(req.db.deleteUser.calledOnceWith("user_id")).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.AUTH_DELETE_USER, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should delete user if user is not superadmin", async function () { - const user = { - _id: "user_id", - email: "test@example.com", - role: ["user"], - teamId: "team_id", - }; - - jwt.decode.returns({ email: "test@example.com" }); - req.db.getUserByEmail.resolves(user); - - await deleteUser(req, res, next); - - expect(req.db.getUserByEmail.calledOnceWith("test@example.com")).to.be.true; - expect( - req.db.getMonitorsByTeamId.calledOnceWith({ - params: { teamId: "team_id" }, - }) - ).to.be.true; - expect(req.db.deleteUser.calledOnceWith("user_id")).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.AUTH_DELETE_USER, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should handle errors", async function () { - const error = new Error("Something went wrong"); - const SERVICE_NAME = "AuthController"; - jwt.decode.returns({ email: "test@example.com" }); - req.db.getUserByEmail.rejects(error); - await deleteUser(req, res, next); - expect(next.calledOnce).to.be.true; - expect(next.firstCall.args[0].message).to.equal("Something went wrong"); - expect(res.status.notCalled).to.be.true; - expect(res.json.notCalled).to.be.true; - }); -}); - -describe("Auth Controller - getAllUsers", function () { - let req, res, next; - - beforeEach(function () { - req = { - db: { - getAllUsers: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); // Restore the original methods after each test - }); - - it("should return 200 and all users", async function () { - const allUsers = [{ id: 1, name: "John Doe" }]; - req.db.getAllUsers.resolves(allUsers); - - await getAllUsers(req, res, next); - - expect(req.db.getAllUsers.calledOnce).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: "Got all users", - data: allUsers, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should call next with error when an exception occurs", async function () { - const error = new Error("Something went wrong"); - req.db.getAllUsers.rejects(error); - await getAllUsers(req, res, next); - expect(req.db.getAllUsers.calledOnce).to.be.true; - expect(next.calledOnce).to.be.true; - expect(res.status.notCalled).to.be.true; - expect(res.json.notCalled).to.be.true; - }); -}); diff --git a/server/tests/controllers/checkController.test.js b/server/tests/controllers/checkController.test.js deleted file mode 100755 index c5fb57d50..000000000 --- a/server/tests/controllers/checkController.test.js +++ /dev/null @@ -1,372 +0,0 @@ -import { createCheck, getChecks, getTeamChecks, deleteChecks, deleteChecksByTeamId, updateChecksTTL } from "../../controllers/checkController.js"; -import jwt from "jsonwebtoken"; -import { errorMessages, successMessages } from "../../utils/messages.js"; -import sinon from "sinon"; -describe("Check Controller - createCheck", function () { - let req, res, next, handleError; - - beforeEach(function () { - req = { - params: {}, - body: {}, - db: { - createCheck: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - handleError = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); // Restore the original methods after each test - }); - - it("should reject with a validation if params are invalid", async function () { - await createCheck(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with a validation error if body is invalid", async function () { - req.params = { - monitorId: "monitorId", - }; - await createCheck(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should call next with error if data retrieval fails", async function () { - req.params = { - monitorId: "monitorId", - }; - req.body = { - monitorId: "monitorId", - status: true, - responseTime: 100, - statusCode: 200, - message: "message", - }; - req.db.createCheck.rejects(new Error("error")); - await createCheck(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - }); - - it("should return a success message if check is created", async function () { - req.params = { - monitorId: "monitorId", - }; - req.db.createCheck.resolves({ id: "123" }); - req.body = { - monitorId: "monitorId", - status: true, - responseTime: 100, - statusCode: 200, - message: "message", - }; - await createCheck(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.CHECK_CREATE, - data: { id: "123" }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); -}); - -describe("Check Controller - getChecks", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: {}, - query: {}, - db: { - getChecks: sinon.stub(), - getChecksCount: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with a validation error if params are invalid", async function () { - await getChecks(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should return a success message if checks are found", async function () { - req.params = { - monitorId: "monitorId", - }; - req.db.getChecks.resolves([{ id: "123" }]); - req.db.getChecksCount.resolves(1); - await getChecks(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.CHECK_GET, - data: { checksCount: 1, checks: [{ id: "123" }] }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should call next with error if data retrieval fails", async function () { - req.params = { - monitorId: "monitorId", - }; - req.db.getChecks.rejects(new Error("error")); - await getChecks(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - }); -}); - -describe("Check Controller - getTeamChecks", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: {}, - query: {}, - db: { - getTeamChecks: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with a validation error if params are invalid", async function () { - await getTeamChecks(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should return 200 and check data on successful validation and data retrieval", async function () { - req.params = { teamId: "1" }; - const checkData = [{ id: 1, name: "Check 1" }]; - req.db.getTeamChecks.resolves(checkData); - - await getTeamChecks(req, res, next); - expect(req.db.getTeamChecks.calledOnceWith(req)).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.CHECK_GET, - data: checkData, - }) - ).to.be.true; - }); - - it("should call next with error if data retrieval fails", async function () { - req.params = { teamId: "1" }; - req.db.getTeamChecks.rejects(new Error("Retrieval Error")); - await getTeamChecks(req, res, next); - expect(req.db.getTeamChecks.calledOnceWith(req)).to.be.true; - expect(next.firstCall.args[0]).to.be.an("error"); - expect(res.status.notCalled).to.be.true; - expect(res.json.notCalled).to.be.true; - }); -}); - -describe("Check Controller - deleteChecks", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: {}, - db: { - deleteChecks: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function () { - await deleteChecks(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should call next with error if data retrieval fails", async function () { - req.params = { monitorId: "1" }; - req.db.deleteChecks.rejects(new Error("Deletion Error")); - await deleteChecks(req, res, next); - expect(req.db.deleteChecks.calledOnceWith(req.params.monitorId)).to.be.true; - expect(next.firstCall.args[0]).to.be.an("error"); - expect(res.status.notCalled).to.be.true; - expect(res.json.notCalled).to.be.true; - }); - - it("should delete checks successfully", async function () { - req.params = { monitorId: "123" }; - req.db.deleteChecks.resolves(1); - await deleteChecks(req, res, next); - expect(req.db.deleteChecks.calledOnceWith(req.params.monitorId)).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.CHECK_DELETE, - data: { deletedCount: 1 }, - }) - ).to.be.true; - }); -}); - -describe("Check Controller - deleteChecksByTeamId", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: {}, - db: { - deleteChecksByTeamId: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function () { - await deleteChecksByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should call next with error if data retrieval fails", async function () { - req.params = { teamId: "1" }; - req.db.deleteChecksByTeamId.rejects(new Error("Deletion Error")); - await deleteChecksByTeamId(req, res, next); - expect(req.db.deleteChecksByTeamId.calledOnceWith(req.params.teamId)).to.be.true; - expect(next.firstCall.args[0]).to.be.an("error"); - expect(res.status.notCalled).to.be.true; - expect(res.json.notCalled).to.be.true; - }); - - it("should delete checks successfully", async function () { - req.params = { teamId: "123" }; - req.db.deleteChecksByTeamId.resolves(1); - await deleteChecksByTeamId(req, res, next); - expect(req.db.deleteChecksByTeamId.calledOnceWith(req.params.teamId)).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.CHECK_DELETE, - data: { deletedCount: 1 }, - }) - ).to.be.true; - }); -}); - -describe("Check Controller - updateCheckTTL", function () { - let stub, req, res, next; - - beforeEach(function () { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - - req = { - body: {}, - headers: { authorization: "Bearer token" }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "my_secret" }), - }, - db: { - updateChecksTTL: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - stub.restore(); - }); - - it("should reject if body validation fails", async function () { - await updateChecksTTL(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should throw a JwtError if verification fails", async function () { - stub.restore(); - req.body = { - ttl: 1, - }; - await updateChecksTTL(req, res, next); - expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); - }); - - it("should call next with error if data retrieval fails", async function () { - req.body = { - ttl: 1, - }; - req.db.updateChecksTTL.rejects(new Error("Update Error")); - await updateChecksTTL(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - }); - - it("should update TTL successfully", async function () { - req.body = { - ttl: 1, - }; - req.db.updateChecksTTL.resolves(); - await updateChecksTTL(req, res, next); - expect(req.db.updateChecksTTL.calledOnceWith("123", 1 * 86400)).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.CHECK_UPDATE_TTL, - }) - ).to.be.true; - }); -}); diff --git a/server/tests/controllers/controllerUtils.test.js b/server/tests/controllers/controllerUtils.test.js deleted file mode 100755 index a55318857..000000000 --- a/server/tests/controllers/controllerUtils.test.js +++ /dev/null @@ -1,157 +0,0 @@ -import sinon from "sinon"; - -import { handleValidationError, handleError, fetchMonitorCertificate } from "../../controllers/controllerUtils.js"; -import { expect } from "chai"; -import sslChecker from "ssl-checker"; -import { afterEach } from "node:test"; -import exp from "constants"; - -describe("controllerUtils - handleValidationError", function () { - it("should set status to 422", function () { - const error = {}; - const serviceName = "TestService"; - const result = handleValidationError(error, serviceName); - expect(result.status).to.equal(422); - }); - - it("should set service to the provided serviceName", function () { - const error = {}; - const serviceName = "TestService"; - const result = handleValidationError(error, serviceName); - expect(result.service).to.equal(serviceName); - }); - - it("should set message to error.details[0].message if present", function () { - const error = { - details: [{ message: "Detail message" }], - }; - const serviceName = "TestService"; - const result = handleValidationError(error, serviceName); - expect(result.message).to.equal("Detail message"); - }); - - it("should set message to error.message if error.details is not present", function () { - const error = { - message: "Error message", - }; - const serviceName = "TestService"; - const result = handleValidationError(error, serviceName); - expect(result.message).to.equal("Error message"); - }); - - it('should set message to "Validation Error" if neither error.details nor error.message is present', function () { - const error = {}; - const serviceName = "TestService"; - const result = handleValidationError(error, serviceName); - expect(result.message).to.equal("Validation Error"); - }); -}); - -describe("controllerUtils - handleError", function () { - it("should set stats to the provided status if error.code is undefined", function () { - const error = {}; - const serviceName = "TestService"; - const method = "testMethod"; - const status = 400; - const result = handleError(error, serviceName, method, status); - expect(result.status).to.equal(status); - }); - - it("should not overwrite error.code if it is already defined", function () { - const error = { status: 404 }; - const serviceName = "TestService"; - const method = "testMethod"; - const status = 400; - const result = handleError(error, serviceName, method, status); - expect(result.status).to.equal(404); - }); - - it("should set service to the provided serviceName if error.service is undefined", function () { - const error = {}; - const serviceName = "TestService"; - const method = "testMethod"; - const result = handleError(error, serviceName, method); - expect(result.service).to.equal(serviceName); - }); - - it("should not overwrite error.service if it is already defined", function () { - const error = { service: "ExistingService" }; - const serviceName = "TestService"; - const method = "testMethod"; - const result = handleError(error, serviceName, method); - expect(result.service).to.equal("ExistingService"); - }); - - it("should set method to the provided method if error.method is undefined", function () { - const error = {}; - const serviceName = "TestService"; - const method = "testMethod"; - const result = handleError(error, serviceName, method); - expect(result.method).to.equal(method); - }); - - it("should not overwrite error.method if it is already defined", function () { - const error = { method: "existingMethod" }; - const serviceName = "TestService"; - const method = "testMethod"; - const result = handleError(error, serviceName, method); - expect(result.method).to.equal("existingMethod"); - }); - - it("should set code to 500 if error.code is undefined and no code is provided", function () { - const error = {}; - const serviceName = "TestService"; - const method = "testMethod"; - const result = handleError(error, serviceName, method); - expect(result.status).to.equal(500); - }); -}); - -describe("controllerUtils - fetchMonitorCertificate", function () { - let sslChecker, monitor; - - beforeEach(function () { - monitor = { - url: "https://www.google.com", - }; - sslChecker = sinon.stub(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should reject with an error if a URL does not parse", async function () { - monitor.url = "invalidurl"; - try { - await fetchMonitorCertificate(sslChecker, monitor); - } catch (error) { - expect(error).to.be.an("error"); - expect(error.message).to.equal("Invalid URL"); - } - }); - - it("should reject with an error if sslChecker throws an error", async function () { - sslChecker.rejects(new Error("Test error")); - try { - await fetchMonitorCertificate(sslChecker, monitor); - } catch (error) { - expect(error).to.be.an("error"); - expect(error.message).to.equal("Test error"); - } - }); - - it("should return a certificate if sslChecker resolves", async function () { - sslChecker.resolves({ validTo: "2022-01-01" }); - const result = await fetchMonitorCertificate(sslChecker, monitor); - expect(result).to.deep.equal({ validTo: "2022-01-01" }); - }); - - it("should throw an error if a ssl-checker returns null", async function () { - sslChecker.returns(null); - await fetchMonitorCertificate(sslChecker, monitor).catch((error) => { - expect(error).to.be.an("error"); - expect(error.message).to.equal("Certificate not found"); - }); - }); -}); diff --git a/server/tests/controllers/inviteController.test.js b/server/tests/controllers/inviteController.test.js deleted file mode 100755 index 635bdf653..000000000 --- a/server/tests/controllers/inviteController.test.js +++ /dev/null @@ -1,202 +0,0 @@ -import { issueInvitation, inviteVerifyController } from "../../controllers/inviteController.js"; -import jwt from "jsonwebtoken"; -import sinon from "sinon"; -import joi from "joi"; -describe("inviteController - issueInvitation", function () { - let req, res, next, stub; - - beforeEach(function () { - req = { - headers: { authorization: "Bearer token" }, - body: { - email: "test@test.com", - role: ["admin"], - teamId: "123", - }, - db: { requestInviteToken: sinon.stub() }, - settingsService: { getSettings: sinon.stub() }, - emailService: { buildAndSendEmail: sinon.stub() }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if role validation fails", async function () { - stub = sinon.stub(jwt, "decode").callsFake(() => { - return { role: ["bad_role"], firstname: "first_name", teamId: "1" }; - }); - await issueInvitation(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0]).to.be.instanceOf(joi.ValidationError); - expect(next.firstCall.args[0].status).to.equal(422); - stub.restore(); - }); - - it("should reject with an error if body validation fails", async function () { - stub = sinon.stub(jwt, "decode").callsFake(() => { - return { role: ["admin"], firstname: "first_name", teamId: "1" }; - }); - req.body = {}; - await issueInvitation(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - stub.restore(); - }); - - it("should reject with an error if DB operations fail", async function () { - stub = sinon.stub(jwt, "decode").callsFake(() => { - return { role: ["admin"], firstname: "first_name", teamId: "1" }; - }); - req.db.requestInviteToken.throws(new Error("DB error")); - await issueInvitation(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - stub.restore(); - }); - - it("should send an invite successfully", async function () { - const token = "token"; - const decodedToken = { - role: "admin", - firstname: "John", - teamId: "team123", - }; - const inviteToken = { token: "inviteToken" }; - const clientHost = "http://localhost"; - - stub = sinon.stub(jwt, "decode").callsFake(() => { - return decodedToken; - }); - req.db.requestInviteToken.resolves(inviteToken); - req.settingsService.getSettings.returns({ clientHost }); - req.emailService.buildAndSendEmail.resolves(); - await issueInvitation(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: "Invite sent", - data: inviteToken, - }) - ).to.be.true; - stub.restore(); - }); - - it("should send an email successfully", async function () { - const token = "token"; - const decodedToken = { - role: "admin", - firstname: "John", - teamId: "team123", - }; - const inviteToken = { token: "inviteToken" }; - const clientHost = "http://localhost"; - - stub = sinon.stub(jwt, "decode").callsFake(() => { - return decodedToken; - }); - req.db.requestInviteToken.resolves(inviteToken); - req.settingsService.getSettings.returns({ clientHost }); - req.emailService.buildAndSendEmail.resolves(); - - await issueInvitation(req, res, next); - expect(req.emailService.buildAndSendEmail.calledOnce).to.be.true; - expect( - req.emailService.buildAndSendEmail.calledWith( - "employeeActivationTemplate", - { - name: "John", - link: "http://localhost/register/inviteToken", - }, - "test@test.com", - "Welcome to Uptime Monitor" - ) - ).to.be.true; - stub.restore(); - }); - - it("should continue executing if sending an email fails", async function () { - const token = "token"; - req.emailService.buildAndSendEmail.rejects(new Error("Email error")); - const decodedToken = { - role: "admin", - firstname: "John", - teamId: "team123", - }; - const inviteToken = { token: "inviteToken" }; - const clientHost = "http://localhost"; - - stub = sinon.stub(jwt, "decode").callsFake(() => { - return decodedToken; - }); - req.db.requestInviteToken.resolves(inviteToken); - req.settingsService.getSettings.returns({ clientHost }); - await issueInvitation(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: "Invite sent", - data: inviteToken, - }) - ).to.be.true; - stub.restore(); - }); -}); - -describe("inviteController - inviteVerifyController", function () { - let req, res, next; - - beforeEach(function () { - req = { - body: { token: "token" }, - db: { - getInviteToken: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if body validation fails", async function () { - req.body = {}; - await inviteVerifyController(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function () { - req.db.getInviteToken.throws(new Error("DB error")); - await inviteVerifyController(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return 200 and invite data when validation and invite retrieval are successful", async function () { - req.db.getInviteToken.resolves({ invite: "data" }); - await inviteVerifyController(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - status: "success", - msg: "Invite verified", - data: { invite: "data" }, - }) - ).to.be.true; - expect(next.called).to.be.false; - }); -}); diff --git a/server/tests/controllers/maintenanceWindowController.test.js b/server/tests/controllers/maintenanceWindowController.test.js deleted file mode 100755 index 0ea07b389..000000000 --- a/server/tests/controllers/maintenanceWindowController.test.js +++ /dev/null @@ -1,422 +0,0 @@ -import { - createMaintenanceWindows, - getMaintenanceWindowById, - getMaintenanceWindowsByTeamId, - getMaintenanceWindowsByMonitorId, - deleteMaintenanceWindow, - editMaintenanceWindow, -} from "../../controllers/maintenanceWindowController.js"; - -import jwt from "jsonwebtoken"; -import { successMessages } from "../../utils/messages.js"; -import sinon from "sinon"; - -describe("maintenanceWindowController - createMaintenanceWindows", function () { - let req, res, next, stub; - - beforeEach(function () { - req = { - body: { - monitors: ["66ff52e7c5911c61698ac724"], - name: "window", - active: true, - start: "2024-10-11T05:27:13.747Z", - end: "2024-10-11T05:27:14.747Z", - repeat: "123", - }, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - createMaintenanceWindow: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if body validation fails", async function () { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - req.body = {}; - await createMaintenanceWindows(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - stub.restore(); - }); - - it("should reject with an error if jwt.verify fails", async function () { - stub = sinon.stub(jwt, "verify").throws(new jwt.JsonWebTokenError()); - await createMaintenanceWindows(req, res, next); - expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); - stub.restore(); - }); - - it("should reject with an error DB operations fail", async function () { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - req.db.createMaintenanceWindow.throws(new Error("DB error")); - await createMaintenanceWindows(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - stub.restore(); - }); - - it("should return success message if all operations are successful", async function () { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - await createMaintenanceWindows(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(201); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_CREATE, - }) - ).to.be.true; - stub.restore(); - }); - - it("should return success message if all operations are successful with active set to undefined", async function () { - req.body.active = undefined; - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - await createMaintenanceWindows(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(201); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_CREATE, - }) - ).to.be.true; - stub.restore(); - }); -}); - -describe("maintenanceWindowController - getMaintenanceWindowById", function () { - let req, res, next; - - beforeEach(function () { - req = { - body: {}, - params: { - id: "123", - }, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - getMaintenanceWindowById: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - it("should reject if param validation fails", async function () { - req.params = {}; - await getMaintenanceWindowById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject if DB operations fail", async function () { - req.db.getMaintenanceWindowById.throws(new Error("DB error")); - await getMaintenanceWindowById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message with data if all operations are successful", async function () { - req.db.getMaintenanceWindowById.returns({ id: "123" }); - await getMaintenanceWindowById(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_GET_BY_ID, - data: { id: "123" }, - }) - ).to.be.true; - }); -}); - -describe("maintenanceWindowController - getMaintenanceWindowsByTeamId", function () { - let req, res, next, stub; - - beforeEach(function () { - req = { - body: {}, - params: {}, - query: {}, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - getMaintenanceWindowsByTeamId: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - it("should reject if query validation fails", async function () { - req.query = { - invalid: 1, - }; - await getMaintenanceWindowsByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject if jwt.verify fails", async function () { - stub = sinon.stub(jwt, "verify").throws(new jwt.JsonWebTokenError()); - await getMaintenanceWindowsByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); - stub.restore(); - }); - - it("should reject with an error if DB operations fail", async function () { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - req.db.getMaintenanceWindowsByTeamId.throws(new Error("DB error")); - await getMaintenanceWindowsByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - stub.restore(); - }); - - it("should return success message with data if all operations are successful", async function () { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - req.db.getMaintenanceWindowsByTeamId.returns([{ id: "123" }]); - await getMaintenanceWindowsByTeamId(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_GET_BY_TEAM, - data: [{ id: jwt.verify().teamId }], - }) - ).to.be.true; - stub.restore(); - }); -}); - -describe("maintenanceWindowController - getMaintenanceWindowsByMonitorId", function () { - let req, res, next; - - beforeEach(function () { - req = { - body: {}, - params: { - monitorId: "123", - }, - query: {}, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - getMaintenanceWindowsByMonitorId: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject if param validation fails", async function () { - req.params = {}; - await getMaintenanceWindowsByMonitorId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function () { - req.db.getMaintenanceWindowsByMonitorId.throws(new Error("DB error")); - await getMaintenanceWindowsByMonitorId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message with data if all operations are successful", async function () { - const data = [{ monitorId: "123" }]; - req.db.getMaintenanceWindowsByMonitorId.returns(data); - await getMaintenanceWindowsByMonitorId(req, res, next); - expect(req.db.getMaintenanceWindowsByMonitorId.calledOnceWith(req.params.monitorId)); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_GET_BY_MONITOR, - data: data, - }) - ).to.be.true; - }); -}); - -describe("maintenanceWindowController - deleteMaintenanceWindow", function () { - let req, res, next; - - beforeEach(function () { - req = { - body: {}, - params: { - id: "123", - }, - query: {}, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - deleteMaintenanceWindowById: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject if param validation fails", async function () { - req.params = {}; - await deleteMaintenanceWindow(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function () { - req.db.deleteMaintenanceWindowById.throws(new Error("DB error")); - await deleteMaintenanceWindow(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message if all operations are successful", async function () { - await deleteMaintenanceWindow(req, res, next); - expect(req.db.deleteMaintenanceWindowById.calledOnceWith(req.params.id)); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_DELETE, - }) - ).to.be.true; - }); -}); - -describe("maintenanceWindowController - editMaintenanceWindow", function () { - let req, res, next; - - beforeEach(function () { - req = { - body: { - active: true, - name: "test", - }, - params: { - id: "123", - }, - query: {}, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - editMaintenanceWindowById: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject if param validation fails", async function () { - req.params = {}; - await editMaintenanceWindow(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject if body validation fails", async function () { - req.body = { invalid: 1 }; - await editMaintenanceWindow(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function () { - req.db.editMaintenanceWindowById.throws(new Error("DB error")); - await editMaintenanceWindow(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message with data if all operations are successful", async function () { - const data = { id: "123" }; - req.db.editMaintenanceWindowById.returns(data); - - await editMaintenanceWindow(req, res, next); - expect(req.db.editMaintenanceWindowById.calledOnceWith(req.params.id, req.body)); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_EDIT, - data: data, - }) - ).to.be.true; - }); -}); diff --git a/server/tests/controllers/monitorController.test.js b/server/tests/controllers/monitorController.test.js deleted file mode 100755 index 963862579..000000000 --- a/server/tests/controllers/monitorController.test.js +++ /dev/null @@ -1,1116 +0,0 @@ -import { - getAllMonitors, - getAllMonitorsWithUptimeStats, - getMonitorStatsById, - getMonitorCertificate, - getMonitorById, - getMonitorsAndSummaryByTeamId, - getMonitorsByTeamId, - createMonitor, - checkEndpointResolution, - deleteMonitor, - deleteAllMonitors, - editMonitor, - pauseMonitor, - addDemoMonitors, -} from "../../controllers/monitorController.js"; -import jwt from "jsonwebtoken"; -import sinon from "sinon"; -import { successMessages } from "../../utils/messages.js"; -import logger from "../../utils/logger.js"; -import axios from "axios"; -const SERVICE_NAME = "monitorController"; - -describe("Monitor Controller - getAllMonitors", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: {}, - query: {}, - body: {}, - db: { - getAllMonitors: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if DB operations fail", async function () { - req.db.getAllMonitors.throws(new Error("DB error")); - await getAllMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message and data if all operations succeed", async function () { - const data = [{ monitor: "data" }]; - req.db.getAllMonitors.returns(data); - await getAllMonitors(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_GET_ALL, - data: data, - }) - ).to.be.true; - }); -}); -describe("Monitor Controller - getAllMonitorsWithUptimeStats", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: {}, - query: {}, - body: {}, - db: { - getAllMonitorsWithUptimeStats: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if DB operations fail", async function () { - req.db.getAllMonitorsWithUptimeStats.throws(new Error("DB error")); - await getAllMonitorsWithUptimeStats(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message and data if all operations succeed", async function () { - const data = [{ monitor: "data" }]; - req.db.getAllMonitorsWithUptimeStats.returns(data); - await getAllMonitorsWithUptimeStats(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_GET_ALL, - data: data, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - getMonitorStatsById", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: { - monitorId: "123", - }, - query: {}, - body: {}, - db: { - getMonitorStatsById: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function () { - req.params = {}; - await getMonitorStatsById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if query validation fails", async function () { - req.query = { invalid: 1 }; - await getMonitorStatsById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function () { - req.db.getMonitorStatsById.throws(new Error("DB error")); - await getMonitorStatsById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message and data if all operations succeed", async function () { - const data = [{ monitorStats: "data" }]; - req.db.getMonitorStatsById.returns(data); - await getMonitorStatsById(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_STATS_BY_ID, - data: data, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - getMonitorCertificate", function () { - let req, res, next, fetchMonitorCertificate; - - beforeEach(function () { - req = { - params: { - monitorId: "123", - }, - query: {}, - body: {}, - db: { - getMonitorById: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - fetchMonitorCertificate = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function () { - req.params = {}; - await getMonitorCertificate(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if getMonitorById operation fails", async function () { - req.db.getMonitorById.throws(new Error("DB error")); - await getMonitorCertificate(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message and data if all operations succeed with a valid cert", async function () { - req.db.getMonitorById.returns({ url: "https://www.google.com" }); - const data = { certificate: "cert", validTo: "2024/08/08" }; - fetchMonitorCertificate.returns(data); - await getMonitorCertificate(req, res, next, fetchMonitorCertificate); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_CERTIFICATE, - data: { certificateDate: new Date(data.validTo) }, - }) - ).to.be.true; - }); - - it("should return an error if fetchMonitorCertificate fails", async function () { - req.db.getMonitorById.returns({ url: "https://www.google.com" }); - fetchMonitorCertificate.throws(new Error("Certificate error")); - await getMonitorCertificate(req, res, next, fetchMonitorCertificate); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Certificate error"); - }); -}); - -describe("Monitor Controller - getMonitorById", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: { - monitorId: "123", - }, - query: {}, - body: {}, - db: { - getMonitorById: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function () { - req.params = {}; - await getMonitorById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if query param validation fails", async function () { - req.query = { invalid: 1 }; - await getMonitorById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function () { - req.db.getMonitorById.throws(new Error("DB error")); - await getMonitorById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return 404 if a monitor is not found", async function () { - const error = new Error("Monitor not found"); - error.status = 404; - req.db.getMonitorById.throws(error); - await getMonitorById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(404); - }); - - it("should return success message and data if all operations succeed", async function () { - const data = { monitor: "data" }; - req.db.getMonitorById.returns(data); - await getMonitorById(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_GET_BY_ID, - data: data, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - getMonitorsAndSummaryByTeamId", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: { - teamId: "123", - }, - query: {}, - body: {}, - db: { - getMonitorsAndSummaryByTeamId: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function () { - req.params = {}; - await getMonitorsAndSummaryByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if query validation fails", async function () { - req.query = { invalid: 1 }; - await getMonitorsAndSummaryByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function () { - req.db.getMonitorsAndSummaryByTeamId.throws(new Error("DB error")); - await getMonitorsAndSummaryByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message and data if all operations succeed", async function () { - const data = { monitors: "data", summary: "data" }; - req.db.getMonitorsAndSummaryByTeamId.returns(data); - await getMonitorsAndSummaryByTeamId(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_GET_BY_USER_ID(req.params.teamId), - data: data, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - getMonitorsByTeamId", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: { - teamId: "123", - }, - query: {}, - body: {}, - db: { - getMonitorsByTeamId: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function () { - req.params = {}; - await getMonitorsByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if query validation fails", async function () { - req.query = { invalid: 1 }; - await getMonitorsByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function () { - req.db.getMonitorsByTeamId.throws(new Error("DB error")); - await getMonitorsByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message and data if all operations succeed", async function () { - const data = { monitors: "data" }; - req.db.getMonitorsByTeamId.returns(data); - await getMonitorsByTeamId(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_GET_BY_USER_ID(req.params.teamId), - data: data, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - createMonitor", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: {}, - query: {}, - body: { - userId: "123", - teamId: "123", - name: "test_monitor", - description: "test_monitor_desc", - type: "http", - url: "https://example.com", - notifications: [{ email: "example@example.com" }], - }, - db: { - createMonitor: sinon.stub(), - createNotification: sinon.stub(), - }, - jobQueue: { - addJob: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if body validation fails", async function () { - req.body = {}; - await createMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB createMonitor operation fail", async function () { - req.db.createMonitor.throws(new Error("DB error")); - await createMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if DB createNotification operation fail", async function () { - req.db.createNotification.throws(new Error("DB error")); - req.db.createMonitor.returns({ _id: "123" }); - await createMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if monitor.save operation fail", async function () { - req.db.createMonitor.returns({ - _id: "123", - save: sinon.stub().throws(new Error("Monitor save error")), - }); - await createMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Monitor save error"); - }); - - it("should throw an error if addJob operation fails", async function () { - req.db.createMonitor.returns({ _id: "123", save: sinon.stub() }); - req.jobQueue.addJob.throws(new Error("Job error")); - await createMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Job error"); - }); - - it("should return success message and data if all operations succeed", async function () { - const monitor = { _id: "123", save: sinon.stub() }; - req.db.createMonitor.returns(monitor); - await createMonitor(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(201); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_CREATE, - data: monitor, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - checkEndpointResolution", function () { - let req, res, next, axiosGetStub; - - beforeEach(function () { - req = { query: { monitorURL: "https://example.com" } }; - res = { status: sinon.stub().returnsThis(), json: sinon.stub() }; - next = sinon.stub(); - axiosGetStub = sinon.stub(axios, "get"); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should resolve the URL successfully", async function () { - axiosGetStub.resolves({ status: 200, statusText: "OK" }); - await checkEndpointResolution(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - code: 200, - statusText: "OK", - msg: "URL resolved successfully", - }) - ).to.be.true; - expect(next.called).to.be.false; - }); - - it("should return an error if endpoint resolution fails", async function () { - const axiosError = new Error("resolution failed"); - axiosError.code = "ENOTFOUND"; - axiosGetStub.rejects(axiosError); - await checkEndpointResolution(req, res, next); - expect(next.calledOnce).to.be.true; - const errorPassedToNext = next.getCall(0).args[0]; - expect(errorPassedToNext).to.be.an.instanceOf(Error); - expect(errorPassedToNext.message).to.include("resolution failed"); - expect(errorPassedToNext.code).to.equal("ENOTFOUND"); - expect(errorPassedToNext.status).to.equal(500); - }); - - it("should reject with an error if query validation fails", async function () { - req.query.monitorURL = "invalid-url"; - await checkEndpointResolution(req, res, next); - expect(next.calledOnce).to.be.true; - const error = next.getCall(0).args[0]; - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - expect(error.message).to.equal('"monitorURL" must be a valid uri'); - }); -}); - -describe("Monitor Controller - deleteMonitor", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: { - monitorId: "123", - }, - query: {}, - body: {}, - db: { - deleteMonitor: sinon.stub(), - deleteChecks: sinon.stub(), - deletePageSpeedChecksByMonitorId: sinon.stub(), - deleteNotificationsByMonitorId: sinon.stub(), - }, - jobQueue: { - deleteJob: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - sinon.stub(logger, "error"); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function () { - req.params = {}; - await deleteMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB deleteMonitor operation fail", async function () { - req.db.deleteMonitor.throws(new Error("DB error")); - await deleteMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should log an error if deleteJob throws an error", async function () { - const error = new Error("Job error"); - const monitor = { name: "test_monitor", _id: "123" }; - req.db.deleteMonitor.returns(monitor); - req.jobQueue.deleteJob.rejects(error); - await deleteMonitor(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal(`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`); - }); - - it("should log an error if deleteChecks throws an error", async function () { - const error = new Error("Checks error"); - const monitor = { name: "test_monitor", _id: "123" }; - req.db.deleteMonitor.returns(monitor); - req.db.deleteChecks.rejects(error); - await deleteMonitor(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal(`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`); - }); - - it("should log an error if deletePageSpeedChecksByMonitorId throws an error", async function () { - const error = new Error("PageSpeed error"); - const monitor = { name: "test_monitor", _id: "123" }; - req.db.deleteMonitor.returns(monitor); - req.db.deletePageSpeedChecksByMonitorId.rejects(error); - await deleteMonitor(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal(`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`); - }); - - it("should log an error if deleteNotificationsByMonitorId throws an error", async function () { - const error = new Error("Notifications error"); - const monitor = { name: "test_monitor", _id: "123" }; - req.db.deleteMonitor.returns(monitor); - req.db.deleteNotificationsByMonitorId.rejects(error); - await deleteMonitor(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal(`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`); - }); - - it("should return success message if all operations succeed", async function () { - const monitor = { name: "test_monitor", _id: "123" }; - req.db.deleteMonitor.returns(monitor); - await deleteMonitor(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_DELETE, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - deleteAllMonitors", function () { - let req, res, next, stub; - - beforeEach(function () { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - req = { - headers: { - authorization: "Bearer token", - }, - params: { - monitorId: "123", - }, - query: {}, - body: {}, - db: { - deleteAllMonitors: sinon.stub(), - deleteChecks: sinon.stub(), - deletePageSpeedChecksByMonitorId: sinon.stub(), - deleteNotificationsByMonitorId: sinon.stub(), - }, - jobQueue: { - deleteJob: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - sinon.stub(logger, "error"); - }); - - afterEach(function () { - sinon.restore(); - stub.restore(); - }); - - it("should reject with an error if getTokenFromHeaders throws an error", async function () { - req.headers = {}; - await deleteAllMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("No auth headers"); - expect(next.firstCall.args[0].status).to.equal(500); - }); - - it("should reject with an error if token validation fails", async function () { - stub.restore(); - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - await deleteAllMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); - }); - - it("should reject with an error if DB deleteAllMonitors operation fail", async function () { - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.deleteAllMonitors.throws(new Error("DB error")); - await deleteAllMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should log an error if deleteChecks throws an error", async function () { - const monitors = [{ name: "test_monitor", _id: "123" }]; - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 }); - const error = new Error("Check error"); - req.db.deleteChecks.rejects(error); - await deleteAllMonitors(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal( - `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}` - ); - }); - - it("should log an error if deletePageSpeedChecksByMonitorId throws an error", async function () { - const monitors = [{ name: "test_monitor", _id: "123" }]; - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 }); - const error = new Error("Pagespeed Check error"); - req.db.deletePageSpeedChecksByMonitorId.rejects(error); - await deleteAllMonitors(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal( - `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}` - ); - }); - - it("should log an error if deleteNotificationsByMonitorId throws an error", async function () { - const monitors = [{ name: "test_monitor", _id: "123" }]; - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 }); - const error = new Error("Notifications Check error"); - req.db.deleteNotificationsByMonitorId.rejects(error); - await deleteAllMonitors(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal( - `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}` - ); - }); - - it("should return success message if all operations succeed", async function () { - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.deleteAllMonitors.returns({ - monitors: [{ name: "test_monitor", _id: "123" }], - deletedCount: 1, - }); - await deleteAllMonitors(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: "Deleted 1 monitors", - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - editMonitor", function () { - let req, res, next; - - beforeEach(function () { - req = { - headers: {}, - params: { - monitorId: "123", - }, - query: {}, - body: { - notifications: [{ email: "example@example.com" }], - }, - db: { - getMonitorById: sinon.stub(), - editMonitor: sinon.stub(), - deleteNotificationsByMonitorId: sinon.stub(), - createNotification: sinon.stub(), - }, - jobQueue: { - deleteJob: sinon.stub(), - addJob: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function () { - req.params = {}; - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if body validation fails", async function () { - req.body = { invalid: 1 }; - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if getMonitorById operation fails", async function () { - req.db.getMonitorById.throws(new Error("DB error")); - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if editMonitor operation fails", async function () { - req.db.getMonitorById.returns({ teamId: "123" }); - req.db.editMonitor.throws(new Error("DB error")); - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if deleteNotificationsByMonitorId operation fails", async function () { - req.db.getMonitorById.returns({ teamId: "123" }); - req.db.editMonitor.returns({ _id: "123" }); - req.db.deleteNotificationsByMonitorId.throws(new Error("DB error")); - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if createNotification operation fails", async function () { - req.db.getMonitorById.returns({ teamId: "123" }); - req.db.editMonitor.returns({ _id: "123" }); - req.db.createNotification.throws(new Error("DB error")); - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if deleteJob operation fails", async function () { - req.db.getMonitorById.returns({ teamId: "123" }); - req.db.editMonitor.returns({ _id: "123" }); - req.jobQueue.deleteJob.throws(new Error("Job error")); - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Job error"); - }); - - it("should reject with an error if addJob operation fails", async function () { - req.db.getMonitorById.returns({ teamId: "123" }); - req.db.editMonitor.returns({ _id: "123" }); - req.jobQueue.addJob.throws(new Error("Add Job error")); - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Add Job error"); - }); - - it("should return success message with data if all operations succeed", async function () { - const monitor = { _id: "123" }; - req.db.getMonitorById.returns({ teamId: "123" }); - req.db.editMonitor.returns(monitor); - await editMonitor(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_EDIT, - data: monitor, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - pauseMonitor", function () { - let req, res, next; - - beforeEach(function () { - req = { - headers: {}, - params: { - monitorId: "123", - }, - query: {}, - body: {}, - db: { - getMonitorById: sinon.stub(), - }, - jobQueue: { - deleteJob: sinon.stub(), - addJob: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function () { - req.params = {}; - await pauseMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if getMonitorById operation fails", async function () { - req.db.getMonitorById.throws(new Error("DB error")); - await pauseMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if deleteJob operation fails", async function () { - const monitor = { _id: req.params.monitorId, isActive: true }; - req.db.getMonitorById.returns(monitor); - req.jobQueue.deleteJob.throws(new Error("Delete Job error")); - await pauseMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Delete Job error"); - }); - - it("should reject with an error if addJob operation fails", async function () { - const monitor = { _id: req.params.monitorId, isActive: false }; - req.db.getMonitorById.returns(monitor); - req.jobQueue.addJob.throws(new Error("Add Job error")); - await pauseMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Add Job error"); - }); - - it("should reject with an error if monitor.save operation fails", async function () { - const monitor = { - _id: req.params.monitorId, - active: false, - save: sinon.stub().throws(new Error("Save error")), - }; - req.db.getMonitorById.returns(monitor); - await pauseMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Save error"); - }); - - it("should return success pause message with data if all operations succeed with inactive monitor", async function () { - const monitor = { - _id: req.params.monitorId, - isActive: false, - save: sinon.stub().resolves(), - }; - req.db.getMonitorById.returns(monitor); - await pauseMonitor(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_PAUSE, - data: monitor, - }) - ).to.be.true; - }); - - it("should return success resume message with data if all operations succeed with active monitor", async function () { - const monitor = { - _id: req.params.monitorId, - isActive: true, - save: sinon.stub().resolves(), - }; - req.db.getMonitorById.returns(monitor); - await pauseMonitor(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_RESUME, - data: monitor, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - addDemoMonitors", function () { - let req, res, next, stub; - - beforeEach(function () { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { _id: "123", teamId: "123" }; - }); - req = { - headers: { - authorization: "Bearer token", - }, - params: {}, - query: {}, - body: {}, - db: { - addDemoMonitors: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub(), - }, - jobQueue: { - addJob: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - stub.restore(); - }); - - it("should reject with an error if getTokenFromHeaders fails", async function () { - req.headers = {}; - await addDemoMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("No auth headers"); - expect(next.firstCall.args[0].status).to.equal(500); - }); - - it("should reject with an error if getting settings fails", async function () { - req.settingsService.getSettings.throws(new Error("Settings error")); - await addDemoMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Settings error"); - }); - - it("should reject with an error if JWT validation fails", async function () { - stub.restore(); - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - await addDemoMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); - }); - - it("should reject with an error if addDemoMonitors operation fails", async function () { - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.addDemoMonitors.throws(new Error("DB error")); - await addDemoMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if addJob operation fails", async function () { - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.addDemoMonitors.returns([{ _id: "123" }]); - req.jobQueue.addJob.throws(new Error("Add Job error")); - await addDemoMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Add Job error"); - }); - - it("should return success message with data if all operations succeed", async function () { - const monitors = [{ _id: "123" }]; - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.addDemoMonitors.returns(monitors); - await addDemoMonitors(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_DEMO_ADDED, - data: monitors.length, - }) - ).to.be.true; - }); -}); diff --git a/server/tests/controllers/queueController.test.js b/server/tests/controllers/queueController.test.js deleted file mode 100755 index 6201bdb5a..000000000 --- a/server/tests/controllers/queueController.test.js +++ /dev/null @@ -1,176 +0,0 @@ -import { afterEach } from "node:test"; -import { getMetrics, getJobs, addJob, obliterateQueue } from "../../controllers/queueController.js"; -import { successMessages } from "../../utils/messages.js"; -import sinon from "sinon"; - -describe("Queue Controller - getMetrics", function () { - let req, res, next; - - beforeEach(function () { - req = { - headers: {}, - params: {}, - body: {}, - db: {}, - jobQueue: { - getMetrics: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should throw an error if getMetrics throws an error", async function () { - req.jobQueue.getMetrics.throws(new Error("getMetrics error")); - await getMetrics(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getMetrics error"); - }); - - it("should return a success message and data if getMetrics is successful", async function () { - const data = { data: "metrics" }; - req.jobQueue.getMetrics.returns(data); - await getMetrics(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.QUEUE_GET_METRICS, - data, - }); - }); -}); - -describe("Queue Controller - getJobs", function () { - let req, res, next; - - beforeEach(function () { - req = { - headers: {}, - params: {}, - body: {}, - db: {}, - jobQueue: { - getJobStats: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should reject with an error if getJobs throws an error", async function () { - req.jobQueue.getJobStats.throws(new Error("getJobs error")); - await getJobs(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getJobs error"); - }); - - it("should return a success message and data if getJobs is successful", async function () { - const data = { data: "jobs" }; - req.jobQueue.getJobStats.returns(data); - await getJobs(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.QUEUE_GET_METRICS, - data, - }); - }); -}); - -describe("Queue Controller - addJob", function () { - let req, res, next; - - beforeEach(function () { - req = { - headers: {}, - params: {}, - body: {}, - db: {}, - jobQueue: { - addJob: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should reject with an error if addJob throws an error", async function () { - req.jobQueue.addJob.throws(new Error("addJob error")); - await addJob(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("addJob error"); - }); - - it("should return a success message if addJob is successful", async function () { - req.jobQueue.addJob.resolves(); - await addJob(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.QUEUE_ADD_JOB, - }); - }); -}); - -describe("Queue Controller - obliterateQueue", function () { - let req, res, next; - - beforeEach(function () { - req = { - headers: {}, - params: {}, - body: {}, - db: {}, - jobQueue: { - obliterate: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should reject with an error if obliterateQueue throws an error", async function () { - req.jobQueue.obliterate.throws(new Error("obliterateQueue error")); - await obliterateQueue(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("obliterateQueue error"); - }); - - it("should return a success message if obliterateQueue is successful", async function () { - req.jobQueue.obliterate.resolves(); - await obliterateQueue(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.QUEUE_OBLITERATE, - }); - }); -}); diff --git a/server/tests/controllers/settingsController.test.js b/server/tests/controllers/settingsController.test.js deleted file mode 100755 index 721c41c02..000000000 --- a/server/tests/controllers/settingsController.test.js +++ /dev/null @@ -1,109 +0,0 @@ -import { afterEach } from "node:test"; -import { getAppSettings, updateAppSettings } from "../../controllers/settingsController.js"; - -import { successMessages } from "../../utils/messages.js"; -import sinon from "sinon"; - -describe("Settings Controller - getAppSettings", function () { - let req, res, next; - - beforeEach(function () { - req = { - headers: {}, - params: {}, - body: {}, - db: {}, - settingsService: { - getSettings: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should throw an error if getSettings throws an error", async function () { - req.settingsService.getSettings.throws(new Error("getSettings error")); - await getAppSettings(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getSettings error"); - }); - - it("should return a success message and data if getSettings is successful", async function () { - const data = { data: "settings" }; - req.settingsService.getSettings.returns(data); - await getAppSettings(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.GET_APP_SETTINGS, - data, - }); - }); -}); - -describe("Settings Controller - updateAppSettings", function () { - let req, res, next; - - beforeEach(function () { - req = { - headers: {}, - params: {}, - body: {}, - db: { - updateAppSettings: sinon.stub(), - }, - settingsService: { - reloadSettings: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should reject with an error if body validation fails", async function () { - req.body = { invalid: 1 }; - await updateAppSettings(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if updateAppSettings throws an error", async function () { - req.db.updateAppSettings.throws(new Error("updateAppSettings error")); - await updateAppSettings(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("updateAppSettings error"); - }); - - it("should reject with an error if reloadSettings throws an error", async function () { - req.settingsService.reloadSettings.throws(new Error("reloadSettings error")); - await updateAppSettings(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("reloadSettings error"); - }); - - it("should return a success message and data if updateAppSettings is successful", async function () { - const data = { data: "settings" }; - req.settingsService.reloadSettings.returns(data); - await updateAppSettings(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.UPDATE_APP_SETTINGS, - data, - }); - }); -}); diff --git a/server/tests/controllers/statusPageController.test.js b/server/tests/controllers/statusPageController.test.js deleted file mode 100755 index cada662e0..000000000 --- a/server/tests/controllers/statusPageController.test.js +++ /dev/null @@ -1,127 +0,0 @@ -import sinon from "sinon"; -import { createStatusPage, getStatusPageByUrl } from "../../controllers/statusPageController.js"; - -describe("statusPageController", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: {}, - body: {}, - db: { - createStatusPage: sinon.stub(), - getStatusPageByUrl: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("createStatusPage", function () { - beforeEach(function () { - req.body = { - companyName: "Test Company", - url: "123456", - timezone: "America/Toronto", - color: "#000000", - theme: "light", - monitors: ["67309ca673788e808884c8ac", "67309ca673788e808884c8ac"], - }; - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should handle a validation error", async function () { - req.body = { - // Invalid data that will trigger validation error - companyName: "", - url: "", - timezone: "", - color: "invalid", - theme: "invalid", - monitors: ["invalid-id"], - }; - try { - await createStatusPage(req, res, next); - } catch (error) { - expect(error).to.be.an.instanceOf(Error); - expect(error.message).to.equal("Validation error"); - } - }); - - it("should handle a db error", async function () { - const err = new Error("DB error"); - req.db.createStatusPage.throws(err); - - try { - await createStatusPage(req, res, next); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - - it("should insert a properly formatted status page", async function () { - const result = await createStatusPage(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0].success).to.be.true; - }); - }); - - describe("getStatusPageByUrl", function () { - beforeEach(function () { - req.params = { - url: "123456", - }; - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should handle a validation error", async function () { - req.params = { - url: "", - }; - - try { - await getStatusPageByUrl(req, res, next); - } catch (error) { - expect(error).to.be.an.instanceOf(Error); - expect(error.message).to.equal("Validation error"); - } - }); - - it("should handle a DB error", async function () { - const err = new Error("DB error"); - req.db.getStatusPageByUrl.throws(err); - - try { - await getStatusPageByUrl(req, res, next); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - - it("should return a status page", async function () { - const statusPage = { - _id: "123456", - companyName: "Test Company", - url: "123456", - }; - req.db.getStatusPageByUrl.resolves(statusPage); - const result = await getStatusPageByUrl(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0].success).to.be.true; - expect(res.json.firstCall.args[0].data).to.deep.equal(statusPage); - }); - }); -}); diff --git a/server/tests/db/checkModule.test.js b/server/tests/db/checkModule.test.js deleted file mode 100755 index 6eef12445..000000000 --- a/server/tests/db/checkModule.test.js +++ /dev/null @@ -1,589 +0,0 @@ -import sinon from "sinon"; -import { - createCheck, - getChecksCount, - getChecks, - getTeamChecks, - deleteChecks, - deleteChecksByTeamId, - updateChecksTTL, -} from "../../db/mongo/modules/checkModule.js"; -import Check from "../../db/models/Check.js"; -import Monitor from "../../db/models/Monitor.js"; -import User from "../../db/models/User.js"; -import logger from "../../utils/logger.js"; - -describe("checkModule", function () { - describe("createCheck", function () { - let checkCountDocumentsStub, checkSaveStub, monitorFindByIdStub, monitorSaveStub; - const mockMonitor = { - _id: "123", - uptimePercentage: 0.5, - status: true, - save: () => this, - }; - const mockCheck = { active: true }; - - beforeEach(function () { - checkSaveStub = sinon.stub(Check.prototype, "save"); - checkCountDocumentsStub = sinon.stub(Check, "countDocuments"); - monitorFindByIdStub = sinon.stub(Monitor, "findById"); - monitorSaveStub = sinon.stub(Monitor.prototype, "save"); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return undefined early if no monitor is found", async function () { - monitorFindByIdStub.returns(null); - const check = await createCheck({ monitorId: "123" }); - expect(check).to.be.undefined; - }); - - it("should return a check", async function () { - monitorFindByIdStub.returns(mockMonitor); - checkSaveStub.returns(mockCheck); - monitorSaveStub.returns(mockMonitor); - const check = await createCheck({ monitorId: "123", status: true }); - expect(check).to.deep.equal(mockCheck); - }); - - it("should return a check if status is down", async function () { - mockMonitor.status = false; - monitorFindByIdStub.returns(mockMonitor); - checkSaveStub.returns(mockCheck); - monitorSaveStub.returns(mockMonitor); - const check = await createCheck({ monitorId: "123", status: false }); - expect(check).to.deep.equal(mockCheck); - }); - - it("should return a check if uptimePercentage is undefined", async function () { - mockMonitor.uptimePercentage = undefined; - monitorFindByIdStub.returns(mockMonitor); - checkSaveStub.returns(mockCheck); - monitorSaveStub.returns(mockMonitor); - const check = await createCheck({ monitorId: "123", status: true }); - expect(check).to.deep.equal(mockCheck); - }); - - it("should return a check if uptimePercentage is undefined and status is down", async function () { - mockMonitor.uptimePercentage = undefined; - monitorFindByIdStub.returns(mockMonitor); - checkSaveStub.returns(mockCheck); - monitorSaveStub.returns(mockMonitor); - const check = await createCheck({ monitorId: "123", status: false }); - expect(check).to.deep.equal(mockCheck); - }); - - it("should monitor save error", async function () { - const err = new Error("Save Error"); - monitorSaveStub.throws(err); - try { - await createCheck({ monitorId: "123" }); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - - it("should handle errors", async function () { - const err = new Error("DB Error"); - checkCountDocumentsStub.throws(err); - try { - await createCheck({ monitorId: "123" }); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getChecksCount", function () { - let checkCountDocumentStub; - - beforeEach(function () { - checkCountDocumentStub = sinon.stub(Check, "countDocuments"); - }); - - afterEach(function () { - checkCountDocumentStub.restore(); - }); - - it("should return count with basic monitorId query", async function () { - const req = { - params: { monitorId: "test123" }, - query: {}, - }; - checkCountDocumentStub.resolves(5); - - const result = await getChecksCount(req); - - expect(result).to.equal(5); - expect(checkCountDocumentStub.calledOnce).to.be.true; - expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({ - monitorId: "test123", - }); - }); - - it("should include dateRange in query when provided", async function () { - const req = { - params: { monitorId: "test123" }, - query: { dateRange: "day" }, - }; - checkCountDocumentStub.resolves(3); - - const result = await getChecksCount(req); - - expect(result).to.equal(3); - expect(checkCountDocumentStub.firstCall.args[0]).to.have.property("createdAt"); - expect(checkCountDocumentStub.firstCall.args[0].createdAt).to.have.property("$gte"); - }); - - it('should handle "all" filter correctly', async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "all" }, - }; - checkCountDocumentStub.resolves(2); - - const result = await getChecksCount(req); - - expect(result).to.equal(2); - expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({ - monitorId: "test123", - status: false, - }); - }); - - it('should handle "down" filter correctly', async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "down" }, - }; - checkCountDocumentStub.resolves(2); - - const result = await getChecksCount(req); - - expect(result).to.equal(2); - expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({ - monitorId: "test123", - status: false, - }); - }); - - it('should handle "resolve" filter correctly', async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "resolve" }, - }; - checkCountDocumentStub.resolves(1); - - const result = await getChecksCount(req); - - expect(result).to.equal(1); - expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({ - monitorId: "test123", - status: false, - statusCode: 5000, - }); - }); - - it("should handle unknown filter correctly", async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "unknown" }, - }; - checkCountDocumentStub.resolves(1); - - const result = await getChecksCount(req); - - expect(result).to.equal(1); - expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({ - monitorId: "test123", - status: false, - }); - }); - - it("should combine dateRange and filter in query", async function () { - const req = { - params: { monitorId: "test123" }, - query: { - dateRange: "week", - filter: "down", - }, - }; - checkCountDocumentStub.resolves(4); - - const result = await getChecksCount(req); - - expect(result).to.equal(4); - expect(checkCountDocumentStub.firstCall.args[0]).to.have.all.keys("monitorId", "createdAt", "status"); - }); - }); - - describe("getChecks", function () { - let checkFindStub, monitorFindStub; - - beforeEach(function () { - checkFindStub = sinon.stub(Check, "find").returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns([{ id: 1 }, { id: 2 }]), - }), - }), - }); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return checks with basic monitorId query", async function () { - const req = { - params: { monitorId: "test123" }, - query: {}, - }; - - const result = await getChecks(req); - - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it("should return checks with limit query", async function () { - const req = { - params: { monitorId: "test123" }, - query: { limit: 10 }, - }; - - const result = await getChecks(req); - - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it("should handle pagination correctly", async function () { - const req = { - params: { monitorId: "test123" }, - query: { - page: 2, - rowsPerPage: 10, - }, - }; - - const result = await getChecks(req); - - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it("should handle dateRange filter", async function () { - const req = { - params: { monitorId: "test123" }, - query: { dateRange: "week" }, - }; - const result = await getChecks(req); - - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it('should handle "all" filter', async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "all" }, - }; - - await getChecks(req); - const result = await getChecks(req); - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it('should handle "down" filter', async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "down" }, - }; - - await getChecks(req); - const result = await getChecks(req); - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it('should handle "resolve" filter', async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "resolve" }, - }; - - await getChecks(req); - const result = await getChecks(req); - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it('should handle "unknown" filter', async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "unknown" }, - }; - - await getChecks(req); - const result = await getChecks(req); - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it("should handle ascending sort order", async function () { - const req = { - params: { monitorId: "test123" }, - query: { sortOrder: "asc" }, - }; - - await getChecks(req); - const result = await getChecks(req); - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it("should handle error case", async function () { - const req = { - params: { monitorId: "test123" }, - query: {}, - }; - - checkFindStub.throws(new Error("Database error")); - - try { - await getChecks(req); - } catch (error) { - expect(error.message).to.equal("Database error"); - expect(error.service).to.equal("checkModule"); - expect(error.method).to.equal("getChecks"); - } - }); - }); - - describe("getTeamChecks", function () { - let checkFindStub, checkCountDocumentsStub, monitorFindStub; - const mockMonitors = [{ _id: "123" }]; - - beforeEach(function () { - monitorFindStub = sinon.stub(Monitor, "find").returns({ - select: sinon.stub().returns(mockMonitors), - }); - checkCountDocumentsStub = sinon.stub(Check, "countDocuments").returns(2); - checkFindStub = sinon.stub(Check, "find").returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns({ - select: sinon.stub().returns([{ id: 1 }, { id: 2 }]), - }), - }), - }), - }); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return checks with basic monitorId query", async function () { - const req = { - params: { teamId: "test123" }, - query: {}, - }; - - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it("should handle pagination correctly", async function () { - const req = { - params: { monitorId: "test123" }, - query: { limit: 1, page: 2, rowsPerPage: 10 }, - }; - - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it("should handle dateRange filter", async function () { - const req = { - params: { monitorId: "test123" }, - query: { dateRange: "week" }, - }; - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it('should handle "all" filter', async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "all" }, - }; - - await getChecks(req); - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it('should handle "down" filter', async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "down" }, - }; - - await getChecks(req); - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it('should handle "resolve" filter', async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "resolve" }, - }; - - await getChecks(req); - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it('should handle "unknown" filter', async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "unknown" }, - }; - - await getChecks(req); - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it("should handle ascending sort order", async function () { - const req = { - params: { monitorId: "test123" }, - query: { sortOrder: "asc" }, - }; - - await getChecks(req); - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it("should handle error case", async function () { - const req = { - params: { monitorId: "test123" }, - query: {}, - }; - - checkFindStub.throws(new Error("Database error")); - - try { - await getTeamChecks(req); - } catch (error) { - expect(error.message).to.equal("Database error"); - expect(error.service).to.equal("checkModule"); - expect(error.method).to.equal("getTeamChecks"); - } - }); - }); - - describe("deleteChecks", function () { - let checkDeleteManyStub; - - beforeEach(function () { - checkDeleteManyStub = sinon.stub(Check, "deleteMany").resolves({ deletedCount: 1 }); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return a value if a check is deleted", async function () { - const result = await deleteChecks("123"); - expect(result).to.equal(1); - }); - - it("should handle an error", async function () { - checkDeleteManyStub.throws(new Error("Database error")); - try { - await deleteChecks("123"); - } catch (error) { - expect(error.message).to.equal("Database error"); - expect(error.method).to.equal("deleteChecks"); - } - }); - }); - - describe("deleteChecksByTeamId", function () { - let mockMonitors = [{ _id: 123, save: () => this }]; - let monitorFindStub, monitorSaveStub, checkDeleteManyStub; - - beforeEach(function () { - monitorSaveStub = sinon.stub(Monitor.prototype, "save"); - monitorFindStub = sinon.stub(Monitor, "find").returns(mockMonitors); - checkDeleteManyStub = sinon.stub(Check, "deleteMany").resolves({ deletedCount: 1 }); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return a deleted count", async function () { - const result = await deleteChecksByTeamId("123"); - expect(result).to.equal(1); - }); - - it("should handle errors", async function () { - const err = new Error("DB Error"); - monitorFindStub.throws(err); - try { - const result = await deleteChecksByTeamId("123"); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("updateChecksTTL", function () { - let userUpdateManyStub; - let loggerStub; - - beforeEach(function () { - loggerStub = sinon.stub(logger, "error"); - userUpdateManyStub = sinon.stub(User, "updateMany"); - Check.collection = { dropIndex: sinon.stub(), createIndex: sinon.stub() }; - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return undefined", async function () { - const result = await updateChecksTTL("123", 10); - expect(result).to.be.undefined; - }); - - it("should log an error if dropIndex throws an error", async function () { - const err = new Error("Drop Index Error"); - Check.collection.dropIndex.throws(err); - await updateChecksTTL("123", 10); - expect(loggerStub.calledOnce).to.be.true; - expect(loggerStub.firstCall.args[0].message).to.equal(err.message); - }); - - it("should throw an error if createIndex throws an error", async function () { - const err = new Error("Create Index Error"); - Check.collection.createIndex.throws(err); - try { - await updateChecksTTL("123", 10); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - - it("should throw an error if User.updateMany throws an error", async function () { - const err = new Error("Update Many Error"); - userUpdateManyStub.throws(err); - try { - await updateChecksTTL("123", 10); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); -}); diff --git a/server/tests/db/hardwareCheckModule.test.js b/server/tests/db/hardwareCheckModule.test.js deleted file mode 100755 index 20ce197fb..000000000 --- a/server/tests/db/hardwareCheckModule.test.js +++ /dev/null @@ -1,136 +0,0 @@ -import sinon from "sinon"; -import HardwareCheck from "../../db/models/HardwareCheck.js"; -import { createHardwareCheck } from "../../db/mongo/modules/hardwareCheckModule.js"; -import Monitor from "../../db/models/Monitor.js"; -import logger from "../../utils/logger.js"; - -const mockHardwareCheck = { - data: { - cpu: { - physical_core: 4, - logical_core: 8, - frequency: 4800, - current_frequency: 1411, - temperature: [45, 50, 46, 47, 45, 50, 46, 47], - free_percent: 0.8552990910595134, - usage_percent: 0.14470090894048657, - }, - memory: { - total_bytes: 16467628032, - available_bytes: 7895044096, - used_bytes: 6599561216, - usage_percent: 0.4008, - }, - disk: [ - { - read_speed_bytes: null, - write_speed_bytes: null, - total_bytes: 931258499072, - free_bytes: 737097256960, - usage_percent: 0.1661, - }, - ], - host: { - os: "linux", - platform: "ubuntu", - kernel_version: "6.8.0-48-generic", - }, - }, - errors: [ - { - metric: ["cpu.temperature"], - err: "unable to read CPU temperature", - }, - ], -}; - -const mockMonitor = { - _id: "123", - uptimePercentage: 1, - status: true, - save: () => this, -}; - -describe("HardwareCheckModule", function () { - let hardwareCheckSaveStub, hardwareCheckCountDocumentsStub, monitorFindByIdStub, loggerStub; - - beforeEach(function () { - loggerStub = sinon.stub(logger, "error"); - hardwareCheckSaveStub = sinon.stub(HardwareCheck.prototype, "save"); - monitorFindByIdStub = sinon.stub(Monitor, "findById"); - hardwareCheckCountDocumentsStub = sinon.stub(HardwareCheck, "countDocuments"); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("createHardwareCheck", function () { - it("should return a hardware check", async function () { - hardwareCheckSaveStub.resolves(mockHardwareCheck); - monitorFindByIdStub.resolves(mockMonitor); - hardwareCheckCountDocumentsStub.resolves(1); - const hardwareCheck = await createHardwareCheck({ status: true }); - expect(hardwareCheck).to.exist; - expect(hardwareCheck).to.deep.equal(mockHardwareCheck); - }); - - it("should return a hardware check for a check with status false", async function () { - hardwareCheckSaveStub.resolves(mockHardwareCheck); - monitorFindByIdStub.resolves(mockMonitor); - hardwareCheckCountDocumentsStub.resolves(1); - const hardwareCheck = await createHardwareCheck({ status: false }); - expect(hardwareCheck).to.exist; - expect(hardwareCheck).to.deep.equal(mockHardwareCheck); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - monitorFindByIdStub.resolves(mockMonitor); - hardwareCheckSaveStub.rejects(err); - try { - await createHardwareCheck({}); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - - it("should log an error if a monitor is not found", async function () { - monitorFindByIdStub.resolves(null); - const res = await createHardwareCheck({}); - expect(loggerStub.calledOnce).to.be.true; - expect(res).to.be.null; - }); - - it("should handle a monitor with undefined uptimePercentage", async function () { - monitorFindByIdStub.resolves({ ...mockMonitor, uptimePercentage: undefined }); - hardwareCheckSaveStub.resolves(mockHardwareCheck); - hardwareCheckCountDocumentsStub.resolves(1); - const res = await createHardwareCheck({}); - expect(res).to.exist; - }); - - it("should handle a monitor with undefined uptimePercentage and true status", async function () { - monitorFindByIdStub.resolves({ - ...mockMonitor, - uptimePercentage: undefined, - }); - hardwareCheckSaveStub.resolves(mockHardwareCheck); - hardwareCheckCountDocumentsStub.resolves(1); - const res = await createHardwareCheck({ status: true }); - expect(res).to.exist; - }); - - it("should handle a monitor with undefined uptimePercentage and false status", async function () { - monitorFindByIdStub.resolves({ - ...mockMonitor, - uptimePercentage: undefined, - }); - hardwareCheckSaveStub.resolves(mockHardwareCheck); - hardwareCheckCountDocumentsStub.resolves(1); - const res = await createHardwareCheck({ status: false }); - expect(res).to.exist; - }); - }); -}); diff --git a/server/tests/db/inviteModule.test.js b/server/tests/db/inviteModule.test.js deleted file mode 100755 index e8913c983..000000000 --- a/server/tests/db/inviteModule.test.js +++ /dev/null @@ -1,103 +0,0 @@ -import sinon from "sinon"; -import InviteToken from "../../db/models/InviteToken.js"; -import { requestInviteToken, getInviteToken, getInviteTokenAndDelete } from "../../db/mongo/modules/inviteModule.js"; -import { errorMessages } from "../../utils/messages.js"; - -describe("Invite Module", function () { - const mockUserData = { - email: "test@test.com", - teamId: "123", - role: ["admin"], - token: "123", - }; - const mockInviteToken = { _id: 123, time: 123 }; - let inviteTokenDeleteManyStub, inviteTokenSaveStub, inviteTokenFindOneStub, inviteTokenFindOneAndDeleteStub; - - beforeEach(function () { - inviteTokenDeleteManyStub = sinon.stub(InviteToken, "deleteMany"); - inviteTokenSaveStub = sinon.stub(InviteToken.prototype, "save"); - inviteTokenFindOneStub = sinon.stub(InviteToken, "findOne"); - inviteTokenFindOneAndDeleteStub = sinon.stub(InviteToken, "findOneAndDelete"); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("requestInviteToken", function () { - it("should return a new invite token", async function () { - inviteTokenDeleteManyStub.resolves(); - inviteTokenSaveStub.resolves(); - const inviteToken = await requestInviteToken(mockUserData); - expect(inviteToken.email).to.equal(mockUserData.email); - expect(inviteToken.role).to.deep.equal(mockUserData.role); - expect(inviteToken.token).to.exist; - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - inviteTokenDeleteManyStub.rejects(err); - try { - await requestInviteToken(mockUserData); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getInviteToken", function () { - it("should return an invite token", async function () { - inviteTokenFindOneStub.resolves(mockInviteToken); - const inviteToken = await getInviteToken(mockUserData.token); - expect(inviteToken).to.deep.equal(mockInviteToken); - }); - - it("should handle a token not found", async function () { - inviteTokenFindOneStub.resolves(null); - try { - await getInviteToken(mockUserData.token); - } catch (error) { - expect(error.message).to.equal(errorMessages.AUTH_INVITE_NOT_FOUND); - } - }); - - it("should handle DB errors", async function () { - const err = new Error("test error"); - inviteTokenFindOneStub.rejects(err); - try { - await getInviteToken(mockUserData.token); - } catch (error) { - expect(error).to.deep.equal(err); - expect(error.method).to.equal("getInviteToken"); - } - }); - }); - - describe("getInviteTokenAndDelete", function () { - it("should return a deleted invite", async function () { - inviteTokenFindOneAndDeleteStub.resolves(mockInviteToken); - const deletedInvite = await getInviteTokenAndDelete(mockUserData.token); - expect(deletedInvite).to.deep.equal(mockInviteToken); - }); - - it("should handle a token not found", async function () { - inviteTokenFindOneAndDeleteStub.resolves(null); - try { - await getInviteTokenAndDelete(mockUserData.token); - } catch (error) { - expect(error.message).to.equal(errorMessages.AUTH_INVITE_NOT_FOUND); - } - }); - - it("should handle DB errors", async function () { - const err = new Error("test error"); - inviteTokenFindOneAndDeleteStub.rejects(err); - try { - await getInviteTokenAndDelete(mockUserData.token); - } catch (error) { - expect(error).to.deep.equal(err); - expect(error.method).to.equal("getInviteTokenAndDelete"); - } - }); - }); -}); diff --git a/server/tests/db/maintenanceWindowModule.test.js b/server/tests/db/maintenanceWindowModule.test.js deleted file mode 100755 index 576c97f84..000000000 --- a/server/tests/db/maintenanceWindowModule.test.js +++ /dev/null @@ -1,255 +0,0 @@ -import sinon from "sinon"; -import MaintenanceWindow from "../../db/models/MaintenanceWindow.js"; -import { - createMaintenanceWindow, - getMaintenanceWindowById, - getMaintenanceWindowsByTeamId, - getMaintenanceWindowsByMonitorId, - deleteMaintenanceWindowById, - deleteMaintenanceWindowByMonitorId, - deleteMaintenanceWindowByUserId, - editMaintenanceWindowById, -} from "../../db/mongo/modules/maintenanceWindowModule.js"; - -describe("MaintenanceWindow Module", function () { - const mockMaintenanceWindow = { - monitorId: "123", - active: true, - oneTime: true, - start: 1, - end: 20000, - }; - - let mockMaintenanceWindows = [mockMaintenanceWindow]; - let maintenanceWindowSaveStub, - maintenanceWindowFindByIdStub, - maintenanceWindowCountDocumentsStub, - maintenanceWindowFindStub, - maintenanceWindowFindByIdAndDeleteStub, - maintenanceWindowDeleteManyStub, - maintenanceWindowFindByIdAndUpdateStub; - - beforeEach(function () { - maintenanceWindowSaveStub = sinon.stub(MaintenanceWindow.prototype, "save"); - maintenanceWindowFindByIdStub = sinon.stub(MaintenanceWindow, "findById"); - maintenanceWindowCountDocumentsStub = sinon.stub(MaintenanceWindow, "countDocuments"); - maintenanceWindowFindStub = sinon.stub(MaintenanceWindow, "find").returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns(mockMaintenanceWindows), - }), - }), - }); - maintenanceWindowFindByIdAndDeleteStub = sinon.stub(MaintenanceWindow, "findByIdAndDelete"); - maintenanceWindowDeleteManyStub = sinon.stub(MaintenanceWindow, "deleteMany"); - maintenanceWindowFindByIdAndUpdateStub = sinon.stub(MaintenanceWindow, "findByIdAndUpdate"); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("createMaintenanceWindow", function () { - it("should save a new maintenance window", async function () { - maintenanceWindowSaveStub.resolves(mockMaintenanceWindow); - const result = await createMaintenanceWindow(mockMaintenanceWindow); - expect(result).to.deep.equal(mockMaintenanceWindow); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - maintenanceWindowSaveStub.rejects(err); - try { - await createMaintenanceWindow(mockMaintenanceWindow); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getMaintenanceWindowById", function () { - it("should return a maintenance window", async function () { - maintenanceWindowFindByIdStub.resolves(mockMaintenanceWindow); - const result = await getMaintenanceWindowById(mockMaintenanceWindow.id); - expect(result).to.deep.equal(mockMaintenanceWindow); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - maintenanceWindowFindByIdStub.rejects(err); - try { - await getMaintenanceWindowById(mockMaintenanceWindow.id); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getMaintenanceWindowsByTeamId", function () { - let query; - - beforeEach(function () { - query = { - active: true, - page: 1, - rowsPerPage: 10, - field: "name", - order: "asc", - }; - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return a list of maintenance windows and count", async function () { - maintenanceWindowCountDocumentsStub.resolves(1); - const result = await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query); - expect(result).to.deep.equal({ - maintenanceWindows: mockMaintenanceWindows, - maintenanceWindowCount: 1, - }); - }); - - it("should return a list of maintenance windows and count with empty query", async function () { - query = undefined; - maintenanceWindowCountDocumentsStub.resolves(1); - const result = await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query); - expect(result).to.deep.equal({ - maintenanceWindows: mockMaintenanceWindows, - maintenanceWindowCount: 1, - }); - }); - - it("should return a list of maintenance windows and count with no pagination provided", async function () { - query.page = undefined; - query.rowsPerPage = undefined; - maintenanceWindowCountDocumentsStub.resolves(1); - const result = await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query); - expect(result).to.deep.equal({ - maintenanceWindows: mockMaintenanceWindows, - maintenanceWindowCount: 1, - }); - }); - - it("should return a list of maintenance windows and count with field and desc order", async function () { - query.order = "desc"; - maintenanceWindowCountDocumentsStub.resolves(1); - const result = await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query); - expect(result).to.deep.equal({ - maintenanceWindows: mockMaintenanceWindows, - maintenanceWindowCount: 1, - }); - }); - - it("should return a list of maintenance windows and count no field", async function () { - query.field = undefined; - maintenanceWindowCountDocumentsStub.resolves(1); - const result = await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query); - expect(result).to.deep.equal({ - maintenanceWindows: mockMaintenanceWindows, - maintenanceWindowCount: 1, - }); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - maintenanceWindowCountDocumentsStub.rejects(err); - try { - await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getMaintenanceWindowsByMonitorId", function () { - it("should return a list of maintenance windows", async function () { - maintenanceWindowFindStub.resolves(mockMaintenanceWindows); - const result = await getMaintenanceWindowsByMonitorId(mockMaintenanceWindow.monitorId); - expect(result).to.deep.equal(mockMaintenanceWindows); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - maintenanceWindowFindStub.rejects(err); - try { - await getMaintenanceWindowsByMonitorId(mockMaintenanceWindow.monitorId); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deleteMaintenanceWindowById", function () { - it("should delete a maintenance window", async function () { - maintenanceWindowFindByIdAndDeleteStub.resolves(mockMaintenanceWindow); - const result = await deleteMaintenanceWindowById(mockMaintenanceWindow.id); - expect(result).to.deep.equal(mockMaintenanceWindow); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - maintenanceWindowFindByIdAndDeleteStub.rejects(err); - try { - await deleteMaintenanceWindowById(mockMaintenanceWindow.id); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deleteMaintenanceWindowByMonitorId", function () { - it("should return the number of documents deleted", async function () { - maintenanceWindowDeleteManyStub.resolves({ deletedCount: 1 }); - const result = await deleteMaintenanceWindowByMonitorId(mockMaintenanceWindow.monitorId); - expect(result).to.deep.equal({ deletedCount: 1 }); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - maintenanceWindowDeleteManyStub.rejects(err); - try { - await deleteMaintenanceWindowByMonitorId(mockMaintenanceWindow.monitorId); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deleteMaintenanceWindowByUserId", function () { - it("should return the number of documents deleted", async function () { - maintenanceWindowDeleteManyStub.resolves({ deletedCount: 1 }); - const result = await deleteMaintenanceWindowByUserId(mockMaintenanceWindow.userId); - expect(result).to.deep.equal({ deletedCount: 1 }); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - maintenanceWindowDeleteManyStub.rejects(err); - try { - await deleteMaintenanceWindowByUserId(mockMaintenanceWindow.userId); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("editMaintenanceWindowById", function () { - it("should return the updated maintenance window", async function () { - maintenanceWindowFindByIdAndUpdateStub.resolves(mockMaintenanceWindow); - const result = await editMaintenanceWindowById(mockMaintenanceWindow.id, mockMaintenanceWindow); - expect(result).to.deep.equal(mockMaintenanceWindow); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - maintenanceWindowFindByIdAndUpdateStub.rejects(err); - try { - await editMaintenanceWindowById(mockMaintenanceWindow.id, mockMaintenanceWindow); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); -}); diff --git a/server/tests/db/monitorModule.test.js b/server/tests/db/monitorModule.test.js deleted file mode 100755 index e59c352cf..000000000 --- a/server/tests/db/monitorModule.test.js +++ /dev/null @@ -1,1865 +0,0 @@ -import sinon from "sinon"; -import Monitor from "../../db/models/Monitor.js"; -import Check from "../../db/models/Check.js"; -import PageSpeedCheck from "../../db/models/PageSpeedCheck.js"; -import HardwareCheck from "../../db/models/HardwareCheck.js"; -import Notification from "../../db/models/Notification.js"; - -import { errorMessages } from "../../utils/messages.js"; -import { - getAllMonitors, - getAllMonitorsWithUptimeStats, - getMonitorStatsById, - getMonitorById, - getMonitorsAndSummaryByTeamId, - getMonitorsByTeamId, - createMonitor, - deleteMonitor, - deleteAllMonitors, - deleteMonitorsByUserId, - editMonitor, - addDemoMonitors, - calculateUptimeDuration, - getLastChecked, - getLatestResponseTime, - getAverageResponseTime, - getUptimePercentage, - getIncidents, - getMonitorChecks, - processChecksForDisplay, - groupChecksByTime, - calculateGroupStats, -} from "../../db/mongo/modules/monitorModule.js"; - -describe("monitorModule", function () { - let monitorFindStub, - monitorFindByIdStub, - monitorFindByIdAndUpdateStub, - monitorFindByIdAndDeleteStub, - monitorDeleteManyStub, - monitorCountStub, - monitorInsertManyStub, - checkFindStub, - pageSpeedCheckFindStub, - hardwareCheckFindStub; - - beforeEach(function () { - monitorFindStub = sinon.stub(Monitor, "find"); - monitorFindByIdStub = sinon.stub(Monitor, "findById"); - monitorFindByIdAndUpdateStub = sinon.stub(Monitor, "findByIdAndUpdate"); - monitorFindByIdAndDeleteStub = sinon.stub(Monitor, "findByIdAndDelete"); - monitorDeleteManyStub = sinon.stub(Monitor, "deleteMany"); - monitorCountStub = sinon.stub(Monitor, "countDocuments"); - - monitorInsertManyStub = sinon.stub(Monitor, "insertMany"); - checkFindStub = sinon.stub(Check, "find").returns({ - sort: sinon.stub(), - }); - pageSpeedCheckFindStub = sinon.stub(PageSpeedCheck, "find").returns({ - sort: sinon.stub(), - }); - hardwareCheckFindStub = sinon.stub(HardwareCheck, "find").returns({ - sort: sinon.stub(), - }); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("getAllMonitors", function () { - it("should return all monitors", async function () { - const mockMonitors = [ - { _id: "1", name: "Monitor 1", url: "test1.com" }, - { _id: "2", name: "Monitor 2", url: "test2.com" }, - ]; - monitorFindStub.returns(mockMonitors); - const result = await getAllMonitors(); - - expect(result).to.deep.equal(mockMonitors); - expect(monitorFindStub.calledOnce).to.be.true; - expect(monitorFindStub.firstCall.args).to.deep.equal([]); - }); - - it("should handle empty results", async function () { - monitorFindStub.returns([]); - const result = await getAllMonitors(); - expect(result).to.be.an("array").that.is.empty; - }); - - it("should throw error when database fails", async function () { - // Arrange - const error = new Error("Database error"); - error.service = "MonitorModule"; - error.method = "getAllMonitors"; - monitorFindStub.rejects(error); - // Act & Assert - try { - await getAllMonitors(); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err).to.equal(error); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("getAllMonitors"); - } - }); - }); - - describe("getAllMonitorsWithUptimeStats", function () { - it("should return monitors with uptime stats for different time periods", async function () { - // Mock data - const mockMonitors = [ - { - _id: "monitor1", - type: "http", - toObject: () => ({ - _id: "monitor1", - type: "http", - name: "Test Monitor", - }), - }, - ]; - - const mockChecks = [{ status: true }, { status: true }, { status: false }, { status: true }]; - - monitorFindStub.resolves(mockMonitors); - checkFindStub.resolves(mockChecks); - - const result = await getAllMonitorsWithUptimeStats(); - - expect(result).to.be.an("array"); - expect(result).to.have.lengthOf(1); - - const monitor = result[0]; - expect(monitor).to.have.property("_id", "monitor1"); - expect(monitor).to.have.property("name", "Test Monitor"); - - // Check uptime percentages exist for all time periods - expect(monitor).to.have.property("1"); - expect(monitor).to.have.property("7"); - expect(monitor).to.have.property("30"); - expect(monitor).to.have.property("90"); - - // Verify uptime percentage calculation (3 successful out of 4 = 75%) - expect(monitor["1"]).to.equal(75); - expect(monitor["7"]).to.equal(75); - expect(monitor["30"]).to.equal(75); - expect(monitor["90"]).to.equal(75); - }); - - it("should return monitors with stats for pagespeed type", async function () { - // Mock data - const mockMonitors = [ - { - _id: "monitor1", - type: "pagespeed", - toObject: () => ({ - _id: "monitor1", - type: "pagespeed", - name: "Test Monitor", - }), - }, - ]; - - const mockChecks = [{ status: true }, { status: true }, { status: false }, { status: true }]; - - monitorFindStub.resolves(mockMonitors); - pageSpeedCheckFindStub.resolves(mockChecks); - - const result = await getAllMonitorsWithUptimeStats(); - - expect(result).to.be.an("array"); - expect(result).to.have.lengthOf(1); - - const monitor = result[0]; - expect(monitor).to.have.property("_id", "monitor1"); - expect(monitor).to.have.property("name", "Test Monitor"); - - // Check uptime percentages exist for all time periods - expect(monitor).to.have.property("1"); - expect(monitor).to.have.property("7"); - expect(monitor).to.have.property("30"); - expect(monitor).to.have.property("90"); - - // Verify uptime percentage calculation (3 successful out of 4 = 75%) - expect(monitor["1"]).to.equal(75); - expect(monitor["7"]).to.equal(75); - expect(monitor["30"]).to.equal(75); - expect(monitor["90"]).to.equal(75); - }); - - it("should return monitors with stats for hardware type", async function () { - // Mock data - const mockMonitors = [ - { - _id: "monitor1", - type: "hardware", - toObject: () => ({ - _id: "monitor1", - type: "hardware", - name: "Test Monitor", - }), - }, - ]; - - const mockChecks = [{ status: true }, { status: true }, { status: false }, { status: true }]; - - monitorFindStub.resolves(mockMonitors); - hardwareCheckFindStub.resolves(mockChecks); - - const result = await getAllMonitorsWithUptimeStats(); - - expect(result).to.be.an("array"); - expect(result).to.have.lengthOf(1); - - const monitor = result[0]; - expect(monitor).to.have.property("_id", "monitor1"); - expect(monitor).to.have.property("name", "Test Monitor"); - - // Check uptime percentages exist for all time periods - expect(monitor).to.have.property("1"); - expect(monitor).to.have.property("7"); - expect(monitor).to.have.property("30"); - expect(monitor).to.have.property("90"); - - // Verify uptime percentage calculation (3 successful out of 4 = 75%) - expect(monitor["1"]).to.equal(75); - expect(monitor["7"]).to.equal(75); - expect(monitor["30"]).to.equal(75); - expect(monitor["90"]).to.equal(75); - }); - - it("should handle errors appropriately", async function () { - // Setup stub to throw error - monitorFindStub.rejects(new Error("Database error")); - - try { - await getAllMonitorsWithUptimeStats(); - } catch (error) { - expect(error).to.be.an("error"); - expect(error.message).to.equal("Database error"); - expect(error.service).to.equal("monitorModule"); - expect(error.method).to.equal("getAllMonitorsWithUptimeStats"); - } - }); - - it("should handle empty monitor list", async function () { - monitorFindStub.resolves([]); - - const result = await getAllMonitorsWithUptimeStats(); - - expect(result).to.be.an("array"); - expect(result).to.have.lengthOf(0); - }); - - it("should handle monitor with no checks", async function () { - const mockMonitors = [ - { - _id: "monitor1", - type: "http", - toObject: () => ({ - _id: "monitor1", - type: "http", - name: "Test Monitor", - }), - }, - ]; - - monitorFindStub.resolves(mockMonitors); - checkFindStub.resolves([]); - - const result = await getAllMonitorsWithUptimeStats(); - - expect(result[0]).to.have.property("1", 0); - expect(result[0]).to.have.property("7", 0); - expect(result[0]).to.have.property("30", 0); - expect(result[0]).to.have.property("90", 0); - }); - }); - - describe("calculateUptimeDuration", function () { - let clock; - const NOW = new Date("2024-01-01T12:00:00Z").getTime(); - - beforeEach(function () { - // Fix the current time - clock = sinon.useFakeTimers(NOW); - }); - - afterEach(function () { - clock.restore(); - }); - - it("should return 0 when checks array is empty", function () { - expect(calculateUptimeDuration([])).to.equal(0); - }); - - it("should return 0 when checks array is null", function () { - expect(calculateUptimeDuration(null)).to.equal(0); - }); - - it("should calculate uptime from last down check to most recent check", function () { - const checks = [ - { status: true, createdAt: "2024-01-01T11:00:00Z" }, // Most recent - { status: true, createdAt: "2024-01-01T10:00:00Z" }, - { status: false, createdAt: "2024-01-01T09:00:00Z" }, // Last down - { status: true, createdAt: "2024-01-01T08:00:00Z" }, - ]; - - // Expected: 2 hours (from 09:00 to 11:00) = 7200000ms - expect(calculateUptimeDuration(checks)).to.equal(7200000); - }); - - it("should calculate uptime from first check when no down checks exist", function () { - const checks = [ - { status: true, createdAt: "2024-01-01T11:00:00Z" }, - { status: true, createdAt: "2024-01-01T10:00:00Z" }, - { status: true, createdAt: "2024-01-01T09:00:00Z" }, - ]; - - // Expected: Current time (12:00) - First check (09:00) = 3 hours = 10800000ms - expect(calculateUptimeDuration(checks)).to.equal(10800000); - }); - }); - - describe("getLastChecked", function () { - let clock; - const NOW = new Date("2024-01-01T12:00:00Z").getTime(); - - beforeEach(function () { - // Fix the current time - clock = sinon.useFakeTimers(NOW); - }); - - afterEach(function () { - clock.restore(); - }); - - it("should return 0 when checks array is empty", function () { - expect(getLastChecked([])).to.equal(0); - }); - - it("should return 0 when checks array is null", function () { - expect(getLastChecked(null)).to.equal(0); - }); - - it("should return time difference between now and most recent check", function () { - const checks = [ - { createdAt: "2024-01-01T11:30:00Z" }, // 30 minutes ago - { createdAt: "2024-01-01T11:00:00Z" }, - { createdAt: "2024-01-01T10:30:00Z" }, - ]; - - // Expected: 30 minutes = 1800000ms - expect(getLastChecked(checks)).to.equal(1800000); - }); - - it("should handle checks from different days", function () { - const checks = [ - { createdAt: "2023-12-31T12:00:00Z" }, // 24 hours ago - { createdAt: "2023-12-30T12:00:00Z" }, - ]; - - // Expected: 24 hours = 86400000ms - expect(getLastChecked(checks)).to.equal(86400000); - }); - }); - - describe("getLatestResponseTime", function () { - it("should return 0 when checks array is empty", function () { - expect(getLatestResponseTime([])).to.equal(0); - }); - - it("should return 0 when checks array is null", function () { - expect(getLatestResponseTime(null)).to.equal(0); - }); - - it("should return response time from most recent check", function () { - const checks = [ - { responseTime: 150, createdAt: "2024-01-01T11:30:00Z" }, // Most recent - { responseTime: 200, createdAt: "2024-01-01T11:00:00Z" }, - { responseTime: 250, createdAt: "2024-01-01T10:30:00Z" }, - ]; - - expect(getLatestResponseTime(checks)).to.equal(150); - }); - - it("should handle missing responseTime in checks", function () { - const checks = [{ createdAt: "2024-01-01T11:30:00Z" }, { responseTime: 200, createdAt: "2024-01-01T11:00:00Z" }]; - - expect(getLatestResponseTime(checks)).to.equal(0); - }); - }); - - describe("getAverageResponseTime", function () { - it("should return 0 when checks array is empty", function () { - expect(getAverageResponseTime([])).to.equal(0); - }); - - it("should return 0 when checks array is null", function () { - expect(getAverageResponseTime(null)).to.equal(0); - }); - - it("should calculate average response time from all checks", function () { - const checks = [ - { responseTime: 100, createdAt: "2024-01-01T11:30:00Z" }, - { responseTime: 200, createdAt: "2024-01-01T11:00:00Z" }, - { responseTime: 300, createdAt: "2024-01-01T10:30:00Z" }, - ]; - - // Average: (100 + 200 + 300) / 3 = 200 - expect(getAverageResponseTime(checks)).to.equal(200); - }); - - it("should handle missing responseTime in some checks", function () { - const checks = [ - { responseTime: 100, createdAt: "2024-01-01T11:30:00Z" }, - { createdAt: "2024-01-01T11:00:00Z" }, - { responseTime: 300, createdAt: "2024-01-01T10:30:00Z" }, - ]; - - // Average: (100 + 300) / 2 = 200 - expect(getAverageResponseTime(checks)).to.equal(200); - }); - - it("should return 0 when no checks have responseTime", function () { - const checks = [{ createdAt: "2024-01-01T11:30:00Z" }, { createdAt: "2024-01-01T11:00:00Z" }]; - - expect(getAverageResponseTime(checks)).to.equal(0); - }); - }); - - describe("getUptimePercentage", function () { - it("should return 0 when checks array is empty", function () { - expect(getUptimePercentage([])).to.equal(0); - }); - - it("should return 0 when checks array is null", function () { - expect(getUptimePercentage(null)).to.equal(0); - }); - - it("should return 100 when all checks are up", function () { - const checks = [{ status: true }, { status: true }, { status: true }]; - expect(getUptimePercentage(checks)).to.equal(100); - }); - - it("should return 0 when all checks are down", function () { - const checks = [{ status: false }, { status: false }, { status: false }]; - expect(getUptimePercentage(checks)).to.equal(0); - }); - - it("should calculate correct percentage for mixed status checks", function () { - const checks = [{ status: true }, { status: false }, { status: true }, { status: true }]; - // 3 up out of 4 total = 75% - expect(getUptimePercentage(checks)).to.equal(75); - }); - - it("should handle undefined status values", function () { - const checks = [{ status: true }, { status: undefined }, { status: true }]; - // 2 up out of 3 total ≈ 66.67% - expect(getUptimePercentage(checks)).to.equal((2 / 3) * 100); - }); - }); - - describe("getIncidents", function () { - it("should return 0 when checks array is empty", function () { - expect(getIncidents([])).to.equal(0); - }); - - it("should return 0 when checks array is null", function () { - expect(getIncidents(null)).to.equal(0); - }); - - it("should return 0 when all checks are up", function () { - const checks = [{ status: true }, { status: true }, { status: true }]; - expect(getIncidents(checks)).to.equal(0); - }); - - it("should count all incidents when all checks are down", function () { - const checks = [{ status: false }, { status: false }, { status: false }]; - expect(getIncidents(checks)).to.equal(3); - }); - - it("should count correct number of incidents for mixed status checks", function () { - const checks = [{ status: true }, { status: false }, { status: true }, { status: false }, { status: true }]; - expect(getIncidents(checks)).to.equal(2); - }); - - it("should handle undefined status values", function () { - const checks = [{ status: true }, { status: undefined }, { status: false }, { status: false }]; - // Only counts explicit false values - expect(getIncidents(checks)).to.equal(2); - }); - }); - - describe("getMonitorChecks", function () { - let mockModel; - - beforeEach(function () { - // Create a mock model with chainable methods - const mockChecks = [ - { monitorId: "123", createdAt: new Date("2024-01-01") }, - { monitorId: "123", createdAt: new Date("2024-01-02") }, - ]; - - mockModel = { - find: sinon.stub().returns({ - sort: sinon.stub().returns(mockChecks), - }), - }; - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return all checks and date-ranged checks", async function () { - // Arrange - const monitorId = "123"; - const dateRange = { - start: new Date("2024-01-01"), - end: new Date("2024-01-02"), - }; - const sortOrder = -1; - - // Act - const result = await getMonitorChecks(monitorId, mockModel, dateRange, sortOrder); - - // Assert - expect(result).to.have.keys(["checksAll", "checksForDateRange"]); - - // Verify find was called with correct parameters - expect(mockModel.find.firstCall.args[0]).to.deep.equal({ monitorId }); - expect(mockModel.find.secondCall.args[0]).to.deep.equal({ - monitorId, - createdAt: { $gte: dateRange.start, $lte: dateRange.end }, - }); - - // Verify sort was called with correct parameters - const sortCalls = mockModel.find().sort.getCalls(); - sortCalls.forEach((call) => { - expect(call.args[0]).to.deep.equal({ createdAt: sortOrder }); - }); - }); - - it("should handle empty results", async function () { - // Arrange - const emptyModel = { - find: sinon.stub().returns({ - sort: sinon.stub().returns([]), - }), - }; - - // Act - const result = await getMonitorChecks( - "123", - emptyModel, - { - start: new Date(), - end: new Date(), - }, - -1 - ); - - // Assert - expect(result.checksAll).to.be.an("array").that.is.empty; - expect(result.checksForDateRange).to.be.an("array").that.is.empty; - }); - - it("should maintain sort order", async function () { - // Arrange - const sortedChecks = [ - { monitorId: "123", createdAt: new Date("2024-01-02") }, - { monitorId: "123", createdAt: new Date("2024-01-01") }, - ]; - - const sortedModel = { - find: sinon.stub().returns({ - sort: sinon.stub().returns(sortedChecks), - }), - }; - - // Act - const result = await getMonitorChecks( - "123", - sortedModel, - { - start: new Date("2024-01-01"), - end: new Date("2024-01-02"), - }, - -1 - ); - - // Assert - expect(result.checksAll[0].createdAt).to.be.greaterThan(result.checksAll[1].createdAt); - expect(result.checksForDateRange[0].createdAt).to.be.greaterThan(result.checksForDateRange[1].createdAt); - }); - }); - - describe("processChecksForDisplay", function () { - let normalizeStub; - - beforeEach(function () { - normalizeStub = sinon.stub(); - }); - - it("should return original checks when numToDisplay is not provided", function () { - const checks = [1, 2, 3, 4, 5]; - const result = processChecksForDisplay(normalizeStub, checks); - expect(result).to.deep.equal(checks); - }); - - it("should return original checks when numToDisplay is greater than checks length", function () { - const checks = [1, 2, 3]; - const result = processChecksForDisplay(normalizeStub, checks, 5); - expect(result).to.deep.equal(checks); - }); - - it("should filter checks based on numToDisplay", function () { - const checks = [1, 2, 3, 4, 5, 6]; - const result = processChecksForDisplay(normalizeStub, checks, 3); - // Should return [1, 3, 5] as n = ceil(6/3) = 2 - expect(result).to.deep.equal([1, 3, 5]); - }); - - it("should handle empty checks array", function () { - const checks = []; - const result = processChecksForDisplay(normalizeStub, checks, 3); - expect(result).to.be.an("array").that.is.empty; - }); - - it("should call normalizeData when normalize is true", function () { - const checks = [1, 2, 3]; - normalizeStub.returns([10, 20, 30]); - - const result = processChecksForDisplay(normalizeStub, checks, null, true); - - expect(normalizeStub.args[0]).to.deep.equal([checks, 1, 100]); - expect(result).to.deep.equal([10, 20, 30]); - }); - - it("should handle both filtering and normalization", function () { - const checks = [1, 2, 3, 4, 5, 6]; - normalizeStub.returns([10, 30, 50]); - - const result = processChecksForDisplay(normalizeStub, checks, 3, true); - - expect(normalizeStub.args[0][0]).to.deep.equal([1, 3, 5]); - expect(result).to.deep.equal([10, 30, 50]); - }); - }); - - describe("groupChecksByTime", function () { - const mockChecks = [ - { createdAt: "2024-01-15T10:30:45Z" }, - { createdAt: "2024-01-15T10:45:15Z" }, - { createdAt: "2024-01-15T11:15:00Z" }, - { createdAt: "2024-01-16T10:30:00Z" }, - ]; - - it("should group checks by hour when dateRange is 'day'", function () { - const result = groupChecksByTime(mockChecks, "day"); - - // Get timestamps for 10:00 and 11:00 on Jan 15 - const time1 = new Date("2024-01-15T10:00:00Z").getTime(); - const time2 = new Date("2024-01-15T11:00:00Z").getTime(); - const time3 = new Date("2024-01-16T10:00:00Z").getTime(); - - expect(Object.keys(result)).to.have.lengthOf(3); - - expect(result[time1].checks).to.have.lengthOf(2); - expect(result[time2].checks).to.have.lengthOf(1); - expect(result[time3].checks).to.have.lengthOf(1); - }); - - it("should group checks by day when dateRange is not 'day'", function () { - const result = groupChecksByTime(mockChecks, "week"); - - expect(Object.keys(result)).to.have.lengthOf(2); - expect(result["2024-01-15"].checks).to.have.lengthOf(3); - expect(result["2024-01-16"].checks).to.have.lengthOf(1); - }); - - it("should handle empty checks array", function () { - const result = groupChecksByTime([], "day"); - expect(result).to.deep.equal({}); - }); - - it("should handle single check", function () { - const singleCheck = [{ createdAt: "2024-01-15T10:30:45Z" }]; - const result = groupChecksByTime(singleCheck, "day"); - - const expectedTime = new Date("2024-01-15T10:00:00Z").getTime(); - expect(Object.keys(result)).to.have.lengthOf(1); - expect(result[expectedTime].checks).to.have.lengthOf(1); - }); - - it("should skip invalid dates and process valid ones", function () { - const checksWithInvalidDate = [ - { createdAt: "invalid-date" }, - { createdAt: "2024-01-15T10:30:45Z" }, - { createdAt: null }, - { createdAt: undefined }, - { createdAt: "" }, - ]; - - const result = groupChecksByTime(checksWithInvalidDate, "day"); - - const expectedTime = new Date("2024-01-15T10:00:00Z").getTime(); - expect(Object.keys(result)).to.have.lengthOf(1); - expect(result[expectedTime].checks).to.have.lengthOf(1); - expect(result[expectedTime].checks[0].createdAt).to.equal("2024-01-15T10:30:45Z"); - }); - - it("should handle checks in same time group", function () { - const checksInSameHour = [{ createdAt: "2024-01-15T10:15:00Z" }, { createdAt: "2024-01-15T10:45:00Z" }]; - - const result = groupChecksByTime(checksInSameHour, "day"); - - const expectedTime = new Date("2024-01-15T10:00:00Z").getTime(); - expect(Object.keys(result)).to.have.lengthOf(1); - expect(result[expectedTime].checks).to.have.lengthOf(2); - }); - }); - - describe("calculateGroupStats", function () { - // Mock getUptimePercentage function - let uptimePercentageStub; - - beforeEach(function () { - uptimePercentageStub = sinon.stub(); - uptimePercentageStub.returns(95); // Default return value - }); - - it("should calculate stats correctly for a group of checks", function () { - const mockGroup = { - time: "2024-01-15", - checks: [ - { status: true, responseTime: 100 }, - { status: false, responseTime: 200 }, - { status: true, responseTime: 300 }, - ], - }; - - const result = calculateGroupStats(mockGroup, uptimePercentageStub); - - expect(result).to.deep.equal({ - time: "2024-01-15", - uptimePercentage: (2 / 3) * 100, - totalChecks: 3, - totalIncidents: 1, - avgResponseTime: 200, // (100 + 200 + 300) / 3 - }); - }); - - it("should handle empty checks array", function () { - const mockGroup = { - time: "2024-01-15", - checks: [], - }; - - const result = calculateGroupStats(mockGroup, uptimePercentageStub); - - expect(result).to.deep.equal({ - time: "2024-01-15", - uptimePercentage: 0, - totalChecks: 0, - totalIncidents: 0, - avgResponseTime: 0, - }); - }); - - it("should handle missing responseTime values", function () { - const mockGroup = { - time: "2024-01-15", - checks: [{ status: true }, { status: false, responseTime: 200 }, { status: true, responseTime: undefined }], - }; - - const result = calculateGroupStats(mockGroup, uptimePercentageStub); - - expect(result).to.deep.equal({ - time: "2024-01-15", - uptimePercentage: (2 / 3) * 100, - totalChecks: 3, - totalIncidents: 1, - avgResponseTime: 200, // 200 / 1 - }); - }); - - it("should handle all checks with status false", function () { - const mockGroup = { - time: "2024-01-15", - checks: [ - { status: false, responseTime: 100 }, - { status: false, responseTime: 200 }, - { status: false, responseTime: 300 }, - ], - }; - - const result = calculateGroupStats(mockGroup, uptimePercentageStub); - - expect(result).to.deep.equal({ - time: "2024-01-15", - uptimePercentage: 0, - totalChecks: 3, - totalIncidents: 3, - avgResponseTime: 200, - }); - }); - - it("should handle all checks with status true", function () { - const mockGroup = { - time: "2024-01-15", - checks: [ - { status: true, responseTime: 100 }, - { status: true, responseTime: 200 }, - { status: true, responseTime: 300 }, - ], - }; - - const result = calculateGroupStats(mockGroup, uptimePercentageStub); - - expect(result).to.deep.equal({ - time: "2024-01-15", - uptimePercentage: 100, - totalChecks: 3, - totalIncidents: 0, - avgResponseTime: 200, - }); - }); - }); - - describe("getMonitorStatsById", function () { - const now = new Date(); - const oneHourAgo = new Date(now - 3600000); - const twoHoursAgo = new Date(now - 7200000); - - const mockMonitor = { - _id: "monitor123", - type: "http", - name: "Test Monitor", - url: "https://test.com", - toObject: () => ({ - _id: "monitor123", - type: "http", - name: "Test Monitor", - url: "https://test.com", - }), - }; - - const mockMonitorPing = { - _id: "monitor123", - type: "ping", - name: "Test Monitor", - url: "https://test.com", - toObject: () => ({ - _id: "monitor123", - type: "http", - name: "Test Monitor", - url: "https://test.com", - }), - }; - const mockMonitorDocker = { - _id: "monitor123", - type: "docker", - name: "Test Monitor", - url: "https://test.com", - toObject: () => ({ - _id: "monitor123", - type: "http", - name: "Test Monitor", - url: "https://test.com", - }), - }; - - const checkDocs = [ - { - monitorId: "monitor123", - status: true, - responseTime: 100, - createdAt: new Date("2024-01-01T12:00:00Z"), - toObject: function () { - return { - monitorId: this.monitorId, - status: this.status, - responseTime: this.responseTime, - createdAt: this.createdAt, - }; - }, - }, - { - monitorId: "monitor123", - status: true, - responseTime: 150, - createdAt: new Date("2024-01-01T11:00:00Z"), - toObject: function () { - return { - monitorId: this.monitorId, - status: this.status, - responseTime: this.responseTime, - createdAt: this.createdAt, - }; - }, - }, - { - monitorId: "monitor123", - status: false, - responseTime: 200, - createdAt: new Date("2024-01-01T10:00:00Z"), - toObject: function () { - return { - monitorId: this.monitorId, - status: this.status, - responseTime: this.responseTime, - createdAt: this.createdAt, - }; - }, - }, - ]; - const req = { - params: { monitorId: "monitor123" }, - query: { - dateRange: "day", - sortOrder: "desc", - numToDisplay: 10, - normalize: true, - }, - }; - - beforeEach(function () { - checkFindStub.returns({ - sort: () => checkDocs, - }); - monitorFindByIdStub.returns(mockMonitor); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return monitor stats with calculated values, sort order desc", async function () { - req.query.sortOrder = "desc"; - const result = await getMonitorStatsById(req); - expect(result).to.include.keys([ - "_id", - "type", - "name", - "url", - "uptimeDuration", - "lastChecked", - "latestResponseTime", - "periodIncidents", - "periodTotalChecks", - "periodAvgResponseTime", - "periodUptime", - "aggregateData", - ]); - expect(result.latestResponseTime).to.equal(100); - expect(result.periodTotalChecks).to.equal(3); - expect(result.periodIncidents).to.equal(1); - expect(result.periodUptime).to.be.a("number"); - expect(result.aggregateData).to.be.an("array"); - }); - - it("should return monitor stats with calculated values, ping type", async function () { - monitorFindByIdStub.returns(mockMonitorPing); - req.query.sortOrder = "desc"; - const result = await getMonitorStatsById(req); - expect(result).to.include.keys([ - "_id", - "type", - "name", - "url", - "uptimeDuration", - "lastChecked", - "latestResponseTime", - "periodIncidents", - "periodTotalChecks", - "periodAvgResponseTime", - "periodUptime", - "aggregateData", - ]); - expect(result.latestResponseTime).to.equal(100); - expect(result.periodTotalChecks).to.equal(3); - expect(result.periodIncidents).to.equal(1); - expect(result.periodUptime).to.be.a("number"); - expect(result.aggregateData).to.be.an("array"); - }); - - it("should return monitor stats with calculated values, docker type", async function () { - monitorFindByIdStub.returns(mockMonitorDocker); - req.query.sortOrder = "desc"; - const result = await getMonitorStatsById(req); - expect(result).to.include.keys([ - "_id", - "type", - "name", - "url", - "uptimeDuration", - "lastChecked", - "latestResponseTime", - "periodIncidents", - "periodTotalChecks", - "periodAvgResponseTime", - "periodUptime", - "aggregateData", - ]); - expect(result.latestResponseTime).to.equal(100); - expect(result.periodTotalChecks).to.equal(3); - expect(result.periodIncidents).to.equal(1); - expect(result.periodUptime).to.be.a("number"); - expect(result.aggregateData).to.be.an("array"); - }); - - it("should return monitor stats with calculated values", async function () { - req.query.sortOrder = "asc"; - const result = await getMonitorStatsById(req); - expect(result).to.include.keys([ - "_id", - "type", - "name", - "url", - "uptimeDuration", - "lastChecked", - "latestResponseTime", - "periodIncidents", - "periodTotalChecks", - "periodAvgResponseTime", - "periodUptime", - "aggregateData", - ]); - expect(result.latestResponseTime).to.equal(100); - expect(result.periodTotalChecks).to.equal(3); - expect(result.periodIncidents).to.equal(1); - expect(result.periodUptime).to.be.a("number"); - expect(result.aggregateData).to.be.an("array"); - }); - - it("should throw error when monitor is not found", async function () { - monitorFindByIdStub.returns(Promise.resolve(null)); - - const req = { - params: { monitorId: "nonexistent" }, - }; - - try { - await getMonitorStatsById(req); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error).to.be.an("Error"); - expect(error.service).to.equal("monitorModule"); - expect(error.method).to.equal("getMonitorStatsById"); - } - }); - }); - - describe("getMonitorById", function () { - let notificationFindStub; - let monitorSaveStub; - - beforeEach(function () { - // Create stubs - notificationFindStub = sinon.stub(Notification, "find"); - monitorSaveStub = sinon.stub().resolves(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return monitor with notifications when found", async function () { - // Arrange - const monitorId = "123"; - const mockMonitor = { - _id: monitorId, - name: "Test Monitor", - save: monitorSaveStub, - }; - const mockNotifications = [ - { _id: "notif1", message: "Test notification 1" }, - { _id: "notif2", message: "Test notification 2" }, - ]; - - monitorFindByIdStub.resolves(mockMonitor); - notificationFindStub.resolves(mockNotifications); - - const result = await getMonitorById(monitorId); - expect(result._id).to.equal(monitorId); - expect(result.name).to.equal("Test Monitor"); - expect(monitorFindByIdStub.calledWith(monitorId)).to.be.true; - expect(notificationFindStub.calledWith({ monitorId })).to.be.true; - expect(monitorSaveStub.calledOnce).to.be.true; - }); - - it("should throw 404 error when monitor not found", async function () { - // Arrange - const monitorId = "nonexistent"; - monitorFindByIdStub.resolves(null); - - // Act & Assert - try { - await getMonitorById(monitorId); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error.message).to.equal(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)); - expect(error.status).to.equal(404); - expect(error.service).to.equal("monitorModule"); - expect(error.method).to.equal("getMonitorById"); - } - }); - - it("should handle database errors properly", async function () { - // Arrange - const monitorId = "123"; - const dbError = new Error("Database connection failed"); - monitorFindByIdStub.rejects(dbError); - - // Act & Assert - try { - await getMonitorById(monitorId); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error.service).to.equal("monitorModule"); - expect(error.method).to.equal("getMonitorById"); - expect(error.message).to.equal("Database connection failed"); - } - }); - - it("should handle notification fetch errors", async function () { - // Arrange - const monitorId = "123"; - const mockMonitor = { - _id: monitorId, - name: "Test Monitor", - save: monitorSaveStub, - }; - const notificationError = new Error("Notification fetch failed"); - - monitorFindByIdStub.resolves(mockMonitor); - notificationFindStub.rejects(notificationError); - - // Act & Assert - try { - await getMonitorById(monitorId); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error.service).to.equal("monitorModule"); - expect(error.method).to.equal("getMonitorById"); - expect(error.message).to.equal("Notification fetch failed"); - } - }); - - it("should handle monitor save errors", async function () { - // Arrange - const monitorId = "123"; - const mockMonitor = { - _id: monitorId, - name: "Test Monitor", - save: sinon.stub().rejects(new Error("Save failed")), - }; - const mockNotifications = []; - - monitorFindByIdStub.resolves(mockMonitor); - notificationFindStub.resolves(mockNotifications); - - // Act & Assert - try { - await getMonitorById(monitorId); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error.service).to.equal("monitorModule"); - expect(error.method).to.equal("getMonitorById"); - expect(error.message).to.equal("Save failed"); - } - }); - }); - - describe("getMonitorsAndSummaryByTeamId", function () { - it("should return monitors and correct summary counts", async function () { - // Arrange - const teamId = "team123"; - const type = "http"; - const mockMonitors = [ - { teamId, type, status: true, isActive: true }, // up - { teamId, type, status: false, isActive: true }, // down - { teamId, type, status: null, isActive: false }, // paused - { teamId, type, status: true, isActive: true }, // up - ]; - monitorFindStub.resolves(mockMonitors); - - // Act - const result = await getMonitorsAndSummaryByTeamId(teamId, type); - - // Assert - expect(result.monitors).to.have.lengthOf(4); - expect(result.monitorCounts).to.deep.equal({ - up: 2, - down: 1, - paused: 1, - total: 4, - }); - expect(monitorFindStub.calledOnceWith({ teamId, type })).to.be.true; - }); - - it("should return empty results for non-existent team", async function () { - // Arrange - monitorFindStub.resolves([]); - - // Act - const result = await getMonitorsAndSummaryByTeamId("nonexistent", "http"); - - // Assert - expect(result.monitors).to.have.lengthOf(0); - expect(result.monitorCounts).to.deep.equal({ - up: 0, - down: 0, - paused: 0, - total: 0, - }); - }); - - it("should handle database errors", async function () { - // Arrange - const error = new Error("Database error"); - error.service = "MonitorModule"; - error.method = "getMonitorsAndSummaryByTeamId"; - monitorFindStub.rejects(error); - - // Act & Assert - try { - await getMonitorsAndSummaryByTeamId("team123", "http"); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err).to.equal(error); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("getMonitorsAndSummaryByTeamId"); - } - }); - }); - - describe("getMonitorsByTeamId", function () { - beforeEach(function () { - // Chain stubs for Monitor.find().skip().limit().sort() - - // Stub for CHECK_MODEL_LOOKUP model find - checkFindStub.returns({ - sort: sinon.stub().returns({ - limit: sinon.stub().returns([]), - }), - }); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return monitors with basic query parameters", async function () { - const mockMonitors = [ - { _id: "1", type: "http", toObject: () => ({ _id: "1", type: "http" }) }, - { _id: "2", type: "ping", toObject: () => ({ _id: "2", type: "ping" }) }, - ]; - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns(mockMonitors), - }), - }), - }); - - const req = { - params: { teamId: "team123" }, - query: { - type: "http", - page: 0, - rowsPerPage: 10, - field: "name", - status: false, - checkOrder: "desc", - }, - }; - - monitorCountStub.resolves(2); - - const result = await getMonitorsByTeamId(req); - - expect(result).to.have.property("monitors"); - expect(result).to.have.property("monitorCount", 2); - }); - - it("should return monitors with basic query parameters", async function () { - const mockMonitors = [ - { _id: "1", type: "http", toObject: () => ({ _id: "1", type: "http" }) }, - { _id: "2", type: "ping", toObject: () => ({ _id: "2", type: "ping" }) }, - ]; - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns(mockMonitors), - }), - }), - }); - - const req = { - params: { teamId: "team123" }, - query: { - type: "http", - page: 0, - rowsPerPage: 10, - field: "name", - status: true, - checkOrder: "asc", - }, - }; - - monitorCountStub.resolves(2); - - const result = await getMonitorsByTeamId(req); - - expect(result).to.have.property("monitors"); - expect(result).to.have.property("monitorCount", 2); - }); - - it("should handle type filter with array input", async function () { - const req = { - params: { teamId: "team123" }, - query: { - type: ["http", "ping"], - }, - }; - - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns([]), - }), - }), - }); - monitorCountStub.resolves(0); - - await getMonitorsByTeamId(req); - - expect(Monitor.find.firstCall.args[0]).to.deep.equal({ - teamId: "team123", - type: { $in: ["http", "ping"] }, - }); - }); - - it("should handle text search filter", async function () { - const req = { - params: { teamId: "team123" }, - query: { - filter: "search", - }, - }; - - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns([]), - }), - }), - }); - monitorCountStub.resolves(0); - - await getMonitorsByTeamId(req); - - expect(Monitor.find.firstCall.args[0]).to.deep.equal({ - teamId: "team123", - $or: [{ name: { $regex: "search", $options: "i" } }, { url: { $regex: "search", $options: "i" } }], - }); - }); - - it("should handle pagination parameters", async function () { - const req = { - params: { teamId: "team123" }, - query: { - page: 2, - rowsPerPage: 5, - }, - }; - - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns([]), - }), - }), - }); - monitorCountStub.resolves(0); - - const result = await getMonitorsByTeamId(req); - expect(result).to.deep.equal({ - monitors: [], - monitorCount: 0, - }); - }); - - it("should handle sorting parameters", async function () { - const req = { - params: { teamId: "team123" }, - query: { - field: "name", - order: "asc", - }, - }; - - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns([]), - }), - }), - }); - monitorCountStub.resolves(0); - - await getMonitorsByTeamId(req); - - const result = await getMonitorsByTeamId(req); - expect(result).to.deep.equal({ - monitors: [], - monitorCount: 0, - }); - }); - - it("should return early when limit is -1", async function () { - // Arrange - const req = { - params: { teamId: "team123" }, - query: { - limit: "-1", - }, - }; - - const mockMonitors = [ - { _id: "1", type: "http" }, - { _id: "2", type: "ping" }, - ]; - - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns(mockMonitors), - }), - }), - }); - - monitorCountStub.resolves(2); - - // Act - const result = await getMonitorsByTeamId(req); - - // Assert - expect(result).to.deep.equal({ - monitors: mockMonitors, - monitorCount: 2, - }); - }); - - it("should normalize checks when normalize parameter is provided", async function () { - const req = { - params: { teamId: "team123" }, - query: { normalize: "true" }, - }; - monitorCountStub.resolves(2); - - const mockMonitors = [ - { _id: "1", type: "http", toObject: () => ({ _id: "1", type: "http" }) }, - { _id: "2", type: "ping", toObject: () => ({ _id: "2", type: "ping" }) }, - ]; - - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns(mockMonitors), - }), - }), - }); - - const result = await getMonitorsByTeamId(req); - expect(result.monitorCount).to.equal(2); - expect(result.monitors).to.have.lengthOf(2); - }); - - it("should handle database errors", async function () { - const req = { - params: { teamId: "team123" }, - query: {}, - }; - - const error = new Error("Database error"); - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().throws(error), - }), - }), - }); - - try { - await getMonitorsByTeamId(req); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("getMonitorsByTeamId"); - expect(err.message).to.equal("Database error"); - } - }); - }); - - describe("createMonitor", function () { - it("should create a monitor without notifications", async function () { - let monitorSaveStub = sinon.stub(Monitor.prototype, "save").resolves(); - - const req = { - body: { - name: "Test Monitor", - url: "http://test.com", - type: "http", - notifications: ["someNotification"], - }, - }; - - const expectedMonitor = { - name: "Test Monitor", - url: "http://test.com", - type: "http", - notifications: undefined, - save: monitorSaveStub, - }; - - const result = await createMonitor(req); - expect(result.name).to.equal(expectedMonitor.name); - expect(result.url).to.equal(expectedMonitor.url); - }); - - it("should handle database errors", async function () { - const req = { - body: { - name: "Test Monitor", - }, - }; - - try { - await createMonitor(req); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("createMonitor"); - } - }); - }); - - describe("deleteMonitor", function () { - it("should delete a monitor successfully", async function () { - const monitorId = "123456789"; - const mockMonitor = { - _id: monitorId, - name: "Test Monitor", - url: "http://test.com", - }; - - const req = { - params: { monitorId }, - }; - - monitorFindByIdAndDeleteStub.resolves(mockMonitor); - - const result = await deleteMonitor(req); - - expect(result).to.deep.equal(mockMonitor); - sinon.assert.calledWith(monitorFindByIdAndDeleteStub, monitorId); - }); - - it("should throw error when monitor not found", async function () { - const monitorId = "nonexistent123"; - const req = { - params: { monitorId }, - }; - - monitorFindByIdAndDeleteStub.resolves(null); - - try { - await deleteMonitor(req); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err.message).to.equal(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("deleteMonitor"); - } - }); - - it("should handle database errors", async function () { - const monitorId = "123456789"; - const req = { - params: { monitorId }, - }; - - const dbError = new Error("Database connection error"); - monitorFindByIdAndDeleteStub.rejects(dbError); - - try { - await deleteMonitor(req); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err.message).to.equal("Database connection error"); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("deleteMonitor"); - } - }); - }); - - describe("deleteAllMonitors", function () { - it("should delete all monitors for a team successfully", async function () { - const teamId = "team123"; - const mockMonitors = [ - { _id: "1", name: "Monitor 1", teamId }, - { _id: "2", name: "Monitor 2", teamId }, - ]; - - monitorFindStub.resolves(mockMonitors); - monitorDeleteManyStub.resolves({ deletedCount: 2 }); - - const result = await deleteAllMonitors(teamId); - - expect(result).to.deep.equal({ - monitors: mockMonitors, - deletedCount: 2, - }); - sinon.assert.calledWith(monitorFindStub, { teamId }); - sinon.assert.calledWith(monitorDeleteManyStub, { teamId }); - }); - - it("should return empty array when no monitors found", async function () { - const teamId = "emptyTeam"; - - monitorFindStub.resolves([]); - monitorDeleteManyStub.resolves({ deletedCount: 0 }); - - const result = await deleteAllMonitors(teamId); - - expect(result).to.deep.equal({ - monitors: [], - deletedCount: 0, - }); - sinon.assert.calledWith(monitorFindStub, { teamId }); - sinon.assert.calledWith(monitorDeleteManyStub, { teamId }); - }); - - it("should handle database errors", async function () { - const teamId = "team123"; - const dbError = new Error("Database connection error"); - monitorFindStub.rejects(dbError); - - try { - await deleteAllMonitors(teamId); - } catch (err) { - expect(err.message).to.equal("Database connection error"); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("deleteAllMonitors"); - } - }); - - it("should handle deleteMany errors", async function () { - const teamId = "team123"; - monitorFindStub.resolves([{ _id: "1", name: "Monitor 1" }]); - monitorDeleteManyStub.rejects(new Error("Delete operation failed")); - - try { - await deleteAllMonitors(teamId); - } catch (err) { - expect(err.message).to.equal("Delete operation failed"); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("deleteAllMonitors"); - } - }); - }); - - describe("deleteMonitorsByUserId", function () { - beforeEach(function () {}); - - afterEach(function () { - sinon.restore(); - }); - - it("should delete all monitors for a user successfully", async function () { - // Arrange - const userId = "user123"; - const mockResult = { - deletedCount: 3, - acknowledged: true, - }; - - monitorDeleteManyStub.resolves(mockResult); - - // Act - const result = await deleteMonitorsByUserId(userId); - - // Assert - expect(result).to.deep.equal(mockResult); - sinon.assert.calledWith(monitorDeleteManyStub, { userId: userId }); - }); - - it("should return zero deletedCount when no monitors found", async function () { - // Arrange - const userId = "nonexistentUser"; - const mockResult = { - deletedCount: 0, - acknowledged: true, - }; - - monitorDeleteManyStub.resolves(mockResult); - - // Act - const result = await deleteMonitorsByUserId(userId); - - // Assert - expect(result.deletedCount).to.equal(0); - sinon.assert.calledWith(monitorDeleteManyStub, { userId: userId }); - }); - - it("should handle database errors", async function () { - // Arrange - const userId = "user123"; - const dbError = new Error("Database connection error"); - monitorDeleteManyStub.rejects(dbError); - - // Act & Assert - try { - await deleteMonitorsByUserId(userId); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err.message).to.equal("Database connection error"); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("deleteMonitorsByUserId"); - } - }); - }); - - describe("editMonitor", function () { - it("should edit a monitor successfully", async function () { - // Arrange - const candidateId = "monitor123"; - const candidateMonitor = { - name: "Updated Monitor", - url: "http://updated.com", - type: "http", - notifications: ["someNotification"], - }; - - const expectedUpdateData = { - name: "Updated Monitor", - url: "http://updated.com", - type: "http", - notifications: undefined, - }; - - const mockUpdatedMonitor = { - _id: candidateId, - ...expectedUpdateData, - }; - - monitorFindByIdAndUpdateStub.resolves(mockUpdatedMonitor); - - // Act - const result = await editMonitor(candidateId, candidateMonitor); - - // Assert - expect(result).to.deep.equal(mockUpdatedMonitor); - sinon.assert.calledWith(monitorFindByIdAndUpdateStub, candidateId, expectedUpdateData, { - new: true, - }); - }); - - it("should return null when monitor not found", async function () { - // Arrange - const candidateId = "nonexistent123"; - const candidateMonitor = { - name: "Updated Monitor", - }; - - monitorFindByIdAndUpdateStub.resolves(null); - - // Act - const result = await editMonitor(candidateId, candidateMonitor); - - // Assert - expect(result).to.be.null; - sinon.assert.calledWith(monitorFindByIdAndUpdateStub, candidateId, { name: "Updated Monitor", notifications: undefined }, { new: true }); - }); - - it("should remove notifications from update data", async function () { - // Arrange - const candidateId = "monitor123"; - const candidateMonitor = { - name: "Updated Monitor", - notifications: ["notification1", "notification2"], - }; - - const expectedUpdateData = { - name: "Updated Monitor", - notifications: undefined, - }; - - monitorFindByIdAndUpdateStub.resolves({ - _id: candidateId, - ...expectedUpdateData, - }); - - // Act - await editMonitor(candidateId, candidateMonitor); - - // Assert - sinon.assert.calledWith(monitorFindByIdAndUpdateStub, candidateId, expectedUpdateData, { - new: true, - }); - }); - - it("should handle database errors", async function () { - // Arrange - const candidateId = "monitor123"; - const candidateMonitor = { - name: "Updated Monitor", - }; - - const dbError = new Error("Database connection error"); - monitorFindByIdAndUpdateStub.rejects(dbError); - - // Act & Assert - try { - await editMonitor(candidateId, candidateMonitor); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err.message).to.equal("Database connection error"); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("editMonitor"); - } - }); - }); - - describe("addDemoMonitors", function () { - it("should add demo monitors successfully", async function () { - // Arrange - const userId = "user123"; - const teamId = "team123"; - monitorInsertManyStub.resolves([{ _id: "123" }]); - const result = await addDemoMonitors(userId, teamId); - expect(result).to.deep.equal([{ _id: "123" }]); - }); - - it("should handle database errors", async function () { - const userId = "user123"; - const teamId = "team123"; - - const dbError = new Error("Database connection error"); - monitorInsertManyStub.rejects(dbError); - - try { - await addDemoMonitors(userId, teamId); - } catch (err) { - expect(err.message).to.equal("Database connection error"); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("addDemoMonitors"); - } - }); - }); -}); diff --git a/server/tests/db/notificationModule.test.js b/server/tests/db/notificationModule.test.js deleted file mode 100755 index fbb97f33a..000000000 --- a/server/tests/db/notificationModule.test.js +++ /dev/null @@ -1,76 +0,0 @@ -import sinon from "sinon"; -import Notification from "../../db/models/Notification.js"; -import { createNotification, getNotificationsByMonitorId, deleteNotificationsByMonitorId } from "../../db/mongo/modules/notificationModule.js"; - -describe("notificationModule", function () { - const mockNotification = { - monitorId: "123", - }; - const mockNotifications = [mockNotification]; - let notificationSaveStub, notificationFindStub, notificationDeleteManyStub; - - beforeEach(function () { - notificationSaveStub = sinon.stub(Notification.prototype, "save").resolves(); - notificationFindStub = sinon.stub(Notification, "find").resolves(); - notificationDeleteManyStub = sinon.stub(Notification, "deleteMany").resolves(); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("createNotification", function () { - it("should create a new notification", async function () { - const notificationData = { _id: "123", name: "test" }; - notificationSaveStub.resolves(notificationData); - const res = await createNotification(notificationData); - expect(res).to.deep.equal(notificationData); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - notificationSaveStub.rejects(err); - try { - await createNotification(mockNotification); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getNotificationsByMonitorId", function () { - it("should return notifications by monitor ID", async function () { - notificationFindStub.resolves(mockNotifications); - const res = await getNotificationsByMonitorId(mockNotification.monitorId); - expect(res).to.deep.equal(mockNotifications); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - notificationFindStub.rejects(err); - try { - await getNotificationsByMonitorId(mockNotification.monitorId); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deleteNotificationsByMonitorId", function () { - it("should delete notifications by monitor ID", async function () { - notificationDeleteManyStub.resolves({ deletedCount: mockNotifications.length }); - const res = await deleteNotificationsByMonitorId(mockNotification.monitorId); - expect(res).to.deep.equal(mockNotifications.length); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - notificationDeleteManyStub.rejects(err); - try { - await deleteNotificationsByMonitorId(mockNotification.monitorId); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); -}); diff --git a/server/tests/db/pageSpeedCheckModule.test.js b/server/tests/db/pageSpeedCheckModule.test.js deleted file mode 100755 index 45135135d..000000000 --- a/server/tests/db/pageSpeedCheckModule.test.js +++ /dev/null @@ -1,63 +0,0 @@ -import sinon from "sinon"; -import PageSpeedCheck from "../../db/models/PageSpeedCheck.js"; -import { createPageSpeedCheck, deletePageSpeedChecksByMonitorId } from "../../db/mongo/modules/pageSpeedCheckModule.js"; - -const mockPageSpeedCheck = { - monitorId: "monitorId", - bestPractices: 1, - seo: 1, - performance: 1, -}; - -const mockDeletedResult = { deletedCount: 1 }; - -describe("pageSpeedCheckModule", function () { - let pageSpeedCheckSaveStub, pageSpeedCheckDeleteManyStub; - - beforeEach(function () { - pageSpeedCheckSaveStub = sinon.stub(PageSpeedCheck.prototype, "save"); - pageSpeedCheckDeleteManyStub = sinon.stub(PageSpeedCheck, "deleteMany"); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("createPageSpeedCheck", function () { - it("should return a page speed check", async function () { - pageSpeedCheckSaveStub.resolves(mockPageSpeedCheck); - const pageSpeedCheck = await createPageSpeedCheck(mockPageSpeedCheck); - expect(pageSpeedCheck).to.deep.equal(mockPageSpeedCheck); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - pageSpeedCheckSaveStub.rejects(err); - try { - await expect(createPageSpeedCheck(mockPageSpeedCheck)); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deletePageSpeedChecksByMonitorId", function () { - it("should return the number of deleted checks", async function () { - pageSpeedCheckDeleteManyStub.resolves(mockDeletedResult); - const result = await deletePageSpeedChecksByMonitorId("monitorId"); - expect(result).to.deep.equal(mockDeletedResult.deletedCount); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - pageSpeedCheckDeleteManyStub.rejects(err); - try { - await expect(deletePageSpeedChecksByMonitorId("monitorId")); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); -}); diff --git a/server/tests/db/recoveryModule.test.js b/server/tests/db/recoveryModule.test.js deleted file mode 100755 index 88afa257a..000000000 --- a/server/tests/db/recoveryModule.test.js +++ /dev/null @@ -1,168 +0,0 @@ -import sinon from "sinon"; -import RecoveryToken from "../../db/models/RecoveryToken.js"; -import User from "../../db/models/User.js"; -import { requestRecoveryToken, validateRecoveryToken, resetPassword } from "../../db/mongo/modules/recoveryModule.js"; -import { errorMessages } from "../../utils/messages.js"; - -const mockRecoveryToken = { - email: "test@test.com", - token: "1234567890", -}; - -const mockUser = { - email: "test@test.com", - password: "oldPassword", -}; - -const mockUserWithoutPassword = { - email: "test@test.com", -}; - -// Create a query builder that logs -const createQueryChain = (finalResult, comparePasswordResult = false) => ({ - select: () => ({ - select: async () => { - if (finalResult === mockUser) { - // Return a new object with all required methods - return { - email: "test@test.com", - password: "oldPassword", - comparePassword: sinon.stub().resolves(comparePasswordResult), - save: sinon.stub().resolves(), - }; - } - return finalResult; - }, - }), - // Add methods to the top level too - comparePassword: sinon.stub().resolves(comparePasswordResult), - save: sinon.stub().resolves(), -}); - -describe("recoveryModule", function () { - let deleteManyStub, saveStub, findOneStub, userCompareStub, userSaveStub, userFindOneStub; - let req, res; - - beforeEach(function () { - req = { - body: { email: "test@test.com" }, - }; - deleteManyStub = sinon.stub(RecoveryToken, "deleteMany"); - saveStub = sinon.stub(RecoveryToken.prototype, "save"); - findOneStub = sinon.stub(RecoveryToken, "findOne"); - userCompareStub = sinon.stub(User.prototype, "comparePassword"); - userSaveStub = sinon.stub(User.prototype, "save"); - userFindOneStub = sinon.stub().resolves(); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("requestRecoveryToken", function () { - it("should return a recovery token", async function () { - deleteManyStub.resolves(); - saveStub.resolves(mockRecoveryToken); - const result = await requestRecoveryToken(req, res); - expect(result.email).to.equal(mockRecoveryToken.email); - }); - - it("should handle an error", async function () { - const err = new Error("Test error"); - deleteManyStub.rejects(err); - try { - await requestRecoveryToken(req, res); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("validateRecoveryToken", function () { - it("should return a recovery token if found", async function () { - findOneStub.resolves(mockRecoveryToken); - const result = await validateRecoveryToken(req, res); - expect(result).to.deep.equal(mockRecoveryToken); - }); - - it("should thrown an error if a token is not found", async function () { - findOneStub.resolves(null); - try { - await validateRecoveryToken(req, res); - } catch (error) { - expect(error).to.exist; - expect(error.message).to.equal(errorMessages.DB_TOKEN_NOT_FOUND); - } - }); - - it("should handle DB errors", async function () { - const err = new Error("Test error"); - findOneStub.rejects(err); - try { - await validateRecoveryToken(req, res); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("resetPassword", function () { - beforeEach(function () { - req.body = { - password: "test", - newPassword: "test1", - }; - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should thrown an error if a recovery token is not found", async function () { - findOneStub.resolves(null); - try { - await resetPassword(req, res); - } catch (error) { - expect(error).to.exist; - expect(error.message).to.equal(errorMessages.DB_TOKEN_NOT_FOUND); - } - }); - - it("should throw an error if a user is not found", async function () { - findOneStub.resolves(mockRecoveryToken); - userFindOneStub = sinon.stub(User, "findOne").resolves(null); - try { - await resetPassword(req, res); - } catch (error) { - expect(error).to.exist; - expect(error.message).to.equal(errorMessages.DB_USER_NOT_FOUND); - } - }); - - it("should throw an error if the passwords match", async function () { - findOneStub.resolves(mockRecoveryToken); - saveStub.resolves(); - userFindOneStub = sinon.stub(User, "findOne").returns(createQueryChain(mockUser, true)); - try { - await resetPassword(req, res); - } catch (error) { - expect(error).to.exist; - expect(error.message).to.equal(errorMessages.DB_RESET_PASSWORD_BAD_MATCH); - } - }); - - it("should return a user without password if successful", async function () { - findOneStub.resolves(mockRecoveryToken); - saveStub.resolves(); - userFindOneStub = sinon - .stub(User, "findOne") - .returns(createQueryChain(mockUser)) // First call will resolve to mockUser - .onSecondCall() - .returns(createQueryChain(mockUserWithoutPassword)); - const result = await resetPassword(req, res); - expect(result).to.deep.equal(mockUserWithoutPassword); - }); - }); -}); diff --git a/server/tests/db/settingsModule.test.js b/server/tests/db/settingsModule.test.js deleted file mode 100755 index 9d1a80c3e..000000000 --- a/server/tests/db/settingsModule.test.js +++ /dev/null @@ -1,56 +0,0 @@ -import sinon from "sinon"; -import { getAppSettings, updateAppSettings } from "../../db/mongo/modules/settingsModule.js"; -import AppSettings from "../../db/models/AppSettings.js"; - -const mockAppSettings = { - appName: "Test App", -}; - -describe("SettingsModule", function () { - let appSettingsFindOneStub, appSettingsFindOneAndUpdateStub; - - beforeEach(function () { - appSettingsFindOneStub = sinon.stub(AppSettings, "findOne"); - appSettingsFindOneAndUpdateStub = sinon.stub(AppSettings, "findOneAndUpdate"); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("getAppSettings", function () { - it("should return app settings", async function () { - appSettingsFindOneStub.resolves(mockAppSettings); - const result = await getAppSettings(); - expect(result).to.deep.equal(mockAppSettings); - }); - - it("should handle an error", async function () { - const err = new Error("Test error"); - appSettingsFindOneStub.throws(err); - try { - await getAppSettings(); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("updateAppSettings", function () { - it("should update app settings", async function () { - appSettingsFindOneAndUpdateStub.resolves(mockAppSettings); - const result = await updateAppSettings(mockAppSettings); - expect(result).to.deep.equal(mockAppSettings); - }); - - it("should handle an error", async function () { - const err = new Error("Test error"); - appSettingsFindOneAndUpdateStub.throws(err); - try { - await updateAppSettings(mockAppSettings); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); -}); diff --git a/server/tests/db/statusPageModule.test.js b/server/tests/db/statusPageModule.test.js deleted file mode 100755 index 54e1a287f..000000000 --- a/server/tests/db/statusPageModule.test.js +++ /dev/null @@ -1,70 +0,0 @@ -import sinon from "sinon"; -import { createStatusPage, getStatusPageByUrl } from "../../db/mongo/modules/statusPageModule.js"; -import StatusPage from "../../db/models/StatusPage.js"; -import { errorMessages } from "../../utils/messages.js"; - -describe("statusPageModule", function () { - let statusPageFindOneStub, statusPageSaveStub, statusPageFindStub; - - beforeEach(function () { - statusPageSaveStub = sinon.stub(StatusPage.prototype, "save"); - statusPageFindOneStub = sinon.stub(StatusPage, "findOne"); - statusPageFindStub = sinon.stub(StatusPage, "find"); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("createStatusPage", function () { - it("should throw an error if a non-unique url is provided", async function () { - statusPageFindOneStub.resolves(true); - try { - await createStatusPage({ url: "test" }); - } catch (error) { - expect(error.status).to.equal(400); - expect(error.message).to.equal(errorMessages.STATUS_PAGE_URL_NOT_UNIQUE); - } - }); - - it("should handle duplicate URL errors", async function () { - const err = new Error("test"); - err.code = 11000; - statusPageSaveStub.rejects(err); - try { - await createStatusPage({ url: "test" }); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - - it("should return a status page if a unique url is provided", async function () { - statusPageFindOneStub.resolves(null); - statusPageFindStub.resolves([]); - const mockStatusPage = { url: "test" }; - const statusPage = await createStatusPage(mockStatusPage); - expect(statusPage).to.exist; - expect(statusPage.url).to.equal(mockStatusPage.url); - }); - }); - - describe("getStatusPageByUrl", function () { - it("should throw an error if a status page is not found", async function () { - statusPageFindOneStub.resolves(null); - try { - await getStatusPageByUrl("test"); - } catch (error) { - expect(error.status).to.equal(404); - expect(error.message).to.equal(errorMessages.STATUS_PAGE_NOT_FOUND); - } - }); - - it("should return a status page if a status page is found", async function () { - const mockStatusPage = { url: "test" }; - statusPageFindOneStub.resolves(mockStatusPage); - const statusPage = await getStatusPageByUrl(mockStatusPage.url); - expect(statusPage).to.exist; - expect(statusPage).to.deep.equal(mockStatusPage); - }); - }); -}); diff --git a/server/tests/db/userModule.test.js b/server/tests/db/userModule.test.js deleted file mode 100755 index d0f5ecb3e..000000000 --- a/server/tests/db/userModule.test.js +++ /dev/null @@ -1,294 +0,0 @@ -import sinon from "sinon"; -import UserModel from "../../db/models/User.js"; -import TeamModel from "../../db/models/Team.js"; -import { - insertUser, - getUserByEmail, - updateUser, - deleteUser, - deleteTeam, - deleteAllOtherUsers, - getAllUsers, - logoutUser, -} from "../../db/mongo/modules/userModule.js"; -import { errorMessages } from "../../utils/messages.js"; - -const mockUser = { - email: "test@test.com", - password: "password", - role: ["user"], -}; -const mockSuperUser = { - email: "test@test.com", - password: "password", - role: ["superadmin"], -}; -const imageFile = { - image: 1, -}; - -describe("userModule", function () { - let teamSaveStub, - teamFindByIdAndDeleteStub, - userSaveStub, - userFindStub, - userFindOneStub, - userFindByIdAndUpdateStub, - userFindByIdAndDeleteStub, - userDeleteManyStub, - userUpdateOneStub, - generateAvatarImageStub, - parseBooleanStub; - - beforeEach(function () { - teamSaveStub = sinon.stub(TeamModel.prototype, "save"); - teamFindByIdAndDeleteStub = sinon.stub(TeamModel, "findByIdAndDelete"); - userSaveStub = sinon.stub(UserModel.prototype, "save"); - userFindStub = sinon.stub(UserModel, "find"); - userFindOneStub = sinon.stub(UserModel, "findOne"); - userFindByIdAndUpdateStub = sinon.stub(UserModel, "findByIdAndUpdate"); - userFindByIdAndDeleteStub = sinon.stub(UserModel, "findByIdAndDelete"); - userDeleteManyStub = sinon.stub(UserModel, "deleteMany"); - userUpdateOneStub = sinon.stub(UserModel, "updateOne"); - generateAvatarImageStub = sinon.stub().resolves({ image: 2 }); - parseBooleanStub = sinon.stub().returns(true); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("insertUser", function () { - it("should insert a regular user", async function () { - userSaveStub.resolves(mockUser); - userFindOneStub.returns({ - select: sinon.stub().returns({ - select: sinon.stub().resolves(mockUser), - }), - }); - const result = await insertUser(mockUser, imageFile, generateAvatarImageStub); - expect(result).to.deep.equal(mockUser); - }); - - it("should insert a superadmin user", async function () { - userSaveStub.resolves(mockSuperUser); - userFindOneStub.returns({ - select: sinon.stub().returns({ - select: sinon.stub().resolves(mockSuperUser), - }), - }); - const result = await insertUser(mockSuperUser, imageFile, generateAvatarImageStub); - expect(result).to.deep.equal(mockSuperUser); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - userSaveStub.rejects(err); - try { - await insertUser(mockUser, imageFile, generateAvatarImageStub); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - - it("should handle a duplicate key error", async function () { - const err = new Error("test error"); - err.code = 11000; - userSaveStub.rejects(err); - try { - await insertUser(mockUser, imageFile, generateAvatarImageStub); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getUserByEmail", function () { - it("should return a user", async function () { - userFindOneStub.returns({ - select: sinon.stub().resolves(mockUser), - }); - const result = await getUserByEmail(mockUser.email); - expect(result).to.deep.equal(mockUser); - }); - }); - - describe("getUserByEmail", function () { - it("throw an error if a user is not found", async function () { - userFindOneStub.returns({ - select: sinon.stub().resolves(null), - }); - try { - await getUserByEmail(mockUser.email); - } catch (error) { - expect(error.message).to.equal(errorMessages.DB_USER_NOT_FOUND); - } - }); - }); - - describe("updateUser", function () { - let req, res; - - beforeEach(function () { - req = { - params: { - userId: "testId", - }, - body: { - deleteProfileImage: "false", - email: "test@test.com", - }, - file: { - buffer: "test", - mimetype: "test", - }, - }; - res = {}; - }); - - afterEach(function () {}); - - it("should update a user", async function () { - parseBooleanStub.returns(false); - userFindByIdAndUpdateStub.returns({ - select: sinon.stub().returns({ - select: sinon.stub().resolves(mockUser), - }), - }); - const result = await updateUser(req, res, parseBooleanStub, generateAvatarImageStub); - expect(result).to.deep.equal(mockUser); - }); - - it("should delete a user profile image", async function () { - req.body.deleteProfileImage = "true"; - userFindByIdAndUpdateStub.returns({ - select: sinon.stub().returns({ - select: sinon.stub().resolves(mockUser), - }), - }); - const result = await updateUser(req, res, parseBooleanStub, generateAvatarImageStub); - expect(result).to.deep.equal(mockUser); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - userFindByIdAndUpdateStub.throws(err); - try { - await updateUser(req, res, parseBooleanStub, generateAvatarImageStub); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deleteUser", function () { - it("should return a deleted user", async function () { - userFindByIdAndDeleteStub.resolves(mockUser); - const result = await deleteUser("testId"); - expect(result).to.deep.equal(mockUser); - }); - - it("should throw an error if a user is not found", async function () { - try { - await deleteUser("testId"); - } catch (error) { - expect(error).to.exist; - expect(error.message).to.equal(errorMessages.DB_USER_NOT_FOUND); - } - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - userFindByIdAndDeleteStub.throws(err); - try { - await deleteUser("testId"); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deleteTeam", function () { - it("should return true if team deleted", async function () { - teamFindByIdAndDeleteStub.resolves(); - const result = await deleteTeam("testId"); - expect(result).to.equal(true); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - teamFindByIdAndDeleteStub.throws(err); - try { - await deleteTeam("testId"); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deleteAllOtherUsers", function () { - it("should return true if all other users deleted", async function () { - userDeleteManyStub.resolves(true); - const result = await deleteAllOtherUsers(); - expect(result).to.equal(true); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - userDeleteManyStub.throws(err); - try { - await deleteAllOtherUsers(); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getAllUsers", function () { - it("should return all users", async function () { - userFindStub.returns({ - select: sinon.stub().returns({ - select: sinon.stub().resolves([mockUser]), - }), - }); - const result = await getAllUsers(); - expect(result).to.deep.equal([mockUser]); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - userFindStub.throws(err); - try { - await getAllUsers(); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("logoutUser", function () { - it("should return true if user logged out", async function () { - userUpdateOneStub.resolves(true); - const result = await logoutUser("testId"); - expect(result).to.equal(true); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - userUpdateOneStub.throws(err); - try { - await logoutUser("testId"); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); -}); diff --git a/server/tests/services/emailService.test.js b/server/tests/services/emailService.test.js deleted file mode 100755 index cca86896a..000000000 --- a/server/tests/services/emailService.test.js +++ /dev/null @@ -1,168 +0,0 @@ -import sinon from "sinon"; -import EmailService from "../../service/emailService.js"; - -describe("EmailService - Constructor", function () { - let settingsServiceMock; - let fsMock; - let pathMock; - let compileMock; - let mjml2htmlMock; - let nodemailerMock; - let loggerMock; - - beforeEach(function () { - settingsServiceMock = { - getSettings: sinon.stub().returns({ - systemEmailHost: "smtp.example.com", - systemEmailPort: 465, - systemEmailAddress: "test@example.com", - systemEmailPassword: "password", - }), - }; - - fsMock = { - readFileSync: sinon.stub().returns(""), - }; - - pathMock = { - join: sinon.stub().callsFake((...args) => args.join("/")), - }; - - compileMock = sinon.stub().returns(() => ""); - - mjml2htmlMock = sinon.stub().returns({ html: "" }); - - nodemailerMock = { - createTransport: sinon.stub().returns({ - sendMail: sinon.stub().resolves({ messageId: "12345" }), - }), - }; - - loggerMock = { - error: sinon.stub(), - }; - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should initialize template loaders and email transporter", function () { - const emailService = new EmailService(settingsServiceMock, fsMock, pathMock, compileMock, mjml2htmlMock, nodemailerMock, loggerMock); - - // Verify that the settingsService is assigned correctly - expect(emailService.settingsService).to.equal(settingsServiceMock); - - // Verify that the template loaders are initialized - expect(emailService.templateLookup.welcomeEmailTemplate).to.be.a("function"); - expect(emailService.templateLookup.employeeActivationTemplate).to.be.a("function"); - - // Verify that the email transporter is initialized - expect(nodemailerMock.createTransport.calledOnce).to.be.true; - const emailConfig = nodemailerMock.createTransport.getCall(0).args[0]; - expect(emailConfig).to.deep.equal({ - host: "smtp.example.com", - port: 465, - secure: true, - auth: { - user: "test@example.com", - pass: "password", - }, - }); - }); - - it("should have undefined templates if FS fails", function () { - fsMock = { - readFileSync: sinon.stub().throws(new Error("File read error")), - }; - const emailService = new EmailService(settingsServiceMock, fsMock, pathMock, compileMock, mjml2htmlMock, nodemailerMock, loggerMock); - expect(loggerMock.error.called).to.be.true; - expect(loggerMock.error.firstCall.args[0].message).to.equal("File read error"); - }); -}); - -describe("EmailService - buildAndSendEmail", function () { - let settingsServiceMock; - let fsMock; - let pathMock; - let compileMock; - let mjml2htmlMock; - let nodemailerMock; - let loggerMock; - let emailService; - - beforeEach(function () { - settingsServiceMock = { - getSettings: sinon.stub().returns({ - systemEmailHost: "smtp.example.com", - systemEmailPort: 465, - systemEmailAddress: "test@example.com", - systemEmailPassword: "password", - }), - }; - - fsMock = { - readFileSync: sinon.stub().returns(""), - }; - - pathMock = { - join: sinon.stub().callsFake((...args) => args.join("/")), - }; - - compileMock = sinon.stub().returns(() => ""); - - mjml2htmlMock = sinon.stub().returns({ html: "" }); - - nodemailerMock = { - createTransport: sinon.stub().returns({ - sendMail: sinon.stub().resolves({ messageId: "12345" }), - }), - }; - - loggerMock = { - error: sinon.stub(), - }; - - emailService = new EmailService(settingsServiceMock, fsMock, pathMock, compileMock, mjml2htmlMock, nodemailerMock, loggerMock); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should build and send email successfully", async function () { - const messageId = await emailService.buildAndSendEmail("welcomeEmailTemplate", {}, "recipient@example.com", "Welcome"); - - expect(messageId).to.equal("12345"); - expect(nodemailerMock.createTransport().sendMail.calledOnce).to.be.true; - }); - - it("should log error if building HTML fails", async function () { - mjml2htmlMock.throws(new Error("MJML error")); - - const messageId = await emailService.buildAndSendEmail("welcomeEmailTemplate", {}, "recipient@example.com", "Welcome"); - expect(loggerMock.error.calledOnce).to.be.true; - expect(loggerMock.error.getCall(0).args[0].message).to.equal("MJML error"); - }); - - it("should log error if sending email fails", async function () { - nodemailerMock.createTransport().sendMail.rejects(new Error("SMTP error")); - await emailService.buildAndSendEmail("welcomeEmailTemplate", {}, "recipient@example.com", "Welcome"); - expect(loggerMock.error.calledOnce).to.be.true; - expect(loggerMock.error.getCall(0).args[0].message).to.equal("SMTP error"); - }); - - it("should log error if both building HTML and sending email fail", async function () { - mjml2htmlMock.throws(new Error("MJML error")); - nodemailerMock.createTransport().sendMail.rejects(new Error("SMTP error")); - - const messageId = await emailService.buildAndSendEmail("welcomeEmailTemplate", {}, "recipient@example.com", "Welcome"); - - expect(messageId).to.be.undefined; - expect(loggerMock.error.calledTwice).to.be.true; - expect(loggerMock.error.getCall(0).args[0].message).to.equal("MJML error"); - expect(loggerMock.error.getCall(1).args[0].message).to.equal("SMTP error"); - }); - - it("should log an error if buildHtml fails", async function () {}); -}); diff --git a/server/tests/services/jobQueue.test.js b/server/tests/services/jobQueue.test.js deleted file mode 100755 index 155f9119e..000000000 --- a/server/tests/services/jobQueue.test.js +++ /dev/null @@ -1,813 +0,0 @@ -import sinon from "sinon"; -import JobQueue from "../../service/jobQueue.js"; -import { log } from "console"; - -class QueueStub { - constructor(queueName, options) { - this.queueName = queueName; - this.options = options; - this.workers = []; - this.jobs = []; - } - - // Add any methods that are expected to be called on the Queue instance - add(job) { - this.jobs.push(job); - } - - removeRepeatable(id) { - const removedJob = this.jobs.find((job) => job.data._id === id); - this.jobs = this.jobs.filter((job) => job.data._id !== id); - if (removedJob) { - return true; - } - return false; - } - - getRepeatableJobs() { - return this.jobs; - } - async getJobs() { - return this.jobs; - } - - async pause() { - return true; - } - - async obliterate() { - return true; - } -} - -class WorkerStub { - constructor(QUEUE_NAME, workerTask) { - this.queueName = QUEUE_NAME; - this.workerTask = async () => workerTask({ data: { _id: 1 } }); - } - - async close() { - return true; - } -} - -describe("JobQueue", function () { - let settingsService, logger, db, networkService, statusService, notificationService, jobQueue; - - beforeEach(async function () { - settingsService = { getSettings: sinon.stub() }; - statusService = { updateStatus: sinon.stub() }; - notificationService = { handleNotifications: sinon.stub() }; - - logger = { error: sinon.stub(), info: sinon.stub() }; - db = { - getAllMonitors: sinon.stub().returns([]), - getMaintenanceWindowsByMonitorId: sinon.stub().returns([]), - }; - networkService = { getStatus: sinon.stub() }; - jobQueue = await JobQueue.createJobQueue(db, networkService, statusService, notificationService, settingsService, logger, QueueStub, WorkerStub); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("createJobQueue", function () { - it("should create a new JobQueue and add jobs for active monitors", async function () { - db.getAllMonitors.returns([ - { id: 1, isActive: true }, - { id: 2, isActive: true }, - ]); - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - // There should be double the jobs, as one is meant to be instantly executed - // And one is meant to be enqueued - expect(jobQueue.queue.jobs.length).to.equal(4); - }); - - it("should reject with an error if an error occurs", async function () { - db.getAllMonitors.throws("Error"); - try { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - } catch (error) { - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("createJobQueue"); - } - }); - - it("should reject with an error if an error occurs, should not overwrite error data", async function () { - const error = new Error("Error"); - error.service = "otherService"; - error.method = "otherMethod"; - db.getAllMonitors.throws(error); - - try { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - } catch (error) { - expect(error.service).to.equal("otherService"); - expect(error.method).to.equal("otherMethod"); - } - }); - }); - - describe("Constructor", function () { - it("should construct a new JobQueue with default port and host if not provided", async function () { - settingsService.getSettings.returns({}); - - expect(jobQueue.connection.host).to.equal("127.0.0.1"); - expect(jobQueue.connection.port).to.equal(6379); - }); - - it("should construct a new JobQueue with provided port and host", async function () { - settingsService.getSettings.returns({ redisHost: "localhost", redisPort: 1234 }); - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - expect(jobQueue.connection.host).to.equal("localhost"); - expect(jobQueue.connection.port).to.equal(1234); - }); - }); - - describe("isMaintenanceWindow", function () { - it("should throw an error if error occurs", async function () { - db.getMaintenanceWindowsByMonitorId.throws("Error"); - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - try { - jobQueue.isInMaintenanceWindow(1); - } catch (error) { - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("createWorker"); - } - }); - - it("should return true if in maintenance window with no repeat", async function () { - db.getMaintenanceWindowsByMonitorId.returns([ - { - active: true, - start: new Date(Date.now() - 1000).toISOString(), - end: new Date(Date.now() + 1000).toISOString(), - repeat: 0, - }, - ]); - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - const inWindow = await jobQueue.isInMaintenanceWindow(1); - expect(inWindow).to.be.true; - }); - - it("should return true if in maintenance window with repeat", async function () { - db.getMaintenanceWindowsByMonitorId.returns([ - { - active: true, - start: new Date(Date.now() - 10000).toISOString(), - end: new Date(Date.now() - 5000).toISOString(), - repeat: 1000, - }, - ]); - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - const inWindow = await jobQueue.isInMaintenanceWindow(1); - expect(inWindow).to.be.true; - }); - - it("should return false if in end < start", async function () { - db.getMaintenanceWindowsByMonitorId.returns([ - { - active: true, - start: new Date(Date.now() - 5000).toISOString(), - end: new Date(Date.now() - 10000).toISOString(), - repeat: 1000, - }, - ]); - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - const inWindow = await jobQueue.isInMaintenanceWindow(1); - expect(inWindow).to.be.false; - }); - - it("should return false if not in maintenance window", async function () { - db.getMaintenanceWindowsByMonitorId.returns([ - { - active: false, - start: new Date(Date.now() - 5000).toISOString(), - end: new Date(Date.now() - 10000).toISOString(), - repeat: 1000, - }, - ]); - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - const inWindow = await jobQueue.isInMaintenanceWindow(1); - expect(inWindow).to.be.false; - }); - }); - - describe("createJobHandler", function () { - it("resolve to an error if an error is thrown within", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.isInMaintenanceWindow = sinon.stub().throws("Error"); - try { - const handler = jobQueue.createJobHandler(); - await handler({ data: { _id: 1 } }); - } catch (error) { - expect(error.service).to.equal("JobQueue"); - expect(error.details).to.equal(`Error processing job 1: Error`); - } - }); - - it("should log info if job is in maintenance window", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.isInMaintenanceWindow = sinon.stub().returns(true); - const handler = jobQueue.createJobHandler(); - await handler({ data: { _id: 1 } }); - expect(logger.info.calledOnce).to.be.true; - expect(logger.info.firstCall.args[0].message).to.equal("Monitor 1 is in maintenance window"); - }); - - it("should return if status has not changed", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.isInMaintenanceWindow = sinon.stub().returns(false); - statusService.updateStatus = sinon.stub().returns({ statusChanged: false }); - const handler = jobQueue.createJobHandler(); - await handler({ data: { _id: 1 } }); - expect(jobQueue.notificationService.handleNotifications.notCalled).to.be.true; - }); - - it("should return if status has changed, but prevStatus was undefined (monitor paused)", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.isInMaintenanceWindow = sinon.stub().returns(false); - statusService.updateStatus = sinon.stub().returns({ statusChanged: true, prevStatus: undefined }); - const handler = jobQueue.createJobHandler(); - await handler({ data: { _id: 1 } }); - expect(jobQueue.notificationService.handleNotifications.notCalled).to.be.true; - }); - - it("should call notification service if status changed and monitor was not paused", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.isInMaintenanceWindow = sinon.stub().returns(false); - statusService.updateStatus = sinon.stub().returns({ statusChanged: true, prevStatus: false }); - const handler = jobQueue.createJobHandler(); - await handler({ data: { _id: 1 } }); - expect(jobQueue.notificationService.handleNotifications.calledOnce).to.be.true; - }); - }); - - describe("getWorkerStats", function () { - it("should throw an error if getRepeatable Jobs fails", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.queue.getRepeatableJobs = async () => { - throw new Error("Error"); - }; - try { - const stats = await jobQueue.getWorkerStats(); - } catch (error) { - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("getWorkerStats"); - } - }); - - it("should throw an error if getRepeatable Jobs fails but respect existing error data", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.queue.getRepeatableJobs = async () => { - const error = new Error("Existing Error"); - error.service = "otherService"; - error.method = "otherMethod"; - throw error; - }; - try { - await jobQueue.getWorkerStats(); - } catch (error) { - expect(error.service).to.equal("otherService"); - expect(error.method).to.equal("otherMethod"); - } - }); - }); - - describe("scaleWorkers", function () { - it("should scale workers to 5 if no workers", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - expect(jobQueue.workers.length).to.equal(5); - }); - - it("should scale workers up", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.scaleWorkers({ - load: 100, - jobs: Array.from({ length: 100 }, (_, i) => i + 1), - }); - expect(jobQueue.workers.length).to.equal(20); - }); - - it("should scale workers down, even with error of worker.close fails", async function () { - WorkerStub.prototype.close = async () => { - throw new Error("Error"); - }; - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - await jobQueue.scaleWorkers({ - load: 100, - jobs: Array.from({ length: 100 }, (_, i) => i + 1), - }); - - const res = await jobQueue.scaleWorkers({ - load: 0, - jobs: [], - }); - expect(jobQueue.workers.length).to.equal(5); - }); - - it("should scale workers down", async function () { - WorkerStub.prototype.close = async () => { - return true; - }; - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - await jobQueue.scaleWorkers({ - load: 40, - jobs: Array.from({ length: 40 }, (_, i) => i + 1), - }); - - const res = await jobQueue.scaleWorkers({ - load: 0, - jobs: [], - }); - expect(jobQueue.workers.length).to.equal(5); - }); - - it("should return false if scaling doesn't happen", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - const res = await jobQueue.scaleWorkers({ load: 5 }); - expect(jobQueue.workers.length).to.equal(5); - expect(res).to.be.false; - }); - }); - - describe("getJobs", function () { - it("should return jobs", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - const jobs = await jobQueue.getJobs(); - expect(jobs.length).to.equal(0); - }); - - it("should throw an error if getRepeatableJobs fails", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - try { - jobQueue.queue.getRepeatableJobs = async () => { - throw new Error("error"); - }; - - await jobQueue.getJobs(true); - } catch (error) { - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("getJobs"); - } - }); - - it("should throw an error if getRepeatableJobs fails but respect existing error data", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - try { - jobQueue.queue.getRepeatableJobs = async () => { - const error = new Error("Existing error"); - error.service = "otherService"; - error.method = "otherMethod"; - throw error; - }; - - await jobQueue.getJobs(true); - } catch (error) { - expect(error.service).to.equal("otherService"); - expect(error.method).to.equal("otherMethod"); - } - }); - }); - - describe("getJobStats", function () { - it("should return job stats for no jobs", async function () { - const jobStats = await jobQueue.getJobStats(); - expect(jobStats).to.deep.equal({ jobs: [], workers: 5 }); - }); - - it("should return job stats for jobs", async function () { - jobQueue.queue.getJobs = async () => { - return [{ data: { url: "test" }, getState: async () => "completed" }]; - }; - const jobStats = await jobQueue.getJobStats(); - expect(jobStats).to.deep.equal({ - jobs: [{ url: "test", state: "completed" }], - workers: 5, - }); - }); - - it("should reject with an error if mapping jobs fails", async function () { - jobQueue.queue.getJobs = async () => { - return [ - { - data: { url: "test" }, - getState: async () => { - throw new Error("Mapping Error"); - }, - }, - ]; - }; - try { - await jobQueue.getJobStats(); - } catch (error) { - expect(error.message).to.equal("Mapping Error"); - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("getJobStats"); - } - }); - - it("should reject with an error if mapping jobs fails but respect existing error data", async function () { - jobQueue.queue.getJobs = async () => { - return [ - { - data: { url: "test" }, - getState: async () => { - const error = new Error("Mapping Error"); - error.service = "otherService"; - error.method = "otherMethod"; - throw error; - }, - }, - ]; - }; - try { - await jobQueue.getJobStats(); - } catch (error) { - expect(error.message).to.equal("Mapping Error"); - expect(error.service).to.equal("otherService"); - expect(error.method).to.equal("otherMethod"); - } - }); - }); - - describe("addJob", function () { - it("should add a job to the queue", async function () { - jobQueue.addJob("test", { url: "test" }); - expect(jobQueue.queue.jobs.length).to.equal(1); - }); - - it("should reject with an error if adding fails", async function () { - jobQueue.queue.add = async () => { - throw new Error("Error adding job"); - }; - try { - await jobQueue.addJob("test", { url: "test" }); - } catch (error) { - expect(error.message).to.equal("Error adding job"); - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("addJob"); - } - }); - - it("should reject with an error if adding fails but respect existing error data", async function () { - jobQueue.queue.add = async () => { - const error = new Error("Error adding job"); - error.service = "otherService"; - error.method = "otherMethod"; - throw error; - }; - try { - await jobQueue.addJob("test", { url: "test" }); - } catch (error) { - expect(error.message).to.equal("Error adding job"); - expect(error.service).to.equal("otherService"); - expect(error.method).to.equal("otherMethod"); - } - }); - }); - - describe("deleteJob", function () { - it("should delete a job from the queue", async function () { - jobQueue.getWorkerStats = sinon.stub().returns({ load: 1, jobs: [{}] }); - jobQueue.scaleWorkers = sinon.stub(); - const monitor = { _id: 1 }; - const job = { data: monitor }; - jobQueue.queue.jobs = [job]; - await jobQueue.deleteJob(monitor); - // expect(jobQueue.queue.jobs.length).to.equal(0); - // expect(logger.info.calledOnce).to.be.true; - // expect(jobQueue.getWorkerStats.calledOnce).to.be.true; - // expect(jobQueue.scaleWorkers.calledOnce).to.be.true; - }); - - it("should log an error if job is not found", async function () { - jobQueue.getWorkerStats = sinon.stub().returns({ load: 1, jobs: [{}] }); - jobQueue.scaleWorkers = sinon.stub(); - const monitor = { _id: 1 }; - const job = { data: monitor }; - jobQueue.queue.jobs = [job]; - await jobQueue.deleteJob({ id_: 2 }); - expect(logger.error.calledOnce).to.be.true; - }); - - it("should reject with an error if removeRepeatable fails", async function () { - jobQueue.queue.removeRepeatable = async () => { - const error = new Error("removeRepeatable error"); - throw error; - }; - - try { - await jobQueue.deleteJob({ _id: 1 }); - } catch (error) { - expect(error.message).to.equal("removeRepeatable error"); - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("deleteJob"); - } - }); - - it("should reject with an error if removeRepeatable fails but respect existing error data", async function () { - jobQueue.queue.removeRepeatable = async () => { - const error = new Error("removeRepeatable error"); - error.service = "otherService"; - error.method = "otherMethod"; - throw error; - }; - - try { - await jobQueue.deleteJob({ _id: 1 }); - } catch (error) { - expect(error.message).to.equal("removeRepeatable error"); - expect(error.service).to.equal("otherService"); - expect(error.method).to.equal("otherMethod"); - } - }); - }); - - describe("getMetrics", function () { - it("should return metrics for the job queue", async function () { - jobQueue.queue.getWaitingCount = async () => 1; - jobQueue.queue.getActiveCount = async () => 2; - jobQueue.queue.getCompletedCount = async () => 3; - jobQueue.queue.getFailedCount = async () => 4; - jobQueue.queue.getDelayedCount = async () => 5; - jobQueue.queue.getRepeatableJobs = async () => [1, 2, 3]; - const metrics = await jobQueue.getMetrics(); - expect(metrics).to.deep.equal({ - waiting: 1, - active: 2, - completed: 3, - failed: 4, - delayed: 5, - repeatableJobs: 3, - }); - }); - - it("should log an error if metrics operations fail", async function () { - jobQueue.queue.getWaitingCount = async () => { - throw new Error("Error"); - }; - await jobQueue.getMetrics(); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal("Error"); - }); - }); - - describe("obliterate", function () { - it("should return true if obliteration is successful", async function () { - jobQueue.queue.pause = async () => true; - jobQueue.getJobs = async () => [{ key: 1, id: 1 }]; - jobQueue.queue.removeRepeatableByKey = async () => true; - jobQueue.queue.remove = async () => true; - jobQueue.queue.obliterate = async () => true; - const obliteration = await jobQueue.obliterate(); - expect(obliteration).to.be.true; - }); - - it("should throw an error if obliteration fails", async function () { - jobQueue.getMetrics = async () => { - throw new Error("Error"); - }; - - try { - await jobQueue.obliterate(); - } catch (error) { - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("obliterate"); - } - }); - - it("should throw an error if obliteration fails but respect existing error data", async function () { - jobQueue.getMetrics = async () => { - const error = new Error("Error"); - error.service = "otherService"; - error.method = "otherMethod"; - throw error; - }; - - try { - await jobQueue.obliterate(); - } catch (error) { - expect(error.service).to.equal("otherService"); - expect(error.method).to.equal("otherMethod"); - } - }); - }); -}); diff --git a/server/tests/services/networkService.test.js b/server/tests/services/networkService.test.js deleted file mode 100755 index cb48ed9b2..000000000 --- a/server/tests/services/networkService.test.js +++ /dev/null @@ -1,439 +0,0 @@ -import sinon from "sinon"; -import NetworkService from "../../service/networkService.js"; -import { expect } from "chai"; -import http from "http"; -import { errorMessages } from "../../utils/messages.js"; -describe("Network Service", function () { - let axios, ping, Docker, logger, networkService; - - beforeEach(function () { - axios = { - get: sinon.stub().resolves({ - data: { foo: "bar" }, - status: 200, - }), - }; - Docker = class { - listContainers = sinon.stub().resolves([ - { - Names: ["http://test.com"], - Id: "http://test.com", - }, - ]); - getContainer = sinon.stub().returns({ - inspect: sinon.stub().resolves({ State: { Status: "running" } }), - }); - }; - ping = { - promise: { - probe: sinon.stub().resolves({ response: { alive: true }, responseTime: 100, alive: true }), - }, - }; - logger = { error: sinon.stub() }; - networkService = new NetworkService(axios, ping, logger, http, Docker); - }); - - describe("constructor", function () { - it("should create a new NetworkService instance", function () { - const networkService = new NetworkService(); - expect(networkService).to.be.an.instanceOf(NetworkService); - }); - }); - - describe("timeRequest", function () { - it("should time an asynchronous operation", async function () { - const operation = sinon.stub().resolves("success"); - const { response, responseTime } = await networkService.timeRequest(operation); - expect(response).to.equal("success"); - expect(responseTime).to.be.a("number"); - }); - - it("should handle errors if operation throws error", async function () { - const error = new Error("Test error"); - const operation = sinon.stub().throws(error); - const { response, responseTime } = await networkService.timeRequest(operation); - expect(response).to.be.null; - expect(responseTime).to.be.a("number"); - expect(error.message).to.equal("Test error"); - }); - }); - - describe("requestPing", function () { - it("should return a response object if ping successful", async function () { - const pingResult = await networkService.requestPing({ - data: { url: "http://test.com", _id: "123" }, - }); - expect(pingResult.monitorId).to.equal("123"); - expect(pingResult.type).to.equal("ping"); - expect(pingResult.responseTime).to.be.a("number"); - expect(pingResult.status).to.be.true; - }); - - it("should return a response object if ping unsuccessful", async function () { - const error = new Error("Test error"); - networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error }); - const pingResult = await networkService.requestPing({ - data: { url: "http://test.com", _id: "123" }, - }); - expect(pingResult.monitorId).to.equal("123"); - expect(pingResult.type).to.equal("ping"); - expect(pingResult.responseTime).to.be.a("number"); - expect(pingResult.status).to.be.false; - expect(pingResult.code).to.equal(networkService.PING_ERROR); - }); - - it("should throw an error if ping cannot resolve", async function () { - const error = new Error("test error"); - networkService.timeRequest = sinon.stub().throws(error); - try { - await networkService.requestPing({ - data: { url: "http://test.com", _id: "123" }, - }); - } catch (error) { - expect(error).to.exist; - expect(error.method).to.equal("requestPing"); - } - }); - }); - - describe("requestHttp", function () { - it("should return a response object if http successful", async function () { - const job = { data: { url: "http://test.com", _id: "123", type: "http" } }; - const httpResult = await networkService.requestHttp(job); - expect(httpResult.monitorId).to.equal("123"); - expect(httpResult.type).to.equal("http"); - expect(httpResult.responseTime).to.be.a("number"); - expect(httpResult.status).to.be.true; - }); - - it("should return a response object if http unsuccessful", async function () { - const error = new Error("Test error"); - error.response = { status: 404 }; - networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error }); - const job = { data: { url: "http://test.com", _id: "123", type: "http" } }; - const httpResult = await networkService.requestHttp(job); - expect(httpResult.monitorId).to.equal("123"); - expect(httpResult.type).to.equal("http"); - expect(httpResult.responseTime).to.be.a("number"); - expect(httpResult.status).to.be.false; - expect(httpResult.code).to.equal(404); - }); - - it("should return a response object if http unsuccessful with unknown code", async function () { - const error = new Error("Test error"); - error.response = {}; - networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error }); - const job = { data: { url: "http://test.com", _id: "123", type: "http" } }; - const httpResult = await networkService.requestHttp(job); - expect(httpResult.monitorId).to.equal("123"); - expect(httpResult.type).to.equal("http"); - expect(httpResult.responseTime).to.be.a("number"); - expect(httpResult.status).to.be.false; - expect(httpResult.code).to.equal(networkService.NETWORK_ERROR); - }); - - it("should throw an error if an error occurs", async function () { - const error = new Error("test error"); - networkService.timeRequest = sinon.stub().throws(error); - try { - await networkService.requestHttp({ - data: { url: "http://test.com", _id: "123" }, - }); - } catch (error) { - expect(error).to.exist; - expect(error.method).to.equal("requestHttp"); - } - }); - }); - - describe("requestPagespeed", function () { - it("should return a response object if pagespeed successful", async function () { - const job = { data: { url: "http://test.com", _id: "123", type: "pagespeed" } }; - const pagespeedResult = await networkService.requestPagespeed(job); - expect(pagespeedResult.monitorId).to.equal("123"); - expect(pagespeedResult.type).to.equal("pagespeed"); - expect(pagespeedResult.responseTime).to.be.a("number"); - expect(pagespeedResult.status).to.be.true; - }); - - it("should return a response object if pagespeed unsuccessful", async function () { - const error = new Error("Test error"); - error.response = { status: 404 }; - networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error }); - const job = { data: { url: "http://test.com", _id: "123", type: "pagespeed" } }; - const pagespeedResult = await networkService.requestPagespeed(job); - expect(pagespeedResult.monitorId).to.equal("123"); - expect(pagespeedResult.type).to.equal("pagespeed"); - expect(pagespeedResult.responseTime).to.be.a("number"); - expect(pagespeedResult.status).to.be.false; - expect(pagespeedResult.code).to.equal(404); - }); - - it("should return a response object if pagespeed unsuccessful with an unknown code", async function () { - const error = new Error("Test error"); - error.response = {}; - networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error }); - const job = { data: { url: "http://test.com", _id: "123", type: "pagespeed" } }; - const pagespeedResult = await networkService.requestPagespeed(job); - expect(pagespeedResult.monitorId).to.equal("123"); - expect(pagespeedResult.type).to.equal("pagespeed"); - expect(pagespeedResult.responseTime).to.be.a("number"); - expect(pagespeedResult.status).to.be.false; - expect(pagespeedResult.code).to.equal(networkService.NETWORK_ERROR); - }); - - it("should throw an error if pagespeed cannot resolve", async function () { - const error = new Error("test error"); - networkService.timeRequest = sinon.stub().throws(error); - try { - await networkService.requestPagespeed({ - data: { url: "http://test.com", _id: "123" }, - }); - } catch (error) { - expect(error).to.exist; - expect(error.method).to.equal("requestPagespeed"); - } - }); - }); - - describe("requestHardware", function () { - it("should return a response object if hardware successful", async function () { - const job = { data: { url: "http://test.com", _id: "123", type: "hardware" } }; - const httpResult = await networkService.requestHardware(job); - expect(httpResult.monitorId).to.equal("123"); - expect(httpResult.type).to.equal("hardware"); - expect(httpResult.responseTime).to.be.a("number"); - expect(httpResult.status).to.be.true; - }); - - it("should return a response object if hardware successful and job has a secret", async function () { - const job = { - data: { - url: "http://test.com", - _id: "123", - type: "hardware", - secret: "my_secret", - }, - }; - const httpResult = await networkService.requestHardware(job); - expect(httpResult.monitorId).to.equal("123"); - expect(httpResult.type).to.equal("hardware"); - expect(httpResult.responseTime).to.be.a("number"); - expect(httpResult.status).to.be.true; - }); - - it("should return a response object if hardware unsuccessful", async function () { - const error = new Error("Test error"); - error.response = { status: 404 }; - networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error }); - const job = { data: { url: "http://test.com", _id: "123", type: "hardware" } }; - const httpResult = await networkService.requestHardware(job); - expect(httpResult.monitorId).to.equal("123"); - expect(httpResult.type).to.equal("hardware"); - expect(httpResult.responseTime).to.be.a("number"); - expect(httpResult.status).to.be.false; - expect(httpResult.code).to.equal(404); - }); - - it("should return a response object if hardware unsuccessful with unknown code", async function () { - const error = new Error("Test error"); - error.response = {}; - networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error }); - const job = { data: { url: "http://test.com", _id: "123", type: "hardware" } }; - const httpResult = await networkService.requestHardware(job); - expect(httpResult.monitorId).to.equal("123"); - expect(httpResult.type).to.equal("hardware"); - expect(httpResult.responseTime).to.be.a("number"); - expect(httpResult.status).to.be.false; - expect(httpResult.code).to.equal(networkService.NETWORK_ERROR); - }); - - it("should throw an error if hardware cannot resolve", async function () { - const error = new Error("test error"); - networkService.timeRequest = sinon.stub().throws(error); - try { - await networkService.requestHardware({ - data: { url: "http://test.com", _id: "123" }, - }); - } catch (error) { - expect(error).to.exist; - expect(error.method).to.equal("requestHardware"); - } - }); - }); - - describe("requestDocker", function () { - it("should return a response object if docker successful", async function () { - const job = { data: { url: "http://test.com", _id: "123", type: "docker" } }; - const dockerResult = await networkService.requestDocker(job); - expect(dockerResult.monitorId).to.equal("123"); - expect(dockerResult.type).to.equal("docker"); - expect(dockerResult.responseTime).to.be.a("number"); - expect(dockerResult.status).to.be.true; - }); - - it("should return a response object with status false if container not running", async function () { - Docker = class { - listContainers = sinon.stub().resolves([ - { - Names: ["/my_container"], - Id: "abc123", - }, - ]); - getContainer = sinon.stub().returns({ - inspect: sinon.stub().resolves({ State: { Status: "stopped" } }), - }); - }; - networkService = new NetworkService(axios, ping, logger, http, Docker); - const job = { data: { url: "abc123", _id: "123", type: "docker" } }; - const dockerResult = await networkService.requestDocker(job); - expect(dockerResult.status).to.be.false; - expect(dockerResult.code).to.equal(200); - }); - - it("should handle an error when fetching the container", async function () { - Docker = class { - listContainers = sinon.stub().resolves([ - { - Names: ["/my_container"], - Id: "abc123", - }, - ]); - getContainer = sinon.stub().returns({ - inspect: sinon.stub().throws(new Error("test error")), - }); - }; - networkService = new NetworkService(axios, ping, logger, http, Docker); - const job = { data: { url: "abc123", _id: "123", type: "docker" } }; - const dockerResult = await networkService.requestDocker(job); - expect(dockerResult.status).to.be.false; - expect(dockerResult.code).to.equal(networkService.NETWORK_ERROR); - }); - - it("should throw an error if operations fail", async function () { - Docker = class { - listContainers = sinon.stub().resolves([ - { - Names: ["/my_container"], - Id: "abc123", - }, - ]); - getContainer = sinon.stub().throws(new Error("test error")); - }; - networkService = new NetworkService(axios, ping, logger, http, Docker); - const job = { data: { url: "abc123", _id: "123", type: "docker" } }; - try { - await networkService.requestDocker(job); - } catch (error) { - expect(error.message).to.equal("test error"); - } - }); - - it("should throw an error if no matching images found", async function () { - Docker = class { - listContainers = sinon.stub().resolves([]); - getContainer = sinon.stub().throws(new Error("test error")); - }; - networkService = new NetworkService(axios, ping, logger, http, Docker); - const job = { data: { url: "abc123", _id: "123", type: "docker" } }; - try { - await networkService.requestDocker(job); - } catch (error) { - expect(error.message).to.equal(errorMessages.DOCKER_NOT_FOUND); - } - }); - }); - - describe("getStatus", function () { - beforeEach(function () { - networkService.requestPing = sinon.stub(); - networkService.requestHttp = sinon.stub(); - networkService.requestPagespeed = sinon.stub(); - networkService.requestHardware = sinon.stub(); - networkService.requestDocker = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should call requestPing if type is ping", async function () { - await networkService.getStatus({ data: { type: "ping" } }); - expect(networkService.requestPing.calledOnce).to.be.true; - expect(networkService.requestDocker.notCalled).to.be.true; - expect(networkService.requestHttp.notCalled).to.be.true; - expect(networkService.requestPagespeed.notCalled).to.be.true; - }); - - it("should call requestHttp if type is http", async function () { - await networkService.getStatus({ data: { type: "http" } }); - expect(networkService.requestPing.notCalled).to.be.true; - expect(networkService.requestDocker.notCalled).to.be.true; - expect(networkService.requestHttp.calledOnce).to.be.true; - expect(networkService.requestPagespeed.notCalled).to.be.true; - }); - - it("should call requestPagespeed if type is pagespeed", async function () { - await networkService.getStatus({ data: { type: "pagespeed" } }); - expect(networkService.requestPing.notCalled).to.be.true; - expect(networkService.requestDocker.notCalled).to.be.true; - expect(networkService.requestHttp.notCalled).to.be.true; - expect(networkService.requestPagespeed.calledOnce).to.be.true; - }); - - it("should call requestHardware if type is hardware", async function () { - await networkService.getStatus({ data: { type: "hardware" } }); - expect(networkService.requestHardware.calledOnce).to.be.true; - expect(networkService.requestDocker.notCalled).to.be.true; - expect(networkService.requestPing.notCalled).to.be.true; - expect(networkService.requestPagespeed.notCalled).to.be.true; - }); - - it("should call requestDocker if type is Docker", async function () { - await networkService.getStatus({ data: { type: "docker" } }); - expect(networkService.requestDocker.calledOnce).to.be.true; - expect(networkService.requestHardware.notCalled).to.be.true; - expect(networkService.requestPing.notCalled).to.be.true; - expect(networkService.requestPagespeed.notCalled).to.be.true; - }); - - it("should throw an error if an unknown type is provided", async function () { - try { - await networkService.getStatus({ data: { type: "unknown" } }); - } catch (error) { - expect(error.service).to.equal("NetworkService"); - expect(error.method).to.equal("getStatus"); - expect(error.message).to.equal("Unsupported type: unknown"); - } - }); - - it("should throw an error if job type is undefined", async function () { - try { - await networkService.getStatus({ data: { type: undefined } }); - } catch (error) { - expect(error.service).to.equal("NetworkService"); - expect(error.method).to.equal("getStatus"); - expect(error.message).to.equal("Unsupported type: unknown"); - } - }); - - it("should throw an error if job is empty", async function () { - try { - await networkService.getStatus({}); - } catch (error) { - expect(error.method).to.equal("getStatus"); - expect(error.message).to.equal("Unsupported type: unknown"); - } - }); - - it("should throw an error if job is null", async function () { - try { - await networkService.getStatus(null); - } catch (error) { - expect(error.service).to.equal("NetworkService"); - expect(error.method).to.equal("getStatus"); - expect(error.message).to.equal("Unsupported type: unknown"); - } - }); - }); -}); diff --git a/server/tests/services/notificationService.test.js b/server/tests/services/notificationService.test.js deleted file mode 100755 index 211cc8b46..000000000 --- a/server/tests/services/notificationService.test.js +++ /dev/null @@ -1,285 +0,0 @@ -import sinon from "sinon"; -import NotificationService from "../../service/notificationService.js"; -import { expect } from "chai"; - -describe("NotificationService", function () { - let emailService, db, logger, notificationService; - - beforeEach(function () { - db = { - getNotificationsByMonitorId: sinon.stub(), - }; - emailService = { - buildAndSendEmail: sinon.stub(), - }; - logger = { - warn: sinon.stub(), - }; - - notificationService = new NotificationService(emailService, db, logger); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("constructor", function () { - it("should create a new instance of NotificationService", function () { - expect(notificationService).to.be.an.instanceOf(NotificationService); - }); - }); - - describe("sendEmail", function () { - it("should send an email notification with Up Template", async function () { - const networkResponse = { - monitor: { - name: "Test Monitor", - url: "http://test.com", - }, - status: true, - prevStatus: false, - }; - const address = "test@test.com"; - await notificationService.sendEmail(networkResponse, address); - expect(notificationService.emailService.buildAndSendEmail.calledOnce).to.be.true; - expect( - notificationService.emailService.buildAndSendEmail.calledWith("serverIsUpTemplate", { monitor: "Test Monitor", url: "http://test.com" }) - ); - }); - - it("should send an email notification with Down Template", async function () { - const networkResponse = { - monitor: { - name: "Test Monitor", - url: "http://test.com", - }, - status: false, - prevStatus: true, - }; - const address = "test@test.com"; - await notificationService.sendEmail(networkResponse, address); - expect(notificationService.emailService.buildAndSendEmail.calledOnce).to.be.true; - }); - - it("should send an email notification with Up Template", async function () { - const networkResponse = { - monitor: { - name: "Test Monitor", - url: "http://test.com", - }, - status: true, - prevStatus: false, - }; - const address = "test@test.com"; - await notificationService.sendEmail(networkResponse, address); - expect(notificationService.emailService.buildAndSendEmail.calledOnce).to.be.true; - }); - }); - - describe("handleNotifications", function () { - it("should handle notifications based on the network response", async function () { - notificationService.sendEmail = sinon.stub(); - const res = await notificationService.handleNotifications({ - monitor: { - type: "email", - address: "www.google.com", - }, - }); - expect(res).to.be.true; - }); - - it("should handle hardware notifications", async function () { - notificationService.sendEmail = sinon.stub(); - const res = await notificationService.handleNotifications({ - monitor: { - type: "hardware", - address: "www.google.com", - }, - }); - expect(res).to.be.true; - }); - - it("should handle an error when getting notifications", async function () { - const testError = new Error("Test Error"); - notificationService.db.getNotificationsByMonitorId.rejects(testError); - await notificationService.handleNotifications({ monitorId: "123" }); - expect(notificationService.logger.warn.calledOnce).to.be.true; - }); - }); - - describe("sendHardwareEmail", function () { - let networkResponse, address, alerts; - - beforeEach(function () { - networkResponse = { - monitor: { - name: "Test Monitor", - url: "http://test.com", - }, - status: true, - prevStatus: false, - }; - address = "test@test.com"; - alerts = ["test"]; - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should send an email notification with Hardware Template", async function () { - emailService.buildAndSendEmail.resolves(true); - const res = await notificationService.sendHardwareEmail(networkResponse, address, alerts); - expect(res).to.be.true; - }); - - it("should return false if no alerts are provided", async function () { - alerts = []; - emailService.buildAndSendEmail.resolves(true); - const res = await notificationService.sendHardwareEmail(networkResponse, address, alerts); - expect(res).to.be.false; - }); - }); - - describe("handleStatusNotifications", function () { - let networkResponse; - - beforeEach(function () { - networkResponse = { - monitor: { - name: "Test Monitor", - url: "http://test.com", - }, - statusChanged: true, - status: true, - prevStatus: false, - }; - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should handle status notifications", async function () { - db.getNotificationsByMonitorId.resolves([{ type: "email", address: "test@test.com" }]); - const res = await notificationService.handleStatusNotifications(networkResponse); - expect(res).to.be.true; - }); - - it("should return false if status hasn't changed", async function () { - networkResponse.statusChanged = false; - const res = await notificationService.handleStatusNotifications(networkResponse); - expect(res).to.be.false; - }); - - it("should return false if prevStatus is undefined", async function () { - networkResponse.prevStatus = undefined; - const res = await notificationService.handleStatusNotifications(networkResponse); - expect(res).to.be.false; - }); - - it("should handle an error", async function () { - const testError = new Error("Test Error"); - db.getNotificationsByMonitorId.rejects(testError); - try { - await notificationService.handleStatusNotifications(networkResponse); - } catch (error) { - expect(error).to.be.an.instanceOf(Error); - expect(error.message).to.equal("Test Error"); - } - }); - }); - - describe("handleHardwareNotifications", function () { - let networkResponse; - - beforeEach(function () { - networkResponse = { - monitor: { - name: "Test Monitor", - url: "http://test.com", - thresholds: { - usage_cpu: 1, - usage_memory: 1, - usage_disk: 1, - }, - }, - payload: { - data: { - cpu: { - usage_percent: 0.655, - }, - memory: { - usage_percent: 0.783, - }, - disk: [ - { - name: "/dev/sda1", - usage_percent: 0.452, - }, - { - name: "/dev/sdb1", - usage_percent: 0.627, - }, - ], - }, - }, - }; - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("it should return false if no thresholds are set", function () { - it("should return false if no thresholds are set", async function () { - networkResponse.monitor.thresholds = undefined; - const res = await notificationService.handleHardwareNotifications(networkResponse); - expect(res).to.be.false; - }); - - it("should return false if metrics are null", async function () { - networkResponse.payload.data = null; - const res = await notificationService.handleHardwareNotifications(networkResponse); - expect(res).to.be.false; - }); - - it("should return true if request is well formed and thresholds > 0", async function () { - db.getNotificationsByMonitorId.resolves([ - { - type: "email", - address: "test@test.com", - alertThreshold: 1, - cpuAlertThreshold: 1, - memoryAlertThreshold: 1, - diskAlertThreshold: 1, - save: sinon.stub().resolves(), - }, - ]); - const res = await notificationService.handleHardwareNotifications(networkResponse); - expect(res).to.be.true; - }); - - it("should return true if thresholds are exceeded", async function () { - db.getNotificationsByMonitorId.resolves([ - { - type: "email", - address: "test@test.com", - alertThreshold: 1, - cpuAlertThreshold: 1, - memoryAlertThreshold: 1, - diskAlertThreshold: 1, - save: sinon.stub().resolves(), - }, - ]); - networkResponse.monitor.thresholds = { - usage_cpu: 0.01, - usage_memory: 0.01, - usage_disk: 0.01, - }; - const res = await notificationService.handleHardwareNotifications(networkResponse); - expect(res).to.be.true; - }); - }); - }); -}); diff --git a/server/tests/services/settingsService.test.js b/server/tests/services/settingsService.test.js deleted file mode 100755 index 6c9960e5e..000000000 --- a/server/tests/services/settingsService.test.js +++ /dev/null @@ -1,140 +0,0 @@ -import sinon from "sinon"; -import SettingsService from "../../service/settingsService.js"; -import { expect } from "chai"; -import NetworkService from "../../service/networkService.js"; -const SERVICE_NAME = "SettingsService"; - -describe("SettingsService", function () { - let sandbox, mockAppSettings; - - beforeEach(function () { - sandbox = sinon.createSandbox(); - sandbox.stub(process.env, "CLIENT_HOST").value("http://localhost"); - sandbox.stub(process.env, "JWT_SECRET").value("secret"); - sandbox.stub(process.env, "REFRESH_TOKEN_SECRET").value("refreshSecret"); - sandbox.stub(process.env, "DB_TYPE").value("postgres"); - sandbox.stub(process.env, "DB_CONNECTION_STRING").value("postgres://user:pass@localhost/db"); - sandbox.stub(process.env, "REDIS_HOST").value("localhost"); - sandbox.stub(process.env, "REDIS_PORT").value("6379"); - sandbox.stub(process.env, "TOKEN_TTL").value("3600"); - sandbox.stub(process.env, "REFRESH_TOKEN_TTL").value("86400"); - sandbox.stub(process.env, "PAGESPEED_API_KEY").value("apiKey"); - sandbox.stub(process.env, "SYSTEM_EMAIL_HOST").value("smtp.mailtrap.io"); - sandbox.stub(process.env, "SYSTEM_EMAIL_PORT").value("2525"); - sandbox.stub(process.env, "SYSTEM_EMAIL_ADDRESS").value("test@example.com"); - sandbox.stub(process.env, "SYSTEM_EMAIL_PASSWORD").value("password"); - }); - - mockAppSettings = { - settingOne: 123, - settingTwo: 456, - }; - - afterEach(function () { - sandbox.restore(); - sinon.restore(); - }); - - describe("constructor", function () { - it("should construct a new SettingsService", function () { - const settingsService = new SettingsService(mockAppSettings); - expect(settingsService.appSettings).to.equal(mockAppSettings); - }); - }); - - describe("loadSettings", function () { - it("should load settings from DB when environment variables are not set", async function () { - const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" }; - const appSettings = { findOne: sinon.stub().returns(dbSettings) }; - const settingsService = new SettingsService(appSettings); - settingsService.settings = {}; - const result = await settingsService.loadSettings(); - expect(result).to.deep.equal(dbSettings); - }); - - it("should throw an error if settings are not found", async function () { - const appSettings = { findOne: sinon.stub().returns(null) }; - const settingsService = new SettingsService(appSettings); - settingsService.settings = null; - - try { - await settingsService.loadSettings(); - } catch (error) { - expect(error.message).to.equal("Settings not found"); - expect(error.service).to.equal(SERVICE_NAME); - expect(error.method).to.equal("loadSettings"); - } - }); - - it("should add its method and service name to error if not present", async function () { - const appSettings = { findOne: sinon.stub().throws(new Error("Test error")) }; - const settingsService = new SettingsService(appSettings); - try { - await settingsService.loadSettings(); - } catch (error) { - expect(error.message).to.equal("Test error"); - expect(error.service).to.equal(SERVICE_NAME); - expect(error.method).to.equal("loadSettings"); - } - }); - - it("should not add its method and service name to error if present", async function () { - const error = new Error("Test error"); - error.method = "otherMethod"; - error.service = "OTHER_SERVICE"; - const appSettings = { findOne: sinon.stub().throws(error) }; - const settingsService = new SettingsService(appSettings); - try { - await settingsService.loadSettings(); - } catch (error) { - expect(error.message).to.equal("Test error"); - expect(error.service).to.equal("OTHER_SERVICE"); - expect(error.method).to.equal("otherMethod"); - } - }); - - it("should merge DB settings with environment variables", async function () { - const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" }; - const appSettings = { findOne: sinon.stub().returns(dbSettings) }; - const settingsService = new SettingsService(appSettings); - const result = await settingsService.loadSettings(); - expect(result).to.deep.equal(settingsService.settings); - expect(settingsService.settings.logLevel).to.equal("debug"); - expect(settingsService.settings.apiBaseUrl).to.equal("http://localhost"); - }); - }); - - describe("reloadSettings", function () { - it("should call loadSettings", async function () { - const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" }; - const appSettings = { findOne: sinon.stub().returns(dbSettings) }; - const settingsService = new SettingsService(appSettings); - settingsService.settings = {}; - const result = await settingsService.reloadSettings(); - expect(result).to.deep.equal(dbSettings); - }); - }); - - describe("getSettings", function () { - it("should return the current settings", function () { - const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" }; - const appSettings = { findOne: sinon.stub().returns(dbSettings) }; - const settingsService = new SettingsService(appSettings); - settingsService.settings = dbSettings; - const result = settingsService.getSettings(); - expect(result).to.deep.equal(dbSettings); - }); - - it("should throw an error if settings have not been loaded", function () { - const appSettings = { findOne: sinon.stub().returns(null) }; - const settingsService = new SettingsService(appSettings); - settingsService.settings = null; - - try { - settingsService.getSettings(); - } catch (error) { - expect(error.message).to.equal("Settings have not been loaded"); - } - }); - }); -}); diff --git a/server/tests/services/statusService.test.js b/server/tests/services/statusService.test.js deleted file mode 100755 index 289a36884..000000000 --- a/server/tests/services/statusService.test.js +++ /dev/null @@ -1,255 +0,0 @@ -import sinon from "sinon"; -import StatusService from "../../service/statusService.js"; -import { afterEach, describe } from "node:test"; - -describe("StatusService", () => { - let db, logger, statusService; - - beforeEach(function () { - db = { - getMonitorById: sinon.stub(), - createCheck: sinon.stub(), - createPagespeedCheck: sinon.stub(), - }; - logger = { - info: sinon.stub(), - error: sinon.stub(), - }; - statusService = new StatusService(db, logger); - }); - - afterEach(() => { - sinon.restore(); - }); - - describe("constructor", () => { - it("should create an instance of StatusService", function () { - expect(statusService).to.be.an.instanceOf(StatusService); - }); - }); - - describe("getStatusString", () => { - it("should return 'up' if status is true", function () { - expect(statusService.getStatusString(true)).to.equal("up"); - }); - - it("should return 'down' if status is false", function () { - expect(statusService.getStatusString(false)).to.equal("down"); - }); - - it("should return 'unknown' if status is undefined or null", function () { - expect(statusService.getStatusString(undefined)).to.equal("unknown"); - }); - }); - - describe("updateStatus", () => { - beforeEach(function () { - // statusService.insertCheck = sinon.stub().resolves; - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should throw an error if an error occurs", async function () { - const error = new Error("Test error"); - statusService.db.getMonitorById = sinon.stub().throws(error); - try { - await statusService.updateStatus({ monitorId: "test", status: true }); - } catch (error) { - expect(error.message).to.equal("Test error"); - } - // expect(statusService.insertCheck.calledOnce).to.be.true; - }); - - it("should return {statusChanged: false} if status hasn't changed", async function () { - statusService.db.getMonitorById = sinon.stub().returns({ status: true }); - const result = await statusService.updateStatus({ - monitorId: "test", - status: true, - }); - expect(result).to.deep.equal({ statusChanged: false }); - // expect(statusService.insertCheck.calledOnce).to.be.true; - }); - - it("should return {statusChanged: true} if status has changed from down to up", async function () { - statusService.db.getMonitorById = sinon.stub().returns({ status: false, save: sinon.stub() }); - const result = await statusService.updateStatus({ - monitorId: "test", - status: true, - }); - expect(result.statusChanged).to.be.true; - expect(result.monitor.status).to.be.true; - expect(result.prevStatus).to.be.false; - // expect(statusService.insertCheck.calledOnce).to.be.true; - }); - - it("should return {statusChanged: true} if status has changed from up to down", async function () { - statusService.db.getMonitorById = sinon.stub().returns({ status: true, save: sinon.stub() }); - const result = await statusService.updateStatus({ - monitorId: "test", - status: false, - }); - expect(result.statusChanged).to.be.true; - expect(result.monitor.status).to.be.false; - expect(result.prevStatus).to.be.true; - // expect(statusService.insertCheck.calledOnce).to.be.true; - }); - }); - - describe("buildCheck", () => { - it("should build a check object", function () { - const check = statusService.buildCheck({ - monitorId: "test", - type: "test", - status: true, - responseTime: 100, - code: 200, - message: "Test message", - payload: { test: "test" }, - }); - expect(check.monitorId).to.equal("test"); - expect(check.status).to.be.true; - expect(check.statusCode).to.equal(200); - expect(check.responseTime).to.equal(100); - expect(check.message).to.equal("Test message"); - }); - - it("should build a check object for pagespeed type", function () { - const check = statusService.buildCheck({ - monitorId: "test", - type: "pagespeed", - status: true, - responseTime: 100, - code: 200, - message: "Test message", - payload: { - lighthouseResult: { - categories: { - accessibility: { score: 1 }, - "best-practices": { score: 1 }, - performance: { score: 1 }, - seo: { score: 1 }, - }, - audits: { - "cumulative-layout-shift": { score: 1 }, - "speed-index": { score: 1 }, - "first-contentful-paint": { score: 1 }, - "largest-contentful-paint": { score: 1 }, - "total-blocking-time": { score: 1 }, - }, - }, - }, - }); - expect(check.monitorId).to.equal("test"); - expect(check.status).to.be.true; - expect(check.statusCode).to.equal(200); - expect(check.responseTime).to.equal(100); - expect(check.message).to.equal("Test message"); - expect(check.accessibility).to.equal(100); - expect(check.bestPractices).to.equal(100); - expect(check.performance).to.equal(100); - expect(check.seo).to.equal(100); - expect(check.audits).to.deep.equal({ - cls: { score: 1 }, - si: { score: 1 }, - fcp: { score: 1 }, - lcp: { score: 1 }, - tbt: { score: 1 }, - }); - }); - - it("should build a check object for pagespeed type with missing data", function () { - const check = statusService.buildCheck({ - monitorId: "test", - type: "pagespeed", - status: true, - responseTime: 100, - code: 200, - message: "Test message", - payload: { - lighthouseResult: { - categories: {}, - audits: {}, - }, - }, - }); - expect(check.monitorId).to.equal("test"); - expect(check.status).to.be.true; - expect(check.statusCode).to.equal(200); - expect(check.responseTime).to.equal(100); - expect(check.message).to.equal("Test message"); - expect(check.accessibility).to.equal(0); - expect(check.bestPractices).to.equal(0); - expect(check.performance).to.equal(0); - expect(check.seo).to.equal(0); - expect(check.audits).to.deep.equal({ - cls: 0, - si: 0, - fcp: 0, - lcp: 0, - tbt: 0, - }); - }); - - it("should build a check for hardware type", function () { - const check = statusService.buildCheck({ - monitorId: "test", - type: "hardware", - status: true, - responseTime: 100, - code: 200, - message: "Test message", - payload: { data: { cpu: "cpu", memory: "memory", disk: "disk", host: "host" } }, - }); - expect(check.monitorId).to.equal("test"); - expect(check.status).to.be.true; - expect(check.statusCode).to.equal(200); - expect(check.responseTime).to.equal(100); - expect(check.message).to.equal("Test message"); - expect(check.cpu).to.equal("cpu"); - expect(check.memory).to.equal("memory"); - expect(check.disk).to.equal("disk"); - expect(check.host).to.equal("host"); - }); - - it("should build a check for hardware type with missing data", function () { - const check = statusService.buildCheck({ - monitorId: "test", - type: "hardware", - status: true, - responseTime: 100, - code: 200, - message: "Test message", - payload: {}, - }); - expect(check.monitorId).to.equal("test"); - expect(check.status).to.be.true; - expect(check.statusCode).to.equal(200); - expect(check.responseTime).to.equal(100); - expect(check.message).to.equal("Test message"); - expect(check.cpu).to.deep.equal({}); - expect(check.memory).to.deep.equal({}); - expect(check.disk).to.deep.equal({}); - expect(check.host).to.deep.equal({}); - }); - }); - - describe("insertCheck", () => { - it("should log an error if one is thrown", async function () { - const testError = new Error("Test error"); - statusService.db.createCheck = sinon.stub().throws(testError); - try { - await statusService.insertCheck({ monitorId: "test" }); - } catch (error) { - expect(error.message).to.equal(testError.message); - } - expect(statusService.logger.error.calledOnce).to.be.true; - }); - - it("should insert a check into the database", async function () { - await statusService.insertCheck({ monitorId: "test", type: "http" }); - expect(statusService.db.createCheck.calledOnce).to.be.true; - }); - }); -}); diff --git a/server/tests/utils/dataUtils.test.js b/server/tests/utils/dataUtils.test.js deleted file mode 100755 index ec9eabb40..000000000 --- a/server/tests/utils/dataUtils.test.js +++ /dev/null @@ -1,61 +0,0 @@ -import { NormalizeData, calculatePercentile } from "../../utils/dataUtils.js"; -import sinon from "sinon"; - -describe("NormalizeData", function () { - it("should normalize response times when checks length is greater than 1", function () { - const checks = [ - { responseTime: 20, _doc: { id: 1 } }, - { responseTime: 40, _doc: { id: 2 } }, - { responseTime: 60, _doc: { id: 3 } }, - ]; - const rangeMin = 1; - const rangeMax = 100; - - const result = NormalizeData(checks, rangeMin, rangeMax); - - expect(result).to.be.an("array"); - expect(result).to.have.lengthOf(3); - result.forEach((check) => { - expect(check).to.have.property("responseTime").that.is.a("number"); - expect(check).to.have.property("originalResponseTime").that.is.a("number"); - }); - }); - - it("should return checks with original response times when checks length is 1", function () { - const checks = [{ responseTime: 20, _doc: { id: 1 } }]; - const rangeMin = 1; - const rangeMax = 100; - - const result = NormalizeData(checks, rangeMin, rangeMax); - expect(result).to.be.an("array"); - expect(result).to.have.lengthOf(1); - expect(result[0]).to.have.property("originalResponseTime", 20); - }); - - it("should handle edge cases with extreme response times", function () { - const checks = [ - { responseTime: 5, _doc: { id: 1 } }, - { responseTime: 95, _doc: { id: 2 } }, - ]; - const rangeMin = 1; - const rangeMax = 100; - - const result = NormalizeData(checks, rangeMin, rangeMax); - - expect(result).to.be.an("array"); - expect(result).to.have.lengthOf(2); - expect(result[0]).to.have.property("responseTime").that.is.at.least(rangeMin); - expect(result[1]).to.have.property("responseTime").that.is.at.most(rangeMax); - }); -}); - -describe("calculatePercentile", function () { - it("should return the lower value when upper is greater than or equal to the length of the sorted array", function () { - const checks = [{ responseTime: 10 }, { responseTime: 20 }, { responseTime: 30 }, { responseTime: 40 }, { responseTime: 50 }]; - - const percentile = 100; - const result = calculatePercentile(checks, percentile); - const expected = 50; - expect(result).to.equal(expected); - }); -}); diff --git a/server/tests/utils/imageProcessing.test.js b/server/tests/utils/imageProcessing.test.js deleted file mode 100755 index 09330c317..000000000 --- a/server/tests/utils/imageProcessing.test.js +++ /dev/null @@ -1,53 +0,0 @@ -import { expect } from "chai"; -import sinon from "sinon"; -import sharp from "sharp"; -import { GenerateAvatarImage } from "../../utils/imageProcessing.js"; - -describe("imageProcessing - GenerateAvatarImage", function () { - it("should resize the image to 64x64 and return a base64 string", async function () { - const file = { - buffer: Buffer.from("test image buffer"), - }; - - // Stub the sharp function - const toBufferStub = sinon.stub().resolves(Buffer.from("resized image buffer")); - const resizeStub = sinon.stub().returns({ toBuffer: toBufferStub }); - const sharpStub = sinon.stub(sharp.prototype, "resize").returns({ toBuffer: toBufferStub }); - - const result = await GenerateAvatarImage(file); - - // Verify the result - const expected = Buffer.from("resized image buffer").toString("base64"); - expect(result).to.equal(expected); - - // Verify that the sharp function was called with the correct arguments - expect(sharpStub.calledOnceWith({ width: 64, height: 64, fit: "cover" })).to.be.true; - expect(toBufferStub.calledOnce).to.be.true; - - // Restore the stubbed functions - sharpStub.restore(); - }); - - it("should throw an error if resizing fails", async function () { - const file = { - buffer: Buffer.from("test image buffer"), - }; - - // Stub the sharp function to throw an error - const toBufferStub = sinon.stub().rejects(new Error("Resizing failed")); - const resizeStub = sinon.stub().returns({ toBuffer: toBufferStub }); - const sharpStub = sinon.stub(sharp.prototype, "resize").returns({ toBuffer: toBufferStub }); - - try { - await GenerateAvatarImage(file); - // If no error is thrown, fail the test - expect.fail("Expected error to be thrown"); - } catch (error) { - // Verify that the error message is correct - expect(error.message).to.equal("Resizing failed"); - } - - // Restore the stubbed functions - sharpStub.restore(); - }); -}); diff --git a/server/tests/utils/logger.test.js b/server/tests/utils/logger.test.js deleted file mode 100755 index 2d5bd82a9..000000000 --- a/server/tests/utils/logger.test.js +++ /dev/null @@ -1,139 +0,0 @@ -import sinon from "sinon"; -import logger from "../../utils/logger.js"; -import { Logger } from "../../utils/logger.js"; -import winston from "winston"; - -describe("Logger", function () { - let infoStub, warnStub, errorStub; - - beforeEach(function () { - infoStub = sinon.stub(logger.logger, "info"); - warnStub = sinon.stub(logger.logger, "warn"); - errorStub = sinon.stub(logger.logger, "error"); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("constructor", function () { - let createLoggerStub; - - beforeEach(function () { - createLoggerStub = sinon.stub(winston, "createLogger"); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should convert message to JSON string if it is an object", function () { - const logMessage = { key: "value" }; - const expectedMessage = JSON.stringify(logMessage, null, 2); - - createLoggerStub.callsFake((config) => { - const consoleTransport = config.transports[0]; - const logEntry = { - level: "info", - message: logMessage, - timestamp: new Date().toISOString(), - }; - const formattedMessage = consoleTransport.format.transform(logEntry); - expect(formattedMessage).to.include(expectedMessage); - return { log: sinon.spy() }; - }); - - const logger = new Logger(); - logger.logger.info(logMessage); - }); - - it("should convert details to JSON string if it is an object", function () { - const logDetails = { key: "value" }; - const expectedDetails = JSON.stringify(logDetails, null, 2); // Removed .s - - createLoggerStub.callsFake((config) => { - const consoleTransport = config.transports[0]; - const logEntry = { - level: "info", - message: "", // Add empty message since it's required - details: logDetails, - timestamp: new Date().toISOString(), - }; - const formattedMessage = consoleTransport.format.transform(logEntry); - expect(formattedMessage).to.include(expectedDetails); - return { info: sinon.spy() }; // Changed to return info method - }); - - const logger = new Logger(); - logger.logger.info("", { details: logDetails }); // Updated to pass details properly - }); - }); - - describe("info", function () { - it("should log an informational message", function () { - const config = { - message: "Info message", - service: "TestService", - method: "TestMethod", - details: { key: "value" }, - }; - - logger.info(config); - - expect(infoStub.calledOnce).to.be.true; - expect( - infoStub.calledWith(config.message, { - service: config.service, - method: config.method, - details: config.details, - }) - ).to.be.true; - }); - }); - - describe("warn", function () { - it("should log a warning message", function () { - const config = { - message: "Warning message", - service: "TestService", - method: "TestMethod", - details: { key: "value" }, - }; - - logger.warn(config); - - expect(warnStub.calledOnce).to.be.true; - expect( - warnStub.calledWith(config.message, { - service: config.service, - method: config.method, - details: config.details, - }) - ).to.be.true; - }); - }); - - describe("error", function () { - it("should log an error message", function () { - const config = { - message: "Error message", - service: "TestService", - method: "TestMethod", - details: { key: "value" }, - stack: "Error stack trace", - }; - - logger.error(config); - - expect(errorStub.calledOnce).to.be.true; - expect( - errorStub.calledWith(config.message, { - service: config.service, - method: config.method, - details: config.details, - stack: config.stack, - }) - ).to.be.true; - }); - }); -}); diff --git a/server/tests/utils/messages.test.js b/server/tests/utils/messages.test.js deleted file mode 100755 index a7e98c477..000000000 --- a/server/tests/utils/messages.test.js +++ /dev/null @@ -1,23 +0,0 @@ -import { errorMessages, successMessages } from "../../utils/messages.js"; -describe("Messages", function () { - describe("messages - errorMessages", function () { - it("should have a DB_FIND_MONITOR_BY_ID function", function () { - const monitorId = "12345"; - expect(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)).to.equal(`Monitor with id ${monitorId} not found`); - }); - - it("should have a DB_DELETE_CHECKS function", function () { - const monitorId = "12345"; - expect(errorMessages.DB_DELETE_CHECKS(monitorId)).to.equal(`No checks found for monitor with id ${monitorId}`); - }); - }); - - describe("messages - successMessages", function () { - it("should have a MONITOR_GET_BY_USER_ID function", function () { - const userId = "12345"; - expect(successMessages.MONITOR_GET_BY_USER_ID(userId)).to.equal(`Got monitor for ${userId} successfully"`); - }); - - // Add more tests for other success messages as needed - }); -}); diff --git a/server/tests/utils/utils.test.js b/server/tests/utils/utils.test.js deleted file mode 100755 index 2630bb8e6..000000000 --- a/server/tests/utils/utils.test.js +++ /dev/null @@ -1,50 +0,0 @@ -import { ParseBoolean, getTokenFromHeaders } from "../../utils/utils.js"; - -describe("utils - ParseBoolean", function () { - it("should return true", function () { - const result = ParseBoolean("true"); - expect(result).to.be.true; - }); - - it("should return false", function () { - const result = ParseBoolean("false"); - expect(result).to.be.false; - }); - - it("should return false", function () { - const result = ParseBoolean(null); - expect(result).to.be.false; - }); - - it("should return false", function () { - const result = ParseBoolean(undefined); - expect(result).to.be.false; - }); -}); - -describe("utils - getTokenFromHeaders", function () { - it("should throw an error if authorization header is missing", function () { - const headers = {}; - expect(() => getTokenFromHeaders(headers)).to.throw("No auth headers"); - }); - - it("should throw an error if authorization header does not start with Bearer", function () { - const headers = { authorization: "Basic abcdef" }; - expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers"); - }); - - it("should return the token if authorization header is correctly formatted", function () { - const headers = { authorization: "Bearer abcdef" }; - expect(getTokenFromHeaders(headers)).to.equal("abcdef"); - }); - - it("should throw an error if authorization header has more than two parts", function () { - const headers = { authorization: "Bearer abc def" }; - expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers"); - }); - - it("should throw an error if authorization header has less than two parts", function () { - const headers = { authorization: "Bearer" }; - expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers"); - }); -}); diff --git a/server/tsconfig.json b/server/tsconfig.json new file mode 100644 index 000000000..3aa773ab1 --- /dev/null +++ b/server/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", + "module": "nodenext", + "target": "esnext", + "types": ["node"], + "sourceMap": true, + "declaration": false, + "declarationMap": false, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "strict": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "checkJs": false + }, + "exclude": ["node_modules", "dist", "**/*.config.js"] +} From f767125acd078252bb3bb1551cda01a944b76f76 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 22 Sep 2025 13:03:54 -0700 Subject: [PATCH 002/103] tsx, tpyescript --- server/package-lock.json | 17 ++++++++++++++++- server/package.json | 3 ++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index e2affbca0..06c1c30b3 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -55,7 +55,8 @@ "nodemon": "3.1.9", "prettier": "^3.3.3", "sinon": "19.0.2", - "tsx": "4.20.5" + "tsx": "4.20.5", + "typescript": "5.9.2" } }, "node_modules/@asamuzakjp/css-color": { @@ -9139,6 +9140,20 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "license": "MIT" }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", diff --git a/server/package.json b/server/package.json index c57c3a4ce..637c785cc 100755 --- a/server/package.json +++ b/server/package.json @@ -64,6 +64,7 @@ "nodemon": "3.1.9", "prettier": "^3.3.3", "sinon": "19.0.2", - "tsx": "4.20.5" + "tsx": "4.20.5", + "typescript": "5.9.2" } } From 23126a92cdcbd856fdbeddcb9b5c46a3b766537f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 22 Sep 2025 13:04:45 -0700 Subject: [PATCH 003/103] move v1 controllers to v1 dir --- server/src/config/controllers.js | 24 +++++++++---------- .../{ => v1}/announcementsController.js | 2 +- .../controllers/{ => v1}/authController.js | 2 +- .../controllers/{ => v1}/baseController.js | 2 +- .../controllers/{ => v1}/checkController.js | 2 +- .../controllers/{ => v1}/controllerUtils.js | 0 .../{ => v1}/diagnosticController.js | 0 .../controllers/{ => v1}/inviteController.js | 2 +- .../src/controllers/{ => v1}/logController.js | 0 .../{ => v1}/maintenanceWindowController.js | 2 +- .../controllers/{ => v1}/monitorController.js | 2 +- .../{ => v1}/notificationController.js | 2 +- .../controllers/{ => v1}/queueController.js | 0 .../{ => v1}/settingsController.js | 4 ++-- .../{ => v1}/statusPageController.js | 2 +- server/src/routes/v1/monitorRoute.js | 2 +- 16 files changed, 24 insertions(+), 24 deletions(-) rename server/src/controllers/{ => v1}/announcementsController.js (96%) rename server/src/controllers/{ => v1}/authController.js (99%) rename server/src/controllers/{ => v1}/baseController.js (97%) rename server/src/controllers/{ => v1}/checkController.js (99%) rename server/src/controllers/{ => v1}/controllerUtils.js (100%) rename server/src/controllers/{ => v1}/diagnosticController.js (100%) rename server/src/controllers/{ => v1}/inviteController.js (99%) rename server/src/controllers/{ => v1}/logController.js (100%) rename server/src/controllers/{ => v1}/maintenanceWindowController.js (99%) mode change 100755 => 100644 rename server/src/controllers/{ => v1}/monitorController.js (99%) mode change 100755 => 100644 rename server/src/controllers/{ => v1}/notificationController.js (98%) mode change 100755 => 100644 rename server/src/controllers/{ => v1}/queueController.js (100%) rename server/src/controllers/{ => v1}/settingsController.js (95%) mode change 100755 => 100644 rename server/src/controllers/{ => v1}/statusPageController.js (99%) mode change 100755 => 100644 diff --git a/server/src/config/controllers.js b/server/src/config/controllers.js index 693e35ab8..5c54579eb 100644 --- a/server/src/config/controllers.js +++ b/server/src/config/controllers.js @@ -1,19 +1,19 @@ -import { createCommonDependencies } from "../controllers/baseController.js"; +import { createCommonDependencies } from "../controllers/v1/baseController.js"; // Services // Controllers -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 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"; export const initializeControllers = (services) => { const controllers = {}; diff --git a/server/src/controllers/announcementsController.js b/server/src/controllers/v1/announcementsController.js similarity index 96% rename from server/src/controllers/announcementsController.js rename to server/src/controllers/v1/announcementsController.js index 430859791..0425b44aa 100755 --- a/server/src/controllers/announcementsController.js +++ b/server/src/controllers/v1/announcementsController.js @@ -1,4 +1,4 @@ -import { createAnnouncementValidation } from "../validation/joi.js"; +import { createAnnouncementValidation } from "../../validation/joi.js"; import BaseController from "./baseController.js"; const SERVICE_NAME = "announcementController"; diff --git a/server/src/controllers/authController.js b/server/src/controllers/v1/authController.js similarity index 99% rename from server/src/controllers/authController.js rename to server/src/controllers/v1/authController.js index c186e8423..17ed517eb 100755 --- a/server/src/controllers/authController.js +++ b/server/src/controllers/v1/authController.js @@ -10,7 +10,7 @@ import { editUserByIdParamValidation, editUserByIdBodyValidation, editSuperadminUserByIdBodyValidation, -} from "../validation/joi.js"; +} from "../../validation/joi.js"; const SERVICE_NAME = "authController"; diff --git a/server/src/controllers/baseController.js b/server/src/controllers/v1/baseController.js similarity index 97% rename from server/src/controllers/baseController.js rename to server/src/controllers/v1/baseController.js index 910cf57b4..314905c82 100644 --- a/server/src/controllers/baseController.js +++ b/server/src/controllers/v1/baseController.js @@ -1,4 +1,4 @@ -import { AppError } from "../service/infrastructure/errorService.js"; +import { AppError } from "../../service/infrastructure/errorService.js"; export const createCommonDependencies = (db, errorService, logger, stringService) => { return { diff --git a/server/src/controllers/checkController.js b/server/src/controllers/v1/checkController.js similarity index 99% rename from server/src/controllers/checkController.js rename to server/src/controllers/v1/checkController.js index 4069df3ff..ea293d74d 100755 --- a/server/src/controllers/checkController.js +++ b/server/src/controllers/v1/checkController.js @@ -9,7 +9,7 @@ import { ackCheckBodyValidation, ackAllChecksParamValidation, ackAllChecksBodyValidation, -} from "../validation/joi.js"; +} from "../../validation/joi.js"; const SERVICE_NAME = "checkController"; diff --git a/server/src/controllers/controllerUtils.js b/server/src/controllers/v1/controllerUtils.js similarity index 100% rename from server/src/controllers/controllerUtils.js rename to server/src/controllers/v1/controllerUtils.js diff --git a/server/src/controllers/diagnosticController.js b/server/src/controllers/v1/diagnosticController.js similarity index 100% rename from server/src/controllers/diagnosticController.js rename to server/src/controllers/v1/diagnosticController.js diff --git a/server/src/controllers/inviteController.js b/server/src/controllers/v1/inviteController.js similarity index 99% rename from server/src/controllers/inviteController.js rename to server/src/controllers/v1/inviteController.js index a377234c0..1ec586f31 100755 --- a/server/src/controllers/inviteController.js +++ b/server/src/controllers/v1/inviteController.js @@ -1,4 +1,4 @@ -import { inviteBodyValidation, inviteVerificationBodyValidation } from "../validation/joi.js"; +import { inviteBodyValidation, inviteVerificationBodyValidation } from "../../validation/joi.js"; import BaseController from "./baseController.js"; const SERVICE_NAME = "inviteController"; diff --git a/server/src/controllers/logController.js b/server/src/controllers/v1/logController.js similarity index 100% rename from server/src/controllers/logController.js rename to server/src/controllers/v1/logController.js diff --git a/server/src/controllers/maintenanceWindowController.js b/server/src/controllers/v1/maintenanceWindowController.js old mode 100755 new mode 100644 similarity index 99% rename from server/src/controllers/maintenanceWindowController.js rename to server/src/controllers/v1/maintenanceWindowController.js index 2fdb1ee15..be373ace5 --- a/server/src/controllers/maintenanceWindowController.js +++ b/server/src/controllers/v1/maintenanceWindowController.js @@ -6,7 +6,7 @@ import { getMaintenanceWindowsByMonitorIdParamValidation, getMaintenanceWindowsByTeamIdQueryValidation, deleteMaintenanceWindowByIdParamValidation, -} from "../validation/joi.js"; +} from "../../validation/joi.js"; import BaseController from "./baseController.js"; const SERVICE_NAME = "maintenanceWindowController"; diff --git a/server/src/controllers/monitorController.js b/server/src/controllers/v1/monitorController.js old mode 100755 new mode 100644 similarity index 99% rename from server/src/controllers/monitorController.js rename to server/src/controllers/v1/monitorController.js index 0f40a4a01..462d5a278 --- a/server/src/controllers/monitorController.js +++ b/server/src/controllers/v1/monitorController.js @@ -11,7 +11,7 @@ import { getCertificateParamValidation, getHardwareDetailsByIdParamValidation, getHardwareDetailsByIdQueryValidation, -} from "../validation/joi.js"; +} from "../../validation/joi.js"; import sslChecker from "ssl-checker"; import { fetchMonitorCertificate } from "./controllerUtils.js"; import BaseController from "./baseController.js"; diff --git a/server/src/controllers/notificationController.js b/server/src/controllers/v1/notificationController.js old mode 100755 new mode 100644 similarity index 98% rename from server/src/controllers/notificationController.js rename to server/src/controllers/v1/notificationController.js index e00dd5b6f..37c2b4e60 --- a/server/src/controllers/notificationController.js +++ b/server/src/controllers/v1/notificationController.js @@ -1,4 +1,4 @@ -import { createNotificationBodyValidation } from "../validation/joi.js"; +import { createNotificationBodyValidation } from "../../validation/joi.js"; import BaseController from "./baseController.js"; const SERVICE_NAME = "NotificationController"; diff --git a/server/src/controllers/queueController.js b/server/src/controllers/v1/queueController.js similarity index 100% rename from server/src/controllers/queueController.js rename to server/src/controllers/v1/queueController.js diff --git a/server/src/controllers/settingsController.js b/server/src/controllers/v1/settingsController.js old mode 100755 new mode 100644 similarity index 95% rename from server/src/controllers/settingsController.js rename to server/src/controllers/v1/settingsController.js index 7da724d23..3db73a05a --- a/server/src/controllers/settingsController.js +++ b/server/src/controllers/v1/settingsController.js @@ -1,5 +1,5 @@ -import { updateAppSettingsBodyValidation } from "../validation/joi.js"; -import { sendTestEmailBodyValidation } from "../validation/joi.js"; +import { updateAppSettingsBodyValidation } from "../../validation/joi.js"; +import { sendTestEmailBodyValidation } from "../../validation/joi.js"; import BaseController from "./baseController.js"; const SERVICE_NAME = "SettingsController"; diff --git a/server/src/controllers/statusPageController.js b/server/src/controllers/v1/statusPageController.js old mode 100755 new mode 100644 similarity index 99% rename from server/src/controllers/statusPageController.js rename to server/src/controllers/v1/statusPageController.js index a46688590..de6a1ca7c --- a/server/src/controllers/statusPageController.js +++ b/server/src/controllers/v1/statusPageController.js @@ -1,4 +1,4 @@ -import { createStatusPageBodyValidation, getStatusPageParamValidation, getStatusPageQueryValidation, imageValidation } from "../validation/joi.js"; +import { createStatusPageBodyValidation, getStatusPageParamValidation, getStatusPageQueryValidation, imageValidation } from "../../validation/joi.js"; import BaseController from "./baseController.js"; const SERVICE_NAME = "statusPageController"; diff --git a/server/src/routes/v1/monitorRoute.js b/server/src/routes/v1/monitorRoute.js index f8f219b83..dde72e256 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/isAllowed.js"; import multer from "multer"; -import { fetchMonitorCertificate } from "../../controllers/controllerUtils.js"; +import { fetchMonitorCertificate } from "../../controllers/v1/controllerUtils.js"; const upload = multer({ storage: multer.memoryStorage(), // Store file in memory as Buffer }); From 0b646ee64f5c6edff0857c2fdaf2812a64edf2bd Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 22 Sep 2025 13:26:45 -0700 Subject: [PATCH 004/103] update docker files to build --- docker/dev/build_images.sh | 3 +-- docker/dev/server.Dockerfile | 4 +++- docker/dist-arm/server.Dockerfile | 8 ++++--- docker/dist-mono/docker-compose-local.yaml | 27 ++++++++++++++++++++++ docker/dist-mono/server.Dockerfile | 7 ++++-- docker/dist/server.Dockerfile | 4 +++- docker/prod/server.Dockerfile | 2 ++ docker/staging/server.Dockerfile | 4 +++- 8 files changed, 49 insertions(+), 10 deletions(-) create mode 100755 docker/dist-mono/docker-compose-local.yaml diff --git a/docker/dev/build_images.sh b/docker/dev/build_images.sh index e94be9fa5..eb43e31d8 100755 --- a/docker/dev/build_images.sh +++ b/docker/dev/build_images.sh @@ -5,11 +5,10 @@ cd "$(dirname "$0")" cd ../.. # Define service names and their corresponding Dockerfiles in parallel arrays -services=("uptime_client" "uptime_mongo" "uptime_redis" "uptime_server") +services=("uptime_client" "uptime_mongo" "uptime_server") dockerfiles=( "./docker/dev/client.Dockerfile" "./docker/dev/mongoDB.Dockerfile" - "./docker/dev/redis.Dockerfile" "./docker/dev/server.Dockerfile" ) diff --git a/docker/dev/server.Dockerfile b/docker/dev/server.Dockerfile index d24bff63f..2a6ebcb7c 100755 --- a/docker/dev/server.Dockerfile +++ b/docker/dev/server.Dockerfile @@ -13,6 +13,8 @@ RUN npm install COPY ./server/ ./ +RUN npm run build + EXPOSE 52345 -CMD ["node", "src/index.js"] \ No newline at end of file +CMD ["node", "dist/index.js"] \ No newline at end of file diff --git a/docker/dist-arm/server.Dockerfile b/docker/dist-arm/server.Dockerfile index e47db9a74..91ccd427e 100644 --- a/docker/dist-arm/server.Dockerfile +++ b/docker/dist-arm/server.Dockerfile @@ -35,10 +35,12 @@ RUN npm install COPY server ./ -COPY --from=frontend-build /app/client/dist ./public - RUN chmod +x ./scripts/inject-vars.sh +RUN npm run build + +COPY --from=frontend-build /app/client/dist ./public + EXPOSE 52345 -CMD ["sh", "-c", "./scripts/inject-vars.sh && node ./src/index.js"] +CMD ["sh", "-c", "./scripts/inject-vars.sh && node ./dist/index.js"] diff --git a/docker/dist-mono/docker-compose-local.yaml b/docker/dist-mono/docker-compose-local.yaml new file mode 100755 index 000000000..a5616695b --- /dev/null +++ b/docker/dist-mono/docker-compose-local.yaml @@ -0,0 +1,27 @@ +services: + server: + image: mono_server + restart: always + ports: + - "52345:52345" + environment: + - UPTIME_APP_API_BASE_URL=http://localhost:52345/api/v1 + - UPTIME_APP_CLIENT_HOST=http://localhost + - DB_CONNECTION_STRING=mongodb://mongodb:27017/uptime_db + - CLIENT_HOST=http://localhost + - JWT_SECRET=my_secret + depends_on: + - mongodb + mongodb: + image: mono_mongo + restart: always + command: ["mongod", "--quiet", "--bind_ip_all"] + volumes: + - ./mongo/data:/data/db + healthcheck: + test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')", "--quiet"] + interval: 5s + timeout: 30s + start_period: 0s + start_interval: 1s + retries: 30 diff --git a/docker/dist-mono/server.Dockerfile b/docker/dist-mono/server.Dockerfile index 405f9e6ac..84b7127ed 100644 --- a/docker/dist-mono/server.Dockerfile +++ b/docker/dist-mono/server.Dockerfile @@ -19,12 +19,15 @@ WORKDIR /app/server COPY server ./ -COPY --from=frontend-build /app/client/dist ./public RUN npm install +RUN npm run build + +COPY --from=frontend-build /app/client/dist ./public + RUN chmod +x ./scripts/inject-vars.sh EXPOSE 52345 -CMD ./scripts/inject-vars.sh && node ./src/index.js +CMD ./scripts/inject-vars.sh && node ./dist/index.js diff --git a/docker/dist/server.Dockerfile b/docker/dist/server.Dockerfile index d24bff63f..2a6ebcb7c 100755 --- a/docker/dist/server.Dockerfile +++ b/docker/dist/server.Dockerfile @@ -13,6 +13,8 @@ RUN npm install COPY ./server/ ./ +RUN npm run build + EXPOSE 52345 -CMD ["node", "src/index.js"] \ No newline at end of file +CMD ["node", "dist/index.js"] \ No newline at end of file diff --git a/docker/prod/server.Dockerfile b/docker/prod/server.Dockerfile index b78de2e33..9d9800e7b 100755 --- a/docker/prod/server.Dockerfile +++ b/docker/prod/server.Dockerfile @@ -15,6 +15,8 @@ RUN npm install COPY ./server ./ +RUN npm run build + EXPOSE 52345 CMD ["node", "src/index.js"] \ No newline at end of file diff --git a/docker/staging/server.Dockerfile b/docker/staging/server.Dockerfile index b78de2e33..7341b1e9b 100755 --- a/docker/staging/server.Dockerfile +++ b/docker/staging/server.Dockerfile @@ -15,6 +15,8 @@ RUN npm install COPY ./server ./ +RUN npm run build + EXPOSE 52345 -CMD ["node", "src/index.js"] \ No newline at end of file +CMD ["node", "dist/index.js"] \ No newline at end of file From 11ff99613ae3f0f95143ca2ce64d1712d1758c85 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 22 Sep 2025 15:56:41 -0700 Subject: [PATCH 005/103] implement auth routes, add user migration --- server/nodemon.json | 4 +- server/package-lock.json | 117 +++++++++++++++ server/package.json | 4 +- server/src/config/controllers.js | 6 + server/src/config/routes.js | 10 ++ server/src/config/services.js | 15 +- server/src/controllers/v2/AuthController.ts | 142 ++++++++++++++++++ server/src/db/models/InviteToken.js | 35 ----- server/src/db/models/auth/Invite.ts | 68 +++++++++ server/src/db/models/auth/Role.ts | 45 ++++++ .../src/db/models/{User.js => auth/User.ts} | 82 ++++++++-- server/src/db/models/index.ts | 7 + .../db/mongo/migration/0002_migrateUsers.ts | 49 ++++++ server/src/db/mongo/migration/index.js | 2 + server/src/db/mongo/modules/inviteModule.js | 12 +- server/src/middleware/v2/VerifyToken.ts | 21 +++ server/src/middleware/verifyTeamAccess.js | 2 + server/src/routes/v2/auth.ts | 31 ++++ server/src/service/v2/business/AuthService.ts | 141 +++++++++++++++++ .../src/service/v2/business/InviteService.ts | 61 ++++++++ server/src/utils/ApiError.ts | 15 ++ server/src/utils/JWTUtils.ts | 22 +++ 22 files changed, 828 insertions(+), 63 deletions(-) create mode 100644 server/src/controllers/v2/AuthController.ts delete mode 100755 server/src/db/models/InviteToken.js create mode 100755 server/src/db/models/auth/Invite.ts create mode 100644 server/src/db/models/auth/Role.ts rename server/src/db/models/{User.js => auth/User.ts} (55%) create mode 100644 server/src/db/models/index.ts create mode 100644 server/src/db/mongo/migration/0002_migrateUsers.ts create mode 100644 server/src/middleware/v2/VerifyToken.ts create mode 100644 server/src/routes/v2/auth.ts create mode 100644 server/src/service/v2/business/AuthService.ts create mode 100644 server/src/service/v2/business/InviteService.ts create mode 100644 server/src/utils/ApiError.ts create mode 100644 server/src/utils/JWTUtils.ts diff --git a/server/nodemon.json b/server/nodemon.json index 88d6ac686..7371b4904 100755 --- a/server/nodemon.json +++ b/server/nodemon.json @@ -1,5 +1,5 @@ { "ignore": ["src/locales/*", "*.log", "node_modules/*"], - "watch": ["src/**/*.js", "*.json"], - "ext": "js,json" + "watch": ["src/**/*.js", "src/**/*.ts", "*.json"], + "ext": "ts,js,json" } diff --git a/server/package-lock.json b/server/package-lock.json index 06c1c30b3..e21b28382 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -45,6 +45,8 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/express": "5.0.3", + "@types/jsonwebtoken": "9.0.10", "c8": "10.1.3", "chai": "5.2.0", "eslint": "^9.17.0", @@ -1678,6 +1680,27 @@ "node": ">=10.13.0" } }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1685,12 +1708,44 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/express": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", + "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", + "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "license": "MIT" }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -1705,6 +1760,31 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.3.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", @@ -1714,12 +1794,49 @@ "undici-types": "~7.10.0" } }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/relateurl": { "version": "0.2.33", "resolved": "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.33.tgz", "integrity": "sha512-bTQCKsVbIdzLqZhLkF5fcJQreE4y1ro4DIyVrlDNSCJRRwHhB8Z+4zXXa8jN6eDvc2HbRsEYgbvrnGvi54EpSw==", "license": "MIT" }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", diff --git a/server/package.json b/server/package.json index 637c785cc..5c655af9d 100755 --- a/server/package.json +++ b/server/package.json @@ -2,7 +2,7 @@ "name": "server", "version": "1.0.0", "description": "", - "main": "index.js", + "main": "dist/index.js", "type": "module", "scripts": { "test": "c8 mocha", @@ -54,6 +54,8 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/express": "5.0.3", + "@types/jsonwebtoken": "9.0.10", "c8": "10.1.3", "chai": "5.2.0", "eslint": "^9.17.0", diff --git a/server/src/config/controllers.js b/server/src/config/controllers.js index 5c54579eb..250d04c4b 100644 --- a/server/src/config/controllers.js +++ b/server/src/config/controllers.js @@ -15,6 +15,9 @@ import StatusPageController from "../controllers/v1/statusPageController.js"; import NotificationController from "../controllers/v1/notificationController.js"; import DiagnosticController from "../controllers/v1/diagnosticController.js"; +// V2 +import AuthV2Controller from "../controllers/v2/AuthController.js"; + export const initializeControllers = (services) => { const controllers = {}; const commonDependencies = createCommonDependencies(services.db, services.errorService, services.logger, services.stringService); @@ -62,5 +65,8 @@ export const initializeControllers = (services) => { diagnosticService: services.diagnosticService, }); + // V2 + controllers.authV2Controller = new AuthV2Controller(services.authV2Service, services.inviteV2Service); + return controllers; }; diff --git a/server/src/config/routes.js b/server/src/config/routes.js index 2ec93a2dc..5ef622f37 100644 --- a/server/src/config/routes.js +++ b/server/src/config/routes.js @@ -13,6 +13,8 @@ import LogRoutes from "../routes/v1/logRoutes.js"; import DiagnosticRoutes from "../routes/v1//diagnosticRoute.js"; import NotificationRoutes from "../routes/v1/notificationRoute.js"; +import authV2 from "../routes/v2/auth.js"; + export const setupRoutes = (app, controllers) => { const authRoutes = new AuthRoutes(controllers.authController); const monitorRoutes = new MonitorRoutes(controllers.monitorController); @@ -37,4 +39,12 @@ export const setupRoutes = (app, controllers) => { app.use("/api/v1/status-page", statusPageRoutes.getRouter()); app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter()); app.use("/api/v1/diagnostic", verifyJWT, diagnosticRoutes.getRouter()); + + // ******************* + // V2 Routes + // ******************* + + const v2AuthRoutes = new authV2(controllers.authV2Controller); + + app.use("/api/v2/auth", authApiLimiter, v2AuthRoutes.getRouter()); }; diff --git a/server/src/config/services.js b/server/src/config/services.js index 08c2525f3..b2b4ac430 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -48,8 +48,8 @@ import { ParseBoolean } from "../utils/utils.js"; // Models 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 { User } from "../db/models/auth/User.js"; +import { Invite } from "../db/models/auth/Invite.js"; import StatusPage from "../db/models/StatusPage.js"; import Team from "../db/models/Team.js"; import MaintenanceWindow from "../db/models/MaintenanceWindow.js"; @@ -68,6 +68,10 @@ import NotificationModule from "../db/mongo/modules/notificationModule.js"; import RecoveryModule from "../db/mongo/modules/recoveryModule.js"; import SettingsModule from "../db/mongo/modules/settingsModule.js"; +// v2 +import AuthV2Service from "../service/v2/business/AuthService.js"; +import InviteV2Service from "../service/v2/business/InviteService.js"; + export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); ServiceRegistry.instance = serviceRegistry; @@ -79,7 +83,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService // Create DB const checkModule = new CheckModule({ logger, Check, Monitor, User }); - const inviteModule = new InviteModule({ InviteToken, crypto, stringService }); + const inviteModule = new InviteModule({ Invite, crypto, stringService }); const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService }); const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean, stringService }); const maintenanceWindowModule = new MaintenanceWindowModule({ MaintenanceWindow }); @@ -208,6 +212,9 @@ export const initializeServices = async ({ logger, envSettings, settingsService games, }); + const authV2Service = new AuthV2Service(); + const inviteV2Service = new InviteV2Service(); + const services = { settingsService, translationService, @@ -227,6 +234,8 @@ export const initializeServices = async ({ logger, envSettings, settingsService monitorService, errorService, logger, + authV2Service, + inviteV2Service, }; Object.values(services).forEach((service) => { diff --git a/server/src/controllers/v2/AuthController.ts b/server/src/controllers/v2/AuthController.ts new file mode 100644 index 000000000..a9d1dd203 --- /dev/null +++ b/server/src/controllers/v2/AuthController.ts @@ -0,0 +1,142 @@ +import { Request, Response, NextFunction } from "express"; +import { encode, decode } from "../../utils/JWTUtils.js"; +import AuthService from "../../service/v2/business/AuthService.js"; +import ApiError from "../../utils/ApiError.js"; +import InviteService from "../../service/v2/business/InviteService.js"; +import { IInvite } from "../../db/models/index.js"; + +export interface IAuthController { + register(req: Request, res: Response, next: NextFunction): Promise; + registerWithInvite(req: Request, res: Response, next: NextFunction): Promise; + login(req: Request, res: Response, next: NextFunction): Promise; + logout(req: Request, res: Response): void; + me(req: Request, res: Response, next: NextFunction): void; +} + +class AuthController implements IAuthController { + private authService: AuthService; + private inviteService: InviteService; + constructor(authService: AuthService, inviteService: InviteService) { + this.authService = authService; + this.inviteService = inviteService; + } + + register = async (req: Request, res: Response, next: NextFunction) => { + try { + const { email, firstName, lastName, password } = req.body; + + if (!email || !firstName || !lastName || !password) { + throw new Error("Email, firstName, lastName, and password are required"); + } + + const result = await this.authService.register({ + email, + firstName, + lastName, + password, + }); + + const token = encode(result); + + res.cookie("token", token, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week + }); + + res.status(201).json({ + message: "User created successfully", + }); + } catch (error) { + next(error); + } + }; + + registerWithInvite = async (req: Request, res: Response, next: NextFunction) => { + try { + const token = req.params.token; + if (!token) { + throw new ApiError("Invite token is required", 400); + } + + const invite: IInvite = await this.inviteService.get(token); + + const { firstName, lastName, password } = req.body; + const email = invite?.email; + const roles = invite?.roles; + + if (!email || !firstName || !lastName || !password || !roles || roles.length === 0) { + throw new Error("Email, firstName, lastName, password, and roles are required"); + } + + const result = await this.authService.registerWithInvite({ + email, + firstName, + lastName, + password, + roles, + }); + + if (!result) { + throw new Error("Registration failed"); + } + + await this.inviteService.delete(invite._id.toString()); + + const jwt = encode(result); + + res.cookie("token", jwt, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week + }); + + res.status(201).json({ message: "User created successfully" }); + } catch (error) { + next(error); + } + }; + + login = async (req: Request, res: Response, next: NextFunction) => { + try { + const { email, password } = req.body; + // Validation + if (!email || !password) { + throw new ApiError("Email and password are required", 400); + } + const result = await this.authService.login({ email, password }); + + const token = encode(result); + + res.cookie("token", token, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week + }); + + res.status(200).json({ + message: "Login successful", + }); + } catch (error) { + next(error); + } + }; + + logout = (req: Request, res: Response) => { + res.clearCookie("token", { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + }); + res.status(200).json({ message: "Logout successful" }); + }; + + me = (req: Request, res: Response, next: NextFunction) => { + return res.status(200).json({ message: "OK" }); + }; +} + +export default AuthController; diff --git a/server/src/db/models/InviteToken.js b/server/src/db/models/InviteToken.js deleted file mode 100755 index 079600647..000000000 --- a/server/src/db/models/InviteToken.js +++ /dev/null @@ -1,35 +0,0 @@ -import mongoose from "mongoose"; -const InviteTokenSchema = mongoose.Schema( - { - email: { - type: String, - required: true, - unique: true, - }, - teamId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Team", - immutable: true, - required: true, - }, - role: { - type: Array, - required: true, - default: ["user"], - }, - token: { - type: String, - required: true, - }, - expiry: { - type: Date, - default: Date.now, - expires: 3600, - }, - }, - { - timestamps: true, - } -); - -export default mongoose.model("InviteToken", InviteTokenSchema); diff --git a/server/src/db/models/auth/Invite.ts b/server/src/db/models/auth/Invite.ts new file mode 100755 index 000000000..148ff65cb --- /dev/null +++ b/server/src/db/models/auth/Invite.ts @@ -0,0 +1,68 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; + +export interface IInvite extends Document { + // v1 + _id: Types.ObjectId; + email: string; + teamId: Types.ObjectId; + role: string[]; + token: string; + expiry: Date; + createdAt: Date; + updatedAt: Date; + + // v2 + tokenHash: string; + roles: Types.ObjectId[]; + createdBy: Types.ObjectId; + updatedBy: Types.ObjectId; +} + +const InviteSchema = new Schema( + { + email: { + type: String, + required: true, + unique: true, + trim: true, + lowercase: true, + }, + teamId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Team", + immutable: true, + required: true, + }, + role: { + type: [String], + required: true, + default: ["user"], + }, + token: { + type: String, + required: true, + }, + expiry: { + type: Date, + default: Date.now, + expires: 3600, + }, + + // v2 + roles: [ + { + type: Schema.Types.ObjectId, + ref: "Role", + required: true, + }, + ], + tokenHash: { type: String, required: true, unique: true }, + createdBy: { type: Schema.Types.ObjectId, ref: "User", required: true }, + updatedBy: { type: Schema.Types.ObjectId, ref: "User", required: true }, + }, + { + timestamps: true, + } +); + +export const Invite = mongoose.model("Invite", InviteSchema); diff --git a/server/src/db/models/auth/Role.ts b/server/src/db/models/auth/Role.ts new file mode 100644 index 000000000..e7ffb2f7d --- /dev/null +++ b/server/src/db/models/auth/Role.ts @@ -0,0 +1,45 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; + +export interface IRole extends Document { + _id: Types.ObjectId; + name: string; + description?: string; + permissions: string[]; + isActive: boolean; + createdAt: Date; + updatedAt: Date; +} + +const roleSchema = new Schema( + { + name: { + type: String, + required: true, + trim: true, + maxlength: 50, + }, + description: { + type: String, + trim: true, + maxlength: 200, + }, + + permissions: [ + { + type: String, + required: true, + }, + ], + isActive: { + type: Boolean, + default: true, + }, + }, + { + timestamps: true, + } +); + +roleSchema.index({ name: 1 }, { unique: true }); + +export const Role = mongoose.model("Role", roleSchema); diff --git a/server/src/db/models/User.js b/server/src/db/models/auth/User.ts similarity index 55% rename from server/src/db/models/User.js rename to server/src/db/models/auth/User.ts index 9368d98b1..82c058ea2 100755 --- a/server/src/db/models/User.js +++ b/server/src/db/models/auth/User.ts @@ -1,11 +1,47 @@ -import mongoose from "mongoose"; -import bcrypt from "bcryptjs"; -import logger from "../../utils/logger.js"; -import Monitor from "./Monitor.js"; -import Team from "./Team.js"; -import Notification from "./Notification.js"; +import mongoose, { Schema, Document, Types } from "mongoose"; -const UserSchema = mongoose.Schema( +import bcrypt from "bcryptjs"; +import logger from "../../../utils/logger.js"; +import Monitor from "../Monitor.js"; +import Team from "../Team.js"; +import Notification from "../Notification.js"; +import { subscribe } from "diagnostics_channel"; + +export const RoleTypes = ["user", "admin", "superadmin", "demo"] as const; +export type RoleType = (typeof RoleTypes)[number]; + +export interface ITokenizedUser { + sub: string; + roles: string[]; +} + +export interface IUser extends Document { + // V1 + _id: Types.ObjectId; + firstName: string; + lastName: string; + email: string; + password: string; + avatarImage: string; + profileImage: { + data: Buffer; + contentType: string; + }; + isActive: boolean; + isVerified: boolean; + role?: RoleType[]; + teamId: Types.ObjectId; + checkTTL: number; + createdAt: Date; + updatedAt: Date; + comparePassword: (submittedPassword: string) => Promise; + // V2 + roles: Types.ObjectId[]; + lastLoginAt?: Date; + version: number; +} + +const UserSchema = new Schema( { firstName: { type: String, @@ -22,7 +58,7 @@ const UserSchema = mongoose.Schema( }, password: { type: String, - required: true, + required: false, }, avatarImage: { type: String, @@ -41,8 +77,8 @@ const UserSchema = mongoose.Schema( }, role: { type: [String], - default: "user", - enum: ["user", "admin", "superadmin", "demo"], + default: ["user"], + enum: RoleTypes, }, teamId: { type: mongoose.Schema.Types.ObjectId, @@ -52,6 +88,22 @@ const UserSchema = mongoose.Schema( checkTTL: { type: Number, }, + + // v2 + roles: [ + { + type: Schema.Types.ObjectId, + ref: "Role", + default: [], + }, + ], + lastLoginAt: { + type: Date, + }, + version: { + type: Number, + default: 1, + }, }, { timestamps: true, @@ -69,7 +121,7 @@ UserSchema.pre("save", function (next) { UserSchema.pre("findOneAndUpdate", function (next) { const update = this.getUpdate(); - if ("password" in update) { + if (update && "password" in update) { const salt = bcrypt.genSaltSync(10); update.password = bcrypt.hashSync(update.password, salt); } @@ -92,15 +144,13 @@ UserSchema.pre("findOneAndDelete", async function (next) { } next(); } catch (error) { - next(error); + next(error as Error); } }); -UserSchema.methods.comparePassword = async function (submittedPassword) { +UserSchema.methods.comparePassword = async function (submittedPassword: string) { const res = await bcrypt.compare(submittedPassword, this.password); return res; }; -const User = mongoose.model("User", UserSchema); - -export default User; +export const User = mongoose.model("User", UserSchema); diff --git a/server/src/db/models/index.ts b/server/src/db/models/index.ts new file mode 100644 index 000000000..0969eafc6 --- /dev/null +++ b/server/src/db/models/index.ts @@ -0,0 +1,7 @@ +export { User } from "./auth/User.js"; +export type { IUser } from "./auth/User.js"; +export type { ITokenizedUser } from "./auth/User.js"; +export { Role } from "./auth/Role.js"; +export type { IRole } from "./auth/Role.js"; +export type { IInvite } from "./auth/Invite.js"; +export { Invite } from "./auth/Invite.js"; diff --git a/server/src/db/mongo/migration/0002_migrateUsers.ts b/server/src/db/mongo/migration/0002_migrateUsers.ts new file mode 100644 index 000000000..196ac6793 --- /dev/null +++ b/server/src/db/mongo/migration/0002_migrateUsers.ts @@ -0,0 +1,49 @@ +import { User, Role } from "../../models/index.js"; +import { DEFAULT_ROLES } from "../../../service/v2/business/AuthService.js"; +async function migrateUsers() { + try { + // Create roles if they don't exist + const roleCount = await Role.countDocuments(); + let roles; + if (roleCount === 0) { + const rolePromises = DEFAULT_ROLES.map((roleData) => + new Role({ + ...roleData, + }).save() + ); + roles = await Promise.all(rolePromises); + } else { + roles = await Role.find(); + } + + // Migrate users + const users = await User.find({ + $or: [{ version: { $exists: false } }, { version: { $lt: 2 } }], + }); + for (const user of users) { + for (const role of user.role || []) { + if (role === "superadmin") { + const superAdminRole = roles.find((role) => role.name === "SuperAdmin"); + user.roles.push(superAdminRole!._id); + } + if (role === "admin") { + const managerRole = roles.find((role) => role.name === "Manager"); + user.roles.push(managerRole!._id); + } + if (role === "user") { + const memberRole = roles.find((role) => role.name === "Member"); + user.roles.push(memberRole!._id); + } + } + user.version = 2; + await user.save(); + } + + return true; + } catch (err) { + console.error("Migration error:", err); + return false; + } +} + +export { migrateUsers }; diff --git a/server/src/db/mongo/migration/index.js b/server/src/db/mongo/migration/index.js index 4bf56895a..2a1da4148 100644 --- a/server/src/db/mongo/migration/index.js +++ b/server/src/db/mongo/migration/index.js @@ -1,7 +1,9 @@ import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshold.js"; +import { migrateUsers } from "./0002_migrateUsers.js"; const runMigrations = async () => { await migrateStatusWindowThreshold(); + await migrateUsers(); }; export { runMigrations }; diff --git a/server/src/db/mongo/modules/inviteModule.js b/server/src/db/mongo/modules/inviteModule.js index 444e51efd..513524fe5 100755 --- a/server/src/db/mongo/modules/inviteModule.js +++ b/server/src/db/mongo/modules/inviteModule.js @@ -1,17 +1,17 @@ const SERVICE_NAME = "inviteModule"; class InviteModule { - constructor({ InviteToken, crypto, stringService }) { - this.InviteToken = InviteToken; + constructor({ Invite, crypto, stringService }) { + this.Invite = Invite; this.crypto = crypto; this.stringService = stringService; } requestInviteToken = async (userData) => { try { - await this.InviteToken.deleteMany({ email: userData.email }); + await this.Invite.deleteMany({ email: userData.email }); userData.token = this.crypto.randomBytes(32).toString("hex"); - let inviteToken = new this.InviteToken(userData); + let inviteToken = new this.Invite(userData); await inviteToken.save(); return inviteToken; } catch (error) { @@ -23,7 +23,7 @@ class InviteModule { getInviteToken = async (token) => { try { - const invite = await this.InviteToken.findOne({ + const invite = await this.Invite.findOne({ token, }); if (invite === null) { @@ -38,7 +38,7 @@ class InviteModule { }; getInviteTokenAndDelete = async (token) => { try { - const invite = await this.InviteToken.findOneAndDelete({ + const invite = await this.Invite.findOneAndDelete({ token, }); if (invite === null) { diff --git a/server/src/middleware/v2/VerifyToken.ts b/server/src/middleware/v2/VerifyToken.ts new file mode 100644 index 000000000..7dabd51cc --- /dev/null +++ b/server/src/middleware/v2/VerifyToken.ts @@ -0,0 +1,21 @@ +import { Request, Response, NextFunction } from "express"; +import { decode } from "../../utils/JWTUtils.js"; +import ApiError from "../../utils/ApiError.js"; +const verifyToken = (req: Request, res: Response, next: NextFunction) => { + const token = req.cookies.token; + + if (!token) { + const error = new ApiError("No token provided", 401); + return next(error); + } + + try { + const decoded = decode(token); + req.user = decoded; + next(); + } catch (error) { + next(error); + } +}; + +export { verifyToken }; diff --git a/server/src/middleware/verifyTeamAccess.js b/server/src/middleware/verifyTeamAccess.js index 60647bdc2..a548cbf23 100644 --- a/server/src/middleware/verifyTeamAccess.js +++ b/server/src/middleware/verifyTeamAccess.js @@ -3,6 +3,8 @@ const SERVICE_NAME = "verifyTeamAccess"; const verifyTeamAccess = (Model, paramName) => { return async (req, res, next) => { try { + const user = req.user; + console.log(user); const documentId = req.params[paramName]; const doc = await Model.findById(documentId); diff --git a/server/src/routes/v2/auth.ts b/server/src/routes/v2/auth.ts new file mode 100644 index 000000000..5a1b8ad63 --- /dev/null +++ b/server/src/routes/v2/auth.ts @@ -0,0 +1,31 @@ +import { Router } from "express"; + +import express from "express"; +import AuthController from "../../controllers/v2/AuthController.js"; +import { verifyToken } from "../../middleware/v2/VerifyToken.js"; + +const router = express.Router(); + +class AuthRoutes { + private controller: AuthController; + private router: Router; + constructor(authController: AuthController) { + this.controller = authController; + this.router = Router(); + this.initRoutes(); + } + + initRoutes = () => { + this.router.post("/register", this.controller.register); + this.router.post("/register/invite/:token", this.controller.registerWithInvite); + this.router.post("/login", this.controller.login); + this.router.post("/logout", this.controller.logout); + this.router.get("/me", verifyToken, this.controller.me); + }; + + getRouter() { + return this.router; + } +} + +export default AuthRoutes; diff --git a/server/src/service/v2/business/AuthService.ts b/server/src/service/v2/business/AuthService.ts new file mode 100644 index 000000000..ae22b981d --- /dev/null +++ b/server/src/service/v2/business/AuthService.ts @@ -0,0 +1,141 @@ +import bcrypt from "bcryptjs"; +import { User, Role, ITokenizedUser } from "../../../db/models/index.js"; +import ApiError from "../../../utils/ApiError.js"; +import { Types } from "mongoose"; + +export const DEFAULT_ROLES = [ + { + name: "SuperAdmin", + description: "Super admin with all permissions", + permissions: ["*"], + isSystem: true, + }, + { + name: "Admin", + description: "Admin with full permissions", + permissions: ["monitor.*", "users.*"], + isSystem: true, + }, + { + name: "Manager", + description: "Can manage users", + permissions: ["users.create", "users.update", "monitors.*"], + isSystem: true, + }, + { + name: "Member", + description: "Basic team member", + permissions: ["users.update", "monitors.create", "monitors.view", "monitors.update"], + isSystem: true, + }, +]; + +export type RegisterData = { + email: string; + firstName: string; + lastName: string; + password: string; + roles?: Types.ObjectId[]; // Optional roles for invite-based registration +}; + +export type LoginData = { + email: string; + password: string; +}; + +export type AuthResult = ITokenizedUser; + +export interface IAuthService { + register(signupData: RegisterData): Promise; + registerWithInvite(signupData: RegisterData): Promise; + login(loginData: LoginData): Promise; +} + +class AuthService implements IAuthService { + constructor() {} + + async register(signupData: RegisterData): Promise { + const userCount = await User.countDocuments(); + + if (userCount > 0) { + throw new Error("Registration is closed. Please request an invite."); + } + + const { email, firstName, lastName, password } = signupData; + + // Create all default roles + const rolePromises = DEFAULT_ROLES.map((roleData) => + new Role({ + ...roleData, + }).save() + ); + const roles = await Promise.all(rolePromises); + + // Find admin role and assign to first user + const superAdminRole = roles.find((role) => role.name === "SuperAdmin"); + + const user = new User({ + email, + firstName, + lastName, + password, + roles: [superAdminRole!._id], + }); + + const savedUser = await user.save(); + return { + sub: savedUser._id.toString(), + roles: savedUser.roles.map((role) => role.toString()), + }; + } + + async registerWithInvite(signupData: RegisterData): Promise { + const { email, firstName, lastName, password, roles } = signupData; + + const user = new User({ + email, + firstName, + lastName, + password, + roles: roles || [], + }); + try { + const savedUser = await user.save(); + return { + sub: savedUser._id.toString(), + roles: savedUser.roles.map((role) => role.toString()), + }; + } catch (error: any) { + if (error?.code === 11000) { + const dupError = new ApiError("Email already in use", 409); + dupError.stack = error?.stack; + throw dupError; + } + throw error; + } + } + + async login(loginData: LoginData): Promise { + const { email, password } = loginData; + + // Find user by email + const user = await User.findOne({ email }); + + if (!user) { + throw new Error("Invalid email or password"); + } + + // Check password + const isPasswordValid = await bcrypt.compare(password, user.passwordHash); + if (!isPasswordValid) { + throw new Error("Invalid email or password"); + } + + return { + sub: user._id.toString(), + roles: user.roles.map((role) => role.toString()), + }; + } +} + +export default AuthService; diff --git a/server/src/service/v2/business/InviteService.ts b/server/src/service/v2/business/InviteService.ts new file mode 100644 index 000000000..f8bd9e2db --- /dev/null +++ b/server/src/service/v2/business/InviteService.ts @@ -0,0 +1,61 @@ +import crypto from "node:crypto"; +import { ITokenizedUser, IInvite, Invite } from "../../../db/models/index.js"; +import ApiError from "../../../utils/ApiError.js"; + +export interface IInviteService { + create: (tokenizedUser: ITokenizedUser, invite: IInvite) => Promise<{ token: string }>; + getAll: () => Promise; + get: (tokenHash: string) => Promise; + delete: (id: string) => Promise; +} + +class InviteService implements IInviteService { + constructor() {} + + create = async (tokenizedUser: ITokenizedUser, inviteData: IInvite) => { + const token = crypto.randomBytes(32).toString("hex"); + const tokenHash = crypto.createHash("sha256").update(token).digest("hex"); + try { + const invite = await Invite.create({ + ...inviteData, + tokenHash, + createdBy: tokenizedUser.sub, + updatedBy: tokenizedUser.sub, + }); + if (!invite) { + throw new ApiError("Failed to create invite", 500); + } + return { token }; + } catch (error: any) { + if (error?.code === 11000) { + const dupError = new ApiError("Invite with this email already exists", 409); + dupError.stack = error?.stack; + throw dupError; + } + throw error; + } + }; + + get = async (token: string) => { + const tokenHash = crypto.createHash("sha256").update(token).digest("hex"); + const invite = await Invite.findOne({ tokenHash }); + if (!invite) { + throw new ApiError("Invite not found", 404); + } + return invite; + }; + + getAll = async () => { + return Invite.find(); + }; + + delete = async (id: string) => { + const result = await Invite.deleteOne({ _id: id }); + if (!result.deletedCount) { + throw new ApiError("Invite not found", 404); + } + return result.deletedCount === 1; + }; +} + +export default InviteService; diff --git a/server/src/utils/ApiError.ts b/server/src/utils/ApiError.ts new file mode 100644 index 000000000..313d01c2a --- /dev/null +++ b/server/src/utils/ApiError.ts @@ -0,0 +1,15 @@ +class ApiError extends Error { + public status: number; + + constructor(message: string, status: number = 500) { + super(message); + this.status = status; + this.name = this.constructor.name; + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor); + } + } +} + +export default ApiError; diff --git a/server/src/utils/JWTUtils.ts b/server/src/utils/JWTUtils.ts new file mode 100644 index 000000000..4eebb78a0 --- /dev/null +++ b/server/src/utils/JWTUtils.ts @@ -0,0 +1,22 @@ +import jwt from "jsonwebtoken"; +import { AuthResult } from "../service/v2/business/AuthService.js"; + +const encode = (data: AuthResult): string => { + const secret = process.env.JWT_SECRET; + if (!secret) { + throw new Error("JWT_SECRET is not defined"); + } + const token = jwt.sign(data, secret, { expiresIn: "99d" }); + return token; +}; + +const decode = (token: string): AuthResult => { + const secret = process.env.JWT_SECRET; + if (!secret) { + throw new Error("JWT_SECRET is not defined"); + } + const decoded = jwt.verify(token, secret) as AuthResult; + return decoded; +}; + +export { encode, decode }; From 003039400896c85eb4af8c2210de4b4e40b5ed78 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Sep 2025 11:34:25 -0700 Subject: [PATCH 006/103] add migrations table --- server/src/db/models/index.ts | 2 ++ server/src/db/models/migration/Migration.ts | 34 +++++++++++++++++++ ...s => 0001_migrateStatusWindowThreshold.ts} | 1 + .../db/mongo/migration/0002_migrateUsers.ts | 13 +++++-- server/src/db/mongo/migration/index.js | 9 ----- server/src/db/mongo/migration/index.ts | 25 ++++++++++++++ 6 files changed, 72 insertions(+), 12 deletions(-) create mode 100644 server/src/db/models/migration/Migration.ts rename server/src/db/mongo/migration/{0001_migrateStatusWindowThreshold.js => 0001_migrateStatusWindowThreshold.ts} (90%) delete mode 100644 server/src/db/mongo/migration/index.js create mode 100644 server/src/db/mongo/migration/index.ts diff --git a/server/src/db/models/index.ts b/server/src/db/models/index.ts index 0969eafc6..537633bfd 100644 --- a/server/src/db/models/index.ts +++ b/server/src/db/models/index.ts @@ -5,3 +5,5 @@ export { Role } from "./auth/Role.js"; export type { IRole } from "./auth/Role.js"; export type { IInvite } from "./auth/Invite.js"; export { Invite } from "./auth/Invite.js"; +export { Migration } from "./migration/Migration.js"; +export type { IMigration } from "./migration/Migration.js"; diff --git a/server/src/db/models/migration/Migration.ts b/server/src/db/models/migration/Migration.ts new file mode 100644 index 000000000..cf76bb48e --- /dev/null +++ b/server/src/db/models/migration/Migration.ts @@ -0,0 +1,34 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; + +export interface IMigration extends Document { + _id: Types.ObjectId; + name: string; + runAt: Date; + success: boolean; + createdAt: Date; + updatedAt: Date; +} + +const migrationSchema = new Schema( + { + name: { + type: String, + required: true, + }, + runAt: { + type: Date, + required: false, + }, + success: { + type: Boolean, + required: false, + }, + }, + { + timestamps: true, + } +); + +migrationSchema.index({ name: 1 }, { unique: true }); + +export const Migration = mongoose.model("Migration", migrationSchema); diff --git a/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js b/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts similarity index 90% rename from server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js rename to server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts index 8f6988de4..b44487e87 100644 --- a/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js +++ b/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts @@ -1,4 +1,5 @@ import Monitor from "../../models/Monitor.js"; +export const MIGRATION_NAME = "0001_migrateStatusWindowThreshold"; async function migrateStatusWindowThreshold() { try { const monitors = await Monitor.find({ statusWindowThreshold: { $lt: 1 } }); diff --git a/server/src/db/mongo/migration/0002_migrateUsers.ts b/server/src/db/mongo/migration/0002_migrateUsers.ts index 196ac6793..a7cdb76fc 100644 --- a/server/src/db/mongo/migration/0002_migrateUsers.ts +++ b/server/src/db/mongo/migration/0002_migrateUsers.ts @@ -1,5 +1,8 @@ import { User, Role } from "../../models/index.js"; +import { Types } from "mongoose"; + import { DEFAULT_ROLES } from "../../../service/v2/business/AuthService.js"; +export const MIGRATION_NAME = "0002_migrateUsers"; async function migrateUsers() { try { // Create roles if they don't exist @@ -21,20 +24,24 @@ async function migrateUsers() { $or: [{ version: { $exists: false } }, { version: { $lt: 2 } }], }); for (const user of users) { + const newRoleIds: Types.ObjectId[] = []; + for (const role of user.role || []) { if (role === "superadmin") { const superAdminRole = roles.find((role) => role.name === "SuperAdmin"); - user.roles.push(superAdminRole!._id); + newRoleIds.push(superAdminRole!._id); } if (role === "admin") { const managerRole = roles.find((role) => role.name === "Manager"); - user.roles.push(managerRole!._id); + newRoleIds.push(managerRole!._id); } if (role === "user") { const memberRole = roles.find((role) => role.name === "Member"); - user.roles.push(memberRole!._id); + newRoleIds.push(memberRole!._id); } } + const merged = [...new Set([...user.roles, ...newRoleIds])]; + user.roles = merged; user.version = 2; await user.save(); } diff --git a/server/src/db/mongo/migration/index.js b/server/src/db/mongo/migration/index.js deleted file mode 100644 index 2a1da4148..000000000 --- a/server/src/db/mongo/migration/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshold.js"; -import { migrateUsers } from "./0002_migrateUsers.js"; - -const runMigrations = async () => { - await migrateStatusWindowThreshold(); - await migrateUsers(); -}; - -export { runMigrations }; diff --git a/server/src/db/mongo/migration/index.ts b/server/src/db/mongo/migration/index.ts new file mode 100644 index 000000000..293e0905f --- /dev/null +++ b/server/src/db/mongo/migration/index.ts @@ -0,0 +1,25 @@ +import { migrateStatusWindowThreshold, MIGRATION_NAME as MIGRATION_0001 } from "./0001_migrateStatusWindowThreshold.js"; +import { migrateUsers, MIGRATION_NAME as MIGRATION_0002 } from "./0002_migrateUsers.js"; +import { Migration, IMigration } from "../../models/index.js"; + +const runMigrations = async () => { + const migrations = [ + { name: MIGRATION_0001, migration: migrateStatusWindowThreshold }, + { name: MIGRATION_0002, migration: migrateUsers }, + ]; + + const migrationNames = migrations.map((m) => m.name); + + const appliedMigrations = await Migration.find({ name: { $in: migrationNames }, success: true }); + + const migrationsToRun = migrations.filter((m) => !appliedMigrations.some((am: IMigration) => am.name === m.name)); + + for (const { name, migration } of migrationsToRun) { + console.log(`Running migration: ${name}`); + const success = await migration(); + await Migration.updateOne({ name }, { $set: { runAt: new Date(), success } }, { upsert: true }); + console.log(`Migration ${name} completed with status: ${success ? "success" : "failure"}`); + } +}; + +export { runMigrations }; From f945a2701151f9b9cd7337f43550973e345af82d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Sep 2025 11:59:02 -0700 Subject: [PATCH 007/103] move v1 to v1 dir, add build workflow --- .github/workflows/build-check.yml | 23 +++++++++ server/src/app.js | 10 ++-- server/src/config/routes.js | 4 +- server/src/config/services.js | 36 +++++++------- server/src/controllers/v1/baseController.js | 2 +- server/src/index.js | 2 +- .../src/middleware/{ => v1}/handleErrors.js | 6 +-- server/src/middleware/{ => v1}/isAllowed.js | 6 +-- .../middleware/{ => v1}/languageMiddleware.js | 2 +- server/src/middleware/{ => v1}/rateLimiter.js | 0 .../middleware/{ => v1}/responseHandler.js | 0 .../src/middleware/{ => v1}/sanitization.js | 0 server/src/middleware/{ => v1}/verifyJWT.js | 6 +-- .../middleware/{ => v1}/verifyOwnership.js | 6 +-- .../middleware/{ => v1}/verifySuperAdmin.js | 2 +- .../middleware/{ => v1}/verifyTeamAccess.js | 0 server/src/routes/v1/authRoute.js | 4 +- server/src/routes/v1/checkRoute.js | 2 +- server/src/routes/v1/diagnosticRoute.js | 4 +- server/src/routes/v1/inviteRoute.js | 4 +- server/src/routes/v1/logRoutes.js | 2 +- server/src/routes/v1/monitorRoute.js | 2 +- server/src/routes/v1/queueRoute.js | 2 +- server/src/routes/v1/settingsRoute.js | 2 +- server/src/routes/v1/statusPageRoute.js | 2 +- .../service/{ => v1}/business/checkService.js | 0 .../{ => v1}/business/diagnosticService.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 | 10 ++-- .../{ => v1}/infrastructure/bufferService.js | 0 .../{ => v1}/infrastructure/emailService.js | 0 .../{ => v1}/infrastructure/errorService.js | 0 .../{ => v1}/infrastructure/networkService.js | 0 .../infrastructure/notificationService.js | 0 .../infrastructure/notificationUtils.js | 0 .../{ => v1}/infrastructure/statusService.js | 2 +- .../{ => v1}/system/serviceRegistry.js | 0 .../{ => v1}/system/settingsService.js | 0 .../service/{ => v1}/system/stringService.js | 0 .../{ => v1}/system/translationService.js | 0 server/src/service/v2/business/AuthService.ts | 47 +++++++++++++++++-- server/src/types/express.d.ts | 10 ++++ 46 files changed, 136 insertions(+), 64 deletions(-) create mode 100644 .github/workflows/build-check.yml rename server/src/middleware/{ => v1}/handleErrors.js (71%) rename server/src/middleware/{ => v1}/isAllowed.js (87%) rename server/src/middleware/{ => v1}/languageMiddleware.js (93%) rename server/src/middleware/{ => v1}/rateLimiter.js (100%) rename server/src/middleware/{ => v1}/responseHandler.js (100%) rename server/src/middleware/{ => v1}/sanitization.js (100%) rename server/src/middleware/{ => v1}/verifyJWT.js (87%) rename server/src/middleware/{ => v1}/verifyOwnership.js (88%) rename server/src/middleware/{ => v1}/verifySuperAdmin.js (97%) rename server/src/middleware/{ => v1}/verifyTeamAccess.js (100%) rename server/src/service/{ => v1}/business/checkService.js (100%) rename server/src/service/{ => v1}/business/diagnosticService.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 (90%) 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/notificationService.js (100%) rename server/src/service/{ => v1}/infrastructure/notificationUtils.js (100%) rename server/src/service/{ => v1}/infrastructure/statusService.js (99%) 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/express.d.ts diff --git a/.github/workflows/build-check.yml b/.github/workflows/build-check.yml new file mode 100644 index 000000000..206a652d5 --- /dev/null +++ b/.github/workflows/build-check.yml @@ -0,0 +1,23 @@ +name: Build Check (Server) +on: + pull_request: + workflow_dispatch: + +jobs: + build-server: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install server dependencies + working-directory: server + run: npm install + + - name: Check server formatting + working-directory: server + run: npm run build diff --git a/server/src/app.js b/server/src/app.js index 9cabda6b6..253baee88 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -1,16 +1,16 @@ import express from "express"; import path from "path"; -import { responseHandler } from "./middleware/responseHandler.js"; +import { responseHandler } from "./middleware/v1/responseHandler.js"; import cors from "cors"; import helmet from "helmet"; import compression from "compression"; import cookieParser from "cookie-parser"; -import languageMiddleware from "./middleware/languageMiddleware.js"; +import languageMiddleware from "./middleware/v1/languageMiddleware.js"; import swaggerUi from "swagger-ui-express"; -import { handleErrors } from "./middleware/handleErrors.js"; +import { handleErrors } from "./middleware/v1/handleErrors.js"; import { setupRoutes } from "./config/routes.js"; -import { generalApiLimiter } from "./middleware/rateLimiter.js"; -import { sanitizeBody, sanitizeQuery } from "./middleware/sanitization.js"; +import { generalApiLimiter } from "./middleware/v1/rateLimiter.js"; +import { sanitizeBody, sanitizeQuery } from "./middleware/v1/sanitization.js"; export const createApp = ({ services, controllers, envSettings, frontendPath, openApiSpec }) => { const allowedOrigin = envSettings.clientHost; diff --git a/server/src/config/routes.js b/server/src/config/routes.js index 5ef622f37..2068cf021 100644 --- a/server/src/config/routes.js +++ b/server/src/config/routes.js @@ -1,5 +1,5 @@ -import { verifyJWT } from "../middleware/verifyJWT.js"; -import { authApiLimiter } from "../middleware/rateLimiter.js"; +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"; diff --git a/server/src/config/services.js b/server/src/config/services.js index b2b4ac430..c5ab7fa02 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -1,22 +1,22 @@ -import ServiceRegistry from "../service/system/serviceRegistry.js"; -import TranslationService from "../service/system/translationService.js"; -import StringService from "../service/system/stringService.js"; +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/mongo/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 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 papaparse from "papaparse"; import axios from "axios"; import got from "got"; diff --git a/server/src/controllers/v1/baseController.js b/server/src/controllers/v1/baseController.js index 314905c82..e8612cec5 100644 --- a/server/src/controllers/v1/baseController.js +++ b/server/src/controllers/v1/baseController.js @@ -1,4 +1,4 @@ -import { AppError } from "../../service/infrastructure/errorService.js"; +import { AppError } from "../../service/v1/infrastructure/errorService.js"; export const createCommonDependencies = (db, errorService, logger, stringService) => { return { diff --git a/server/src/index.js b/server/src/index.js index d1ff649eb..a4e477d5f 100755 --- a/server/src/index.js +++ b/server/src/index.js @@ -7,7 +7,7 @@ import path from "path"; import fs from "fs"; import Logger from "./utils/logger.js"; -import SettingsService from "./service/system/settingsService.js"; +import SettingsService from "./service/v1/system/settingsService.js"; import AppSettings from "./db/models/AppSettings.js"; const SERVICE_NAME = "Server"; diff --git a/server/src/middleware/handleErrors.js b/server/src/middleware/v1/handleErrors.js similarity index 71% rename from server/src/middleware/handleErrors.js rename to server/src/middleware/v1/handleErrors.js index b0ded5ab7..43671fbe2 100755 --- a/server/src/middleware/handleErrors.js +++ b/server/src/middleware/v1/handleErrors.js @@ -1,6 +1,6 @@ -import { logger } from "../utils/logger.js"; -import ServiceRegistry from "../service/system/serviceRegistry.js"; -import StringService from "../service/system/stringService.js"; +import { logger } from "../../utils/logger.js"; +import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; +import StringService from "../../service/v1/system/stringService.js"; const handleErrors = (error, req, res, next) => { const status = error.status || 500; diff --git a/server/src/middleware/isAllowed.js b/server/src/middleware/v1/isAllowed.js similarity index 87% rename from server/src/middleware/isAllowed.js rename to server/src/middleware/v1/isAllowed.js index 5d61974be..cc240f531 100755 --- a/server/src/middleware/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/system/serviceRegistry.js"; -import StringService from "../service/system/stringService.js"; -import SettingsService from "../service/system/settingsService.js"; +import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; +import StringService from "../../service/v1/system/stringService.js"; +import SettingsService from "../../service/v1/system/settingsService.js"; const isAllowed = (allowedRoles) => { return (req, res, next) => { diff --git a/server/src/middleware/languageMiddleware.js b/server/src/middleware/v1/languageMiddleware.js similarity index 93% rename from server/src/middleware/languageMiddleware.js rename to server/src/middleware/v1/languageMiddleware.js index 6293dc837..819858a21 100755 --- a/server/src/middleware/languageMiddleware.js +++ b/server/src/middleware/v1/languageMiddleware.js @@ -1,4 +1,4 @@ -import { logger } from "../utils/logger.js"; +import { logger } from "../../utils/logger.js"; const languageMiddleware = (stringService, translationService) => async (req, res, next) => { try { diff --git a/server/src/middleware/rateLimiter.js b/server/src/middleware/v1/rateLimiter.js similarity index 100% rename from server/src/middleware/rateLimiter.js rename to server/src/middleware/v1/rateLimiter.js diff --git a/server/src/middleware/responseHandler.js b/server/src/middleware/v1/responseHandler.js similarity index 100% rename from server/src/middleware/responseHandler.js rename to server/src/middleware/v1/responseHandler.js diff --git a/server/src/middleware/sanitization.js b/server/src/middleware/v1/sanitization.js similarity index 100% rename from server/src/middleware/sanitization.js rename to server/src/middleware/v1/sanitization.js diff --git a/server/src/middleware/verifyJWT.js b/server/src/middleware/v1/verifyJWT.js similarity index 87% rename from server/src/middleware/verifyJWT.js rename to server/src/middleware/v1/verifyJWT.js index d01db0a97..b57391dfd 100755 --- a/server/src/middleware/verifyJWT.js +++ b/server/src/middleware/v1/verifyJWT.js @@ -1,7 +1,7 @@ import jwt from "jsonwebtoken"; -import ServiceRegistry from "../service/system/serviceRegistry.js"; -import SettingsService from "../service/system/settingsService.js"; -import StringService from "../service/system/stringService.js"; +import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; +import SettingsService from "../../service/v1/system/settingsService.js"; +import StringService from "../../service/v1/system/stringService.js"; const SERVICE_NAME = "verifyJWT"; const TOKEN_PREFIX = "Bearer "; diff --git a/server/src/middleware/verifyOwnership.js b/server/src/middleware/v1/verifyOwnership.js similarity index 88% rename from server/src/middleware/verifyOwnership.js rename to server/src/middleware/v1/verifyOwnership.js index 4431b0c26..741fb6436 100755 --- a/server/src/middleware/verifyOwnership.js +++ b/server/src/middleware/v1/verifyOwnership.js @@ -1,6 +1,6 @@ -import { logger } from "../utils/logger.js"; -import ServiceRegistry from "../service/system/serviceRegistry.js"; -import StringService from "../service/system/stringService.js"; +import { logger } from "../../utils/logger.js"; +import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; +import StringService from "../../service/v1/system/stringService.js"; import { ObjectId } from "mongodb"; const SERVICE_NAME = "verifyOwnership"; diff --git a/server/src/middleware/verifySuperAdmin.js b/server/src/middleware/v1/verifySuperAdmin.js similarity index 97% rename from server/src/middleware/verifySuperAdmin.js rename to server/src/middleware/v1/verifySuperAdmin.js index a5a93eede..41ac0a3e0 100755 --- a/server/src/middleware/verifySuperAdmin.js +++ b/server/src/middleware/v1/verifySuperAdmin.js @@ -1,5 +1,5 @@ const jwt = require("jsonwebtoken"); -const logger = require("../utils/logger"); +const logger = require("../../utils/logger.js"); const SERVICE_NAME = "verifyAdmin"; const TOKEN_PREFIX = "Bearer "; import ServiceRegistry from "../service/serviceRegistry.js"; diff --git a/server/src/middleware/verifyTeamAccess.js b/server/src/middleware/v1/verifyTeamAccess.js similarity index 100% rename from server/src/middleware/verifyTeamAccess.js rename to server/src/middleware/v1/verifyTeamAccess.js diff --git a/server/src/routes/v1/authRoute.js b/server/src/routes/v1/authRoute.js index f69f94f43..d9b036b05 100755 --- a/server/src/routes/v1/authRoute.js +++ b/server/src/routes/v1/authRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/verifyJWT.js"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; import multer from "multer"; const upload = multer(); diff --git a/server/src/routes/v1/checkRoute.js b/server/src/routes/v1/checkRoute.js index 673fe51e0..78f6b8a42 100755 --- a/server/src/routes/v1/checkRoute.js +++ b/server/src/routes/v1/checkRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class CheckRoutes { constructor(checkController) { diff --git a/server/src/routes/v1/diagnosticRoute.js b/server/src/routes/v1/diagnosticRoute.js index 1b7cc06be..2cf481e08 100755 --- a/server/src/routes/v1/diagnosticRoute.js +++ b/server/src/routes/v1/diagnosticRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/verifyJWT.js"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class DiagnosticRoutes { constructor(diagnosticController) { diff --git a/server/src/routes/v1/inviteRoute.js b/server/src/routes/v1/inviteRoute.js index 74fcfeb91..a47550536 100755 --- a/server/src/routes/v1/inviteRoute.js +++ b/server/src/routes/v1/inviteRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/verifyJWT.js"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class InviteRoutes { constructor(inviteController) { diff --git a/server/src/routes/v1/logRoutes.js b/server/src/routes/v1/logRoutes.js index feabe669a..43da8b75a 100755 --- a/server/src/routes/v1/logRoutes.js +++ b/server/src/routes/v1/logRoutes.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class LogRoutes { constructor(logController) { this.router = Router(); diff --git a/server/src/routes/v1/monitorRoute.js b/server/src/routes/v1/monitorRoute.js index dde72e256..107964f6b 100755 --- a/server/src/routes/v1/monitorRoute.js +++ b/server/src/routes/v1/monitorRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; import multer from "multer"; import { fetchMonitorCertificate } from "../../controllers/v1/controllerUtils.js"; const upload = multer({ diff --git a/server/src/routes/v1/queueRoute.js b/server/src/routes/v1/queueRoute.js index 59e1cb744..c6cfa05da 100755 --- a/server/src/routes/v1/queueRoute.js +++ b/server/src/routes/v1/queueRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class QueueRoutes { constructor(queueController) { this.router = Router(); diff --git a/server/src/routes/v1/settingsRoute.js b/server/src/routes/v1/settingsRoute.js index 76556e923..e88186af2 100755 --- a/server/src/routes/v1/settingsRoute.js +++ b/server/src/routes/v1/settingsRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class SettingsRoutes { constructor(settingsController) { diff --git a/server/src/routes/v1/statusPageRoute.js b/server/src/routes/v1/statusPageRoute.js index 8300066a8..2c3b0b265 100755 --- a/server/src/routes/v1/statusPageRoute.js +++ b/server/src/routes/v1/statusPageRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/verifyJWT.js"; +import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; import multer from "multer"; const upload = multer(); diff --git a/server/src/service/business/checkService.js b/server/src/service/v1/business/checkService.js similarity index 100% rename from server/src/service/business/checkService.js rename to server/src/service/v1/business/checkService.js diff --git a/server/src/service/business/diagnosticService.js b/server/src/service/v1/business/diagnosticService.js similarity index 100% rename from server/src/service/business/diagnosticService.js rename to server/src/service/v1/business/diagnosticService.js diff --git a/server/src/service/business/inviteService.js b/server/src/service/v1/business/inviteService.js similarity index 100% rename from server/src/service/business/inviteService.js rename to server/src/service/v1/business/inviteService.js diff --git a/server/src/service/business/maintenanceWindowService.js b/server/src/service/v1/business/maintenanceWindowService.js similarity index 100% rename from server/src/service/business/maintenanceWindowService.js rename to server/src/service/v1/business/maintenanceWindowService.js diff --git a/server/src/service/business/monitorService.js b/server/src/service/v1/business/monitorService.js similarity index 99% rename from server/src/service/business/monitorService.js rename to server/src/service/v1/business/monitorService.js index 637870b71..061234102 100644 --- a/server/src/service/business/monitorService.js +++ b/server/src/service/v1/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/business/userService.js b/server/src/service/v1/business/userService.js similarity index 100% rename from server/src/service/business/userService.js rename to server/src/service/v1/business/userService.js diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js b/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js similarity index 100% rename from server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js rename to server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js b/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js similarity index 90% rename from server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js rename to server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js index b348e2534..2833a59f5 100644 --- a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js +++ b/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js @@ -5,11 +5,11 @@ class SuperSimpleQueueHelper { /** * @param {{ - * db: import("../database").Database, - * logger: import("../logger").Logger, - * networkService: import("../networkService").NetworkService, - * statusService: import("../statusService").StatusService, - * notificationService: import("../notificationService").NotificationService + * db: import("../database.js").Database, + * logger: import("../logger.js").Logger, + * networkService: import("../networkService.js").NetworkService, + * statusService: import("../statusService.js").StatusService, + * notificationService: import("../notificationService.js").NotificationService * }} */ constructor({ db, logger, networkService, statusService, notificationService }) { diff --git a/server/src/service/infrastructure/bufferService.js b/server/src/service/v1/infrastructure/bufferService.js similarity index 100% rename from server/src/service/infrastructure/bufferService.js rename to server/src/service/v1/infrastructure/bufferService.js diff --git a/server/src/service/infrastructure/emailService.js b/server/src/service/v1/infrastructure/emailService.js similarity index 100% rename from server/src/service/infrastructure/emailService.js rename to server/src/service/v1/infrastructure/emailService.js diff --git a/server/src/service/infrastructure/errorService.js b/server/src/service/v1/infrastructure/errorService.js similarity index 100% rename from server/src/service/infrastructure/errorService.js rename to server/src/service/v1/infrastructure/errorService.js diff --git a/server/src/service/infrastructure/networkService.js b/server/src/service/v1/infrastructure/networkService.js similarity index 100% rename from server/src/service/infrastructure/networkService.js rename to server/src/service/v1/infrastructure/networkService.js diff --git a/server/src/service/infrastructure/notificationService.js b/server/src/service/v1/infrastructure/notificationService.js similarity index 100% rename from server/src/service/infrastructure/notificationService.js rename to server/src/service/v1/infrastructure/notificationService.js diff --git a/server/src/service/infrastructure/notificationUtils.js b/server/src/service/v1/infrastructure/notificationUtils.js similarity index 100% rename from server/src/service/infrastructure/notificationUtils.js rename to server/src/service/v1/infrastructure/notificationUtils.js diff --git a/server/src/service/infrastructure/statusService.js b/server/src/service/v1/infrastructure/statusService.js similarity index 99% rename from server/src/service/infrastructure/statusService.js rename to server/src/service/v1/infrastructure/statusService.js index 72fa359d7..983541cd9 100755 --- a/server/src/service/infrastructure/statusService.js +++ b/server/src/service/v1/infrastructure/statusService.js @@ -1,4 +1,4 @@ -import MonitorStats from "../../db/models/MonitorStats.js"; +import MonitorStats from "../../../db/models/MonitorStats.js"; const SERVICE_NAME = "StatusService"; class StatusService { diff --git a/server/src/service/system/serviceRegistry.js b/server/src/service/v1/system/serviceRegistry.js similarity index 100% rename from server/src/service/system/serviceRegistry.js rename to server/src/service/v1/system/serviceRegistry.js diff --git a/server/src/service/system/settingsService.js b/server/src/service/v1/system/settingsService.js similarity index 100% rename from server/src/service/system/settingsService.js rename to server/src/service/v1/system/settingsService.js diff --git a/server/src/service/system/stringService.js b/server/src/service/v1/system/stringService.js similarity index 100% rename from server/src/service/system/stringService.js rename to server/src/service/v1/system/stringService.js diff --git a/server/src/service/system/translationService.js b/server/src/service/v1/system/translationService.js similarity index 100% rename from server/src/service/system/translationService.js rename to server/src/service/v1/system/translationService.js diff --git a/server/src/service/v2/business/AuthService.ts b/server/src/service/v2/business/AuthService.ts index ae22b981d..52f586dbf 100644 --- a/server/src/service/v2/business/AuthService.ts +++ b/server/src/service/v2/business/AuthService.ts @@ -3,6 +3,45 @@ import { User, Role, ITokenizedUser } from "../../../db/models/index.js"; import ApiError from "../../../utils/ApiError.js"; import { Types } from "mongoose"; +export const PERMISSIONS = { + users: { + all: "users.*", + create: "users.create", + view: "users.view", + update: "users.update", + delete: "users.delete", + }, + + monitors: { + all: "monitors.*", + create: "monitors.create", + view: "monitors.view", + update: "monitors.update", + delete: "monitors.delete", + }, + notifications: { + all: "notifications.*", + create: "notifications.create", + view: "notifications.view", + update: "notifications.update", + delete: "notifications.delete", + }, + checks: { + all: "checks.*", + create: "checks.create", + view: "checks.view", + update: "checks.update", + delete: "checks.delete", + }, + statusPages: { + all: "statusPages.*", + create: "statusPages.create", + view: "statusPages.view", + update: "statusPages.update", + delete: "statusPages.delete", + }, +}; + export const DEFAULT_ROLES = [ { name: "SuperAdmin", @@ -13,19 +52,19 @@ export const DEFAULT_ROLES = [ { name: "Admin", description: "Admin with full permissions", - permissions: ["monitor.*", "users.*"], + permissions: [PERMISSIONS.monitors.all, PERMISSIONS.users.all], isSystem: true, }, { name: "Manager", description: "Can manage users", - permissions: ["users.create", "users.update", "monitors.*"], + permissions: [PERMISSIONS.users.create, PERMISSIONS.users.update, PERMISSIONS.monitors.all], isSystem: true, }, { name: "Member", description: "Basic team member", - permissions: ["users.update", "monitors.create", "monitors.view", "monitors.update"], + permissions: [PERMISSIONS.users.update, PERMISSIONS.monitors.create, PERMISSIONS.monitors.view, PERMISSIONS.monitors.update], isSystem: true, }, ]; @@ -126,7 +165,7 @@ class AuthService implements IAuthService { } // Check password - const isPasswordValid = await bcrypt.compare(password, user.passwordHash); + const isPasswordValid = await bcrypt.compare(password, user.password); if (!isPasswordValid) { throw new Error("Invalid email or password"); } diff --git a/server/src/types/express.d.ts b/server/src/types/express.d.ts new file mode 100644 index 000000000..7c78a053c --- /dev/null +++ b/server/src/types/express.d.ts @@ -0,0 +1,10 @@ +import { ITokenizedUser } from "../db/models/index.ts"; + +declare global { + namespace Express { + interface Request { + user?: ITokenizedUser; + resource?: any; + } + } +} From 003d419a6453167b81b6037b8ae6ae3f004f10ca Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Sep 2025 14:05:16 -0700 Subject: [PATCH 008/103] Revert "Merge pull request #2974 from bluewave-labs/fix/cleanup" This reverts commit 7368cb2567c0e992cfebe05efddcda110ff16e34, reversing changes made to 72882fded9b57b1d0f76a521e12afea9f63135ce. --- .github/workflows/build-check.yml | 23 --------- server/src/app.js | 10 ++-- server/src/config/routes.js | 4 +- server/src/config/services.js | 36 +++++++------- server/src/controllers/v1/baseController.js | 2 +- server/src/index.js | 2 +- .../src/middleware/{v1 => }/handleErrors.js | 6 +-- server/src/middleware/{v1 => }/isAllowed.js | 6 +-- .../middleware/{v1 => }/languageMiddleware.js | 2 +- server/src/middleware/{v1 => }/rateLimiter.js | 0 .../middleware/{v1 => }/responseHandler.js | 0 .../src/middleware/{v1 => }/sanitization.js | 0 server/src/middleware/{v1 => }/verifyJWT.js | 6 +-- .../middleware/{v1 => }/verifyOwnership.js | 6 +-- .../middleware/{v1 => }/verifySuperAdmin.js | 2 +- .../middleware/{v1 => }/verifyTeamAccess.js | 0 server/src/routes/v1/authRoute.js | 4 +- server/src/routes/v1/checkRoute.js | 2 +- server/src/routes/v1/diagnosticRoute.js | 4 +- server/src/routes/v1/inviteRoute.js | 4 +- server/src/routes/v1/logRoutes.js | 2 +- server/src/routes/v1/monitorRoute.js | 2 +- server/src/routes/v1/queueRoute.js | 2 +- server/src/routes/v1/settingsRoute.js | 2 +- server/src/routes/v1/statusPageRoute.js | 2 +- .../service/{v1 => }/business/checkService.js | 0 .../{v1 => }/business/diagnosticService.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 | 10 ++-- .../{v1 => }/infrastructure/bufferService.js | 0 .../{v1 => }/infrastructure/emailService.js | 0 .../{v1 => }/infrastructure/errorService.js | 0 .../{v1 => }/infrastructure/networkService.js | 0 .../infrastructure/notificationService.js | 0 .../infrastructure/notificationUtils.js | 0 .../{v1 => }/infrastructure/statusService.js | 2 +- .../{v1 => }/system/serviceRegistry.js | 0 .../{v1 => }/system/settingsService.js | 0 .../service/{v1 => }/system/stringService.js | 0 .../{v1 => }/system/translationService.js | 0 server/src/service/v2/business/AuthService.ts | 47 ++----------------- server/src/types/express.d.ts | 10 ---- 46 files changed, 64 insertions(+), 136 deletions(-) delete mode 100644 .github/workflows/build-check.yml rename server/src/middleware/{v1 => }/handleErrors.js (71%) rename server/src/middleware/{v1 => }/isAllowed.js (87%) rename server/src/middleware/{v1 => }/languageMiddleware.js (93%) rename server/src/middleware/{v1 => }/rateLimiter.js (100%) rename server/src/middleware/{v1 => }/responseHandler.js (100%) rename server/src/middleware/{v1 => }/sanitization.js (100%) rename server/src/middleware/{v1 => }/verifyJWT.js (87%) rename server/src/middleware/{v1 => }/verifyOwnership.js (88%) rename server/src/middleware/{v1 => }/verifySuperAdmin.js (97%) rename server/src/middleware/{v1 => }/verifyTeamAccess.js (100%) rename server/src/service/{v1 => }/business/checkService.js (100%) rename server/src/service/{v1 => }/business/diagnosticService.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 (90%) 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/notificationService.js (100%) rename server/src/service/{v1 => }/infrastructure/notificationUtils.js (100%) rename server/src/service/{v1 => }/infrastructure/statusService.js (99%) 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%) delete mode 100644 server/src/types/express.d.ts diff --git a/.github/workflows/build-check.yml b/.github/workflows/build-check.yml deleted file mode 100644 index 206a652d5..000000000 --- a/.github/workflows/build-check.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Build Check (Server) -on: - pull_request: - workflow_dispatch: - -jobs: - build-server: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Node - uses: actions/setup-node@v4 - with: - node-version: 22 - - - name: Install server dependencies - working-directory: server - run: npm install - - - name: Check server formatting - working-directory: server - run: npm run build diff --git a/server/src/app.js b/server/src/app.js index 253baee88..9cabda6b6 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -1,16 +1,16 @@ import express from "express"; import path from "path"; -import { responseHandler } from "./middleware/v1/responseHandler.js"; +import { responseHandler } from "./middleware/responseHandler.js"; import cors from "cors"; import helmet from "helmet"; import compression from "compression"; import cookieParser from "cookie-parser"; -import languageMiddleware from "./middleware/v1/languageMiddleware.js"; +import languageMiddleware from "./middleware/languageMiddleware.js"; import swaggerUi from "swagger-ui-express"; -import { handleErrors } from "./middleware/v1/handleErrors.js"; +import { handleErrors } from "./middleware/handleErrors.js"; import { setupRoutes } from "./config/routes.js"; -import { generalApiLimiter } from "./middleware/v1/rateLimiter.js"; -import { sanitizeBody, sanitizeQuery } from "./middleware/v1/sanitization.js"; +import { generalApiLimiter } from "./middleware/rateLimiter.js"; +import { sanitizeBody, sanitizeQuery } from "./middleware/sanitization.js"; export const createApp = ({ services, controllers, envSettings, frontendPath, openApiSpec }) => { const allowedOrigin = envSettings.clientHost; diff --git a/server/src/config/routes.js b/server/src/config/routes.js index 2068cf021..5ef622f37 100644 --- a/server/src/config/routes.js +++ b/server/src/config/routes.js @@ -1,5 +1,5 @@ -import { verifyJWT } from "../middleware/v1/verifyJWT.js"; -import { authApiLimiter } from "../middleware/v1/rateLimiter.js"; +import { verifyJWT } from "../middleware/verifyJWT.js"; +import { authApiLimiter } from "../middleware/rateLimiter.js"; import AuthRoutes from "../routes/v1/authRoute.js"; import InviteRoutes from "../routes/v1//inviteRoute.js"; diff --git a/server/src/config/services.js b/server/src/config/services.js index c5ab7fa02..b2b4ac430 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -1,22 +1,22 @@ -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 ServiceRegistry from "../service/system/serviceRegistry.js"; +import TranslationService from "../service/system/translationService.js"; +import StringService from "../service/system/stringService.js"; import MongoDB from "../db/mongo/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 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 papaparse from "papaparse"; import axios from "axios"; import got from "got"; diff --git a/server/src/controllers/v1/baseController.js b/server/src/controllers/v1/baseController.js index e8612cec5..314905c82 100644 --- a/server/src/controllers/v1/baseController.js +++ b/server/src/controllers/v1/baseController.js @@ -1,4 +1,4 @@ -import { AppError } from "../../service/v1/infrastructure/errorService.js"; +import { AppError } from "../../service/infrastructure/errorService.js"; export const createCommonDependencies = (db, errorService, logger, stringService) => { return { diff --git a/server/src/index.js b/server/src/index.js index a4e477d5f..d1ff649eb 100755 --- a/server/src/index.js +++ b/server/src/index.js @@ -7,7 +7,7 @@ import path from "path"; import fs from "fs"; import Logger from "./utils/logger.js"; -import SettingsService from "./service/v1/system/settingsService.js"; +import SettingsService from "./service/system/settingsService.js"; import AppSettings from "./db/models/AppSettings.js"; const SERVICE_NAME = "Server"; diff --git a/server/src/middleware/v1/handleErrors.js b/server/src/middleware/handleErrors.js similarity index 71% rename from server/src/middleware/v1/handleErrors.js rename to server/src/middleware/handleErrors.js index 43671fbe2..b0ded5ab7 100755 --- a/server/src/middleware/v1/handleErrors.js +++ b/server/src/middleware/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 { logger } from "../utils/logger.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/isAllowed.js similarity index 87% rename from server/src/middleware/v1/isAllowed.js rename to server/src/middleware/isAllowed.js index cc240f531..5d61974be 100755 --- a/server/src/middleware/v1/isAllowed.js +++ b/server/src/middleware/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/languageMiddleware.js b/server/src/middleware/languageMiddleware.js similarity index 93% rename from server/src/middleware/v1/languageMiddleware.js rename to server/src/middleware/languageMiddleware.js index 819858a21..6293dc837 100755 --- a/server/src/middleware/v1/languageMiddleware.js +++ b/server/src/middleware/languageMiddleware.js @@ -1,4 +1,4 @@ -import { logger } from "../../utils/logger.js"; +import { logger } from "../utils/logger.js"; const languageMiddleware = (stringService, translationService) => async (req, res, next) => { try { diff --git a/server/src/middleware/v1/rateLimiter.js b/server/src/middleware/rateLimiter.js similarity index 100% rename from server/src/middleware/v1/rateLimiter.js rename to server/src/middleware/rateLimiter.js diff --git a/server/src/middleware/v1/responseHandler.js b/server/src/middleware/responseHandler.js similarity index 100% rename from server/src/middleware/v1/responseHandler.js rename to server/src/middleware/responseHandler.js diff --git a/server/src/middleware/v1/sanitization.js b/server/src/middleware/sanitization.js similarity index 100% rename from server/src/middleware/v1/sanitization.js rename to server/src/middleware/sanitization.js diff --git a/server/src/middleware/v1/verifyJWT.js b/server/src/middleware/verifyJWT.js similarity index 87% rename from server/src/middleware/v1/verifyJWT.js rename to server/src/middleware/verifyJWT.js index b57391dfd..d01db0a97 100755 --- a/server/src/middleware/v1/verifyJWT.js +++ b/server/src/middleware/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/verifyOwnership.js similarity index 88% rename from server/src/middleware/v1/verifyOwnership.js rename to server/src/middleware/verifyOwnership.js index 741fb6436..4431b0c26 100755 --- a/server/src/middleware/v1/verifyOwnership.js +++ b/server/src/middleware/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 { logger } from "../utils/logger.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/middleware/v1/verifySuperAdmin.js b/server/src/middleware/verifySuperAdmin.js similarity index 97% rename from server/src/middleware/v1/verifySuperAdmin.js rename to server/src/middleware/verifySuperAdmin.js index 41ac0a3e0..a5a93eede 100755 --- a/server/src/middleware/v1/verifySuperAdmin.js +++ b/server/src/middleware/verifySuperAdmin.js @@ -1,5 +1,5 @@ const jwt = require("jsonwebtoken"); -const logger = require("../../utils/logger.js"); +const logger = require("../utils/logger"); const SERVICE_NAME = "verifyAdmin"; const TOKEN_PREFIX = "Bearer "; import ServiceRegistry from "../service/serviceRegistry.js"; diff --git a/server/src/middleware/v1/verifyTeamAccess.js b/server/src/middleware/verifyTeamAccess.js similarity index 100% rename from server/src/middleware/v1/verifyTeamAccess.js rename to server/src/middleware/verifyTeamAccess.js diff --git a/server/src/routes/v1/authRoute.js b/server/src/routes/v1/authRoute.js index d9b036b05..f69f94f43 100755 --- a/server/src/routes/v1/authRoute.js +++ b/server/src/routes/v1/authRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { verifyJWT } from "../../middleware/verifyJWT.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; import multer from "multer"; const upload = multer(); diff --git a/server/src/routes/v1/checkRoute.js b/server/src/routes/v1/checkRoute.js index 78f6b8a42..673fe51e0 100755 --- a/server/src/routes/v1/checkRoute.js +++ b/server/src/routes/v1/checkRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class CheckRoutes { constructor(checkController) { diff --git a/server/src/routes/v1/diagnosticRoute.js b/server/src/routes/v1/diagnosticRoute.js index 2cf481e08..1b7cc06be 100755 --- a/server/src/routes/v1/diagnosticRoute.js +++ b/server/src/routes/v1/diagnosticRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { verifyJWT } from "../../middleware/verifyJWT.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class DiagnosticRoutes { constructor(diagnosticController) { diff --git a/server/src/routes/v1/inviteRoute.js b/server/src/routes/v1/inviteRoute.js index a47550536..74fcfeb91 100755 --- a/server/src/routes/v1/inviteRoute.js +++ b/server/src/routes/v1/inviteRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { verifyJWT } from "../../middleware/verifyJWT.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class InviteRoutes { constructor(inviteController) { diff --git a/server/src/routes/v1/logRoutes.js b/server/src/routes/v1/logRoutes.js index 43da8b75a..feabe669a 100755 --- a/server/src/routes/v1/logRoutes.js +++ b/server/src/routes/v1/logRoutes.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class LogRoutes { constructor(logController) { this.router = Router(); diff --git a/server/src/routes/v1/monitorRoute.js b/server/src/routes/v1/monitorRoute.js index 107964f6b..dde72e256 100755 --- a/server/src/routes/v1/monitorRoute.js +++ b/server/src/routes/v1/monitorRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; import multer from "multer"; import { fetchMonitorCertificate } from "../../controllers/v1/controllerUtils.js"; const upload = multer({ diff --git a/server/src/routes/v1/queueRoute.js b/server/src/routes/v1/queueRoute.js index c6cfa05da..59e1cb744 100755 --- a/server/src/routes/v1/queueRoute.js +++ b/server/src/routes/v1/queueRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class QueueRoutes { constructor(queueController) { this.router = Router(); diff --git a/server/src/routes/v1/settingsRoute.js b/server/src/routes/v1/settingsRoute.js index e88186af2..76556e923 100755 --- a/server/src/routes/v1/settingsRoute.js +++ b/server/src/routes/v1/settingsRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class SettingsRoutes { constructor(settingsController) { diff --git a/server/src/routes/v1/statusPageRoute.js b/server/src/routes/v1/statusPageRoute.js index 2c3b0b265..8300066a8 100755 --- a/server/src/routes/v1/statusPageRoute.js +++ b/server/src/routes/v1/statusPageRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; +import { verifyJWT } from "../../middleware/verifyJWT.js"; import multer from "multer"; const upload = multer(); 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/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 061234102..637870b71 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 90% rename from server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js rename to server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js index 2833a59f5..b348e2534 100644 --- a/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js +++ b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js @@ -5,11 +5,11 @@ class SuperSimpleQueueHelper { /** * @param {{ - * db: import("../database.js").Database, - * logger: import("../logger.js").Logger, - * networkService: import("../networkService.js").NetworkService, - * statusService: import("../statusService.js").StatusService, - * notificationService: import("../notificationService.js").NotificationService + * db: import("../database").Database, + * logger: import("../logger").Logger, + * networkService: import("../networkService").NetworkService, + * statusService: import("../statusService").StatusService, + * notificationService: import("../notificationService").NotificationService * }} */ constructor({ db, logger, networkService, statusService, notificationService }) { 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/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 99% rename from server/src/service/v1/infrastructure/statusService.js rename to server/src/service/infrastructure/statusService.js index 983541cd9..72fa359d7 100755 --- a/server/src/service/v1/infrastructure/statusService.js +++ b/server/src/service/infrastructure/statusService.js @@ -1,4 +1,4 @@ -import MonitorStats from "../../../db/models/MonitorStats.js"; +import MonitorStats from "../../db/models/MonitorStats.js"; const SERVICE_NAME = "StatusService"; class StatusService { 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/service/v2/business/AuthService.ts b/server/src/service/v2/business/AuthService.ts index 52f586dbf..ae22b981d 100644 --- a/server/src/service/v2/business/AuthService.ts +++ b/server/src/service/v2/business/AuthService.ts @@ -3,45 +3,6 @@ import { User, Role, ITokenizedUser } from "../../../db/models/index.js"; import ApiError from "../../../utils/ApiError.js"; import { Types } from "mongoose"; -export const PERMISSIONS = { - users: { - all: "users.*", - create: "users.create", - view: "users.view", - update: "users.update", - delete: "users.delete", - }, - - monitors: { - all: "monitors.*", - create: "monitors.create", - view: "monitors.view", - update: "monitors.update", - delete: "monitors.delete", - }, - notifications: { - all: "notifications.*", - create: "notifications.create", - view: "notifications.view", - update: "notifications.update", - delete: "notifications.delete", - }, - checks: { - all: "checks.*", - create: "checks.create", - view: "checks.view", - update: "checks.update", - delete: "checks.delete", - }, - statusPages: { - all: "statusPages.*", - create: "statusPages.create", - view: "statusPages.view", - update: "statusPages.update", - delete: "statusPages.delete", - }, -}; - export const DEFAULT_ROLES = [ { name: "SuperAdmin", @@ -52,19 +13,19 @@ export const DEFAULT_ROLES = [ { name: "Admin", description: "Admin with full permissions", - permissions: [PERMISSIONS.monitors.all, PERMISSIONS.users.all], + permissions: ["monitor.*", "users.*"], isSystem: true, }, { name: "Manager", description: "Can manage users", - permissions: [PERMISSIONS.users.create, PERMISSIONS.users.update, PERMISSIONS.monitors.all], + permissions: ["users.create", "users.update", "monitors.*"], isSystem: true, }, { name: "Member", description: "Basic team member", - permissions: [PERMISSIONS.users.update, PERMISSIONS.monitors.create, PERMISSIONS.monitors.view, PERMISSIONS.monitors.update], + permissions: ["users.update", "monitors.create", "monitors.view", "monitors.update"], isSystem: true, }, ]; @@ -165,7 +126,7 @@ class AuthService implements IAuthService { } // Check password - const isPasswordValid = await bcrypt.compare(password, user.password); + const isPasswordValid = await bcrypt.compare(password, user.passwordHash); if (!isPasswordValid) { throw new Error("Invalid email or password"); } diff --git a/server/src/types/express.d.ts b/server/src/types/express.d.ts deleted file mode 100644 index 7c78a053c..000000000 --- a/server/src/types/express.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ITokenizedUser } from "../db/models/index.ts"; - -declare global { - namespace Express { - interface Request { - user?: ITokenizedUser; - resource?: any; - } - } -} From 990aed9f2e01c459135d6de835b084d452fe4abe Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Sep 2025 14:05:23 -0700 Subject: [PATCH 009/103] Revert "Merge pull request #2973 from bluewave-labs/feat/v2/auth" This reverts commit 72882fded9b57b1d0f76a521e12afea9f63135ce, reversing changes made to bfb299cfcaa4dcfaff68daed9f4629bc8fcce554. --- server/nodemon.json | 4 +- server/package-lock.json | 117 --------------- server/package.json | 4 +- server/src/config/controllers.js | 6 - server/src/config/routes.js | 10 -- server/src/config/services.js | 15 +- server/src/controllers/v2/AuthController.ts | 142 ------------------ server/src/db/models/InviteToken.js | 35 +++++ .../src/db/models/{auth/User.ts => User.js} | 80 ++-------- server/src/db/models/auth/Invite.ts | 68 --------- server/src/db/models/auth/Role.ts | 45 ------ server/src/db/models/index.ts | 9 -- server/src/db/models/migration/Migration.ts | 34 ----- ...s => 0001_migrateStatusWindowThreshold.js} | 1 - .../db/mongo/migration/0002_migrateUsers.ts | 56 ------- server/src/db/mongo/migration/index.js | 7 + server/src/db/mongo/migration/index.ts | 25 --- server/src/db/mongo/modules/inviteModule.js | 12 +- server/src/middleware/v2/VerifyToken.ts | 21 --- server/src/middleware/verifyTeamAccess.js | 2 - server/src/routes/v2/auth.ts | 31 ---- server/src/service/v2/business/AuthService.ts | 141 ----------------- .../src/service/v2/business/InviteService.ts | 61 -------- server/src/utils/ApiError.ts | 15 -- server/src/utils/JWTUtils.ts | 22 --- 25 files changed, 69 insertions(+), 894 deletions(-) delete mode 100644 server/src/controllers/v2/AuthController.ts create mode 100755 server/src/db/models/InviteToken.js rename server/src/db/models/{auth/User.ts => User.js} (55%) delete mode 100755 server/src/db/models/auth/Invite.ts delete mode 100644 server/src/db/models/auth/Role.ts delete mode 100644 server/src/db/models/index.ts delete mode 100644 server/src/db/models/migration/Migration.ts rename server/src/db/mongo/migration/{0001_migrateStatusWindowThreshold.ts => 0001_migrateStatusWindowThreshold.js} (90%) delete mode 100644 server/src/db/mongo/migration/0002_migrateUsers.ts create mode 100644 server/src/db/mongo/migration/index.js delete mode 100644 server/src/db/mongo/migration/index.ts delete mode 100644 server/src/middleware/v2/VerifyToken.ts delete mode 100644 server/src/routes/v2/auth.ts delete mode 100644 server/src/service/v2/business/AuthService.ts delete mode 100644 server/src/service/v2/business/InviteService.ts delete mode 100644 server/src/utils/ApiError.ts delete mode 100644 server/src/utils/JWTUtils.ts diff --git a/server/nodemon.json b/server/nodemon.json index 7371b4904..88d6ac686 100755 --- a/server/nodemon.json +++ b/server/nodemon.json @@ -1,5 +1,5 @@ { "ignore": ["src/locales/*", "*.log", "node_modules/*"], - "watch": ["src/**/*.js", "src/**/*.ts", "*.json"], - "ext": "ts,js,json" + "watch": ["src/**/*.js", "*.json"], + "ext": "js,json" } diff --git a/server/package-lock.json b/server/package-lock.json index e21b28382..06c1c30b3 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -45,8 +45,6 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", - "@types/express": "5.0.3", - "@types/jsonwebtoken": "9.0.10", "c8": "10.1.3", "chai": "5.2.0", "eslint": "^9.17.0", @@ -1680,27 +1678,6 @@ "node": ">=10.13.0" } }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1708,44 +1685,12 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/express": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", - "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", - "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "license": "MIT" }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -1760,31 +1705,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/jsonwebtoken": { - "version": "9.0.10", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", - "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/ms": "*", - "@types/node": "*" - } - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { "version": "24.3.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", @@ -1794,49 +1714,12 @@ "undici-types": "~7.10.0" } }, - "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/relateurl": { "version": "0.2.33", "resolved": "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.33.tgz", "integrity": "sha512-bTQCKsVbIdzLqZhLkF5fcJQreE4y1ro4DIyVrlDNSCJRRwHhB8Z+4zXXa8jN6eDvc2HbRsEYgbvrnGvi54EpSw==", "license": "MIT" }, - "node_modules/@types/send": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", - "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" - } - }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", diff --git a/server/package.json b/server/package.json index 5c655af9d..637c785cc 100755 --- a/server/package.json +++ b/server/package.json @@ -2,7 +2,7 @@ "name": "server", "version": "1.0.0", "description": "", - "main": "dist/index.js", + "main": "index.js", "type": "module", "scripts": { "test": "c8 mocha", @@ -54,8 +54,6 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", - "@types/express": "5.0.3", - "@types/jsonwebtoken": "9.0.10", "c8": "10.1.3", "chai": "5.2.0", "eslint": "^9.17.0", diff --git a/server/src/config/controllers.js b/server/src/config/controllers.js index 250d04c4b..5c54579eb 100644 --- a/server/src/config/controllers.js +++ b/server/src/config/controllers.js @@ -15,9 +15,6 @@ import StatusPageController from "../controllers/v1/statusPageController.js"; import NotificationController from "../controllers/v1/notificationController.js"; import DiagnosticController from "../controllers/v1/diagnosticController.js"; -// V2 -import AuthV2Controller from "../controllers/v2/AuthController.js"; - export const initializeControllers = (services) => { const controllers = {}; const commonDependencies = createCommonDependencies(services.db, services.errorService, services.logger, services.stringService); @@ -65,8 +62,5 @@ export const initializeControllers = (services) => { diagnosticService: services.diagnosticService, }); - // V2 - controllers.authV2Controller = new AuthV2Controller(services.authV2Service, services.inviteV2Service); - return controllers; }; diff --git a/server/src/config/routes.js b/server/src/config/routes.js index 5ef622f37..2ec93a2dc 100644 --- a/server/src/config/routes.js +++ b/server/src/config/routes.js @@ -13,8 +13,6 @@ import LogRoutes from "../routes/v1/logRoutes.js"; import DiagnosticRoutes from "../routes/v1//diagnosticRoute.js"; import NotificationRoutes from "../routes/v1/notificationRoute.js"; -import authV2 from "../routes/v2/auth.js"; - export const setupRoutes = (app, controllers) => { const authRoutes = new AuthRoutes(controllers.authController); const monitorRoutes = new MonitorRoutes(controllers.monitorController); @@ -39,12 +37,4 @@ export const setupRoutes = (app, controllers) => { app.use("/api/v1/status-page", statusPageRoutes.getRouter()); app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter()); app.use("/api/v1/diagnostic", verifyJWT, diagnosticRoutes.getRouter()); - - // ******************* - // V2 Routes - // ******************* - - const v2AuthRoutes = new authV2(controllers.authV2Controller); - - app.use("/api/v2/auth", authApiLimiter, v2AuthRoutes.getRouter()); }; diff --git a/server/src/config/services.js b/server/src/config/services.js index b2b4ac430..08c2525f3 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -48,8 +48,8 @@ import { ParseBoolean } from "../utils/utils.js"; // Models import Check from "../db/models/Check.js"; import Monitor from "../db/models/Monitor.js"; -import { User } from "../db/models/auth/User.js"; -import { Invite } from "../db/models/auth/Invite.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"; @@ -68,10 +68,6 @@ import NotificationModule from "../db/mongo/modules/notificationModule.js"; import RecoveryModule from "../db/mongo/modules/recoveryModule.js"; import SettingsModule from "../db/mongo/modules/settingsModule.js"; -// v2 -import AuthV2Service from "../service/v2/business/AuthService.js"; -import InviteV2Service from "../service/v2/business/InviteService.js"; - export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); ServiceRegistry.instance = serviceRegistry; @@ -83,7 +79,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService // Create DB const checkModule = new CheckModule({ logger, Check, Monitor, User }); - const inviteModule = new InviteModule({ Invite, crypto, stringService }); + const inviteModule = new InviteModule({ InviteToken, crypto, stringService }); const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService }); const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean, stringService }); const maintenanceWindowModule = new MaintenanceWindowModule({ MaintenanceWindow }); @@ -212,9 +208,6 @@ export const initializeServices = async ({ logger, envSettings, settingsService games, }); - const authV2Service = new AuthV2Service(); - const inviteV2Service = new InviteV2Service(); - const services = { settingsService, translationService, @@ -234,8 +227,6 @@ export const initializeServices = async ({ logger, envSettings, settingsService monitorService, errorService, logger, - authV2Service, - inviteV2Service, }; Object.values(services).forEach((service) => { diff --git a/server/src/controllers/v2/AuthController.ts b/server/src/controllers/v2/AuthController.ts deleted file mode 100644 index a9d1dd203..000000000 --- a/server/src/controllers/v2/AuthController.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { Request, Response, NextFunction } from "express"; -import { encode, decode } from "../../utils/JWTUtils.js"; -import AuthService from "../../service/v2/business/AuthService.js"; -import ApiError from "../../utils/ApiError.js"; -import InviteService from "../../service/v2/business/InviteService.js"; -import { IInvite } from "../../db/models/index.js"; - -export interface IAuthController { - register(req: Request, res: Response, next: NextFunction): Promise; - registerWithInvite(req: Request, res: Response, next: NextFunction): Promise; - login(req: Request, res: Response, next: NextFunction): Promise; - logout(req: Request, res: Response): void; - me(req: Request, res: Response, next: NextFunction): void; -} - -class AuthController implements IAuthController { - private authService: AuthService; - private inviteService: InviteService; - constructor(authService: AuthService, inviteService: InviteService) { - this.authService = authService; - this.inviteService = inviteService; - } - - register = async (req: Request, res: Response, next: NextFunction) => { - try { - const { email, firstName, lastName, password } = req.body; - - if (!email || !firstName || !lastName || !password) { - throw new Error("Email, firstName, lastName, and password are required"); - } - - const result = await this.authService.register({ - email, - firstName, - lastName, - password, - }); - - const token = encode(result); - - res.cookie("token", token, { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict", - maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week - }); - - res.status(201).json({ - message: "User created successfully", - }); - } catch (error) { - next(error); - } - }; - - registerWithInvite = async (req: Request, res: Response, next: NextFunction) => { - try { - const token = req.params.token; - if (!token) { - throw new ApiError("Invite token is required", 400); - } - - const invite: IInvite = await this.inviteService.get(token); - - const { firstName, lastName, password } = req.body; - const email = invite?.email; - const roles = invite?.roles; - - if (!email || !firstName || !lastName || !password || !roles || roles.length === 0) { - throw new Error("Email, firstName, lastName, password, and roles are required"); - } - - const result = await this.authService.registerWithInvite({ - email, - firstName, - lastName, - password, - roles, - }); - - if (!result) { - throw new Error("Registration failed"); - } - - await this.inviteService.delete(invite._id.toString()); - - const jwt = encode(result); - - res.cookie("token", jwt, { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict", - maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week - }); - - res.status(201).json({ message: "User created successfully" }); - } catch (error) { - next(error); - } - }; - - login = async (req: Request, res: Response, next: NextFunction) => { - try { - const { email, password } = req.body; - // Validation - if (!email || !password) { - throw new ApiError("Email and password are required", 400); - } - const result = await this.authService.login({ email, password }); - - const token = encode(result); - - res.cookie("token", token, { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict", - maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week - }); - - res.status(200).json({ - message: "Login successful", - }); - } catch (error) { - next(error); - } - }; - - logout = (req: Request, res: Response) => { - res.clearCookie("token", { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict", - }); - res.status(200).json({ message: "Logout successful" }); - }; - - me = (req: Request, res: Response, next: NextFunction) => { - return res.status(200).json({ message: "OK" }); - }; -} - -export default AuthController; diff --git a/server/src/db/models/InviteToken.js b/server/src/db/models/InviteToken.js new file mode 100755 index 000000000..079600647 --- /dev/null +++ b/server/src/db/models/InviteToken.js @@ -0,0 +1,35 @@ +import mongoose from "mongoose"; +const InviteTokenSchema = mongoose.Schema( + { + email: { + type: String, + required: true, + unique: true, + }, + teamId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Team", + immutable: true, + required: true, + }, + role: { + type: Array, + required: true, + default: ["user"], + }, + token: { + type: String, + required: true, + }, + expiry: { + type: Date, + default: Date.now, + expires: 3600, + }, + }, + { + timestamps: true, + } +); + +export default mongoose.model("InviteToken", InviteTokenSchema); diff --git a/server/src/db/models/auth/User.ts b/server/src/db/models/User.js similarity index 55% rename from server/src/db/models/auth/User.ts rename to server/src/db/models/User.js index 82c058ea2..9368d98b1 100755 --- a/server/src/db/models/auth/User.ts +++ b/server/src/db/models/User.js @@ -1,47 +1,11 @@ -import mongoose, { Schema, Document, Types } from "mongoose"; - +import mongoose from "mongoose"; import bcrypt from "bcryptjs"; -import logger from "../../../utils/logger.js"; -import Monitor from "../Monitor.js"; -import Team from "../Team.js"; -import Notification from "../Notification.js"; -import { subscribe } from "diagnostics_channel"; +import logger from "../../utils/logger.js"; +import Monitor from "./Monitor.js"; +import Team from "./Team.js"; +import Notification from "./Notification.js"; -export const RoleTypes = ["user", "admin", "superadmin", "demo"] as const; -export type RoleType = (typeof RoleTypes)[number]; - -export interface ITokenizedUser { - sub: string; - roles: string[]; -} - -export interface IUser extends Document { - // V1 - _id: Types.ObjectId; - firstName: string; - lastName: string; - email: string; - password: string; - avatarImage: string; - profileImage: { - data: Buffer; - contentType: string; - }; - isActive: boolean; - isVerified: boolean; - role?: RoleType[]; - teamId: Types.ObjectId; - checkTTL: number; - createdAt: Date; - updatedAt: Date; - comparePassword: (submittedPassword: string) => Promise; - // V2 - roles: Types.ObjectId[]; - lastLoginAt?: Date; - version: number; -} - -const UserSchema = new Schema( +const UserSchema = mongoose.Schema( { firstName: { type: String, @@ -58,7 +22,7 @@ const UserSchema = new Schema( }, password: { type: String, - required: false, + required: true, }, avatarImage: { type: String, @@ -77,8 +41,8 @@ const UserSchema = new Schema( }, role: { type: [String], - default: ["user"], - enum: RoleTypes, + default: "user", + enum: ["user", "admin", "superadmin", "demo"], }, teamId: { type: mongoose.Schema.Types.ObjectId, @@ -88,22 +52,6 @@ const UserSchema = new Schema( checkTTL: { type: Number, }, - - // v2 - roles: [ - { - type: Schema.Types.ObjectId, - ref: "Role", - default: [], - }, - ], - lastLoginAt: { - type: Date, - }, - version: { - type: Number, - default: 1, - }, }, { timestamps: true, @@ -121,7 +69,7 @@ UserSchema.pre("save", function (next) { UserSchema.pre("findOneAndUpdate", function (next) { const update = this.getUpdate(); - if (update && "password" in update) { + if ("password" in update) { const salt = bcrypt.genSaltSync(10); update.password = bcrypt.hashSync(update.password, salt); } @@ -144,13 +92,15 @@ UserSchema.pre("findOneAndDelete", async function (next) { } next(); } catch (error) { - next(error as Error); + next(error); } }); -UserSchema.methods.comparePassword = async function (submittedPassword: string) { +UserSchema.methods.comparePassword = async function (submittedPassword) { const res = await bcrypt.compare(submittedPassword, this.password); return res; }; -export const User = mongoose.model("User", UserSchema); +const User = mongoose.model("User", UserSchema); + +export default User; diff --git a/server/src/db/models/auth/Invite.ts b/server/src/db/models/auth/Invite.ts deleted file mode 100755 index 148ff65cb..000000000 --- a/server/src/db/models/auth/Invite.ts +++ /dev/null @@ -1,68 +0,0 @@ -import mongoose, { Schema, Document, Types } from "mongoose"; - -export interface IInvite extends Document { - // v1 - _id: Types.ObjectId; - email: string; - teamId: Types.ObjectId; - role: string[]; - token: string; - expiry: Date; - createdAt: Date; - updatedAt: Date; - - // v2 - tokenHash: string; - roles: Types.ObjectId[]; - createdBy: Types.ObjectId; - updatedBy: Types.ObjectId; -} - -const InviteSchema = new Schema( - { - email: { - type: String, - required: true, - unique: true, - trim: true, - lowercase: true, - }, - teamId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Team", - immutable: true, - required: true, - }, - role: { - type: [String], - required: true, - default: ["user"], - }, - token: { - type: String, - required: true, - }, - expiry: { - type: Date, - default: Date.now, - expires: 3600, - }, - - // v2 - roles: [ - { - type: Schema.Types.ObjectId, - ref: "Role", - required: true, - }, - ], - tokenHash: { type: String, required: true, unique: true }, - createdBy: { type: Schema.Types.ObjectId, ref: "User", required: true }, - updatedBy: { type: Schema.Types.ObjectId, ref: "User", required: true }, - }, - { - timestamps: true, - } -); - -export const Invite = mongoose.model("Invite", InviteSchema); diff --git a/server/src/db/models/auth/Role.ts b/server/src/db/models/auth/Role.ts deleted file mode 100644 index e7ffb2f7d..000000000 --- a/server/src/db/models/auth/Role.ts +++ /dev/null @@ -1,45 +0,0 @@ -import mongoose, { Schema, Document, Types } from "mongoose"; - -export interface IRole extends Document { - _id: Types.ObjectId; - name: string; - description?: string; - permissions: string[]; - isActive: boolean; - createdAt: Date; - updatedAt: Date; -} - -const roleSchema = new Schema( - { - name: { - type: String, - required: true, - trim: true, - maxlength: 50, - }, - description: { - type: String, - trim: true, - maxlength: 200, - }, - - permissions: [ - { - type: String, - required: true, - }, - ], - isActive: { - type: Boolean, - default: true, - }, - }, - { - timestamps: true, - } -); - -roleSchema.index({ name: 1 }, { unique: true }); - -export const Role = mongoose.model("Role", roleSchema); diff --git a/server/src/db/models/index.ts b/server/src/db/models/index.ts deleted file mode 100644 index 537633bfd..000000000 --- a/server/src/db/models/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export { User } from "./auth/User.js"; -export type { IUser } from "./auth/User.js"; -export type { ITokenizedUser } from "./auth/User.js"; -export { Role } from "./auth/Role.js"; -export type { IRole } from "./auth/Role.js"; -export type { IInvite } from "./auth/Invite.js"; -export { Invite } from "./auth/Invite.js"; -export { Migration } from "./migration/Migration.js"; -export type { IMigration } from "./migration/Migration.js"; diff --git a/server/src/db/models/migration/Migration.ts b/server/src/db/models/migration/Migration.ts deleted file mode 100644 index cf76bb48e..000000000 --- a/server/src/db/models/migration/Migration.ts +++ /dev/null @@ -1,34 +0,0 @@ -import mongoose, { Schema, Document, Types } from "mongoose"; - -export interface IMigration extends Document { - _id: Types.ObjectId; - name: string; - runAt: Date; - success: boolean; - createdAt: Date; - updatedAt: Date; -} - -const migrationSchema = new Schema( - { - name: { - type: String, - required: true, - }, - runAt: { - type: Date, - required: false, - }, - success: { - type: Boolean, - required: false, - }, - }, - { - timestamps: true, - } -); - -migrationSchema.index({ name: 1 }, { unique: true }); - -export const Migration = mongoose.model("Migration", migrationSchema); diff --git a/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts b/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js similarity index 90% rename from server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts rename to server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js index b44487e87..8f6988de4 100644 --- a/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts +++ b/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js @@ -1,5 +1,4 @@ import Monitor from "../../models/Monitor.js"; -export const MIGRATION_NAME = "0001_migrateStatusWindowThreshold"; async function migrateStatusWindowThreshold() { try { const monitors = await Monitor.find({ statusWindowThreshold: { $lt: 1 } }); diff --git a/server/src/db/mongo/migration/0002_migrateUsers.ts b/server/src/db/mongo/migration/0002_migrateUsers.ts deleted file mode 100644 index a7cdb76fc..000000000 --- a/server/src/db/mongo/migration/0002_migrateUsers.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { User, Role } from "../../models/index.js"; -import { Types } from "mongoose"; - -import { DEFAULT_ROLES } from "../../../service/v2/business/AuthService.js"; -export const MIGRATION_NAME = "0002_migrateUsers"; -async function migrateUsers() { - try { - // Create roles if they don't exist - const roleCount = await Role.countDocuments(); - let roles; - if (roleCount === 0) { - const rolePromises = DEFAULT_ROLES.map((roleData) => - new Role({ - ...roleData, - }).save() - ); - roles = await Promise.all(rolePromises); - } else { - roles = await Role.find(); - } - - // Migrate users - const users = await User.find({ - $or: [{ version: { $exists: false } }, { version: { $lt: 2 } }], - }); - for (const user of users) { - const newRoleIds: Types.ObjectId[] = []; - - for (const role of user.role || []) { - if (role === "superadmin") { - const superAdminRole = roles.find((role) => role.name === "SuperAdmin"); - newRoleIds.push(superAdminRole!._id); - } - if (role === "admin") { - const managerRole = roles.find((role) => role.name === "Manager"); - newRoleIds.push(managerRole!._id); - } - if (role === "user") { - const memberRole = roles.find((role) => role.name === "Member"); - newRoleIds.push(memberRole!._id); - } - } - const merged = [...new Set([...user.roles, ...newRoleIds])]; - user.roles = merged; - user.version = 2; - await user.save(); - } - - return true; - } catch (err) { - console.error("Migration error:", err); - return false; - } -} - -export { migrateUsers }; diff --git a/server/src/db/mongo/migration/index.js b/server/src/db/mongo/migration/index.js new file mode 100644 index 000000000..4bf56895a --- /dev/null +++ b/server/src/db/mongo/migration/index.js @@ -0,0 +1,7 @@ +import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshold.js"; + +const runMigrations = async () => { + await migrateStatusWindowThreshold(); +}; + +export { runMigrations }; diff --git a/server/src/db/mongo/migration/index.ts b/server/src/db/mongo/migration/index.ts deleted file mode 100644 index 293e0905f..000000000 --- a/server/src/db/mongo/migration/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { migrateStatusWindowThreshold, MIGRATION_NAME as MIGRATION_0001 } from "./0001_migrateStatusWindowThreshold.js"; -import { migrateUsers, MIGRATION_NAME as MIGRATION_0002 } from "./0002_migrateUsers.js"; -import { Migration, IMigration } from "../../models/index.js"; - -const runMigrations = async () => { - const migrations = [ - { name: MIGRATION_0001, migration: migrateStatusWindowThreshold }, - { name: MIGRATION_0002, migration: migrateUsers }, - ]; - - const migrationNames = migrations.map((m) => m.name); - - const appliedMigrations = await Migration.find({ name: { $in: migrationNames }, success: true }); - - const migrationsToRun = migrations.filter((m) => !appliedMigrations.some((am: IMigration) => am.name === m.name)); - - for (const { name, migration } of migrationsToRun) { - console.log(`Running migration: ${name}`); - const success = await migration(); - await Migration.updateOne({ name }, { $set: { runAt: new Date(), success } }, { upsert: true }); - console.log(`Migration ${name} completed with status: ${success ? "success" : "failure"}`); - } -}; - -export { runMigrations }; diff --git a/server/src/db/mongo/modules/inviteModule.js b/server/src/db/mongo/modules/inviteModule.js index 513524fe5..444e51efd 100755 --- a/server/src/db/mongo/modules/inviteModule.js +++ b/server/src/db/mongo/modules/inviteModule.js @@ -1,17 +1,17 @@ const SERVICE_NAME = "inviteModule"; class InviteModule { - constructor({ Invite, crypto, stringService }) { - this.Invite = Invite; + constructor({ InviteToken, crypto, stringService }) { + this.InviteToken = InviteToken; this.crypto = crypto; this.stringService = stringService; } requestInviteToken = async (userData) => { try { - await this.Invite.deleteMany({ email: userData.email }); + await this.InviteToken.deleteMany({ email: userData.email }); userData.token = this.crypto.randomBytes(32).toString("hex"); - let inviteToken = new this.Invite(userData); + let inviteToken = new this.InviteToken(userData); await inviteToken.save(); return inviteToken; } catch (error) { @@ -23,7 +23,7 @@ class InviteModule { getInviteToken = async (token) => { try { - const invite = await this.Invite.findOne({ + const invite = await this.InviteToken.findOne({ token, }); if (invite === null) { @@ -38,7 +38,7 @@ class InviteModule { }; getInviteTokenAndDelete = async (token) => { try { - const invite = await this.Invite.findOneAndDelete({ + const invite = await this.InviteToken.findOneAndDelete({ token, }); if (invite === null) { diff --git a/server/src/middleware/v2/VerifyToken.ts b/server/src/middleware/v2/VerifyToken.ts deleted file mode 100644 index 7dabd51cc..000000000 --- a/server/src/middleware/v2/VerifyToken.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Request, Response, NextFunction } from "express"; -import { decode } from "../../utils/JWTUtils.js"; -import ApiError from "../../utils/ApiError.js"; -const verifyToken = (req: Request, res: Response, next: NextFunction) => { - const token = req.cookies.token; - - if (!token) { - const error = new ApiError("No token provided", 401); - return next(error); - } - - try { - const decoded = decode(token); - req.user = decoded; - next(); - } catch (error) { - next(error); - } -}; - -export { verifyToken }; diff --git a/server/src/middleware/verifyTeamAccess.js b/server/src/middleware/verifyTeamAccess.js index a548cbf23..60647bdc2 100644 --- a/server/src/middleware/verifyTeamAccess.js +++ b/server/src/middleware/verifyTeamAccess.js @@ -3,8 +3,6 @@ const SERVICE_NAME = "verifyTeamAccess"; const verifyTeamAccess = (Model, paramName) => { return async (req, res, next) => { try { - const user = req.user; - console.log(user); const documentId = req.params[paramName]; const doc = await Model.findById(documentId); diff --git a/server/src/routes/v2/auth.ts b/server/src/routes/v2/auth.ts deleted file mode 100644 index 5a1b8ad63..000000000 --- a/server/src/routes/v2/auth.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Router } from "express"; - -import express from "express"; -import AuthController from "../../controllers/v2/AuthController.js"; -import { verifyToken } from "../../middleware/v2/VerifyToken.js"; - -const router = express.Router(); - -class AuthRoutes { - private controller: AuthController; - private router: Router; - constructor(authController: AuthController) { - this.controller = authController; - this.router = Router(); - this.initRoutes(); - } - - initRoutes = () => { - this.router.post("/register", this.controller.register); - this.router.post("/register/invite/:token", this.controller.registerWithInvite); - this.router.post("/login", this.controller.login); - this.router.post("/logout", this.controller.logout); - this.router.get("/me", verifyToken, this.controller.me); - }; - - getRouter() { - return this.router; - } -} - -export default AuthRoutes; diff --git a/server/src/service/v2/business/AuthService.ts b/server/src/service/v2/business/AuthService.ts deleted file mode 100644 index ae22b981d..000000000 --- a/server/src/service/v2/business/AuthService.ts +++ /dev/null @@ -1,141 +0,0 @@ -import bcrypt from "bcryptjs"; -import { User, Role, ITokenizedUser } from "../../../db/models/index.js"; -import ApiError from "../../../utils/ApiError.js"; -import { Types } from "mongoose"; - -export const DEFAULT_ROLES = [ - { - name: "SuperAdmin", - description: "Super admin with all permissions", - permissions: ["*"], - isSystem: true, - }, - { - name: "Admin", - description: "Admin with full permissions", - permissions: ["monitor.*", "users.*"], - isSystem: true, - }, - { - name: "Manager", - description: "Can manage users", - permissions: ["users.create", "users.update", "monitors.*"], - isSystem: true, - }, - { - name: "Member", - description: "Basic team member", - permissions: ["users.update", "monitors.create", "monitors.view", "monitors.update"], - isSystem: true, - }, -]; - -export type RegisterData = { - email: string; - firstName: string; - lastName: string; - password: string; - roles?: Types.ObjectId[]; // Optional roles for invite-based registration -}; - -export type LoginData = { - email: string; - password: string; -}; - -export type AuthResult = ITokenizedUser; - -export interface IAuthService { - register(signupData: RegisterData): Promise; - registerWithInvite(signupData: RegisterData): Promise; - login(loginData: LoginData): Promise; -} - -class AuthService implements IAuthService { - constructor() {} - - async register(signupData: RegisterData): Promise { - const userCount = await User.countDocuments(); - - if (userCount > 0) { - throw new Error("Registration is closed. Please request an invite."); - } - - const { email, firstName, lastName, password } = signupData; - - // Create all default roles - const rolePromises = DEFAULT_ROLES.map((roleData) => - new Role({ - ...roleData, - }).save() - ); - const roles = await Promise.all(rolePromises); - - // Find admin role and assign to first user - const superAdminRole = roles.find((role) => role.name === "SuperAdmin"); - - const user = new User({ - email, - firstName, - lastName, - password, - roles: [superAdminRole!._id], - }); - - const savedUser = await user.save(); - return { - sub: savedUser._id.toString(), - roles: savedUser.roles.map((role) => role.toString()), - }; - } - - async registerWithInvite(signupData: RegisterData): Promise { - const { email, firstName, lastName, password, roles } = signupData; - - const user = new User({ - email, - firstName, - lastName, - password, - roles: roles || [], - }); - try { - const savedUser = await user.save(); - return { - sub: savedUser._id.toString(), - roles: savedUser.roles.map((role) => role.toString()), - }; - } catch (error: any) { - if (error?.code === 11000) { - const dupError = new ApiError("Email already in use", 409); - dupError.stack = error?.stack; - throw dupError; - } - throw error; - } - } - - async login(loginData: LoginData): Promise { - const { email, password } = loginData; - - // Find user by email - const user = await User.findOne({ email }); - - if (!user) { - throw new Error("Invalid email or password"); - } - - // Check password - const isPasswordValid = await bcrypt.compare(password, user.passwordHash); - if (!isPasswordValid) { - throw new Error("Invalid email or password"); - } - - return { - sub: user._id.toString(), - roles: user.roles.map((role) => role.toString()), - }; - } -} - -export default AuthService; diff --git a/server/src/service/v2/business/InviteService.ts b/server/src/service/v2/business/InviteService.ts deleted file mode 100644 index f8bd9e2db..000000000 --- a/server/src/service/v2/business/InviteService.ts +++ /dev/null @@ -1,61 +0,0 @@ -import crypto from "node:crypto"; -import { ITokenizedUser, IInvite, Invite } from "../../../db/models/index.js"; -import ApiError from "../../../utils/ApiError.js"; - -export interface IInviteService { - create: (tokenizedUser: ITokenizedUser, invite: IInvite) => Promise<{ token: string }>; - getAll: () => Promise; - get: (tokenHash: string) => Promise; - delete: (id: string) => Promise; -} - -class InviteService implements IInviteService { - constructor() {} - - create = async (tokenizedUser: ITokenizedUser, inviteData: IInvite) => { - const token = crypto.randomBytes(32).toString("hex"); - const tokenHash = crypto.createHash("sha256").update(token).digest("hex"); - try { - const invite = await Invite.create({ - ...inviteData, - tokenHash, - createdBy: tokenizedUser.sub, - updatedBy: tokenizedUser.sub, - }); - if (!invite) { - throw new ApiError("Failed to create invite", 500); - } - return { token }; - } catch (error: any) { - if (error?.code === 11000) { - const dupError = new ApiError("Invite with this email already exists", 409); - dupError.stack = error?.stack; - throw dupError; - } - throw error; - } - }; - - get = async (token: string) => { - const tokenHash = crypto.createHash("sha256").update(token).digest("hex"); - const invite = await Invite.findOne({ tokenHash }); - if (!invite) { - throw new ApiError("Invite not found", 404); - } - return invite; - }; - - getAll = async () => { - return Invite.find(); - }; - - delete = async (id: string) => { - const result = await Invite.deleteOne({ _id: id }); - if (!result.deletedCount) { - throw new ApiError("Invite not found", 404); - } - return result.deletedCount === 1; - }; -} - -export default InviteService; diff --git a/server/src/utils/ApiError.ts b/server/src/utils/ApiError.ts deleted file mode 100644 index 313d01c2a..000000000 --- a/server/src/utils/ApiError.ts +++ /dev/null @@ -1,15 +0,0 @@ -class ApiError extends Error { - public status: number; - - constructor(message: string, status: number = 500) { - super(message); - this.status = status; - this.name = this.constructor.name; - - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor); - } - } -} - -export default ApiError; diff --git a/server/src/utils/JWTUtils.ts b/server/src/utils/JWTUtils.ts deleted file mode 100644 index 4eebb78a0..000000000 --- a/server/src/utils/JWTUtils.ts +++ /dev/null @@ -1,22 +0,0 @@ -import jwt from "jsonwebtoken"; -import { AuthResult } from "../service/v2/business/AuthService.js"; - -const encode = (data: AuthResult): string => { - const secret = process.env.JWT_SECRET; - if (!secret) { - throw new Error("JWT_SECRET is not defined"); - } - const token = jwt.sign(data, secret, { expiresIn: "99d" }); - return token; -}; - -const decode = (token: string): AuthResult => { - const secret = process.env.JWT_SECRET; - if (!secret) { - throw new Error("JWT_SECRET is not defined"); - } - const decoded = jwt.verify(token, secret) as AuthResult; - return decoded; -}; - -export { encode, decode }; From 1383d8b59acb7db9852c410b3838d15cdeb7d693 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Sep 2025 14:12:17 -0700 Subject: [PATCH 010/103] add workflow --- .github/workflows/check-build.yml | 23 +++++++++++++++++++ ...s => 0001_migrateStatusWindowThreshold.ts} | 0 .../db/mongo/migration/{index.js => index.ts} | 0 server/tsconfig.json | 2 +- 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/check-build.yml rename server/src/db/mongo/migration/{0001_migrateStatusWindowThreshold.js => 0001_migrateStatusWindowThreshold.ts} (100%) rename server/src/db/mongo/migration/{index.js => index.ts} (100%) diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml new file mode 100644 index 000000000..8417b823f --- /dev/null +++ b/.github/workflows/check-build.yml @@ -0,0 +1,23 @@ +name: Build Check ( Server) +on: + pull_request: + workflow_dispatch: + +jobs: + build-server: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install server dependencies + working-directory: server + run: npm install + + - name: Check server build + working-directory: server + run: npm run build diff --git a/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js b/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts similarity index 100% rename from server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js rename to server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts diff --git a/server/src/db/mongo/migration/index.js b/server/src/db/mongo/migration/index.ts similarity index 100% rename from server/src/db/mongo/migration/index.js rename to server/src/db/mongo/migration/index.ts diff --git a/server/tsconfig.json b/server/tsconfig.json index 3aa773ab1..260ccb964 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -22,5 +22,5 @@ "allowJs": true, "checkJs": false }, - "exclude": ["node_modules", "dist", "**/*.config.js"] + "exclude": ["node_modules", "dist/", "**/*.config.js"] } From b3b6f1d834ce64623d3c9a4c9953de79b633db62 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Sep 2025 14:23:45 -0700 Subject: [PATCH 011/103] move all v1 files to v1 dirs --- server/src/app.js | 10 +- server/src/config/routes.js | 4 +- server/src/config/services.js | 78 +-- server/src/controllers/v1/baseController.js | 2 +- .../src/controllers/v2/MonitorController.ts | 157 ++++++ server/src/db/{mongo => v1}/MongoDB.js | 2 +- .../0001_migrateStatusWindowThreshold.ts | 2 +- .../src/db/{mongo => v1}/migration/index.ts | 0 server/src/db/{ => v1}/models/AppSettings.js | 0 server/src/db/{ => v1}/models/Check.js | 0 server/src/db/{ => v1}/models/InviteToken.js | 0 .../db/{ => v1}/models/MaintenanceWindow.js | 0 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 | 1 - .../db/{mongo => v1}/modules/checkModule.js | 0 .../modules/checkModuleQueries.js | 0 .../db/{mongo => v1}/modules/inviteModule.js | 0 .../modules/maintenanceWindowModule.js | 0 .../db/{mongo => v1}/modules/monitorModule.js | 0 .../modules/monitorModuleQueries.js | 0 .../modules/notificationModule.js | 0 .../{mongo => v1}/modules/recoveryModule.js | 0 .../{mongo => v1}/modules/settingsModule.js | 0 .../{mongo => v1}/modules/statusPageModule.js | 0 .../db/{mongo => v1}/modules/userModule.js | 0 server/src/db/{mongo => v1}/utils/seedDb.js | 4 +- server/src/index.js | 4 +- .../src/middleware/{ => v1}/handleErrors.js | 6 +- server/src/middleware/{ => v1}/isAllowed.js | 6 +- .../middleware/{ => v1}/languageMiddleware.js | 2 +- server/src/middleware/{ => v1}/rateLimiter.js | 0 .../middleware/{ => v1}/responseHandler.js | 0 .../src/middleware/{ => v1}/sanitization.js | 0 server/src/middleware/{ => v1}/verifyJWT.js | 6 +- .../middleware/{ => v1}/verifyOwnership.js | 6 +- .../middleware/{ => v1}/verifySuperAdmin.js | 2 +- .../middleware/{ => v1}/verifyTeamAccess.js | 0 server/src/routes/v1/authRoute.js | 4 +- server/src/routes/v1/checkRoute.js | 2 +- server/src/routes/v1/diagnosticRoute.js | 4 +- server/src/routes/v1/inviteRoute.js | 4 +- server/src/routes/v1/logRoutes.js | 2 +- .../src/routes/v1/maintenanceWindowRoute.js | 2 +- server/src/routes/v1/monitorRoute.js | 2 +- server/src/routes/v1/queueRoute.js | 2 +- server/src/routes/v1/settingsRoute.js | 2 +- server/src/routes/v1/statusPageRoute.js | 2 +- .../service/{ => v1}/business/checkService.js | 0 .../{ => v1}/business/diagnosticService.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 | 10 +- .../{ => v1}/infrastructure/bufferService.js | 0 .../{ => v1}/infrastructure/emailService.js | 0 .../{ => v1}/infrastructure/errorService.js | 0 .../{ => v1}/infrastructure/networkService.js | 0 .../infrastructure/notificationService.js | 0 .../infrastructure/notificationUtils.js | 0 .../{ => v1}/infrastructure/statusService.js | 2 +- .../{ => v1}/system/serviceRegistry.js | 0 .../{ => v1}/system/settingsService.js | 0 .../service/{ => v1}/system/stringService.js | 0 .../{ => v1}/system/translationService.js | 0 .../src/service/v2/business/CheckService.ts | 132 +++++ .../src/service/v2/business/MonitorService.ts | 464 ++++++++++++++++++ 73 files changed, 840 insertions(+), 88 deletions(-) create mode 100644 server/src/controllers/v2/MonitorController.ts rename server/src/db/{mongo => v1}/MongoDB.js (97%) rename server/src/db/{mongo => v1}/migration/0001_migrateStatusWindowThreshold.ts (92%) rename server/src/db/{mongo => v1}/migration/index.ts (100%) rename server/src/db/{ => v1}/models/AppSettings.js (100%) rename server/src/db/{ => v1}/models/Check.js (100%) rename server/src/db/{ => v1}/models/InviteToken.js (100%) rename server/src/db/{ => v1}/models/MaintenanceWindow.js (100%) 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 (98%) rename server/src/db/{mongo => v1}/modules/checkModule.js (100%) rename server/src/db/{mongo => v1}/modules/checkModuleQueries.js (100%) rename server/src/db/{mongo => v1}/modules/inviteModule.js (100%) rename server/src/db/{mongo => v1}/modules/maintenanceWindowModule.js (100%) rename server/src/db/{mongo => v1}/modules/monitorModule.js (100%) rename server/src/db/{mongo => v1}/modules/monitorModuleQueries.js (100%) rename server/src/db/{mongo => v1}/modules/notificationModule.js (100%) rename server/src/db/{mongo => v1}/modules/recoveryModule.js (100%) rename server/src/db/{mongo => v1}/modules/settingsModule.js (100%) rename server/src/db/{mongo => v1}/modules/statusPageModule.js (100%) rename server/src/db/{mongo => v1}/modules/userModule.js (100%) rename server/src/db/{mongo => v1}/utils/seedDb.js (95%) rename server/src/middleware/{ => v1}/handleErrors.js (71%) rename server/src/middleware/{ => v1}/isAllowed.js (87%) rename server/src/middleware/{ => v1}/languageMiddleware.js (93%) rename server/src/middleware/{ => v1}/rateLimiter.js (100%) rename server/src/middleware/{ => v1}/responseHandler.js (100%) rename server/src/middleware/{ => v1}/sanitization.js (100%) rename server/src/middleware/{ => v1}/verifyJWT.js (87%) rename server/src/middleware/{ => v1}/verifyOwnership.js (88%) rename server/src/middleware/{ => v1}/verifySuperAdmin.js (97%) rename server/src/middleware/{ => v1}/verifyTeamAccess.js (100%) rename server/src/service/{ => v1}/business/checkService.js (100%) rename server/src/service/{ => v1}/business/diagnosticService.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 (90%) 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/notificationService.js (100%) rename server/src/service/{ => v1}/infrastructure/notificationUtils.js (100%) rename server/src/service/{ => v1}/infrastructure/statusService.js (99%) 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/service/v2/business/CheckService.ts create mode 100644 server/src/service/v2/business/MonitorService.ts diff --git a/server/src/app.js b/server/src/app.js index 9cabda6b6..253baee88 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -1,16 +1,16 @@ import express from "express"; import path from "path"; -import { responseHandler } from "./middleware/responseHandler.js"; +import { responseHandler } from "./middleware/v1/responseHandler.js"; import cors from "cors"; import helmet from "helmet"; import compression from "compression"; import cookieParser from "cookie-parser"; -import languageMiddleware from "./middleware/languageMiddleware.js"; +import languageMiddleware from "./middleware/v1/languageMiddleware.js"; import swaggerUi from "swagger-ui-express"; -import { handleErrors } from "./middleware/handleErrors.js"; +import { handleErrors } from "./middleware/v1/handleErrors.js"; import { setupRoutes } from "./config/routes.js"; -import { generalApiLimiter } from "./middleware/rateLimiter.js"; -import { sanitizeBody, sanitizeQuery } from "./middleware/sanitization.js"; +import { generalApiLimiter } from "./middleware/v1/rateLimiter.js"; +import { sanitizeBody, sanitizeQuery } from "./middleware/v1/sanitization.js"; export const createApp = ({ services, controllers, envSettings, frontendPath, openApiSpec }) => { const allowedOrigin = envSettings.clientHost; diff --git a/server/src/config/routes.js b/server/src/config/routes.js index 2ec93a2dc..40bf8e994 100644 --- a/server/src/config/routes.js +++ b/server/src/config/routes.js @@ -1,5 +1,5 @@ -import { verifyJWT } from "../middleware/verifyJWT.js"; -import { authApiLimiter } from "../middleware/rateLimiter.js"; +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"; diff --git a/server/src/config/services.js b/server/src/config/services.js index 08c2525f3..22f978071 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -1,22 +1,22 @@ -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/mongo/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 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 papaparse from "papaparse"; import axios from "axios"; import got from "got"; @@ -46,27 +46,27 @@ import { GenerateAvatarImage } from "../utils/imageProcessing.js"; import { ParseBoolean } from "../utils/utils.js"; // Models -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 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 InviteModule from "../db/mongo/modules/inviteModule.js"; -import CheckModule from "../db/mongo/modules/checkModule.js"; -import StatusPageModule from "../db/mongo/modules/statusPageModule.js"; -import UserModule from "../db/mongo/modules/userModule.js"; -import MaintenanceWindowModule from "../db/mongo/modules/maintenanceWindowModule.js"; -import MonitorModule from "../db/mongo/modules/monitorModule.js"; -import NotificationModule from "../db/mongo/modules/notificationModule.js"; -import RecoveryModule from "../db/mongo/modules/recoveryModule.js"; -import SettingsModule from "../db/mongo/modules/settingsModule.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"; export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); diff --git a/server/src/controllers/v1/baseController.js b/server/src/controllers/v1/baseController.js index 314905c82..e8612cec5 100644 --- a/server/src/controllers/v1/baseController.js +++ b/server/src/controllers/v1/baseController.js @@ -1,4 +1,4 @@ -import { AppError } from "../../service/infrastructure/errorService.js"; +import { AppError } from "../../service/v1/infrastructure/errorService.js"; export const createCommonDependencies = (db, errorService, logger, stringService) => { return { diff --git a/server/src/controllers/v2/MonitorController.ts b/server/src/controllers/v2/MonitorController.ts new file mode 100644 index 000000000..6493b7f44 --- /dev/null +++ b/server/src/controllers/v2/MonitorController.ts @@ -0,0 +1,157 @@ +import { Request, Response, NextFunction } from "express"; +import ApiError from "../../utils/ApiError.js"; +import MonitorService from "../../service/v2/business/MonitorService.js"; +import { MonitorType } from "../../db/v1/models/Monitor.js"; +class MonitorController { + private monitorService: MonitorService; + constructor(monitorService: MonitorService) { + this.monitorService = monitorService; + } + + create = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + + const monitor = await this.monitorService.create(tokenizedUser, req.body); + res.status(201).json({ + message: "Monitor created successfully", + data: monitor, + }); + } catch (error) { + next(error); + } + }; + + get = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + + const id = req.params.id; + if (!id) { + throw new ApiError("Monitor ID is required", 400); + } + + const range = req.query.range; + if (!range || typeof range !== "string") throw new ApiError("Range query parameter is required", 400); + + let monitor; + + const status = req.query.status; + if (status && typeof status !== "string") { + throw new ApiError("Status query parameter must be a string", 400); + } + + if (req.query.embedChecks === "true") { + monitor = await this.monitorService.getEmbedChecks(id, range, status); + } else { + monitor = await this.monitorService.get(id); + } + + res.status(200).json({ + message: "Monitor retrieved successfully", + data: monitor, + }); + } catch (error) { + next(error); + } + }; + + getAll = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + + let monitors; + if (req.query.embedChecks === "true") { + const page = Math.max(1, Number(req.query.page) || 1); + const limit = Math.max(1, Number(req.query.limit) || 10); + const type: MonitorType[] = req.query.type as MonitorType[]; + + monitors = await this.monitorService.getAllEmbedChecks(page, limit, type); + } else { + monitors = await this.monitorService.getAll(); + } + + res.status(200).json({ + message: "Monitors retrieved successfully", + data: monitors, + }); + } catch (error) { + next(error); + } + }; + + toggleActive = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + + const id = req.params.id; + if (!id) { + throw new ApiError("Monitor ID is required", 400); + } + + const monitor = await this.monitorService.toggleActive(id, tokenizedUser); + res.status(200).json({ + message: "Monitor paused/unpaused successfully", + data: monitor, + }); + } catch (error) { + next(error); + } + }; + + update = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + + const id = req.params.id; + if (!id) { + throw new ApiError("Monitor ID is required", 400); + } + + const monitor = await this.monitorService.update(tokenizedUser, id, req.body); + res.status(200).json({ + message: "Monitor updated successfully", + data: monitor, + }); + } catch (error) { + next(error); + } + }; + + delete = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + const id = req.params.id; + if (!id) { + throw new ApiError("Monitor ID is required", 400); + } + await this.monitorService.delete(id); + + res.status(200).json({ + message: "Monitor deleted successfully", + }); + } catch (error) { + next(error); + } + }; +} + +export default MonitorController; diff --git a/server/src/db/mongo/MongoDB.js b/server/src/db/v1/MongoDB.js similarity index 97% rename from server/src/db/mongo/MongoDB.js rename to server/src/db/v1/MongoDB.js index 74fcbd89f..3bdae3525 100755 --- a/server/src/db/mongo/MongoDB.js +++ b/server/src/db/v1/MongoDB.js @@ -1,5 +1,5 @@ import mongoose from "mongoose"; -import AppSettings from "../models/AppSettings.js"; +import AppSettings from "./models/AppSettings.js"; import { runMigrations } from "./migration/index.js"; class MongoDB { static SERVICE_NAME = "MongoDB"; diff --git a/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts b/server/src/db/v1/migration/0001_migrateStatusWindowThreshold.ts similarity index 92% rename from server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts rename to server/src/db/v1/migration/0001_migrateStatusWindowThreshold.ts index 8f6988de4..857d36381 100644 --- a/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts +++ b/server/src/db/v1/migration/0001_migrateStatusWindowThreshold.ts @@ -1,4 +1,4 @@ -import Monitor from "../../models/Monitor.js"; +import Monitor from "../models/Monitor.js"; async function migrateStatusWindowThreshold() { try { const monitors = await Monitor.find({ statusWindowThreshold: { $lt: 1 } }); diff --git a/server/src/db/mongo/migration/index.ts b/server/src/db/v1/migration/index.ts similarity index 100% rename from server/src/db/mongo/migration/index.ts rename to server/src/db/v1/migration/index.ts diff --git a/server/src/db/models/AppSettings.js b/server/src/db/v1/models/AppSettings.js similarity index 100% rename from server/src/db/models/AppSettings.js rename to server/src/db/v1/models/AppSettings.js diff --git a/server/src/db/models/Check.js b/server/src/db/v1/models/Check.js similarity index 100% rename from server/src/db/models/Check.js rename to server/src/db/v1/models/Check.js diff --git a/server/src/db/models/InviteToken.js b/server/src/db/v1/models/InviteToken.js similarity index 100% rename from server/src/db/models/InviteToken.js rename to server/src/db/v1/models/InviteToken.js diff --git a/server/src/db/models/MaintenanceWindow.js b/server/src/db/v1/models/MaintenanceWindow.js similarity index 100% rename from server/src/db/models/MaintenanceWindow.js rename to server/src/db/v1/models/MaintenanceWindow.js diff --git a/server/src/db/models/Monitor.js b/server/src/db/v1/models/Monitor.js similarity index 100% rename from server/src/db/models/Monitor.js rename to server/src/db/v1/models/Monitor.js diff --git a/server/src/db/models/MonitorStats.js b/server/src/db/v1/models/MonitorStats.js similarity index 100% rename from server/src/db/models/MonitorStats.js rename to server/src/db/v1/models/MonitorStats.js diff --git a/server/src/db/models/Notification.js b/server/src/db/v1/models/Notification.js similarity index 100% rename from server/src/db/models/Notification.js rename to server/src/db/v1/models/Notification.js diff --git a/server/src/db/models/RecoveryToken.js b/server/src/db/v1/models/RecoveryToken.js similarity index 100% rename from server/src/db/models/RecoveryToken.js rename to server/src/db/v1/models/RecoveryToken.js diff --git a/server/src/db/models/StatusPage.js b/server/src/db/v1/models/StatusPage.js similarity index 100% rename from server/src/db/models/StatusPage.js rename to server/src/db/v1/models/StatusPage.js diff --git a/server/src/db/models/Team.js b/server/src/db/v1/models/Team.js similarity index 100% rename from server/src/db/models/Team.js rename to server/src/db/v1/models/Team.js diff --git a/server/src/db/models/User.js b/server/src/db/v1/models/User.js similarity index 98% rename from server/src/db/models/User.js rename to server/src/db/v1/models/User.js index 9368d98b1..bf3012b26 100755 --- a/server/src/db/models/User.js +++ b/server/src/db/v1/models/User.js @@ -1,6 +1,5 @@ import mongoose from "mongoose"; import bcrypt from "bcryptjs"; -import logger from "../../utils/logger.js"; import Monitor from "./Monitor.js"; import Team from "./Team.js"; import Notification from "./Notification.js"; diff --git a/server/src/db/mongo/modules/checkModule.js b/server/src/db/v1/modules/checkModule.js similarity index 100% rename from server/src/db/mongo/modules/checkModule.js rename to server/src/db/v1/modules/checkModule.js diff --git a/server/src/db/mongo/modules/checkModuleQueries.js b/server/src/db/v1/modules/checkModuleQueries.js similarity index 100% rename from server/src/db/mongo/modules/checkModuleQueries.js rename to server/src/db/v1/modules/checkModuleQueries.js diff --git a/server/src/db/mongo/modules/inviteModule.js b/server/src/db/v1/modules/inviteModule.js similarity index 100% rename from server/src/db/mongo/modules/inviteModule.js rename to server/src/db/v1/modules/inviteModule.js diff --git a/server/src/db/mongo/modules/maintenanceWindowModule.js b/server/src/db/v1/modules/maintenanceWindowModule.js similarity index 100% rename from server/src/db/mongo/modules/maintenanceWindowModule.js rename to server/src/db/v1/modules/maintenanceWindowModule.js diff --git a/server/src/db/mongo/modules/monitorModule.js b/server/src/db/v1/modules/monitorModule.js similarity index 100% rename from server/src/db/mongo/modules/monitorModule.js rename to server/src/db/v1/modules/monitorModule.js diff --git a/server/src/db/mongo/modules/monitorModuleQueries.js b/server/src/db/v1/modules/monitorModuleQueries.js similarity index 100% rename from server/src/db/mongo/modules/monitorModuleQueries.js rename to server/src/db/v1/modules/monitorModuleQueries.js diff --git a/server/src/db/mongo/modules/notificationModule.js b/server/src/db/v1/modules/notificationModule.js similarity index 100% rename from server/src/db/mongo/modules/notificationModule.js rename to server/src/db/v1/modules/notificationModule.js diff --git a/server/src/db/mongo/modules/recoveryModule.js b/server/src/db/v1/modules/recoveryModule.js similarity index 100% rename from server/src/db/mongo/modules/recoveryModule.js rename to server/src/db/v1/modules/recoveryModule.js diff --git a/server/src/db/mongo/modules/settingsModule.js b/server/src/db/v1/modules/settingsModule.js similarity index 100% rename from server/src/db/mongo/modules/settingsModule.js rename to server/src/db/v1/modules/settingsModule.js diff --git a/server/src/db/mongo/modules/statusPageModule.js b/server/src/db/v1/modules/statusPageModule.js similarity index 100% rename from server/src/db/mongo/modules/statusPageModule.js rename to server/src/db/v1/modules/statusPageModule.js diff --git a/server/src/db/mongo/modules/userModule.js b/server/src/db/v1/modules/userModule.js similarity index 100% rename from server/src/db/mongo/modules/userModule.js rename to server/src/db/v1/modules/userModule.js diff --git a/server/src/db/mongo/utils/seedDb.js b/server/src/db/v1/utils/seedDb.js similarity index 95% rename from server/src/db/mongo/utils/seedDb.js rename to server/src/db/v1/utils/seedDb.js index 224744a51..cdfe3675c 100755 --- a/server/src/db/mongo/utils/seedDb.js +++ b/server/src/db/v1/utils/seedDb.js @@ -1,5 +1,5 @@ -import Monitor from "../../models/Monitor.js"; -import Check from "../../models/Check.js"; +import Monitor from "../models/Monitor.js"; +import Check from "../models/Check.js"; import { logger } from "../../../utils/logger.js"; const generateRandomUrl = () => { diff --git a/server/src/index.js b/server/src/index.js index d1ff649eb..e8b756d0e 100755 --- a/server/src/index.js +++ b/server/src/index.js @@ -7,8 +7,8 @@ import path from "path"; import fs from "fs"; import Logger from "./utils/logger.js"; -import SettingsService from "./service/system/settingsService.js"; -import AppSettings from "./db/models/AppSettings.js"; +import SettingsService from "./service/v1/system/settingsService.js"; +import AppSettings from "./db/v1/models/AppSettings.js"; const SERVICE_NAME = "Server"; let logger; diff --git a/server/src/middleware/handleErrors.js b/server/src/middleware/v1/handleErrors.js similarity index 71% rename from server/src/middleware/handleErrors.js rename to server/src/middleware/v1/handleErrors.js index b0ded5ab7..43671fbe2 100755 --- a/server/src/middleware/handleErrors.js +++ b/server/src/middleware/v1/handleErrors.js @@ -1,6 +1,6 @@ -import { logger } from "../utils/logger.js"; -import ServiceRegistry from "../service/system/serviceRegistry.js"; -import StringService from "../service/system/stringService.js"; +import { logger } from "../../utils/logger.js"; +import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; +import StringService from "../../service/v1/system/stringService.js"; const handleErrors = (error, req, res, next) => { const status = error.status || 500; diff --git a/server/src/middleware/isAllowed.js b/server/src/middleware/v1/isAllowed.js similarity index 87% rename from server/src/middleware/isAllowed.js rename to server/src/middleware/v1/isAllowed.js index 5d61974be..cc240f531 100755 --- a/server/src/middleware/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/system/serviceRegistry.js"; -import StringService from "../service/system/stringService.js"; -import SettingsService from "../service/system/settingsService.js"; +import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; +import StringService from "../../service/v1/system/stringService.js"; +import SettingsService from "../../service/v1/system/settingsService.js"; const isAllowed = (allowedRoles) => { return (req, res, next) => { diff --git a/server/src/middleware/languageMiddleware.js b/server/src/middleware/v1/languageMiddleware.js similarity index 93% rename from server/src/middleware/languageMiddleware.js rename to server/src/middleware/v1/languageMiddleware.js index 6293dc837..819858a21 100755 --- a/server/src/middleware/languageMiddleware.js +++ b/server/src/middleware/v1/languageMiddleware.js @@ -1,4 +1,4 @@ -import { logger } from "../utils/logger.js"; +import { logger } from "../../utils/logger.js"; const languageMiddleware = (stringService, translationService) => async (req, res, next) => { try { diff --git a/server/src/middleware/rateLimiter.js b/server/src/middleware/v1/rateLimiter.js similarity index 100% rename from server/src/middleware/rateLimiter.js rename to server/src/middleware/v1/rateLimiter.js diff --git a/server/src/middleware/responseHandler.js b/server/src/middleware/v1/responseHandler.js similarity index 100% rename from server/src/middleware/responseHandler.js rename to server/src/middleware/v1/responseHandler.js diff --git a/server/src/middleware/sanitization.js b/server/src/middleware/v1/sanitization.js similarity index 100% rename from server/src/middleware/sanitization.js rename to server/src/middleware/v1/sanitization.js diff --git a/server/src/middleware/verifyJWT.js b/server/src/middleware/v1/verifyJWT.js similarity index 87% rename from server/src/middleware/verifyJWT.js rename to server/src/middleware/v1/verifyJWT.js index d01db0a97..b57391dfd 100755 --- a/server/src/middleware/verifyJWT.js +++ b/server/src/middleware/v1/verifyJWT.js @@ -1,7 +1,7 @@ import jwt from "jsonwebtoken"; -import ServiceRegistry from "../service/system/serviceRegistry.js"; -import SettingsService from "../service/system/settingsService.js"; -import StringService from "../service/system/stringService.js"; +import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; +import SettingsService from "../../service/v1/system/settingsService.js"; +import StringService from "../../service/v1/system/stringService.js"; const SERVICE_NAME = "verifyJWT"; const TOKEN_PREFIX = "Bearer "; diff --git a/server/src/middleware/verifyOwnership.js b/server/src/middleware/v1/verifyOwnership.js similarity index 88% rename from server/src/middleware/verifyOwnership.js rename to server/src/middleware/v1/verifyOwnership.js index 4431b0c26..741fb6436 100755 --- a/server/src/middleware/verifyOwnership.js +++ b/server/src/middleware/v1/verifyOwnership.js @@ -1,6 +1,6 @@ -import { logger } from "../utils/logger.js"; -import ServiceRegistry from "../service/system/serviceRegistry.js"; -import StringService from "../service/system/stringService.js"; +import { logger } from "../../utils/logger.js"; +import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; +import StringService from "../../service/v1/system/stringService.js"; import { ObjectId } from "mongodb"; const SERVICE_NAME = "verifyOwnership"; diff --git a/server/src/middleware/verifySuperAdmin.js b/server/src/middleware/v1/verifySuperAdmin.js similarity index 97% rename from server/src/middleware/verifySuperAdmin.js rename to server/src/middleware/v1/verifySuperAdmin.js index a5a93eede..41ac0a3e0 100755 --- a/server/src/middleware/verifySuperAdmin.js +++ b/server/src/middleware/v1/verifySuperAdmin.js @@ -1,5 +1,5 @@ const jwt = require("jsonwebtoken"); -const logger = require("../utils/logger"); +const logger = require("../../utils/logger.js"); const SERVICE_NAME = "verifyAdmin"; const TOKEN_PREFIX = "Bearer "; import ServiceRegistry from "../service/serviceRegistry.js"; diff --git a/server/src/middleware/verifyTeamAccess.js b/server/src/middleware/v1/verifyTeamAccess.js similarity index 100% rename from server/src/middleware/verifyTeamAccess.js rename to server/src/middleware/v1/verifyTeamAccess.js diff --git a/server/src/routes/v1/authRoute.js b/server/src/routes/v1/authRoute.js index f69f94f43..d9b036b05 100755 --- a/server/src/routes/v1/authRoute.js +++ b/server/src/routes/v1/authRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/verifyJWT.js"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; import multer from "multer"; const upload = multer(); diff --git a/server/src/routes/v1/checkRoute.js b/server/src/routes/v1/checkRoute.js index 673fe51e0..78f6b8a42 100755 --- a/server/src/routes/v1/checkRoute.js +++ b/server/src/routes/v1/checkRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class CheckRoutes { constructor(checkController) { diff --git a/server/src/routes/v1/diagnosticRoute.js b/server/src/routes/v1/diagnosticRoute.js index 1b7cc06be..2cf481e08 100755 --- a/server/src/routes/v1/diagnosticRoute.js +++ b/server/src/routes/v1/diagnosticRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/verifyJWT.js"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class DiagnosticRoutes { constructor(diagnosticController) { diff --git a/server/src/routes/v1/inviteRoute.js b/server/src/routes/v1/inviteRoute.js index 74fcfeb91..a47550536 100755 --- a/server/src/routes/v1/inviteRoute.js +++ b/server/src/routes/v1/inviteRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/verifyJWT.js"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class InviteRoutes { constructor(inviteController) { diff --git a/server/src/routes/v1/logRoutes.js b/server/src/routes/v1/logRoutes.js index feabe669a..43da8b75a 100755 --- a/server/src/routes/v1/logRoutes.js +++ b/server/src/routes/v1/logRoutes.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class LogRoutes { constructor(logController) { this.router = Router(); diff --git a/server/src/routes/v1/maintenanceWindowRoute.js b/server/src/routes/v1/maintenanceWindowRoute.js index 622e98f88..3df040994 100755 --- a/server/src/routes/v1/maintenanceWindowRoute.js +++ b/server/src/routes/v1/maintenanceWindowRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import MaintenanceWindow from "../../db/models/MaintenanceWindow.js"; +import MaintenanceWindow from "../../db/v1/models/MaintenanceWindow.js"; class MaintenanceWindowRoutes { constructor(maintenanceWindowController) { this.router = Router(); diff --git a/server/src/routes/v1/monitorRoute.js b/server/src/routes/v1/monitorRoute.js index dde72e256..107964f6b 100755 --- a/server/src/routes/v1/monitorRoute.js +++ b/server/src/routes/v1/monitorRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; import multer from "multer"; import { fetchMonitorCertificate } from "../../controllers/v1/controllerUtils.js"; const upload = multer({ diff --git a/server/src/routes/v1/queueRoute.js b/server/src/routes/v1/queueRoute.js index 59e1cb744..c6cfa05da 100755 --- a/server/src/routes/v1/queueRoute.js +++ b/server/src/routes/v1/queueRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class QueueRoutes { constructor(queueController) { this.router = Router(); diff --git a/server/src/routes/v1/settingsRoute.js b/server/src/routes/v1/settingsRoute.js index 76556e923..e88186af2 100755 --- a/server/src/routes/v1/settingsRoute.js +++ b/server/src/routes/v1/settingsRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class SettingsRoutes { constructor(settingsController) { diff --git a/server/src/routes/v1/statusPageRoute.js b/server/src/routes/v1/statusPageRoute.js index 8300066a8..2c3b0b265 100755 --- a/server/src/routes/v1/statusPageRoute.js +++ b/server/src/routes/v1/statusPageRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/verifyJWT.js"; +import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; import multer from "multer"; const upload = multer(); diff --git a/server/src/service/business/checkService.js b/server/src/service/v1/business/checkService.js similarity index 100% rename from server/src/service/business/checkService.js rename to server/src/service/v1/business/checkService.js diff --git a/server/src/service/business/diagnosticService.js b/server/src/service/v1/business/diagnosticService.js similarity index 100% rename from server/src/service/business/diagnosticService.js rename to server/src/service/v1/business/diagnosticService.js diff --git a/server/src/service/business/inviteService.js b/server/src/service/v1/business/inviteService.js similarity index 100% rename from server/src/service/business/inviteService.js rename to server/src/service/v1/business/inviteService.js diff --git a/server/src/service/business/maintenanceWindowService.js b/server/src/service/v1/business/maintenanceWindowService.js similarity index 100% rename from server/src/service/business/maintenanceWindowService.js rename to server/src/service/v1/business/maintenanceWindowService.js diff --git a/server/src/service/business/monitorService.js b/server/src/service/v1/business/monitorService.js similarity index 99% rename from server/src/service/business/monitorService.js rename to server/src/service/v1/business/monitorService.js index 637870b71..061234102 100644 --- a/server/src/service/business/monitorService.js +++ b/server/src/service/v1/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/business/userService.js b/server/src/service/v1/business/userService.js similarity index 100% rename from server/src/service/business/userService.js rename to server/src/service/v1/business/userService.js diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js b/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js similarity index 100% rename from server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js rename to server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js b/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js similarity index 90% rename from server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js rename to server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js index b348e2534..2833a59f5 100644 --- a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js +++ b/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js @@ -5,11 +5,11 @@ class SuperSimpleQueueHelper { /** * @param {{ - * db: import("../database").Database, - * logger: import("../logger").Logger, - * networkService: import("../networkService").NetworkService, - * statusService: import("../statusService").StatusService, - * notificationService: import("../notificationService").NotificationService + * db: import("../database.js").Database, + * logger: import("../logger.js").Logger, + * networkService: import("../networkService.js").NetworkService, + * statusService: import("../statusService.js").StatusService, + * notificationService: import("../notificationService.js").NotificationService * }} */ constructor({ db, logger, networkService, statusService, notificationService }) { diff --git a/server/src/service/infrastructure/bufferService.js b/server/src/service/v1/infrastructure/bufferService.js similarity index 100% rename from server/src/service/infrastructure/bufferService.js rename to server/src/service/v1/infrastructure/bufferService.js diff --git a/server/src/service/infrastructure/emailService.js b/server/src/service/v1/infrastructure/emailService.js similarity index 100% rename from server/src/service/infrastructure/emailService.js rename to server/src/service/v1/infrastructure/emailService.js diff --git a/server/src/service/infrastructure/errorService.js b/server/src/service/v1/infrastructure/errorService.js similarity index 100% rename from server/src/service/infrastructure/errorService.js rename to server/src/service/v1/infrastructure/errorService.js diff --git a/server/src/service/infrastructure/networkService.js b/server/src/service/v1/infrastructure/networkService.js similarity index 100% rename from server/src/service/infrastructure/networkService.js rename to server/src/service/v1/infrastructure/networkService.js diff --git a/server/src/service/infrastructure/notificationService.js b/server/src/service/v1/infrastructure/notificationService.js similarity index 100% rename from server/src/service/infrastructure/notificationService.js rename to server/src/service/v1/infrastructure/notificationService.js diff --git a/server/src/service/infrastructure/notificationUtils.js b/server/src/service/v1/infrastructure/notificationUtils.js similarity index 100% rename from server/src/service/infrastructure/notificationUtils.js rename to server/src/service/v1/infrastructure/notificationUtils.js diff --git a/server/src/service/infrastructure/statusService.js b/server/src/service/v1/infrastructure/statusService.js similarity index 99% rename from server/src/service/infrastructure/statusService.js rename to server/src/service/v1/infrastructure/statusService.js index 72fa359d7..248d1c910 100755 --- a/server/src/service/infrastructure/statusService.js +++ b/server/src/service/v1/infrastructure/statusService.js @@ -1,4 +1,4 @@ -import MonitorStats from "../../db/models/MonitorStats.js"; +import MonitorStats from "../../../db/v1/models/MonitorStats.js"; const SERVICE_NAME = "StatusService"; class StatusService { diff --git a/server/src/service/system/serviceRegistry.js b/server/src/service/v1/system/serviceRegistry.js similarity index 100% rename from server/src/service/system/serviceRegistry.js rename to server/src/service/v1/system/serviceRegistry.js diff --git a/server/src/service/system/settingsService.js b/server/src/service/v1/system/settingsService.js similarity index 100% rename from server/src/service/system/settingsService.js rename to server/src/service/v1/system/settingsService.js diff --git a/server/src/service/system/stringService.js b/server/src/service/v1/system/stringService.js similarity index 100% rename from server/src/service/system/stringService.js rename to server/src/service/v1/system/stringService.js diff --git a/server/src/service/system/translationService.js b/server/src/service/v1/system/translationService.js similarity index 100% rename from server/src/service/system/translationService.js rename to server/src/service/v1/system/translationService.js diff --git a/server/src/service/v2/business/CheckService.ts b/server/src/service/v2/business/CheckService.ts new file mode 100644 index 000000000..181fe048d --- /dev/null +++ b/server/src/service/v2/business/CheckService.ts @@ -0,0 +1,132 @@ +import { json } from "stream/consumers"; +import { ICheck, Check, Monitor, ISystemInfo, ICaptureInfo } from "../../../db/v1/models/index.js"; +import { MonitorType } from "../../../db/v1/models/Monitor.js"; +import { StatusResponse } from "../infrastructure/NetworkService.js"; +import type { ICapturePayload, ILighthousePayload } from "../infrastructure/NetworkService.js"; +import mongoose from "mongoose"; + +export interface ICheckService { + buildCheck: (statusResponse: StatusResponse, type: MonitorType) => Promise; + cleanupOrphanedChecks: () => Promise; +} + +class CheckService implements ICheckService { + private isCapturePayload = (payload: any): payload is ICapturePayload => { + if (!payload || typeof payload !== "object") return false; + + if (!("data" in payload) || typeof payload.data !== "object") { + return false; + } + + const data = payload.data as Partial; + if (!data.cpu || typeof data.cpu !== "object" || typeof data.cpu.usage_percent !== "number") { + return false; + } + s; + + if (!data.memory || typeof data.memory !== "object" || typeof data.memory.usage_percent !== "number") { + return false; + } + + if (data.disk && !Array.isArray(data.disk)) { + return false; + } + if (data.net && !Array.isArray(data.net)) { + return false; + } + + if (!("capture" in payload) || typeof payload.capture !== "object") return false; + const capture = payload.capture as Record; + if (typeof capture.version !== "string" || typeof capture.mode !== "string") return false; + + return true; + }; + + private isPagespeedPayload = (payload: any): payload is ILighthousePayload => { + if (!payload || typeof payload !== "object") return false; + + if (!("lighthouseResult" in payload) || typeof payload.lighthouseResult !== "object") { + return false; + } + return true; + }; + + private buildBaseCheck = (statusResponse: StatusResponse) => { + const monitorId = new mongoose.Types.ObjectId(statusResponse.monitorId); + const check = new Check({ + monitorId: monitorId, + type: statusResponse?.type, + status: statusResponse?.status, + message: statusResponse?.message, + responseTime: statusResponse?.responseTime, + timings: statusResponse?.timings, + }); + return check; + }; + + private buildInfrastructureCheck = (statusResponse: StatusResponse) => { + if (!this.isCapturePayload(statusResponse.payload)) { + throw new Error("Invalid payload for infrastructure monitor"); + } + const check = this.buildBaseCheck(statusResponse); + check.system = statusResponse.payload.data; + check.capture = statusResponse.payload.capture; + return check; + }; + + private buildPagespeedCheck = (statusResponse: StatusResponse) => { + if (!this.isPagespeedPayload(statusResponse.payload)) { + throw new Error("Invalid payload for pagespeed monitor"); + } + const check = this.buildBaseCheck(statusResponse); + const lighthouseResult = statusResponse?.payload?.lighthouseResult; + check.lighthouse = { + accessibility: lighthouseResult?.categories?.accessibility?.score || 0, + bestPractices: lighthouseResult?.categories?.["best-practices"]?.score || 0, + seo: lighthouseResult?.categories?.seo?.score || 0, + performance: lighthouseResult?.categories?.performance?.score || 0, + audits: { + cls: lighthouseResult?.audits?.["cumulative-layout-shift"] || {}, + si: lighthouseResult?.audits?.["speed-index"] || {}, + fcp: lighthouseResult?.audits?.["first-contentful-paint"] || {}, + lcp: lighthouseResult?.audits?.["largest-contentful-paint"] || {}, + tbt: lighthouseResult?.audits?.["total-blocking-time"] || {}, + }, + }; + return check; + }; + + buildCheck = async (statusResponse: StatusResponse, type: MonitorType): Promise => { + switch (type) { + case "infrastructure": + return this.buildInfrastructureCheck(statusResponse as StatusResponse); + + case "pagespeed": + return this.buildPagespeedCheck(statusResponse as StatusResponse); + case "http": + case "https": + return this.buildBaseCheck(statusResponse); + + case "ping": + return this.buildBaseCheck(statusResponse); + default: + throw new Error(`Unsupported monitor type: ${type}`); + } + }; + + cleanupOrphanedChecks = async () => { + try { + const monitorIds = await Monitor.find().distinct("_id"); + const result = await Check.deleteMany({ + monitorId: { $nin: monitorIds }, + }); + console.log(`Deleted ${result.deletedCount} orphaned Checks.`); + return true; + } catch (error) { + console.error("Error cleaning up orphaned Checks:", error); + return false; + } + }; +} + +export default CheckService; diff --git a/server/src/service/v2/business/MonitorService.ts b/server/src/service/v2/business/MonitorService.ts new file mode 100644 index 000000000..6e48e79e5 --- /dev/null +++ b/server/src/service/v2/business/MonitorService.ts @@ -0,0 +1,464 @@ +import mongoose from "mongoose"; + +import { IMonitor, Monitor, ITokenizedUser, MonitorStats, Check } from "../../../db/v1/models/index.js"; +import ApiError from "../../../utils/ApiError.js"; +import { IJobQueue } from "../infrastructure/JobQueue.js"; +import { MonitorWithChecksResponse } from "../../../types/monitor-response-with-checks.js"; +import { MonitorStatus, MonitorType } from "../../../db/v1/models/monitors/Monitor.js"; +export interface IMonitorService { + create: (tokenizedUser: ITokenizedUser, monitorData: IMonitor) => Promise; + getAll: () => Promise; + getAllEmbedChecks: (page: number, limit: number, type: MonitorType[]) => Promise; + get: (monitorId: string) => Promise; + getEmbedChecks: (monitorId: string, range: string, status?: string) => Promise; + toggleActive: (monitorId: string, tokenizedUser: ITokenizedUser) => Promise; + update: (tokenizedUser: ITokenizedUser, monitorId: string, updateData: Partial) => Promise; + delete: (monitorId: string) => Promise; +} + +class MonitorService implements IMonitorService { + private jobQueue: IJobQueue; + constructor(jobQueue: IJobQueue) { + this.jobQueue = jobQueue; + } + + create = async (tokenizedUser: ITokenizedUser, monitorData: IMonitor) => { + const monitor = await Monitor.create({ + ...monitorData, + createdBy: tokenizedUser.sub, + updatedBy: tokenizedUser.sub, + }); + await MonitorStats.create({ + monitorId: monitor._id, + currentStreakStartedAt: Date.now(), + }); + await this.jobQueue.addJob(monitor); + return monitor; + }; + + getAll = async () => { + return Monitor.find(); + }; + + getAllEmbedChecks = async (page: number, limit: number, type: MonitorType[] = []) => { + const skip = (page - 1) * limit; + let find = {}; + if (type.length > 0) find = { type: { $in: type } }; + const monitors = await Monitor.find(find).skip(skip).limit(limit); + return monitors; + }; + + get = async (monitorId: string) => { + const monitor = await Monitor.findById(monitorId); + if (!monitor) { + throw new ApiError("Monitor not found", 404); + } + return monitor; + }; + + private getStartDate(range: string): Date { + const now = new Date(); + switch (range) { + case "30m": + return new Date(now.getTime() - 30 * 60 * 1000); + case "24h": + return new Date(now.getTime() - 24 * 60 * 60 * 1000); + case "7d": + return new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); + case "30d": + return new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); + default: + throw new ApiError("Invalid range parameter", 400); + } + } + + private getDateFormat(range: string): string { + switch (range) { + case "30m": + return "%Y-%m-%dT%H:%M:00Z"; + case "24h": + case "7d": + return "%Y-%m-%dT%H:00:00Z"; + case "30d": + return "%Y-%m-%d"; + default: + throw new ApiError("Invalid range parameter", 400); + } + } + + private getBaseGroup = (dateFormat: string): Record => { + return { + _id: { $dateToString: { format: dateFormat, date: "$createdAt" } }, + count: { $sum: 1 }, + avgResponseTime: { $avg: "$responseTime" }, + }; + }; + + private getBaseProjection = (): object => { + return { status: 1, responseTime: 1, createdAt: 1 }; + }; + + private getPageSpeedGroup = (dateFormat: string): Record => { + return { + _id: { $dateToString: { format: dateFormat, date: "$createdAt" } }, + count: { $sum: 1 }, + avgResponseTime: { $avg: "$responseTime" }, + accessibility: { $avg: "$lighthouse.accessibility" }, + bestPractices: { $avg: "$lighthouse.bestPractices" }, + seo: { $avg: "$lighthouse.seo" }, + performance: { $avg: "$lighthouse.performance" }, + cls: { $avg: "$lighthouse.audits.cls.score" }, + si: { $avg: "$lighthouse.audits.si.score" }, + fcp: { $avg: "$lighthouse.audits.fcp.score" }, + lcp: { $avg: "$lighthouse.audits.lcp.score" }, + tbt: { $avg: "$lighthouse.audits.tbt.score" }, + }; + }; + + private getPageSpeedProjection = (): object => { + const projectStage: any = { status: 1, responseTime: 1, createdAt: 1 }; + projectStage["lighthouse.accessibility"] = 1; + projectStage["lighthouse.seo"] = 1; + projectStage["lighthouse.bestPractices"] = 1; + projectStage["lighthouse.performance"] = 1; + projectStage["lighthouse.audits.cls.score"] = 1; + projectStage["lighthouse.audits.si.score"] = 1; + projectStage["lighthouse.audits.fcp.score"] = 1; + projectStage["lighthouse.audits.lcp.score"] = 1; + projectStage["lighthouse.audits.tbt.score"] = 1; + return projectStage; + }; + + private getInfraGroup = (dateFormat: string): Record => { + return { + _id: { $dateToString: { format: dateFormat, date: "$createdAt" } }, + count: { $sum: 1 }, + avgResponseTime: { $avg: "$responseTime" }, + physicalCores: { $last: "$system.cpu.physical_core" }, + logicalCores: { $last: "$system.cpu.logical_core" }, + frequency: { $avg: "$system.cpu.frequency" }, + currentFrequency: { $last: "$system.cpu.current_frequency" }, + tempsArrays: { $push: "$system.cpu.temperature" }, + freePercent: { $avg: "$system.cpu.free_percent" }, + usedPercent: { $avg: "$system.cpu.usage_percent" }, + total_bytes: { $last: "$system.memory.total_bytes" }, + available_bytes: { $last: "$system.memory.available_bytes" }, + used_bytes: { $last: "$system.memory.used_bytes" }, + memory_usage_percent: { $avg: "$system.memory.usage_percent" }, + disksArray: { $push: "$system.disk" }, + os: { $last: "$system.host.os" }, + platform: { $last: "$system.host.platform" }, + kernel_version: { $last: "$system.host.kernel_version" }, + pretty_name: { $last: "$system.host.pretty_name" }, + netsArray: { $push: "$system.net" }, + }; + }; + + private getInfraProjection = (): object => { + const projectStage: any = { status: 1, responseTime: 1, createdAt: 1 }; + projectStage["system.cpu.physical_core"] = 1; + projectStage["system.cpu.logical_core"] = 1; + projectStage["system.cpu.frequency"] = 1; + projectStage["system.cpu.current_frequency"] = 1; + projectStage["system.cpu.temperature"] = 1; + projectStage["system.cpu.free_percent"] = 1; + projectStage["system.cpu.usage_percent"] = 1; + projectStage["system.memory.total_bytes"] = 1; + projectStage["system.memory.available_bytes"] = 1; + projectStage["system.memory.used_bytes"] = 1; + projectStage["system.memory.usage_percent"] = 1; + projectStage["system.disk"] = 1; + projectStage["system.host.os"] = 1; + projectStage["system.host.platform"] = 1; + projectStage["system.host.kernel_version"] = 1; + projectStage["system.host.pretty_name"] = 1; + projectStage["system.net"] = 1; + return projectStage; + }; + + private getFinalProjection = (type: string): object => { + if (type === "pagespeed") { + return { + _id: 1, + count: 1, + avgResponseTime: 1, + accessibility: "$accessibility", + seo: "$seo", + bestPractices: "$bestPractices", + performance: "$performance", + cls: "$cls", + si: "$si", + fcp: "$fcp", + lcp: "$lcp", + tbt: "$tbt", + }; + } + + if (type === "infrastructure") { + return { + _id: 1, + count: 1, + avgResponseTime: 1, + cpu: { + physicalCores: "$physicalCores", + logicalCores: "$logicalCores", + frequency: "$frequency", + currentFrequency: "$currentFrequency", + temperatures: { + $map: { + input: { + $range: [0, { $size: { $arrayElemAt: ["$tempsArrays", 0] } }], + }, + as: "idx", + in: { + $avg: { + $map: { + input: "$tempsArrays", + as: "arr", + in: { $arrayElemAt: ["$$arr", "$$idx"] }, + }, + }, + }, + }, + }, + freePercent: "$freePercent", + usedPercent: "$usedPercent", + }, + memory: { + total_bytes: "$total_bytes", + available_bytes: "$available_bytes", + used_bytes: "$used_bytes", + usage_percent: "$memory_usage_percent", + }, + disks: { + $map: { + input: { + $range: [0, { $size: { $arrayElemAt: ["$disksArray", 0] } }], + }, + as: "idx", + in: { + $let: { + vars: { + diskGroup: { + $map: { + input: "$disksArray", + as: "diskArr", + in: { $arrayElemAt: ["$$diskArr", "$$idx"] }, + }, + }, + }, + in: { + device: { $arrayElemAt: ["$$diskGroup.device", 0] }, + total_bytes: { $avg: "$$diskGroup.total_bytes" }, + free_bytes: { $avg: "$$diskGroup.free_bytes" }, + used_bytes: { $avg: "$$diskGroup.used_bytes" }, + usage_percent: { $avg: "$$diskGroup.usage_percent" }, + total_inodes: { $avg: "$$diskGroup.total_inodes" }, + free_inodes: { $avg: "$$diskGroup.free_inodes" }, + used_inodes: { $avg: "$$diskGroup.used_inodes" }, + inodes_usage_percent: { + $avg: "$$diskGroup.inodes_usage_percent", + }, + read_bytes: { $avg: "$$diskGroup.read_bytes" }, + write_bytes: { $avg: "$$diskGroup.write_bytes" }, + read_time: { $avg: "$$diskGroup.read_time" }, + write_time: { $avg: "$$diskGroup.write_time" }, + }, + }, + }, + }, + }, + host: { + os: "$os", + platform: "$platform", + kernel_version: "$kernel_version", + pretty_name: "$pretty_name", + }, + net: { + $map: { + input: { + $range: [0, { $size: { $arrayElemAt: ["$netsArray", 0] } }], + }, + as: "idx", + in: { + $let: { + vars: { + netGroup: { + $map: { + input: "$netsArray", + as: "netArr", + in: { $arrayElemAt: ["$$netArr", "$$idx"] }, + }, + }, + }, + in: { + name: { $arrayElemAt: ["$$netGroup.name", 0] }, + bytes_sent: { $avg: "$$netGroup.bytes_sent" }, + bytes_recv: { $avg: "$$netGroup.bytes_recv" }, + packets_sent: { $avg: "$$netGroup.packets_sent" }, + packets_recv: { $avg: "$$netGroup.packets_recv" }, + err_in: { $avg: "$$netGroup.err_in" }, + err_out: { $avg: "$$netGroup.err_out" }, + drop_in: { $avg: "$$netGroup.drop_in" }, + drop_out: { $avg: "$$netGroup.drop_out" }, + fifo_in: { $avg: "$$netGroup.fifo_in" }, + fifo_out: { $avg: "$$netGroup.fifo_out" }, + }, + }, + }, + }, + }, + }; + } + return {}; + }; + + getEmbedChecks = async (monitorId: string, range: string, status: string | undefined): Promise => { + const monitor = await Monitor.findById(monitorId); + if (!monitor) { + throw new ApiError("Monitor not found", 404); + } + const startDate = this.getStartDate(range); + const dateFormat = this.getDateFormat(range); + + // Build match stage + const matchStage: { + monitorId: mongoose.Types.ObjectId; + createdAt: { $gte: Date }; + status?: string; + } = { + monitorId: monitor._id, + createdAt: { $gte: startDate }, + }; + + if (status) { + matchStage.status = status; + } + + let groupClause; + + if (monitor.type === "pagespeed") { + groupClause = this.getPageSpeedGroup(dateFormat); + } else if (monitor.type === "infrastructure") { + groupClause = this.getInfraGroup(dateFormat); + } else { + groupClause = this.getBaseGroup(dateFormat); + } + + let projectStage; + if (monitor.type === "pagespeed") { + projectStage = this.getPageSpeedProjection(); + } else if (monitor.type === "infrastructure") { + projectStage = this.getInfraProjection(); + } else { + projectStage = this.getBaseProjection(); + } + + let finalProjection = {}; + if (monitor.type === "pagespeed" || monitor.type === "infrastructure") { + finalProjection = this.getFinalProjection(monitor.type); + } else { + finalProjection = { _id: 1, count: 1, avgResponseTime: 1 }; + } + + const checks = await Check.aggregate([ + { + $match: matchStage, + }, + { $sort: { createdAt: 1 } }, + { $project: projectStage }, + { $group: groupClause }, + { $sort: { _id: -1 } }, + { + $project: finalProjection, + }, + ]); + + // Get monitor stats + const monitorStats = await MonitorStats.findOne({ + monitorId: monitor._id, + }).lean(); + + if (!monitorStats) { + throw new ApiError("Monitor stats not found", 404); + } + + return { + monitor: monitor.toObject(), + checks, + stats: monitorStats, + }; + }; + + async toggleActive(id: string, tokenizedUser: ITokenizedUser) { + const pendingStatus: MonitorStatus = "initializing"; + const updatedMonitor = await Monitor.findOneAndUpdate( + { _id: id }, + [ + { + $set: { + isActive: { $not: "$isActive" }, + status: pendingStatus, + updatedBy: tokenizedUser.sub, + updatedAt: new Date(), + }, + }, + ], + { new: true } + ); + + if (!updatedMonitor) { + throw new ApiError("Monitor not found", 404); + } + + await this.jobQueue.updateJob(updatedMonitor); + + if (updatedMonitor?.isActive) { + await this.jobQueue.resumeJob(updatedMonitor); + } else { + await this.jobQueue.pauseJob(updatedMonitor); + } + return updatedMonitor; + } + + async update(tokenizedUser: ITokenizedUser, monitorId: string, updateData: Partial) { + const allowedFields: (keyof IMonitor)[] = ["name", "interval", "isActive", "n", "m", "notificationChannels"]; + const safeUpdate: Partial = {}; + + for (const field of allowedFields) { + if (updateData[field] !== undefined) { + (safeUpdate as any)[field] = updateData[field]; + } + } + + const updatedMonitor = await Monitor.findByIdAndUpdate( + monitorId, + { + $set: { + ...safeUpdate, + updatedAt: new Date(), + updatedBy: tokenizedUser.sub, + }, + }, + { new: true, runValidators: true } + ); + + if (!updatedMonitor) { + throw new ApiError("Monitor not found", 404); + } + await this.jobQueue.updateJob(updatedMonitor); + return updatedMonitor; + } + + async delete(monitorId: string) { + const monitor = await Monitor.findById(monitorId); + if (!monitor) { + throw new ApiError("Monitor not found", 404); + } + await monitor.deleteOne(); + await this.jobQueue.deleteJob(monitor); + return true; + } +} + +export default MonitorService; From 059408f098a9307884cc34c7bc4d9d29296b7a7b Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Sep 2025 14:40:57 -0700 Subject: [PATCH 012/103] add v2 models --- server/src/db/v2/models/auth/Role.ts | 50 +++ server/src/db/v2/models/auth/User.ts | 108 +++++++ server/src/db/v2/models/checks/Check.ts | 297 ++++++++++++++++++ server/src/db/v2/models/index.ts | 33 ++ server/src/db/v2/models/invite/Invite.ts | 43 +++ .../db/v2/models/maintenance/Maintenance.ts | 39 +++ server/src/db/v2/models/monitors/Monitor.ts | 100 ++++++ .../src/db/v2/models/monitors/MonitorStats.ts | 77 +++++ .../NotificationChannel.ts | 50 +++ 9 files changed, 797 insertions(+) create mode 100644 server/src/db/v2/models/auth/Role.ts create mode 100644 server/src/db/v2/models/auth/User.ts create mode 100644 server/src/db/v2/models/checks/Check.ts create mode 100644 server/src/db/v2/models/index.ts create mode 100644 server/src/db/v2/models/invite/Invite.ts create mode 100644 server/src/db/v2/models/maintenance/Maintenance.ts create mode 100644 server/src/db/v2/models/monitors/Monitor.ts create mode 100644 server/src/db/v2/models/monitors/MonitorStats.ts create mode 100644 server/src/db/v2/models/notification-channel/NotificationChannel.ts diff --git a/server/src/db/v2/models/auth/Role.ts b/server/src/db/v2/models/auth/Role.ts new file mode 100644 index 000000000..1031de8b2 --- /dev/null +++ b/server/src/db/v2/models/auth/Role.ts @@ -0,0 +1,50 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; + +export interface IRole extends Document { + _id: Types.ObjectId; + name: string; + description?: string; + permissions: string[]; + isSystem: boolean; + isActive: boolean; + createdAt: Date; + updatedAt: Date; +} + +const roleSchema = new Schema( + { + name: { + type: String, + required: true, + trim: true, + maxlength: 50, + }, + description: { + type: String, + trim: true, + maxlength: 200, + }, + + permissions: [ + { + type: String, + required: true, + }, + ], + isSystem: { + type: Boolean, + default: false, + }, + isActive: { + type: Boolean, + default: true, + }, + }, + { + timestamps: true, + } +); + +roleSchema.index({ name: 1 }, { unique: true }); + +export const Role = mongoose.model("Role_v2", roleSchema); diff --git a/server/src/db/v2/models/auth/User.ts b/server/src/db/v2/models/auth/User.ts new file mode 100644 index 000000000..b72a29e70 --- /dev/null +++ b/server/src/db/v2/models/auth/User.ts @@ -0,0 +1,108 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; + +export interface ITokenizedUser { + sub: string; + roles: string[]; +} +export interface IUser extends Document { + _id: Types.ObjectId; + email: string; + firstName: string; + lastName: string; + passwordHash: string; + roles: Types.ObjectId[]; + profile: { + avatar?: string; + bio?: string; + timezone: string; + locale: string; + }; + preferences: { + theme: "light" | "dark" | "system"; + }; + lastLoginAt?: Date; + isActive: boolean; + isVerified: boolean; + createdAt: Date; + updatedAt: Date; +} + +const userSchema = new Schema( + { + email: { + type: String, + required: true, + unique: true, + trim: true, + lowercase: true, + }, + firstName: { + type: String, + required: true, + trim: true, + maxlength: 50, + }, + lastName: { + type: String, + required: true, + trim: true, + maxlength: 50, + }, + passwordHash: { + type: String, + required: true, + }, + roles: [ + { + type: Schema.Types.ObjectId, + ref: "Role_v2", + }, + ], + profile: { + avatar: { + type: String, + }, + bio: { + type: String, + maxlength: 200, + }, + timezone: { + type: String, + default: "UTC", + }, + locale: { + type: String, + default: "en", + }, + }, + preferences: { + theme: { + type: String, + enum: ["light", "dark", "system"], + default: "system", + }, + }, + lastLoginAt: { + type: Date, + }, + isActive: { + type: Boolean, + default: true, + }, + isVerified: { + type: Boolean, + default: false, + }, + }, + { + timestamps: true, + toJSON: { + transform: function (_doc, ret) { + delete ret.passwordHash; + return ret; + }, + }, + } +); + +export const User = mongoose.model("User_v2", userSchema); diff --git a/server/src/db/v2/models/checks/Check.ts b/server/src/db/v2/models/checks/Check.ts new file mode 100644 index 000000000..83bbc9f91 --- /dev/null +++ b/server/src/db/v2/models/checks/Check.ts @@ -0,0 +1,297 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; +import { MonitorType, MonitorTypes, MonitorStatus, MonitorStatuses } from "../monitors/Monitor.js"; +import type { Response } from "got"; +export type GotTimings = Response["timings"]; +export interface ITimingPhases { + wait: number; + dns: number; + tcp: number; + tls: number; + request: number; + firstByte: number; + download: number; + total: number; +} + +export interface ICpuInfo { + physical_core: number; + logical_core: number; + frequency: number; + current_frequency: number; + temperature: number[]; // per-core temps + free_percent: number; + usage_percent: number; +} + +export interface IMemoryInfo { + total_bytes: number; + available_bytes: number; + used_bytes: number; + usage_percent: number; +} + +export interface IHostInfo { + os?: string; + platform?: string; + kernel_version?: string; + pretty_name?: string; +} + +export interface IDiskInfo { + device: string; + total_bytes: number; + free_bytes: number; + used_bytes: number; + usage_percent: number; + total_inodes?: number; + free_inodes?: number; + used_inodes?: number; + inodes_usage_percent?: number; + read_bytes?: number; + write_bytes?: number; + read_time?: number; + write_time?: number; +} + +export interface INetInfo { + 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 ICaptureInfo { + version?: string; + mode?: string; +} + +export interface ISystemInfo { + cpu: ICpuInfo; + memory: IMemoryInfo; + disk: IDiskInfo[]; + host: IHostInfo; + net: INetInfo[]; +} + +export interface ILighthouseAudit { + id?: string; + title?: string; + score?: number | null; + displayValue?: string; + numericValue?: number; + numericUnit?: string; +} +export interface ILighthouseCategories { + accessibility?: { score?: number | null }; + "best-practices"?: { score?: number | null }; + seo?: { score?: number | null }; + performance?: { score?: number | null }; +} + +export interface ILighthouseResult { + categories?: ILighthouseCategories; + audits?: Record; +} + +export interface ICheckLighthouseFields { + accessibility: number; + bestPractices: number; + seo: number; + performance: number; + audits: { + cls: ILighthouseAudit; + si: ILighthouseAudit; + fcp: ILighthouseAudit; + lcp: ILighthouseAudit; + tbt: ILighthouseAudit; + }; +} + +export interface ICheck extends Document { + _id: Types.ObjectId; + monitorId: Types.ObjectId; + type: MonitorType; + status: MonitorStatus; + message: string; + responseTime?: number; // in ms + timings?: GotTimings; + httpStatusCode?: number; + errorMessage?: string; + ack: boolean; + ackAt?: Date; + ackBy?: Types.ObjectId; + expiry: Date; + createdAt: Date; + updatedAt: Date; + system?: ISystemInfo; + capture?: ICaptureInfo; + lighthouse?: ICheckLighthouseFields; +} + +const CheckSchema = new Schema( + { + monitorId: { type: Schema.Types.ObjectId, ref: "Monitor_v2", required: true }, + + type: { + type: String, + required: true, + enum: MonitorTypes, + }, + status: { + type: String, + required: true, + enum: MonitorStatuses, + }, + message: { type: String, trim: true }, + responseTime: { type: Number }, + timings: { + start: { type: Date }, + socket: { type: Date }, + lookup: { type: Date }, + connect: { type: Date }, + secureConnect: { type: Date }, + response: { type: Date }, + end: { type: Date }, + phases: { + wait: { type: Number }, + dns: { type: Number }, + tcp: { type: Number }, + tls: { type: Number }, + request: { type: Number }, + firstByte: { type: Number }, + download: { type: Number }, + total: { type: Number }, + }, + }, + system: { + type: { + cpu: { + physical_core: { type: Number }, + logical_core: { type: Number }, + frequency: { type: Number }, + current_frequency: { type: Number }, + temperature: [{ type: Number }], + free_percent: { type: Number }, + usage_percent: { type: Number }, + }, + memory: { + total_bytes: { type: Number }, + available_bytes: { type: Number }, + used_bytes: { type: Number }, + usage_percent: { type: Number }, + }, + disk: [ + { + device: { type: String }, + total_bytes: { type: Number }, + free_bytes: { type: Number }, + used_bytes: { type: Number }, + usage_percent: { type: Number }, + total_inodes: { type: Number }, + free_inodes: { type: Number }, + used_inodes: { type: Number }, + inodes_usage_percent: { type: Number }, + read_bytes: { type: Number }, + write_bytes: { type: Number }, + read_time: { type: Number }, + write_time: { type: Number }, + }, + ], + host: { + os: { type: String }, + platform: { type: String }, + kernel_version: { type: String }, + pretty_name: { type: String }, + }, + net: [ + { + name: { type: String }, + bytes_sent: { type: Number }, + bytes_recv: { type: Number }, + packets_sent: { type: Number }, + packets_recv: { type: Number }, + err_in: { type: Number }, + err_out: { type: Number }, + drop_in: { type: Number }, + drop_out: { type: Number }, + fifo_in: { type: Number }, + fifo_out: { type: Number }, + }, + ], + }, + required: false, + }, + + capture: { + type: { + version: { type: String }, + mode: { type: String }, + }, + required: false, + }, + lighthouse: { + accessibility: { type: Number, required: false }, + bestPractices: { type: Number, required: false }, + seo: { type: Number, required: false }, + performance: { type: Number, required: false }, + audits: { + cls: { + type: Object, + }, + si: { + type: Object, + }, + fcp: { + type: Object, + }, + lcp: { + type: Object, + }, + tbt: { + type: Object, + }, + }, + type: { + accessibility: { type: Number }, + bestPractices: { type: Number }, + seo: { type: Number }, + performance: { type: Number }, + audits: { + cls: { type: Object }, + si: { type: Object }, + fcp: { type: Object }, + lcp: { type: Object }, + tbt: { type: Object }, + }, + }, + required: false, + }, + + httpStatusCode: { type: Number }, + errorMessage: { type: String, trim: true }, + ack: { type: Boolean, required: true, default: false }, + ackAt: { type: Date }, + ackBy: { type: Schema.Types.ObjectId, ref: "User_v2" }, + expiry: { + type: Date, + default: Date.now, + expires: 60 * 60 * 24 * 30, + }, + }, + { timestamps: true } +); + +CheckSchema.index({ monitorId: 1, createdAt: -1 }); +CheckSchema.index({ status: 1 }); +CheckSchema.index({ status: 1, ack: 1 }); +CheckSchema.index({ ack: 1, ackAt: 1 }); +CheckSchema.index({ createdAt: -1 }); + +export const Check = mongoose.model("Check_v2", CheckSchema); diff --git a/server/src/db/v2/models/index.ts b/server/src/db/v2/models/index.ts new file mode 100644 index 000000000..fbc05c10f --- /dev/null +++ b/server/src/db/v2/models/index.ts @@ -0,0 +1,33 @@ +export { User } from "./auth/User.js"; +export type { IUser } from "./auth/User.js"; +export type { ITokenizedUser } from "./auth/User.js"; +export { Role } from "./auth/Role.js"; +export type { IRole } from "./auth/Role.js"; +export { connectDatabase, disconnectDatabase } from "../index.js"; +export { Monitor } from "./monitors/Monitor.js"; +export { MonitorStatuses } from "./monitors/Monitor.js"; +export type { IMonitor } from "./monitors/Monitor.js"; +export { Check } from "./checks/Check.js"; +export type { + ICheck, + ISystemInfo, + ICaptureInfo, + INetInfo, + IDiskInfo, + IHostInfo, + IMemoryInfo, + ICpuInfo, + ILighthouseAudit, + ITimingPhases, + ILighthouseCategories, + ILighthouseResult, + ICheckLighthouseFields, +} from "./checks/Check.js"; +export type { IMonitorStats } from "./monitors/MonitorStats.js"; +export { MonitorStats } from "./monitors/MonitorStats.js"; +export type { INotificationChannel } from "./notification-channel/NotificationChannel.js"; +export { NotificationChannel } from "./notification-channel/NotificationChannel.js"; +export type { IMaintenance } from "./maintenance/Maintenance.js"; +export { Maintenance } from "./maintenance/Maintenance.js"; +export type { IInvite } from "./invite/Invite.js"; +export { Invite } from "./invite/Invite.js"; diff --git a/server/src/db/v2/models/invite/Invite.ts b/server/src/db/v2/models/invite/Invite.ts new file mode 100644 index 000000000..2bc569900 --- /dev/null +++ b/server/src/db/v2/models/invite/Invite.ts @@ -0,0 +1,43 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; + +export interface IInvite extends Document { + _id: Types.ObjectId; + email: string; + tokenHash: string; + roles: Types.ObjectId[]; + createdBy: Types.ObjectId; + updatedBy: Types.ObjectId; + expiry: Date; + createdAt: Date; + updatedAt: Date; +} + +const InviteSchema = new Schema( + { + email: { + type: String, + required: true, + unique: true, + trim: true, + lowercase: true, + }, + roles: [ + { + type: Schema.Types.ObjectId, + ref: "Role_v2", + required: true, + }, + ], + tokenHash: { type: String, required: true, unique: true }, + expiry: { + type: Date, + default: Date.now, + expires: 60 * 60 * 24, + }, + createdBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true }, + updatedBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true }, + }, + { timestamps: true } +); + +export const Invite = mongoose.model("Invite_v2", InviteSchema); diff --git a/server/src/db/v2/models/maintenance/Maintenance.ts b/server/src/db/v2/models/maintenance/Maintenance.ts new file mode 100644 index 000000000..c2ce33a2b --- /dev/null +++ b/server/src/db/v2/models/maintenance/Maintenance.ts @@ -0,0 +1,39 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; + +export interface IMaintenance extends Document { + _id: Types.ObjectId; + name: string; + isActive: boolean; + monitors: Types.ObjectId[]; + startTime: Date; + endTime: Date; + createdBy: Types.ObjectId; + updatedBy: Types.ObjectId; + createdAt: Date; + updatedAt: Date; +} + +const MaintenanceSchema = new Schema( + { + name: { type: String, required: true, trim: true }, + isActive: { type: Boolean, required: true, default: true }, + monitors: [ + { + type: Schema.Types.ObjectId, + ref: "Monitor_v2", + required: true, + }, + ], + startTime: { type: Date, required: true }, + endTime: { type: Date, required: true }, + createdBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true }, + updatedBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true }, + }, + { timestamps: true } +); + +MaintenanceSchema.index({ isActive: 1 }); +MaintenanceSchema.index({ startTime: 1 }); +MaintenanceSchema.index({ endTime: 1 }); + +export const Maintenance = mongoose.model("Maintenance_v2", MaintenanceSchema); diff --git a/server/src/db/v2/models/monitors/Monitor.ts b/server/src/db/v2/models/monitors/Monitor.ts new file mode 100644 index 000000000..055d36a1b --- /dev/null +++ b/server/src/db/v2/models/monitors/Monitor.ts @@ -0,0 +1,100 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; +import { Check, MonitorStats } from "../index.js"; + +export const MonitorTypes = ["http", "https", "ping", "infrastructure", "pagespeed"] as const; +export type MonitorType = (typeof MonitorTypes)[number]; + +export const MonitorStatuses = ["up", "down", "paused", "initializing"] as const; +export type MonitorStatus = (typeof MonitorStatuses)[number]; +export interface IMonitor extends Document { + _id: Types.ObjectId; + name: string; + url: string; + secret?: string; + type: MonitorType; + interval: number; // in ms + isActive: boolean; + status: MonitorStatus; + n: number; // Number of consecutive successes required to change status + lastCheckedAt?: Date; + latestChecks: { + status: MonitorStatus; + responseTime: number; + checkedAt: Date; + }[]; + notificationChannels?: Types.ObjectId[]; + createdBy: Types.ObjectId; + updatedBy: Types.ObjectId; + createdAt: Date; + updatedAt: Date; +} + +const MonitorSchema = new Schema( + { + name: { type: String, required: true, trim: true, maxlength: 100 }, + url: { type: String, required: true, trim: true }, + secret: { type: String, required: false }, + type: { + type: String, + required: true, + enum: MonitorTypes, + }, + interval: { type: Number, required: true, default: 60000 }, + isActive: { type: Boolean, required: true, default: true }, + status: { + type: String, + required: true, + default: "initializing", + enum: MonitorStatuses, + }, + n: { type: Number, required: true, default: 1 }, + lastCheckedAt: { type: Date }, + latestChecks: { + type: [ + { + status: { + type: String, + required: true, + enum: MonitorStatuses, + }, + responseTime: { type: Number, required: true }, + checkedAt: { type: Date, required: true }, + }, + ], + default: [], + }, + notificationChannels: [ + { + type: Schema.Types.ObjectId, + ref: "NotificationChannel_v2", + required: false, + default: [], + }, + ], + + createdBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true }, + updatedBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true }, + }, + { timestamps: true } +); + +MonitorSchema.pre("deleteOne", { document: true, query: false }, async function (next) { + try { + const monitorId = this._id; + await Check.deleteMany({ monitorId }); + await MonitorStats.deleteMany({ monitorId }); + next(); + } catch (error: any) { + next(error); + } +}); + +MonitorSchema.index({ isActive: 1 }); +MonitorSchema.index({ status: 1 }); +MonitorSchema.index({ type: 1 }); +MonitorSchema.index({ lastCheckedAt: 1 }); +MonitorSchema.index({ isActive: 1, status: 1 }); +MonitorSchema.index({ createdBy: 1 }); +MonitorSchema.index({ updatedBy: 1 }); + +export const Monitor = mongoose.model("Monitor_v2", MonitorSchema); diff --git a/server/src/db/v2/models/monitors/MonitorStats.ts b/server/src/db/v2/models/monitors/MonitorStats.ts new file mode 100644 index 000000000..56e76f9ab --- /dev/null +++ b/server/src/db/v2/models/monitors/MonitorStats.ts @@ -0,0 +1,77 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; +import { MonitorStatus, MonitorStatuses } from "./Monitor.js"; + +export interface IMonitorStats extends mongoose.Document { + monitorId: mongoose.Types.ObjectId; + avgResponseTime: number; + maxResponseTime: number; + totalChecks: number; + totalUpChecks: number; + totalDownChecks: number; + uptimePercentage: number; + lastCheckTimestamp: number; + lastResponseTime: number; + timeOfLastFailure: number; + currentStreak: number; + currentStreakStatus: MonitorStatus; + currentStreakStartedAt: number; + createdAt: Date; + updatedAt: Date; +} + +const MonitorStatsSchema = new Schema( + { + monitorId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Monitor_v2", + immutable: true, + index: true, + }, + avgResponseTime: { + type: Number, + default: 0, + }, + maxResponseTime: { + type: Number, + default: 0, + }, + lastResponseTime: { + 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, + }, + timeOfLastFailure: { + type: Number, + default: 0, + }, + currentStreak: { type: Number, required: false, default: 0 }, + currentStreakStatus: { + type: String, + required: false, + enum: MonitorStatuses, + }, + currentStreakStartedAt: { type: Number, required: false }, + }, + { timestamps: true } +); + +export const MonitorStats = mongoose.model("MonitorStats_v2", MonitorStatsSchema); diff --git a/server/src/db/v2/models/notification-channel/NotificationChannel.ts b/server/src/db/v2/models/notification-channel/NotificationChannel.ts new file mode 100644 index 000000000..e77639608 --- /dev/null +++ b/server/src/db/v2/models/notification-channel/NotificationChannel.ts @@ -0,0 +1,50 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; +export const ChannelTypes = ["email", "slack", "discord", "webhook"] as const; +export type ChannelType = (typeof ChannelTypes)[number]; + +export interface INotificationChannelConfig { + url?: string; // For webhook, slack, discord + emailAddress?: string; // For email +} + +export interface INotificationChannel { + _id: Types.ObjectId; + name: string; + type: ChannelType; + config: INotificationChannelConfig; + isActive: boolean; + createdBy: Types.ObjectId; + updatedBy: Types.ObjectId; + createdAt: Date; + updatedAt: Date; +} + +const NotificationChannelConfigSchema = new Schema( + { + url: { type: String, required: false }, + emailAddress: { type: String, required: false }, + }, + { _id: false, strict: "throw" } +); + +const NotificationChannelSchema = new Schema( + { + name: { type: String, required: true, trim: true }, + type: { + type: String, + required: true, + enum: ChannelTypes, + }, + config: { type: NotificationChannelConfigSchema, required: true }, + isActive: { type: Boolean, required: true, default: true }, + createdBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true }, + updatedBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true }, + }, + { timestamps: true } +); + +NotificationChannelSchema.index({ isActive: 1 }); +NotificationChannelSchema.index({ type: 1 }); +NotificationChannelSchema.index({ type: 1, isActive: 1 }); + +export const NotificationChannel = mongoose.model("NotificationChannel_v2", NotificationChannelSchema); From c0ff162ed925de8f70280ce40e043a4dfea50915 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Sep 2025 14:46:32 -0700 Subject: [PATCH 013/103] remove v2 files --- .../src/controllers/v2/MonitorController.ts | 157 ------ server/src/db/v2/index.ts | 25 + server/src/db/v2/models/auth/User.ts | 6 - .../src/service/v2/business/CheckService.ts | 132 ----- .../src/service/v2/business/MonitorService.ts | 464 ------------------ 5 files changed, 25 insertions(+), 759 deletions(-) delete mode 100644 server/src/controllers/v2/MonitorController.ts create mode 100644 server/src/db/v2/index.ts delete mode 100644 server/src/service/v2/business/CheckService.ts delete mode 100644 server/src/service/v2/business/MonitorService.ts diff --git a/server/src/controllers/v2/MonitorController.ts b/server/src/controllers/v2/MonitorController.ts deleted file mode 100644 index 6493b7f44..000000000 --- a/server/src/controllers/v2/MonitorController.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { Request, Response, NextFunction } from "express"; -import ApiError from "../../utils/ApiError.js"; -import MonitorService from "../../service/v2/business/MonitorService.js"; -import { MonitorType } from "../../db/v1/models/Monitor.js"; -class MonitorController { - private monitorService: MonitorService; - constructor(monitorService: MonitorService) { - this.monitorService = monitorService; - } - - create = async (req: Request, res: Response, next: NextFunction) => { - try { - const tokenizedUser = req.user; - if (!tokenizedUser) { - return res.status(401).json({ message: "Unauthorized" }); - } - - const monitor = await this.monitorService.create(tokenizedUser, req.body); - res.status(201).json({ - message: "Monitor created successfully", - data: monitor, - }); - } catch (error) { - next(error); - } - }; - - get = async (req: Request, res: Response, next: NextFunction) => { - try { - const tokenizedUser = req.user; - if (!tokenizedUser) { - return res.status(401).json({ message: "Unauthorized" }); - } - - const id = req.params.id; - if (!id) { - throw new ApiError("Monitor ID is required", 400); - } - - const range = req.query.range; - if (!range || typeof range !== "string") throw new ApiError("Range query parameter is required", 400); - - let monitor; - - const status = req.query.status; - if (status && typeof status !== "string") { - throw new ApiError("Status query parameter must be a string", 400); - } - - if (req.query.embedChecks === "true") { - monitor = await this.monitorService.getEmbedChecks(id, range, status); - } else { - monitor = await this.monitorService.get(id); - } - - res.status(200).json({ - message: "Monitor retrieved successfully", - data: monitor, - }); - } catch (error) { - next(error); - } - }; - - getAll = async (req: Request, res: Response, next: NextFunction) => { - try { - const tokenizedUser = req.user; - if (!tokenizedUser) { - return res.status(401).json({ message: "Unauthorized" }); - } - - let monitors; - if (req.query.embedChecks === "true") { - const page = Math.max(1, Number(req.query.page) || 1); - const limit = Math.max(1, Number(req.query.limit) || 10); - const type: MonitorType[] = req.query.type as MonitorType[]; - - monitors = await this.monitorService.getAllEmbedChecks(page, limit, type); - } else { - monitors = await this.monitorService.getAll(); - } - - res.status(200).json({ - message: "Monitors retrieved successfully", - data: monitors, - }); - } catch (error) { - next(error); - } - }; - - toggleActive = async (req: Request, res: Response, next: NextFunction) => { - try { - const tokenizedUser = req.user; - if (!tokenizedUser) { - return res.status(401).json({ message: "Unauthorized" }); - } - - const id = req.params.id; - if (!id) { - throw new ApiError("Monitor ID is required", 400); - } - - const monitor = await this.monitorService.toggleActive(id, tokenizedUser); - res.status(200).json({ - message: "Monitor paused/unpaused successfully", - data: monitor, - }); - } catch (error) { - next(error); - } - }; - - update = async (req: Request, res: Response, next: NextFunction) => { - try { - const tokenizedUser = req.user; - if (!tokenizedUser) { - return res.status(401).json({ message: "Unauthorized" }); - } - - const id = req.params.id; - if (!id) { - throw new ApiError("Monitor ID is required", 400); - } - - const monitor = await this.monitorService.update(tokenizedUser, id, req.body); - res.status(200).json({ - message: "Monitor updated successfully", - data: monitor, - }); - } catch (error) { - next(error); - } - }; - - delete = async (req: Request, res: Response, next: NextFunction) => { - try { - const tokenizedUser = req.user; - if (!tokenizedUser) { - return res.status(401).json({ message: "Unauthorized" }); - } - const id = req.params.id; - if (!id) { - throw new ApiError("Monitor ID is required", 400); - } - await this.monitorService.delete(id); - - res.status(200).json({ - message: "Monitor deleted successfully", - }); - } catch (error) { - next(error); - } - }; -} - -export default MonitorController; diff --git a/server/src/db/v2/index.ts b/server/src/db/v2/index.ts new file mode 100644 index 000000000..2cd194283 --- /dev/null +++ b/server/src/db/v2/index.ts @@ -0,0 +1,25 @@ +import mongoose from "mongoose"; + +const MONGODB_URI = process.env.MONGODB_URI || "mongodb://localhost:27017/checkmate"; + +export const connectDatabase = async (): Promise => { + try { + await mongoose.connect(MONGODB_URI); + console.log("Connected to MongoDB"); + return true; + } catch (error) { + console.error("MongoDB connection error:", error); + process.exit(1); + } +}; + +export const disconnectDatabase = async (): Promise => { + try { + await mongoose.disconnect(); + console.log("Disconnected from MongoDB"); + return true; + } catch (error) { + console.error("MongoDB disconnection error:", error); + return false; + } +}; diff --git a/server/src/db/v2/models/auth/User.ts b/server/src/db/v2/models/auth/User.ts index b72a29e70..25b9794c7 100644 --- a/server/src/db/v2/models/auth/User.ts +++ b/server/src/db/v2/models/auth/User.ts @@ -96,12 +96,6 @@ const userSchema = new Schema( }, { timestamps: true, - toJSON: { - transform: function (_doc, ret) { - delete ret.passwordHash; - return ret; - }, - }, } ); diff --git a/server/src/service/v2/business/CheckService.ts b/server/src/service/v2/business/CheckService.ts deleted file mode 100644 index 181fe048d..000000000 --- a/server/src/service/v2/business/CheckService.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { json } from "stream/consumers"; -import { ICheck, Check, Monitor, ISystemInfo, ICaptureInfo } from "../../../db/v1/models/index.js"; -import { MonitorType } from "../../../db/v1/models/Monitor.js"; -import { StatusResponse } from "../infrastructure/NetworkService.js"; -import type { ICapturePayload, ILighthousePayload } from "../infrastructure/NetworkService.js"; -import mongoose from "mongoose"; - -export interface ICheckService { - buildCheck: (statusResponse: StatusResponse, type: MonitorType) => Promise; - cleanupOrphanedChecks: () => Promise; -} - -class CheckService implements ICheckService { - private isCapturePayload = (payload: any): payload is ICapturePayload => { - if (!payload || typeof payload !== "object") return false; - - if (!("data" in payload) || typeof payload.data !== "object") { - return false; - } - - const data = payload.data as Partial; - if (!data.cpu || typeof data.cpu !== "object" || typeof data.cpu.usage_percent !== "number") { - return false; - } - s; - - if (!data.memory || typeof data.memory !== "object" || typeof data.memory.usage_percent !== "number") { - return false; - } - - if (data.disk && !Array.isArray(data.disk)) { - return false; - } - if (data.net && !Array.isArray(data.net)) { - return false; - } - - if (!("capture" in payload) || typeof payload.capture !== "object") return false; - const capture = payload.capture as Record; - if (typeof capture.version !== "string" || typeof capture.mode !== "string") return false; - - return true; - }; - - private isPagespeedPayload = (payload: any): payload is ILighthousePayload => { - if (!payload || typeof payload !== "object") return false; - - if (!("lighthouseResult" in payload) || typeof payload.lighthouseResult !== "object") { - return false; - } - return true; - }; - - private buildBaseCheck = (statusResponse: StatusResponse) => { - const monitorId = new mongoose.Types.ObjectId(statusResponse.monitorId); - const check = new Check({ - monitorId: monitorId, - type: statusResponse?.type, - status: statusResponse?.status, - message: statusResponse?.message, - responseTime: statusResponse?.responseTime, - timings: statusResponse?.timings, - }); - return check; - }; - - private buildInfrastructureCheck = (statusResponse: StatusResponse) => { - if (!this.isCapturePayload(statusResponse.payload)) { - throw new Error("Invalid payload for infrastructure monitor"); - } - const check = this.buildBaseCheck(statusResponse); - check.system = statusResponse.payload.data; - check.capture = statusResponse.payload.capture; - return check; - }; - - private buildPagespeedCheck = (statusResponse: StatusResponse) => { - if (!this.isPagespeedPayload(statusResponse.payload)) { - throw new Error("Invalid payload for pagespeed monitor"); - } - const check = this.buildBaseCheck(statusResponse); - const lighthouseResult = statusResponse?.payload?.lighthouseResult; - check.lighthouse = { - accessibility: lighthouseResult?.categories?.accessibility?.score || 0, - bestPractices: lighthouseResult?.categories?.["best-practices"]?.score || 0, - seo: lighthouseResult?.categories?.seo?.score || 0, - performance: lighthouseResult?.categories?.performance?.score || 0, - audits: { - cls: lighthouseResult?.audits?.["cumulative-layout-shift"] || {}, - si: lighthouseResult?.audits?.["speed-index"] || {}, - fcp: lighthouseResult?.audits?.["first-contentful-paint"] || {}, - lcp: lighthouseResult?.audits?.["largest-contentful-paint"] || {}, - tbt: lighthouseResult?.audits?.["total-blocking-time"] || {}, - }, - }; - return check; - }; - - buildCheck = async (statusResponse: StatusResponse, type: MonitorType): Promise => { - switch (type) { - case "infrastructure": - return this.buildInfrastructureCheck(statusResponse as StatusResponse); - - case "pagespeed": - return this.buildPagespeedCheck(statusResponse as StatusResponse); - case "http": - case "https": - return this.buildBaseCheck(statusResponse); - - case "ping": - return this.buildBaseCheck(statusResponse); - default: - throw new Error(`Unsupported monitor type: ${type}`); - } - }; - - cleanupOrphanedChecks = async () => { - try { - const monitorIds = await Monitor.find().distinct("_id"); - const result = await Check.deleteMany({ - monitorId: { $nin: monitorIds }, - }); - console.log(`Deleted ${result.deletedCount} orphaned Checks.`); - return true; - } catch (error) { - console.error("Error cleaning up orphaned Checks:", error); - return false; - } - }; -} - -export default CheckService; diff --git a/server/src/service/v2/business/MonitorService.ts b/server/src/service/v2/business/MonitorService.ts deleted file mode 100644 index 6e48e79e5..000000000 --- a/server/src/service/v2/business/MonitorService.ts +++ /dev/null @@ -1,464 +0,0 @@ -import mongoose from "mongoose"; - -import { IMonitor, Monitor, ITokenizedUser, MonitorStats, Check } from "../../../db/v1/models/index.js"; -import ApiError from "../../../utils/ApiError.js"; -import { IJobQueue } from "../infrastructure/JobQueue.js"; -import { MonitorWithChecksResponse } from "../../../types/monitor-response-with-checks.js"; -import { MonitorStatus, MonitorType } from "../../../db/v1/models/monitors/Monitor.js"; -export interface IMonitorService { - create: (tokenizedUser: ITokenizedUser, monitorData: IMonitor) => Promise; - getAll: () => Promise; - getAllEmbedChecks: (page: number, limit: number, type: MonitorType[]) => Promise; - get: (monitorId: string) => Promise; - getEmbedChecks: (monitorId: string, range: string, status?: string) => Promise; - toggleActive: (monitorId: string, tokenizedUser: ITokenizedUser) => Promise; - update: (tokenizedUser: ITokenizedUser, monitorId: string, updateData: Partial) => Promise; - delete: (monitorId: string) => Promise; -} - -class MonitorService implements IMonitorService { - private jobQueue: IJobQueue; - constructor(jobQueue: IJobQueue) { - this.jobQueue = jobQueue; - } - - create = async (tokenizedUser: ITokenizedUser, monitorData: IMonitor) => { - const monitor = await Monitor.create({ - ...monitorData, - createdBy: tokenizedUser.sub, - updatedBy: tokenizedUser.sub, - }); - await MonitorStats.create({ - monitorId: monitor._id, - currentStreakStartedAt: Date.now(), - }); - await this.jobQueue.addJob(monitor); - return monitor; - }; - - getAll = async () => { - return Monitor.find(); - }; - - getAllEmbedChecks = async (page: number, limit: number, type: MonitorType[] = []) => { - const skip = (page - 1) * limit; - let find = {}; - if (type.length > 0) find = { type: { $in: type } }; - const monitors = await Monitor.find(find).skip(skip).limit(limit); - return monitors; - }; - - get = async (monitorId: string) => { - const monitor = await Monitor.findById(monitorId); - if (!monitor) { - throw new ApiError("Monitor not found", 404); - } - return monitor; - }; - - private getStartDate(range: string): Date { - const now = new Date(); - switch (range) { - case "30m": - return new Date(now.getTime() - 30 * 60 * 1000); - case "24h": - return new Date(now.getTime() - 24 * 60 * 60 * 1000); - case "7d": - return new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); - case "30d": - return new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); - default: - throw new ApiError("Invalid range parameter", 400); - } - } - - private getDateFormat(range: string): string { - switch (range) { - case "30m": - return "%Y-%m-%dT%H:%M:00Z"; - case "24h": - case "7d": - return "%Y-%m-%dT%H:00:00Z"; - case "30d": - return "%Y-%m-%d"; - default: - throw new ApiError("Invalid range parameter", 400); - } - } - - private getBaseGroup = (dateFormat: string): Record => { - return { - _id: { $dateToString: { format: dateFormat, date: "$createdAt" } }, - count: { $sum: 1 }, - avgResponseTime: { $avg: "$responseTime" }, - }; - }; - - private getBaseProjection = (): object => { - return { status: 1, responseTime: 1, createdAt: 1 }; - }; - - private getPageSpeedGroup = (dateFormat: string): Record => { - return { - _id: { $dateToString: { format: dateFormat, date: "$createdAt" } }, - count: { $sum: 1 }, - avgResponseTime: { $avg: "$responseTime" }, - accessibility: { $avg: "$lighthouse.accessibility" }, - bestPractices: { $avg: "$lighthouse.bestPractices" }, - seo: { $avg: "$lighthouse.seo" }, - performance: { $avg: "$lighthouse.performance" }, - cls: { $avg: "$lighthouse.audits.cls.score" }, - si: { $avg: "$lighthouse.audits.si.score" }, - fcp: { $avg: "$lighthouse.audits.fcp.score" }, - lcp: { $avg: "$lighthouse.audits.lcp.score" }, - tbt: { $avg: "$lighthouse.audits.tbt.score" }, - }; - }; - - private getPageSpeedProjection = (): object => { - const projectStage: any = { status: 1, responseTime: 1, createdAt: 1 }; - projectStage["lighthouse.accessibility"] = 1; - projectStage["lighthouse.seo"] = 1; - projectStage["lighthouse.bestPractices"] = 1; - projectStage["lighthouse.performance"] = 1; - projectStage["lighthouse.audits.cls.score"] = 1; - projectStage["lighthouse.audits.si.score"] = 1; - projectStage["lighthouse.audits.fcp.score"] = 1; - projectStage["lighthouse.audits.lcp.score"] = 1; - projectStage["lighthouse.audits.tbt.score"] = 1; - return projectStage; - }; - - private getInfraGroup = (dateFormat: string): Record => { - return { - _id: { $dateToString: { format: dateFormat, date: "$createdAt" } }, - count: { $sum: 1 }, - avgResponseTime: { $avg: "$responseTime" }, - physicalCores: { $last: "$system.cpu.physical_core" }, - logicalCores: { $last: "$system.cpu.logical_core" }, - frequency: { $avg: "$system.cpu.frequency" }, - currentFrequency: { $last: "$system.cpu.current_frequency" }, - tempsArrays: { $push: "$system.cpu.temperature" }, - freePercent: { $avg: "$system.cpu.free_percent" }, - usedPercent: { $avg: "$system.cpu.usage_percent" }, - total_bytes: { $last: "$system.memory.total_bytes" }, - available_bytes: { $last: "$system.memory.available_bytes" }, - used_bytes: { $last: "$system.memory.used_bytes" }, - memory_usage_percent: { $avg: "$system.memory.usage_percent" }, - disksArray: { $push: "$system.disk" }, - os: { $last: "$system.host.os" }, - platform: { $last: "$system.host.platform" }, - kernel_version: { $last: "$system.host.kernel_version" }, - pretty_name: { $last: "$system.host.pretty_name" }, - netsArray: { $push: "$system.net" }, - }; - }; - - private getInfraProjection = (): object => { - const projectStage: any = { status: 1, responseTime: 1, createdAt: 1 }; - projectStage["system.cpu.physical_core"] = 1; - projectStage["system.cpu.logical_core"] = 1; - projectStage["system.cpu.frequency"] = 1; - projectStage["system.cpu.current_frequency"] = 1; - projectStage["system.cpu.temperature"] = 1; - projectStage["system.cpu.free_percent"] = 1; - projectStage["system.cpu.usage_percent"] = 1; - projectStage["system.memory.total_bytes"] = 1; - projectStage["system.memory.available_bytes"] = 1; - projectStage["system.memory.used_bytes"] = 1; - projectStage["system.memory.usage_percent"] = 1; - projectStage["system.disk"] = 1; - projectStage["system.host.os"] = 1; - projectStage["system.host.platform"] = 1; - projectStage["system.host.kernel_version"] = 1; - projectStage["system.host.pretty_name"] = 1; - projectStage["system.net"] = 1; - return projectStage; - }; - - private getFinalProjection = (type: string): object => { - if (type === "pagespeed") { - return { - _id: 1, - count: 1, - avgResponseTime: 1, - accessibility: "$accessibility", - seo: "$seo", - bestPractices: "$bestPractices", - performance: "$performance", - cls: "$cls", - si: "$si", - fcp: "$fcp", - lcp: "$lcp", - tbt: "$tbt", - }; - } - - if (type === "infrastructure") { - return { - _id: 1, - count: 1, - avgResponseTime: 1, - cpu: { - physicalCores: "$physicalCores", - logicalCores: "$logicalCores", - frequency: "$frequency", - currentFrequency: "$currentFrequency", - temperatures: { - $map: { - input: { - $range: [0, { $size: { $arrayElemAt: ["$tempsArrays", 0] } }], - }, - as: "idx", - in: { - $avg: { - $map: { - input: "$tempsArrays", - as: "arr", - in: { $arrayElemAt: ["$$arr", "$$idx"] }, - }, - }, - }, - }, - }, - freePercent: "$freePercent", - usedPercent: "$usedPercent", - }, - memory: { - total_bytes: "$total_bytes", - available_bytes: "$available_bytes", - used_bytes: "$used_bytes", - usage_percent: "$memory_usage_percent", - }, - disks: { - $map: { - input: { - $range: [0, { $size: { $arrayElemAt: ["$disksArray", 0] } }], - }, - as: "idx", - in: { - $let: { - vars: { - diskGroup: { - $map: { - input: "$disksArray", - as: "diskArr", - in: { $arrayElemAt: ["$$diskArr", "$$idx"] }, - }, - }, - }, - in: { - device: { $arrayElemAt: ["$$diskGroup.device", 0] }, - total_bytes: { $avg: "$$diskGroup.total_bytes" }, - free_bytes: { $avg: "$$diskGroup.free_bytes" }, - used_bytes: { $avg: "$$diskGroup.used_bytes" }, - usage_percent: { $avg: "$$diskGroup.usage_percent" }, - total_inodes: { $avg: "$$diskGroup.total_inodes" }, - free_inodes: { $avg: "$$diskGroup.free_inodes" }, - used_inodes: { $avg: "$$diskGroup.used_inodes" }, - inodes_usage_percent: { - $avg: "$$diskGroup.inodes_usage_percent", - }, - read_bytes: { $avg: "$$diskGroup.read_bytes" }, - write_bytes: { $avg: "$$diskGroup.write_bytes" }, - read_time: { $avg: "$$diskGroup.read_time" }, - write_time: { $avg: "$$diskGroup.write_time" }, - }, - }, - }, - }, - }, - host: { - os: "$os", - platform: "$platform", - kernel_version: "$kernel_version", - pretty_name: "$pretty_name", - }, - net: { - $map: { - input: { - $range: [0, { $size: { $arrayElemAt: ["$netsArray", 0] } }], - }, - as: "idx", - in: { - $let: { - vars: { - netGroup: { - $map: { - input: "$netsArray", - as: "netArr", - in: { $arrayElemAt: ["$$netArr", "$$idx"] }, - }, - }, - }, - in: { - name: { $arrayElemAt: ["$$netGroup.name", 0] }, - bytes_sent: { $avg: "$$netGroup.bytes_sent" }, - bytes_recv: { $avg: "$$netGroup.bytes_recv" }, - packets_sent: { $avg: "$$netGroup.packets_sent" }, - packets_recv: { $avg: "$$netGroup.packets_recv" }, - err_in: { $avg: "$$netGroup.err_in" }, - err_out: { $avg: "$$netGroup.err_out" }, - drop_in: { $avg: "$$netGroup.drop_in" }, - drop_out: { $avg: "$$netGroup.drop_out" }, - fifo_in: { $avg: "$$netGroup.fifo_in" }, - fifo_out: { $avg: "$$netGroup.fifo_out" }, - }, - }, - }, - }, - }, - }; - } - return {}; - }; - - getEmbedChecks = async (monitorId: string, range: string, status: string | undefined): Promise => { - const monitor = await Monitor.findById(monitorId); - if (!monitor) { - throw new ApiError("Monitor not found", 404); - } - const startDate = this.getStartDate(range); - const dateFormat = this.getDateFormat(range); - - // Build match stage - const matchStage: { - monitorId: mongoose.Types.ObjectId; - createdAt: { $gte: Date }; - status?: string; - } = { - monitorId: monitor._id, - createdAt: { $gte: startDate }, - }; - - if (status) { - matchStage.status = status; - } - - let groupClause; - - if (monitor.type === "pagespeed") { - groupClause = this.getPageSpeedGroup(dateFormat); - } else if (monitor.type === "infrastructure") { - groupClause = this.getInfraGroup(dateFormat); - } else { - groupClause = this.getBaseGroup(dateFormat); - } - - let projectStage; - if (monitor.type === "pagespeed") { - projectStage = this.getPageSpeedProjection(); - } else if (monitor.type === "infrastructure") { - projectStage = this.getInfraProjection(); - } else { - projectStage = this.getBaseProjection(); - } - - let finalProjection = {}; - if (monitor.type === "pagespeed" || monitor.type === "infrastructure") { - finalProjection = this.getFinalProjection(monitor.type); - } else { - finalProjection = { _id: 1, count: 1, avgResponseTime: 1 }; - } - - const checks = await Check.aggregate([ - { - $match: matchStage, - }, - { $sort: { createdAt: 1 } }, - { $project: projectStage }, - { $group: groupClause }, - { $sort: { _id: -1 } }, - { - $project: finalProjection, - }, - ]); - - // Get monitor stats - const monitorStats = await MonitorStats.findOne({ - monitorId: monitor._id, - }).lean(); - - if (!monitorStats) { - throw new ApiError("Monitor stats not found", 404); - } - - return { - monitor: monitor.toObject(), - checks, - stats: monitorStats, - }; - }; - - async toggleActive(id: string, tokenizedUser: ITokenizedUser) { - const pendingStatus: MonitorStatus = "initializing"; - const updatedMonitor = await Monitor.findOneAndUpdate( - { _id: id }, - [ - { - $set: { - isActive: { $not: "$isActive" }, - status: pendingStatus, - updatedBy: tokenizedUser.sub, - updatedAt: new Date(), - }, - }, - ], - { new: true } - ); - - if (!updatedMonitor) { - throw new ApiError("Monitor not found", 404); - } - - await this.jobQueue.updateJob(updatedMonitor); - - if (updatedMonitor?.isActive) { - await this.jobQueue.resumeJob(updatedMonitor); - } else { - await this.jobQueue.pauseJob(updatedMonitor); - } - return updatedMonitor; - } - - async update(tokenizedUser: ITokenizedUser, monitorId: string, updateData: Partial) { - const allowedFields: (keyof IMonitor)[] = ["name", "interval", "isActive", "n", "m", "notificationChannels"]; - const safeUpdate: Partial = {}; - - for (const field of allowedFields) { - if (updateData[field] !== undefined) { - (safeUpdate as any)[field] = updateData[field]; - } - } - - const updatedMonitor = await Monitor.findByIdAndUpdate( - monitorId, - { - $set: { - ...safeUpdate, - updatedAt: new Date(), - updatedBy: tokenizedUser.sub, - }, - }, - { new: true, runValidators: true } - ); - - if (!updatedMonitor) { - throw new ApiError("Monitor not found", 404); - } - await this.jobQueue.updateJob(updatedMonitor); - return updatedMonitor; - } - - async delete(monitorId: string) { - const monitor = await Monitor.findById(monitorId); - if (!monitor) { - throw new ApiError("Monitor not found", 404); - } - await monitor.deleteOne(); - await this.jobQueue.deleteJob(monitor); - return true; - } -} - -export default MonitorService; From 2d12fbdff11c2c3fae0600258e4544d0da044487 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Sep 2025 14:56:02 -0700 Subject: [PATCH 014/103] fix notification schema --- server/src/db/v2/models/monitors/Monitor.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/server/src/db/v2/models/monitors/Monitor.ts b/server/src/db/v2/models/monitors/Monitor.ts index 055d36a1b..52cbae9d8 100644 --- a/server/src/db/v2/models/monitors/Monitor.ts +++ b/server/src/db/v2/models/monitors/Monitor.ts @@ -63,14 +63,10 @@ const MonitorSchema = new Schema( ], default: [], }, - notificationChannels: [ - { - type: Schema.Types.ObjectId, - ref: "NotificationChannel_v2", - required: false, - default: [], - }, - ], + notificationChannels: { + type: [{ type: Schema.Types.ObjectId, ref: "NotificationChannel_v2" }], + default: [], + }, createdBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true }, updatedBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true }, From 68f4adfbfef319683fb675d6d2df1203ad50448d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Sep 2025 15:22:08 -0700 Subject: [PATCH 015/103] add v2 services --- server/package-lock.json | 1392 +++++++++++++++++ server/package.json | 2 + server/src/config/index.ts | 25 + server/src/service/v2/business/AuthService.ts | 206 +++ .../src/service/v2/business/CheckService.ts | 132 ++ .../src/service/v2/business/InviteService.ts | 61 + .../service/v2/business/MaintenanceService.ts | 141 ++ .../v2/business/MonitorStatsService.ts | 25 + .../v2/business/NotificationChannelService.ts | 100 ++ .../src/service/v2/business/QueueService.ts | 23 + server/src/service/v2/business/UserService.ts | 13 + .../service/v2/infrastructure/JobGenerator.ts | 80 + .../src/service/v2/infrastructure/JobQueue.ts | 227 +++ .../v2/infrastructure/NetworkService.ts | 193 +++ .../v2/infrastructure/NotificationService.ts | 58 + .../NotificationServices/Discord.ts | 73 + .../NotificationServices/Email.ts | 65 + .../NotificationServices/IMessageService.ts | 15 + .../NotificationServices/Slack.ts | 116 ++ .../NotificationServices/Webhook.ts | 43 + .../NotificationServices/index.ts | 5 + .../v2/infrastructure/StatusService.ts | 100 ++ server/src/utils/ApiError.ts | 15 + server/src/utils/JWTUtils.ts | 22 + 24 files changed, 3132 insertions(+) create mode 100644 server/src/config/index.ts create mode 100644 server/src/service/v2/business/AuthService.ts create mode 100644 server/src/service/v2/business/CheckService.ts create mode 100644 server/src/service/v2/business/InviteService.ts create mode 100644 server/src/service/v2/business/MaintenanceService.ts create mode 100644 server/src/service/v2/business/MonitorStatsService.ts create mode 100644 server/src/service/v2/business/NotificationChannelService.ts create mode 100644 server/src/service/v2/business/QueueService.ts create mode 100644 server/src/service/v2/business/UserService.ts create mode 100644 server/src/service/v2/infrastructure/JobGenerator.ts create mode 100644 server/src/service/v2/infrastructure/JobQueue.ts create mode 100644 server/src/service/v2/infrastructure/NetworkService.ts create mode 100644 server/src/service/v2/infrastructure/NotificationService.ts create mode 100644 server/src/service/v2/infrastructure/NotificationServices/Discord.ts create mode 100644 server/src/service/v2/infrastructure/NotificationServices/Email.ts create mode 100644 server/src/service/v2/infrastructure/NotificationServices/IMessageService.ts create mode 100644 server/src/service/v2/infrastructure/NotificationServices/Slack.ts create mode 100644 server/src/service/v2/infrastructure/NotificationServices/Webhook.ts create mode 100644 server/src/service/v2/infrastructure/NotificationServices/index.ts create mode 100644 server/src/service/v2/infrastructure/StatusService.ts create mode 100644 server/src/utils/ApiError.ts create mode 100644 server/src/utils/JWTUtils.ts diff --git a/server/package-lock.json b/server/package-lock.json index 06c1c30b3..92d258433 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -45,6 +45,8 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/nodemailer": "7.0.1", + "@types/ping": "0.4.4", "c8": "10.1.3", "chai": "5.2.0", "eslint": "^9.17.0", @@ -72,6 +74,751 @@ "lru-cache": "^10.4.3" } }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sesv2": { + "version": "3.895.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.895.0.tgz", + "integrity": "sha512-tYfgwoG4n8LEc0NHVriNNS+YqeDMJHHcUzJxJJ7Nols5Y3EJzv76QRvu2rdLa9XoxvTSFGXJSuqrd8s0ElbCQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.894.0", + "@aws-sdk/credential-provider-node": "3.895.0", + "@aws-sdk/middleware-host-header": "3.893.0", + "@aws-sdk/middleware-logger": "3.893.0", + "@aws-sdk/middleware-recursion-detection": "3.893.0", + "@aws-sdk/middleware-user-agent": "3.895.0", + "@aws-sdk/region-config-resolver": "3.893.0", + "@aws-sdk/signature-v4-multi-region": "3.894.0", + "@aws-sdk/types": "3.893.0", + "@aws-sdk/util-endpoints": "3.895.0", + "@aws-sdk/util-user-agent-browser": "3.893.0", + "@aws-sdk/util-user-agent-node": "3.895.0", + "@smithy/config-resolver": "^4.2.2", + "@smithy/core": "^3.11.1", + "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/hash-node": "^4.1.1", + "@smithy/invalid-dependency": "^4.1.1", + "@smithy/middleware-content-length": "^4.1.1", + "@smithy/middleware-endpoint": "^4.2.3", + "@smithy/middleware-retry": "^4.2.4", + "@smithy/middleware-serde": "^4.1.1", + "@smithy/middleware-stack": "^4.1.1", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/node-http-handler": "^4.2.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/smithy-client": "^4.6.3", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-body-length-node": "^4.1.0", + "@smithy/util-defaults-mode-browser": "^4.1.3", + "@smithy/util-defaults-mode-node": "^4.1.3", + "@smithy/util-endpoints": "^3.1.2", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-retry": "^4.1.2", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.895.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.895.0.tgz", + "integrity": "sha512-AQHk6iJrwce/NwZa5/Njy0ZGoHdxWCajkgufhXk53L0kRiC3vUPPWEV1m1F3etQWhaUsatcO2xtRuKvLpe4zgA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.894.0", + "@aws-sdk/middleware-host-header": "3.893.0", + "@aws-sdk/middleware-logger": "3.893.0", + "@aws-sdk/middleware-recursion-detection": "3.893.0", + "@aws-sdk/middleware-user-agent": "3.895.0", + "@aws-sdk/region-config-resolver": "3.893.0", + "@aws-sdk/types": "3.893.0", + "@aws-sdk/util-endpoints": "3.895.0", + "@aws-sdk/util-user-agent-browser": "3.893.0", + "@aws-sdk/util-user-agent-node": "3.895.0", + "@smithy/config-resolver": "^4.2.2", + "@smithy/core": "^3.11.1", + "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/hash-node": "^4.1.1", + "@smithy/invalid-dependency": "^4.1.1", + "@smithy/middleware-content-length": "^4.1.1", + "@smithy/middleware-endpoint": "^4.2.3", + "@smithy/middleware-retry": "^4.2.4", + "@smithy/middleware-serde": "^4.1.1", + "@smithy/middleware-stack": "^4.1.1", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/node-http-handler": "^4.2.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/smithy-client": "^4.6.3", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-body-length-node": "^4.1.0", + "@smithy/util-defaults-mode-browser": "^4.1.3", + "@smithy/util-defaults-mode-node": "^4.1.3", + "@smithy/util-endpoints": "^3.1.2", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-retry": "^4.1.2", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.894.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.894.0.tgz", + "integrity": "sha512-7zbO31NV2FaocmMtWOg/fuTk3PC2Ji2AC0Fi2KqrppEDIcwLlTTuT9w/rdu/93Pz+wyUhCxWnDc0tPbwtCLs+A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.893.0", + "@aws-sdk/xml-builder": "3.894.0", + "@smithy/core": "^3.11.1", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/property-provider": "^4.1.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/signature-v4": "^5.2.1", + "@smithy/smithy-client": "^4.6.3", + "@smithy/types": "^4.5.0", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.894.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.894.0.tgz", + "integrity": "sha512-2aiQJIRWOuROPPISKgzQnH/HqSfucdk5z5VMemVH3Mm2EYOrzBwmmiiFpmSMN3ST+sE8c7gusqycUchP+KfALQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.894.0", + "@aws-sdk/types": "3.893.0", + "@smithy/property-provider": "^4.1.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.894.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.894.0.tgz", + "integrity": "sha512-Z5QQpqFRflszrT+lUq6+ORuu4jRDcpgCUSoTtlhczidMqfdOSckKmK3chZEfmUUJPSwoFQZ7EiVTsX3c886fBg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.894.0", + "@aws-sdk/types": "3.893.0", + "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/node-http-handler": "^4.2.1", + "@smithy/property-provider": "^4.1.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/smithy-client": "^4.6.3", + "@smithy/types": "^4.5.0", + "@smithy/util-stream": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.895.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.895.0.tgz", + "integrity": "sha512-uIh7N4IN/yIk+qYMAkVpVkjhB90SGKSfaXEVcnmxzBDG6e5304HKT0esqoCVZvtFfLKasjm2TOpalM5l3fi/dA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.894.0", + "@aws-sdk/credential-provider-env": "3.894.0", + "@aws-sdk/credential-provider-http": "3.894.0", + "@aws-sdk/credential-provider-process": "3.894.0", + "@aws-sdk/credential-provider-sso": "3.895.0", + "@aws-sdk/credential-provider-web-identity": "3.895.0", + "@aws-sdk/nested-clients": "3.895.0", + "@aws-sdk/types": "3.893.0", + "@smithy/credential-provider-imds": "^4.1.2", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.895.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.895.0.tgz", + "integrity": "sha512-7xsBCmkBUz+2sNqNsDJ1uyQsBvwhNFzwFt8wX39WrFJTpTQh3uNQ5g8QH21BbkKqIFKCLdvgHgwt3Ub5RGVuPA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.894.0", + "@aws-sdk/credential-provider-http": "3.894.0", + "@aws-sdk/credential-provider-ini": "3.895.0", + "@aws-sdk/credential-provider-process": "3.894.0", + "@aws-sdk/credential-provider-sso": "3.895.0", + "@aws-sdk/credential-provider-web-identity": "3.895.0", + "@aws-sdk/types": "3.893.0", + "@smithy/credential-provider-imds": "^4.1.2", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.894.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.894.0.tgz", + "integrity": "sha512-VU74GNsj+SsO+pl4d+JimlQ7+AcderZaC6bFndQssQdFZ5NRad8yFNz5Xbec8CPJr+z/VAwHib6431F5nYF46g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.894.0", + "@aws-sdk/types": "3.893.0", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.895.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.895.0.tgz", + "integrity": "sha512-bZCcHUZGz+XlCaK0KEOHGHkMtlwIvnpxJvlZtSCVaBdX/IgouxaB42fxChflxSMRWF45ygdezfky4i17f6vC4w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.895.0", + "@aws-sdk/core": "3.894.0", + "@aws-sdk/token-providers": "3.895.0", + "@aws-sdk/types": "3.893.0", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.895.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.895.0.tgz", + "integrity": "sha512-tKbXbOp2xrL02fxKvB7ko1E4Uvyy5TF9qi5pT2MVWNnfSsBlUM80aJ6tyUPKWXdUTdAlPrU3XcwgQl/DnnRa9A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.894.0", + "@aws-sdk/nested-clients": "3.895.0", + "@aws-sdk/types": "3.893.0", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.893.0.tgz", + "integrity": "sha512-qL5xYRt80ahDfj9nDYLhpCNkDinEXvjLe/Qen/Y/u12+djrR2MB4DRa6mzBCkLkdXDtf0WAoW2EZsNCfGrmOEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.893.0", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.893.0.tgz", + "integrity": "sha512-ZqzMecjju5zkBquSIfVfCORI/3Mge21nUY4nWaGQy+NUXehqCGG4W7AiVpiHGOcY2cGJa7xeEkYcr2E2U9U0AA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.893.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.893.0.tgz", + "integrity": "sha512-H7Zotd9zUHQAr/wr3bcWHULYhEeoQrF54artgsoUGIf/9emv6LzY89QUccKIxYd6oHKNTrTyXm9F0ZZrzXNxlg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.893.0", + "@aws/lambda-invoke-store": "^0.0.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.894.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.894.0.tgz", + "integrity": "sha512-0C3lTVdTuv5CkJ4LulpA7FmGFSKrGUKxnFZ6+qGjYjNzbdiHXfq0TyEBiDmVqDkoV2k4AT2H/m0Xw//rTkcNEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.894.0", + "@aws-sdk/types": "3.893.0", + "@aws-sdk/util-arn-parser": "3.893.0", + "@smithy/core": "^3.11.1", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/protocol-http": "^5.2.1", + "@smithy/signature-v4": "^5.2.1", + "@smithy/smithy-client": "^4.6.3", + "@smithy/types": "^4.5.0", + "@smithy/util-config-provider": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-stream": "^4.3.2", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.895.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.895.0.tgz", + "integrity": "sha512-JUqQW2RPp4I95wZ/Im9fTiaX3DF55oJgeoiNlLdHkQZPSNNS/pT1WMWMReSvJdcfSNU3xSUaLtI+h4mQjQUDbQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.894.0", + "@aws-sdk/types": "3.893.0", + "@aws-sdk/util-endpoints": "3.895.0", + "@smithy/core": "^3.11.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.895.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.895.0.tgz", + "integrity": "sha512-8w1ihfYgvds6kfal/qJXQQrHRsKYh2nujSyzWMo2TMKMze9WPZA93G4mRbRtKtbSuQ66mVWePH8Cksq35ABu2Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.894.0", + "@aws-sdk/middleware-host-header": "3.893.0", + "@aws-sdk/middleware-logger": "3.893.0", + "@aws-sdk/middleware-recursion-detection": "3.893.0", + "@aws-sdk/middleware-user-agent": "3.895.0", + "@aws-sdk/region-config-resolver": "3.893.0", + "@aws-sdk/types": "3.893.0", + "@aws-sdk/util-endpoints": "3.895.0", + "@aws-sdk/util-user-agent-browser": "3.893.0", + "@aws-sdk/util-user-agent-node": "3.895.0", + "@smithy/config-resolver": "^4.2.2", + "@smithy/core": "^3.11.1", + "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/hash-node": "^4.1.1", + "@smithy/invalid-dependency": "^4.1.1", + "@smithy/middleware-content-length": "^4.1.1", + "@smithy/middleware-endpoint": "^4.2.3", + "@smithy/middleware-retry": "^4.2.4", + "@smithy/middleware-serde": "^4.1.1", + "@smithy/middleware-stack": "^4.1.1", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/node-http-handler": "^4.2.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/smithy-client": "^4.6.3", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-body-length-node": "^4.1.0", + "@smithy/util-defaults-mode-browser": "^4.1.3", + "@smithy/util-defaults-mode-node": "^4.1.3", + "@smithy/util-endpoints": "^3.1.2", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-retry": "^4.1.2", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.893.0.tgz", + "integrity": "sha512-/cJvh3Zsa+Of0Zbg7vl9wp/kZtdb40yk/2+XcroAMVPO9hPvmS9r/UOm6tO7FeX4TtkRFwWaQJiTZTgSdsPY+Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.893.0", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/types": "^4.5.0", + "@smithy/util-config-provider": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.894.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.894.0.tgz", + "integrity": "sha512-Te5b3fSbatkZrh3eYNmpOadZFKsCLNSwiolQKQeEeKHxdnqORwYXa+0ypcTHle6ukic+tFRRd9n3NuMVo9uiVg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.894.0", + "@aws-sdk/types": "3.893.0", + "@smithy/protocol-http": "^5.2.1", + "@smithy/signature-v4": "^5.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.895.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.895.0.tgz", + "integrity": "sha512-vJqrEHFFGRZ3ok5T+jII00sa2DQ3HdVkTBIfM0DcrcPssqDV18VKdA767qiBdIEN/cygjdBg8Ri/cuq6ER9BeQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.894.0", + "@aws-sdk/nested-clients": "3.895.0", + "@aws-sdk/types": "3.893.0", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.893.0.tgz", + "integrity": "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.895.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.895.0.tgz", + "integrity": "sha512-MhxBvWbwxmKknuggO2NeMwOVkHOYL98pZ+1ZRI5YwckoCL3AvISMnPJgfN60ww6AIXHGpkp+HhpFdKOe8RHSEg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.893.0", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "@smithy/util-endpoints": "^3.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz", + "integrity": "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.893.0.tgz", + "integrity": "sha512-PE9NtbDBW6Kgl1bG6A5fF3EPo168tnkj8TgMcT0sg4xYBWsBpq0bpJZRh+Jm5Bkwiw9IgTCLjEU7mR6xWaMB9w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.893.0", + "@smithy/types": "^4.5.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.895.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.895.0.tgz", + "integrity": "sha512-lLRC7BAFOPtJk4cZC0Q0MZBMCGF109QpGnug3L3n/2TJW02Sinz9lzA0ykBpYXe9j60LjIYSENCg+F4DZE5vxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.895.0", + "@aws-sdk/types": "3.893.0", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.894.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.894.0.tgz", + "integrity": "sha512-E6EAMc9dT1a2DOdo4zyOf3fp5+NJ2wI+mcm7RaW1baFIWDwcb99PpvWoV7YEiK7oaBDshuOEGWKUSYXdW+JYgA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.0.1.tgz", + "integrity": "sha512-ORHRQ2tmvnBXc8t/X9Z8IcSbBA4xTLKuN873FopzklHMeqBst7YG0d+AX97inkvDX+NChYtSr+qGfcqGFaI8Zw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -1655,6 +2402,626 @@ "dev": true, "license": "(Unlicense OR Apache-2.0)" }, + "node_modules/@smithy/abort-controller": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.1.1.tgz", + "integrity": "sha512-vkzula+IwRvPR6oKQhMYioM3A/oX/lFCZiwuxkQbRhqJS2S4YRY2k7k/SyR2jMf3607HLtbEwlRxi0ndXHMjRg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.2.2.tgz", + "integrity": "sha512-IT6MatgBWagLybZl1xQcURXRICvqz1z3APSCAI9IqdvfCkrA7RaQIEfgC6G/KvfxnDfQUDqFV+ZlixcuFznGBQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.2.2", + "@smithy/types": "^4.5.0", + "@smithy/util-config-provider": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.12.0.tgz", + "integrity": "sha512-zJeAgogZfbwlPGL93y4Z/XNeIN37YCreRUd6YMIRvaq+6RnBK8PPYYIQ85Is/GglPh3kNImD5riDCXbVSDpCiQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.1.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-stream": "^4.3.2", + "@smithy/util-utf8": "^4.1.0", + "@smithy/uuid": "^1.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.1.2.tgz", + "integrity": "sha512-JlYNq8TShnqCLg0h+afqe2wLAwZpuoSgOyzhYvTgbiKBWRov+uUve+vrZEQO6lkdLOWPh7gK5dtb9dS+KGendg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.2.2", + "@smithy/property-provider": "^4.1.1", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.2.1.tgz", + "integrity": "sha512-5/3wxKNtV3wO/hk1is+CZUhL8a1yy/U+9u9LKQ9kZTkMsHaQjJhc3stFfiujtMnkITjzWfndGA2f7g9Uh9vKng==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.2.1", + "@smithy/querystring-builder": "^4.1.1", + "@smithy/types": "^4.5.0", + "@smithy/util-base64": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.1.1.tgz", + "integrity": "sha512-H9DIU9WBLhYrvPs9v4sYvnZ1PiAI0oc8CgNQUJ1rpN3pP7QADbTOUjchI2FB764Ub0DstH5xbTqcMJu1pnVqxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "@smithy/util-buffer-from": "^4.1.0", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.1.1.tgz", + "integrity": "sha512-1AqLyFlfrrDkyES8uhINRlJXmHA2FkG+3DY8X+rmLSqmFwk3DJnvhyGzyByPyewh2jbmV+TYQBEfngQax8IFGg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.1.0.tgz", + "integrity": "sha512-ePTYUOV54wMogio+he4pBybe8fwg4sDvEVDBU8ZlHOZXbXK3/C0XfJgUCu6qAZcawv05ZhZzODGUerFBPsPUDQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.1.1.tgz", + "integrity": "sha512-9wlfBBgTsRvC2JxLJxv4xDGNBrZuio3AgSl0lSFX7fneW2cGskXTYpFxCdRYD2+5yzmsiTuaAJD1Wp7gWt9y9w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.2.4.tgz", + "integrity": "sha512-FZ4hzupOmthm8Q8ujYrd0I+/MHwVMuSTdkDtIQE0xVuvJt9pLT6Q+b0p4/t+slDyrpcf+Wj7SN+ZqT5OryaaZg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.12.0", + "@smithy/middleware-serde": "^4.1.1", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "@smithy/util-middleware": "^4.1.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.3.0.tgz", + "integrity": "sha512-qhEX9745fAxZvtLM4bQJAVC98elWjiMO2OiHl1s6p7hUzS4QfZO1gXUYNwEK8m0J6NoCD5W52ggWxbIDHI0XSg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.2.2", + "@smithy/protocol-http": "^5.2.1", + "@smithy/service-error-classification": "^4.1.2", + "@smithy/smithy-client": "^4.6.4", + "@smithy/types": "^4.5.0", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-retry": "^4.1.2", + "@smithy/uuid": "^1.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.1.1.tgz", + "integrity": "sha512-lh48uQdbCoj619kRouev5XbWhCwRKLmphAif16c4J6JgJ4uXjub1PI6RL38d3BLliUvSso6klyB/LTNpWSNIyg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.1.1.tgz", + "integrity": "sha512-ygRnniqNcDhHzs6QAPIdia26M7e7z9gpkIMUe/pK0RsrQ7i5MblwxY8078/QCnGq6AmlUUWgljK2HlelsKIb/A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.2.2.tgz", + "integrity": "sha512-SYGTKyPvyCfEzIN5rD8q/bYaOPZprYUPD2f5g9M7OjaYupWOoQFYJ5ho+0wvxIRf471i2SR4GoiZ2r94Jq9h6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.2.1.tgz", + "integrity": "sha512-REyybygHlxo3TJICPF89N2pMQSf+p+tBJqpVe1+77Cfi9HBPReNjTgtZ1Vg73exq24vkqJskKDpfF74reXjxfw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.1.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/querystring-builder": "^4.1.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.1.1.tgz", + "integrity": "sha512-gm3ZS7DHxUbzC2wr8MUCsAabyiXY0gaj3ROWnhSx/9sPMc6eYLMM4rX81w1zsMaObj2Lq3PZtNCC1J6lpEY7zg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.2.1.tgz", + "integrity": "sha512-T8SlkLYCwfT/6m33SIU/JOVGNwoelkrvGjFKDSDtVvAXj/9gOT78JVJEas5a+ETjOu4SVvpCstKgd0PxSu/aHw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.1.1.tgz", + "integrity": "sha512-J9b55bfimP4z/Jg1gNo+AT84hr90p716/nvxDkPGCD4W70MPms0h8KF50RDRgBGZeL83/u59DWNqJv6tEP/DHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "@smithy/util-uri-escape": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.1.1.tgz", + "integrity": "sha512-63TEp92YFz0oQ7Pj9IuI3IgnprP92LrZtRAkE3c6wLWJxfy/yOPRt39IOKerVr0JS770olzl0kGafXlAXZ1vng==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.1.2.tgz", + "integrity": "sha512-Kqd8wyfmBWHZNppZSMfrQFpc3M9Y/kjyN8n8P4DqJJtuwgK1H914R471HTw7+RL+T7+kI1f1gOnL7Vb5z9+NgQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.2.0.tgz", + "integrity": "sha512-OQTfmIEp2LLuWdxa8nEEPhZmiOREO6bcB6pjs0AySf4yiZhl6kMOfqmcwcY8BaBPX+0Tb+tG7/Ia/6mwpoZ7Pw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.2.1.tgz", + "integrity": "sha512-M9rZhWQLjlQVCCR37cSjHfhriGRN+FQ8UfgrYNufv66TJgk+acaggShl3KS5U/ssxivvZLlnj7QH2CUOKlxPyA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.1.0", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "@smithy/util-hex-encoding": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-uri-escape": "^4.1.0", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.6.4.tgz", + "integrity": "sha512-qL7O3VDyfzCSN9r+sdbQXGhaHtrfSJL30En6Jboj0I3bobf2g1/T0eP2L4qxqrEW26gWhJ4THI4ElVVLjYyBHg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.12.0", + "@smithy/middleware-endpoint": "^4.2.4", + "@smithy/middleware-stack": "^4.1.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "@smithy/util-stream": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.5.0.tgz", + "integrity": "sha512-RkUpIOsVlAwUIZXO1dsz8Zm+N72LClFfsNqf173catVlvRZiwPy0x2u0JLEA4byreOPKDZPGjmPDylMoP8ZJRg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.1.1.tgz", + "integrity": "sha512-bx32FUpkhcaKlEoOMbScvc93isaSiRM75pQ5IgIBaMkT7qMlIibpPRONyx/0CvrXHzJLpOn/u6YiDX2hcvs7Dg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.1.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.1.0.tgz", + "integrity": "sha512-RUGd4wNb8GeW7xk+AY5ghGnIwM96V0l2uzvs/uVHf+tIuVX2WSvynk5CxNoBCsM2rQRSZElAo9rt3G5mJ/gktQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.1.0", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.1.0.tgz", + "integrity": "sha512-V2E2Iez+bo6bUMOTENPr6eEmepdY8Hbs+Uc1vkDKgKNA/brTJqOW/ai3JO1BGj9GbCeLqw90pbbH7HFQyFotGQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.1.0.tgz", + "integrity": "sha512-BOI5dYjheZdgR9XiEM3HJcEMCXSoqbzu7CzIgYrx0UtmvtC3tC2iDGpJLsSRFffUpy8ymsg2ARMP5fR8mtuUQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.1.0.tgz", + "integrity": "sha512-N6yXcjfe/E+xKEccWEKzK6M+crMrlwaCepKja0pNnlSkm6SjAeLKKA++er5Ba0I17gvKfN/ThV+ZOx/CntKTVw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.1.0.tgz", + "integrity": "sha512-swXz2vMjrP1ZusZWVTB/ai5gK+J8U0BWvP10v9fpcFvg+Xi/87LHvHfst2IgCs1i0v4qFZfGwCmeD/KNCdJZbQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.1.4.tgz", + "integrity": "sha512-mLDJ1s4eA3vwOGaQOEPlg5LB4LdZUUMpB5UMOMofeGhWqiS7WR7dTpLiNi9zVn+YziKUd3Af5NLfxDs7NJqmIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.1.1", + "@smithy/smithy-client": "^4.6.4", + "@smithy/types": "^4.5.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.1.4.tgz", + "integrity": "sha512-pjX2iMTcOASaSanAd7bu6i3fcMMezr3NTr8Rh64etB0uHRZi+Aw86DoCxPESjY4UTIuA06hhqtTtw95o//imYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.2.2", + "@smithy/credential-provider-imds": "^4.1.2", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/property-provider": "^4.1.1", + "@smithy/smithy-client": "^4.6.4", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.1.2.tgz", + "integrity": "sha512-+AJsaaEGb5ySvf1SKMRrPZdYHRYSzMkCoK16jWnIMpREAnflVspMIDeCVSZJuj+5muZfgGpNpijE3mUNtjv01Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.2.2", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.1.0.tgz", + "integrity": "sha512-1LcueNN5GYC4tr8mo14yVYbh/Ur8jHhWOxniZXii+1+ePiIbsLZ5fEI0QQGtbRRP5mOhmooos+rLmVASGGoq5w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.1.1.tgz", + "integrity": "sha512-CGmZ72mL29VMfESz7S6dekqzCh8ZISj3B+w0g1hZFXaOjGTVaSqfAEFAq8EGp8fUL+Q2l8aqNmt8U1tglTikeg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.1.2.tgz", + "integrity": "sha512-NCgr1d0/EdeP6U5PSZ9Uv5SMR5XRRYoVr1kRVtKZxWL3tixEL3UatrPIMFZSKwHlCcp2zPLDvMubVDULRqeunA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.1.2", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.3.2.tgz", + "integrity": "sha512-Ka+FA2UCC/Q1dEqUanCdpqwxOFdf5Dg2VXtPtB1qxLcSGh5C1HdzklIt18xL504Wiy9nNUKwDMRTVCbKGoK69g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/node-http-handler": "^4.2.1", + "@smithy/types": "^4.5.0", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-buffer-from": "^4.1.0", + "@smithy/util-hex-encoding": "^4.1.0", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.1.0.tgz", + "integrity": "sha512-b0EFQkq35K5NHUYxU72JuoheM6+pytEVUGlTwiFxWFpmddA+Bpz3LgsPRIpBk8lnPE47yT7AF2Egc3jVnKLuPg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.1.0.tgz", + "integrity": "sha512-mEu1/UIXAdNYuBcyEPbjScKi/+MQVXNIuY/7Cm5XLIWe319kDrT5SizBE95jqtmEXoDbGoZxKLCMttdZdqTZKQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.0.0.tgz", + "integrity": "sha512-OlA/yZHh0ekYFnbUkmYBDQPE6fGfdrvgz39ktp8Xf+FA6BfxLejPTMDOG0Nfk5/rDySAz1dRbFf24zaAFYVXlQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@szmarczak/http-timer": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", @@ -1714,6 +3081,24 @@ "undici-types": "~7.10.0" } }, + "node_modules/@types/nodemailer": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.1.tgz", + "integrity": "sha512-UfHAghPmGZVzaL8x9y+mKZMWyHC399+iq0MOmya5tIyenWX3lcdSb60vOmp0DocR6gCDTYTozv/ULQnREyyjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@aws-sdk/client-sesv2": "^3.839.0", + "@types/node": "*" + } + }, + "node_modules/@types/ping": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@types/ping/-/ping-0.4.4.tgz", + "integrity": "sha512-ifvo6w2f5eJYlXm+HiVx67iJe8WZp87sfa683nlqED5Vnt9Z93onkokNoWqOG21EaE8fMxyKPobE+mkPEyxsdw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/relateurl": { "version": "0.2.33", "resolved": "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.33.tgz", @@ -2073,6 +3458,13 @@ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "license": "ISC" }, + "node_modules/bowser": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.1.tgz", + "integrity": "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==", + "dev": true, + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", diff --git a/server/package.json b/server/package.json index 637c785cc..2bee352e0 100755 --- a/server/package.json +++ b/server/package.json @@ -54,6 +54,8 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/nodemailer": "7.0.1", + "@types/ping": "0.4.4", "c8": "10.1.3", "chai": "5.2.0", "eslint": "^9.17.0", diff --git a/server/src/config/index.ts b/server/src/config/index.ts new file mode 100644 index 000000000..7ace6f99c --- /dev/null +++ b/server/src/config/index.ts @@ -0,0 +1,25 @@ +import dotenv from "dotenv"; + +export interface IEevConfig { + NODE_ENV: string; + JWT_SECRET: string; + PORT: number; + PAGESPEED_API_KEY: string; + SMTP_HOST: string; + SMTP_PORT: number; + SMTP_USER: string; + SMTP_PASS: string; +} + +dotenv.config(); + +export const config: IEevConfig = { + NODE_ENV: process.env.NODE_ENV || "development", + JWT_SECRET: process.env.JWT_SECRET || "your_jwt_secret", + PORT: process.env.PORT ? parseInt(process.env.PORT, 10) : 3000, + PAGESPEED_API_KEY: process.env.PAGESPEED_API_KEY || "", + SMTP_HOST: process.env.SMTP_HOST || "smtp.example.com", + SMTP_PORT: process.env.SMTP_PORT ? parseInt(process.env.SMTP_PORT, 10) : 587, + SMTP_USER: process.env.SMTP_USER || "user@example.com", + SMTP_PASS: process.env.SMTP_PASS || "your_smtp_password", +}; diff --git a/server/src/service/v2/business/AuthService.ts b/server/src/service/v2/business/AuthService.ts new file mode 100644 index 000000000..cf49a71c6 --- /dev/null +++ b/server/src/service/v2/business/AuthService.ts @@ -0,0 +1,206 @@ +import bcrypt from "bcryptjs"; +import { User, Role, ITokenizedUser, Monitor, Check, NotificationChannel } from "../../../db/v2/models/index.js"; +import ApiError from "../../../utils/ApiError.js"; +import { Types } from "mongoose"; +import { IJobQueue } from "../infrastructure/JobQueue.js"; + +export const PERMISSIONS = { + users: { + all: "users.*", + create: "users.create", + view: "users.view", + update: "users.update", + delete: "users.delete", + }, + + monitors: { + all: "monitors.*", + create: "monitors.create", + view: "monitors.view", + update: "monitors.update", + delete: "monitors.delete", + }, + notifications: { + all: "notifications.*", + create: "notifications.create", + view: "notifications.view", + update: "notifications.update", + delete: "notifications.delete", + }, + checks: { + all: "checks.*", + create: "checks.create", + view: "checks.view", + update: "checks.update", + delete: "checks.delete", + }, + statusPages: { + all: "statusPages.*", + create: "statusPages.create", + view: "statusPages.view", + update: "statusPages.update", + delete: "statusPages.delete", + }, +}; + +const DEFAULT_ROLES = [ + { + name: "SuperAdmin", + description: "Super admin with all permissions", + permissions: ["*"], + isSystem: true, + }, + { + name: "Admin", + description: "Admin with full permissions", + permissions: [PERMISSIONS.monitors.all, PERMISSIONS.users.all], + isSystem: true, + }, + { + name: "Manager", + description: "Can manage users", + permissions: [PERMISSIONS.users.create, PERMISSIONS.users.update, PERMISSIONS.monitors.all], + isSystem: true, + }, + { + name: "Member", + description: "Basic team member", + permissions: [PERMISSIONS.users.update, PERMISSIONS.monitors.create, PERMISSIONS.monitors.view, PERMISSIONS.monitors.update], + isSystem: true, + }, +]; + +export type RegisterData = { + email: string; + firstName: string; + lastName: string; + password: string; + roles?: Types.ObjectId[]; // Optional roles for invite-based registration +}; + +export type LoginData = { + email: string; + password: string; +}; + +export type AuthResult = ITokenizedUser; + +export interface IAuthService { + register(signupData: RegisterData): Promise; + registerWithInvite(signupData: RegisterData): Promise; + login(loginData: LoginData): Promise; + cleanup(): Promise; + cleanMonitors(): Promise; +} + +class AuthService implements IAuthService { + private jobQueue: IJobQueue; + constructor(jobQueue: IJobQueue) { + this.jobQueue = jobQueue; + } + + async register(signupData: RegisterData): Promise { + const userCount = await User.countDocuments(); + + if (userCount > 0) { + throw new Error("Registration is closed. Please request an invite."); + } + + const { email, firstName, lastName, password } = signupData; + + // Create all default roles + const rolePromises = DEFAULT_ROLES.map((roleData) => + new Role({ + ...roleData, + }).save() + ); + const roles = await Promise.all(rolePromises); + + // Hash password and create user + const saltRounds = 12; + const passwordHash = await bcrypt.hash(password, saltRounds); + + // Find admin role and assign to first user + const superAdminRole = roles.find((role) => role.name === "SuperAdmin"); + + const user = new User({ + email, + firstName, + lastName, + passwordHash, + roles: [superAdminRole!._id], + }); + + const savedUser = await user.save(); + return { + sub: savedUser._id.toString(), + roles: savedUser.roles.map((role) => role.toString()), + }; + } + + async registerWithInvite(signupData: RegisterData): Promise { + const { email, firstName, lastName, password, roles } = signupData; + + const saltRounds = 12; + const passwordHash = await bcrypt.hash(password, saltRounds); + const user = new User({ + email, + firstName, + lastName, + passwordHash, + roles: roles || [], + }); + try { + const savedUser = await user.save(); + return { + sub: savedUser._id.toString(), + roles: savedUser.roles.map((role) => role.toString()), + }; + } catch (error: any) { + if (error?.code === 11000) { + const dupError = new ApiError("Email already in use", 409); + dupError.stack = error?.stack; + throw dupError; + } + throw error; + } + } + + async login(loginData: LoginData): Promise { + const { email, password } = loginData; + + // Find user by email + const user = await User.findOne({ email }); + + if (!user) { + throw new Error("Invalid email or password"); + } + + // Check password + const isPasswordValid = await bcrypt.compare(password, user.passwordHash); + if (!isPasswordValid) { + throw new Error("Invalid email or password"); + } + + return { + sub: user._id.toString(), + roles: user.roles.map((role) => role.toString()), + }; + } + + async cleanup() { + await User.deleteMany({}); + await Role.deleteMany({}); + await Monitor.deleteMany({}); + await Check.deleteMany({}); + await NotificationChannel.deleteMany({}); + await this.jobQueue.flush(); + } + + async cleanMonitors() { + await Monitor.deleteMany({}); + await Check.deleteMany({}); + } +} + +export default AuthService; diff --git a/server/src/service/v2/business/CheckService.ts b/server/src/service/v2/business/CheckService.ts new file mode 100644 index 000000000..5c68891a9 --- /dev/null +++ b/server/src/service/v2/business/CheckService.ts @@ -0,0 +1,132 @@ +import { json } from "stream/consumers"; +import { ICheck, Check, Monitor } from "../../../db/v2/models/index.js"; +import type { ISystemInfo, ICaptureInfo } from "../../../db/v2/models/index.js"; +import { MonitorType } from "../../../db/v2/models/monitors/Monitor.js"; +import { StatusResponse } from "../infrastructure/NetworkService.js"; +import type { ICapturePayload, ILighthousePayload } from "../infrastructure/NetworkService.js"; +import mongoose from "mongoose"; + +export interface ICheckService { + buildCheck: (statusResponse: StatusResponse, type: MonitorType) => Promise; + cleanupOrphanedChecks: () => Promise; +} + +class CheckService implements ICheckService { + private isCapturePayload = (payload: any): payload is ICapturePayload => { + if (!payload || typeof payload !== "object") return false; + + if (!("data" in payload) || typeof payload.data !== "object") { + return false; + } + + const data = payload.data as Partial; + if (!data.cpu || typeof data.cpu !== "object" || typeof data.cpu.usage_percent !== "number") { + return false; + } + + if (!data.memory || typeof data.memory !== "object" || typeof data.memory.usage_percent !== "number") { + return false; + } + + if (data.disk && !Array.isArray(data.disk)) { + return false; + } + if (data.net && !Array.isArray(data.net)) { + return false; + } + + if (!("capture" in payload) || typeof payload.capture !== "object") return false; + const capture = payload.capture as Record; + if (typeof capture.version !== "string" || typeof capture.mode !== "string") return false; + + return true; + }; + + private isPagespeedPayload = (payload: any): payload is ILighthousePayload => { + if (!payload || typeof payload !== "object") return false; + + if (!("lighthouseResult" in payload) || typeof payload.lighthouseResult !== "object") { + return false; + } + return true; + }; + + private buildBaseCheck = (statusResponse: StatusResponse) => { + const monitorId = new mongoose.Types.ObjectId(statusResponse.monitorId); + const check = new Check({ + monitorId: monitorId, + type: statusResponse?.type, + status: statusResponse?.status, + message: statusResponse?.message, + responseTime: statusResponse?.responseTime, + timings: statusResponse?.timings, + }); + return check; + }; + + private buildInfrastructureCheck = (statusResponse: StatusResponse) => { + if (!this.isCapturePayload(statusResponse.payload)) { + throw new Error("Invalid payload for infrastructure monitor"); + } + const check = this.buildBaseCheck(statusResponse); + check.system = statusResponse.payload.data; + check.capture = statusResponse.payload.capture; + return check; + }; + + private buildPagespeedCheck = (statusResponse: StatusResponse) => { + if (!this.isPagespeedPayload(statusResponse.payload)) { + throw new Error("Invalid payload for pagespeed monitor"); + } + const check = this.buildBaseCheck(statusResponse); + const lighthouseResult = statusResponse?.payload?.lighthouseResult; + check.lighthouse = { + accessibility: lighthouseResult?.categories?.accessibility?.score || 0, + bestPractices: lighthouseResult?.categories?.["best-practices"]?.score || 0, + seo: lighthouseResult?.categories?.seo?.score || 0, + performance: lighthouseResult?.categories?.performance?.score || 0, + audits: { + cls: lighthouseResult?.audits?.["cumulative-layout-shift"] || {}, + si: lighthouseResult?.audits?.["speed-index"] || {}, + fcp: lighthouseResult?.audits?.["first-contentful-paint"] || {}, + lcp: lighthouseResult?.audits?.["largest-contentful-paint"] || {}, + tbt: lighthouseResult?.audits?.["total-blocking-time"] || {}, + }, + }; + return check; + }; + + buildCheck = async (statusResponse: StatusResponse, type: MonitorType): Promise => { + switch (type) { + case "infrastructure": + return this.buildInfrastructureCheck(statusResponse as StatusResponse); + + case "pagespeed": + return this.buildPagespeedCheck(statusResponse as StatusResponse); + case "http": + case "https": + return this.buildBaseCheck(statusResponse); + + case "ping": + return this.buildBaseCheck(statusResponse); + default: + throw new Error(`Unsupported monitor type: ${type}`); + } + }; + + cleanupOrphanedChecks = async () => { + try { + const monitorIds = await Monitor.find().distinct("_id"); + const result = await Check.deleteMany({ + monitorId: { $nin: monitorIds }, + }); + console.log(`Deleted ${result.deletedCount} orphaned Checks.`); + return true; + } catch (error) { + console.error("Error cleaning up orphaned Checks:", error); + return false; + } + }; +} + +export default CheckService; diff --git a/server/src/service/v2/business/InviteService.ts b/server/src/service/v2/business/InviteService.ts new file mode 100644 index 000000000..1c954d75f --- /dev/null +++ b/server/src/service/v2/business/InviteService.ts @@ -0,0 +1,61 @@ +import crypto from "node:crypto"; +import { ITokenizedUser, IInvite, Invite } from "../../../db/v2/models/index.js"; +import ApiError from "../../../utils/ApiError.js"; + +export interface IInviteService { + create: (tokenizedUser: ITokenizedUser, invite: IInvite) => Promise<{ token: string }>; + getAll: () => Promise; + get: (tokenHash: string) => Promise; + delete: (id: string) => Promise; +} + +class InviteService implements IInviteService { + constructor() {} + + create = async (tokenizedUser: ITokenizedUser, inviteData: IInvite) => { + const token = crypto.randomBytes(32).toString("hex"); + const tokenHash = crypto.createHash("sha256").update(token).digest("hex"); + try { + const invite = await Invite.create({ + ...inviteData, + tokenHash, + createdBy: tokenizedUser.sub, + updatedBy: tokenizedUser.sub, + }); + if (!invite) { + throw new ApiError("Failed to create invite", 500); + } + return { token }; + } catch (error: any) { + if (error?.code === 11000) { + const dupError = new ApiError("Invite with this email already exists", 409); + dupError.stack = error?.stack; + throw dupError; + } + throw error; + } + }; + + get = async (token: string) => { + const tokenHash = crypto.createHash("sha256").update(token).digest("hex"); + const invite = await Invite.findOne({ tokenHash }); + if (!invite) { + throw new ApiError("Invite not found", 404); + } + return invite; + }; + + getAll = async () => { + return Invite.find(); + }; + + delete = async (id: string) => { + const result = await Invite.deleteOne({ _id: id }); + if (!result.deletedCount) { + throw new ApiError("Invite not found", 404); + } + return result.deletedCount === 1; + }; +} + +export default InviteService; diff --git a/server/src/service/v2/business/MaintenanceService.ts b/server/src/service/v2/business/MaintenanceService.ts new file mode 100644 index 000000000..ef8c8152f --- /dev/null +++ b/server/src/service/v2/business/MaintenanceService.ts @@ -0,0 +1,141 @@ +import { ITokenizedUser, IMaintenance, Maintenance } from "../../../db/v2/models/index.js"; +import ApiError from "../../../utils/ApiError.js"; + +export interface IMaintenanceService { + create: ( + tokenizedUser: ITokenizedUser, + + maintenance: IMaintenance + ) => Promise; + getAll: () => Promise; + get: (id: string) => Promise; + toggleActive: (tokenizedUser: ITokenizedUser, id: string) => Promise; + update: (tokenizedUser: ITokenizedUser, id: string, updateData: Partial) => Promise; + delete: (id: string) => Promise; + isInMaintenance: (monitorId: string) => Promise; +} + +type MaintenanceCache = Map; + +class MaintenanceService implements IMaintenanceService { + private maintenanceCache: MaintenanceCache; + private lastRefresh: number; + private CACHE_TTL_MS = 60 * 1000; + + constructor() { + this.maintenanceCache = new Map(); + this.lastRefresh = 0; + } + + create = async (tokenizedUser: ITokenizedUser, maintenanceData: IMaintenance) => { + const maintenance = await Maintenance.create({ + ...maintenanceData, + createdBy: tokenizedUser.sub, + updatedBy: tokenizedUser.sub, + }); + return maintenance; + }; + + get = async (id: string) => { + const maintenance = await Maintenance.findById(id); + if (!maintenance) { + throw new ApiError("Maintenance not found", 404); + } + return maintenance; + }; + + getAll = async () => { + return Maintenance.find(); + }; + + toggleActive = async (tokenizedUser: ITokenizedUser, id: string) => { + const updatedMaintenance = await Maintenance.findOneAndUpdate( + { _id: id }, + [ + { + $set: { + isActive: { $not: "$isActive" }, + updatedBy: tokenizedUser.sub, + updatedAt: new Date(), + }, + }, + ], + { new: true } + ); + if (!updatedMaintenance) { + throw new ApiError("Maintenance not found", 404); + } + return updatedMaintenance; + }; + + update = async (tokenizedUser: ITokenizedUser, id: string, updateData: Partial) => { + const allowedFields: (keyof IMaintenance)[] = ["name", "monitors", "startTime", "endTime", "isActive"]; + const safeUpdate: Partial = {}; + for (const field of allowedFields) { + if (updateData[field] !== undefined) { + (safeUpdate as any)[field] = updateData[field]; + } + } + + const updatedMaintenance = await Maintenance.findByIdAndUpdate( + id, + { + $set: { + ...safeUpdate, + updatedAt: new Date(), + updatedBy: tokenizedUser.sub, + }, + }, + { new: true, runValidators: true } + ); + + if (!updatedMaintenance) { + throw new ApiError("Failed to update maintenance", 500); + } + + return updatedMaintenance; + }; + + delete = async (id: string) => { + const result = await Maintenance.deleteOne({ _id: id }); + if (!result.deletedCount) { + throw new ApiError("Maintenance not found", 404); + } + return result.deletedCount === 1; + }; + + private refreshCache = async () => { + const now = new Date(); + + const activeMaintenances = await Maintenance.find({ + isActive: true, + startTime: { $lte: now }, + endTime: { $gte: now }, + }).lean(); + + // Reset cache + const newCache = new Map(); + + for (const m of activeMaintenances) { + for (const monitorId of m.monitors) { + const key = monitorId.toString(); + if (!newCache.has(key)) newCache.set(key, []); + newCache.get(key)!.push(m); + } + } + + this.maintenanceCache = newCache; + this.lastRefresh = Date.now(); + }; + + isInMaintenance = async (monitorId: string) => { + const now = Date.now(); + if (now - this.lastRefresh > this.CACHE_TTL_MS) { + await this.refreshCache(); + } + const maintenances = this.maintenanceCache.get(monitorId) || []; + return maintenances.length > 0; + }; +} + +export default MaintenanceService; diff --git a/server/src/service/v2/business/MonitorStatsService.ts b/server/src/service/v2/business/MonitorStatsService.ts new file mode 100644 index 000000000..dc7d4acb3 --- /dev/null +++ b/server/src/service/v2/business/MonitorStatsService.ts @@ -0,0 +1,25 @@ +import { MonitorStats } from "../../../db/v2/models/index.js"; +import { Monitor } from "../../../db/v2/models/index.js"; +export interface IMonitorStatsService { + cleanupOrphanedMonitorStats: () => Promise; +} + +class MonitorStatsService implements IMonitorStatsService { + constructor() {} + + async cleanupOrphanedMonitorStats() { + try { + const monitorIds = await Monitor.find().distinct("_id"); + const result = await MonitorStats.deleteMany({ + monitorId: { $nin: monitorIds }, + }); + console.log(`Deleted ${result.deletedCount} orphaned MonitorStats.`); + return true; + } catch (error) { + console.error("Error cleaning up orphaned MonitorStats:", error); + return false; + } + } +} + +export default MonitorStatsService; diff --git a/server/src/service/v2/business/NotificationChannelService.ts b/server/src/service/v2/business/NotificationChannelService.ts new file mode 100644 index 000000000..35eae5b12 --- /dev/null +++ b/server/src/service/v2/business/NotificationChannelService.ts @@ -0,0 +1,100 @@ +import { ITokenizedUser, INotificationChannel, NotificationChannel, Monitor } from "../../../db/v2/models/index.js"; +import ApiError from "../../../utils/ApiError.js"; + +export interface INotificationChannelService { + create: ( + tokenizedUser: ITokenizedUser, + + notificationChannel: INotificationChannel + ) => Promise; + getAll: () => Promise; + get: (id: string) => Promise; + toggleActive: (tokenizedUser: ITokenizedUser, id: string) => Promise; + update: (tokenizedUser: ITokenizedUser, id: string, updateData: Partial) => Promise; + delete: (id: string) => Promise; +} + +class NotificationChannelService implements INotificationChannelService { + constructor() {} + + create = async (tokenizedUser: ITokenizedUser, notificationChannelData: INotificationChannel) => { + const notificationChannel = await NotificationChannel.create({ + ...notificationChannelData, + createdBy: tokenizedUser.sub, + updatedBy: tokenizedUser.sub, + }); + return notificationChannel; + }; + + get = async (id: string) => { + const channel = await NotificationChannel.findById(id); + if (!channel) { + throw new ApiError("Notification channel not found", 404); + } + return channel; + }; + + getAll = async () => { + return NotificationChannel.find(); + }; + + toggleActive = async (tokenizedUser: ITokenizedUser, id: string) => { + const updatedChannel = await NotificationChannel.findOneAndUpdate( + { _id: id }, + [ + { + $set: { + isActive: { $not: "$isActive" }, + updatedBy: tokenizedUser.sub, + updatedAt: new Date(), + }, + }, + ], + { new: true } + ); + if (!updatedChannel) { + throw new ApiError("Notification channel not found", 404); + } + return updatedChannel; + }; + + update = async (tokenizedUser: ITokenizedUser, id: string, updateData: Partial) => { + const allowedFields: (keyof INotificationChannel)[] = ["name", "config", "isActive"]; + const safeUpdate: Partial = {}; + for (const field of allowedFields) { + if (updateData[field] !== undefined) { + (safeUpdate as any)[field] = updateData[field]; + } + } + + const updatedChannel = await NotificationChannel.findByIdAndUpdate( + id, + { + $set: { + ...safeUpdate, + updatedAt: new Date(), + updatedBy: tokenizedUser.sub, + }, + }, + { new: true, runValidators: true } + ); + + if (!updatedChannel) { + throw new ApiError("Failed to update notification channel", 500); + } + + return updatedChannel; + }; + + delete = async (id: string) => { + const result = await NotificationChannel.deleteOne({ _id: id }); + if (!result.deletedCount) { + throw new ApiError("Notification channel not found", 404); + } + + await Monitor.updateMany({ notificationChannels: id }, { $pull: { notificationChannels: id } }); + return result.deletedCount === 1; + }; +} + +export default NotificationChannelService; diff --git a/server/src/service/v2/business/QueueService.ts b/server/src/service/v2/business/QueueService.ts new file mode 100644 index 000000000..20069072e --- /dev/null +++ b/server/src/service/v2/business/QueueService.ts @@ -0,0 +1,23 @@ +import { IJobQueue } from "../infrastructure/JobQueue.js"; + +class QueueService { + private jobQueue: IJobQueue; + + constructor(jobQueue: IJobQueue) { + this.jobQueue = jobQueue; + } + + async getMetrics() { + return await this.jobQueue.getMetrics(); + } + + async getJobs() { + return await this.jobQueue.getJobs(); + } + + async flush() { + return await this.jobQueue.flush(); + } +} + +export default QueueService; diff --git a/server/src/service/v2/business/UserService.ts b/server/src/service/v2/business/UserService.ts new file mode 100644 index 000000000..46d221759 --- /dev/null +++ b/server/src/service/v2/business/UserService.ts @@ -0,0 +1,13 @@ +import { IUser, User } from "../../../db/v2/models/index.js"; + +export interface IUserService { + getAllUsers(): Promise; +} + +class UserService implements IUserService { + async getAllUsers(): Promise { + return await User.find(); + } +} + +export default UserService; diff --git a/server/src/service/v2/infrastructure/JobGenerator.ts b/server/src/service/v2/infrastructure/JobGenerator.ts new file mode 100644 index 000000000..4f2beaf71 --- /dev/null +++ b/server/src/service/v2/infrastructure/JobGenerator.ts @@ -0,0 +1,80 @@ +import { IMonitor } from "../../../db/v2/models/index.js"; +import { INetworkService } from "./NetworkService.js"; +import { ICheckService } from "../business/CheckService.js"; +import { IMonitorStatsService } from "../business/MonitorStatsService.js"; +import { IStatusService } from "./StatusService.js"; +import { INotificationService } from "./NotificationService.js"; +import { IMaintenanceService } from "../business/MaintenanceService.js"; +import ApiError from "../../../utils/ApiError.js"; + +export interface IJobGenerator { + generateJob: () => (Monitor: IMonitor) => Promise; + generateCleanupJob: () => () => Promise; +} + +class JobGenerator implements IJobGenerator { + private networkService: INetworkService; + private checkService: ICheckService; + private monitorStatsService: IMonitorStatsService; + private statusService: IStatusService; + private notificationService: INotificationService; + private maintenanceService: IMaintenanceService; + + constructor( + networkService: INetworkService, + checkService: ICheckService, + monitorStatsService: IMonitorStatsService, + statusService: IStatusService, + notificationService: INotificationService, + maintenanceService: IMaintenanceService + ) { + this.networkService = networkService; + this.checkService = checkService; + this.monitorStatsService = monitorStatsService; + this.statusService = statusService; + this.notificationService = notificationService; + this.maintenanceService = maintenanceService; + } + + generateJob = () => { + return async (monitor: IMonitor) => { + try { + const monitorId = monitor._id.toString(); + if (!monitorId) { + throw new ApiError("No monitorID for creating job", 400); + } + + // Check for active maintenance window, if found, skip the check + const isInMaintenance = await this.maintenanceService.isInMaintenance(monitorId); + if (isInMaintenance) { + return; + } + + const status = await this.networkService.requestStatus(monitor); + const check = await this.checkService.buildCheck(status, monitor.type); + await check.save(); + const [updatedMonitor, statusChanged] = await this.statusService.updateMonitorStatus(monitor, status); + + if (statusChanged) { + await this.notificationService.handleNotifications(updatedMonitor); + } + await this.statusService.updateMonitorStats(updatedMonitor, status, statusChanged); + } catch (error) { + throw error; + } + }; + }; + + generateCleanupJob = () => { + return async () => { + try { + await this.checkService.cleanupOrphanedChecks(); + await this.monitorStatsService.cleanupOrphanedMonitorStats(); + } catch (error) { + throw error; + } + }; + }; +} + +export default JobGenerator; diff --git a/server/src/service/v2/infrastructure/JobQueue.ts b/server/src/service/v2/infrastructure/JobQueue.ts new file mode 100644 index 000000000..af415d3f7 --- /dev/null +++ b/server/src/service/v2/infrastructure/JobQueue.ts @@ -0,0 +1,227 @@ +import { IJob } from "super-simple-scheduler/dist/job/job.js"; +import { Monitor, IMonitor } from "../../../db/v2/models/index.js"; +import Scheduler from "super-simple-scheduler"; +import { IJobGenerator } from "./JobGenerator.js"; +export interface IJobMetrics { + jobs: number; + activeJobs: number; + failingJobs: number; + jobsWithFailures: Array<{ + monitorId: string | number; + monitorUrl: string | null; + monitorType: string | null; + failedAt: number | null; + failCount: number | null; + failReason: string | null; + }>; + totalRuns: number; + totalFailures: number; +} + +export interface IJobData extends IJob { + lastRunTook: number | null; +} + +export interface IJobQueue { + init: () => Promise; + addJob: (monitor: IMonitor) => Promise; + pauseJob: (monitor: IMonitor) => Promise; + resumeJob: (monitor: IMonitor) => Promise; + updateJob: (monitor: IMonitor) => Promise; + deleteJob: (monitor: IMonitor) => Promise; + getMetrics: () => Promise; + getJobs: () => Promise; + flush: () => Promise; + shutdown: () => Promise; +} + +export default class JobQueue implements IJobQueue { + private scheduler: Scheduler; + private static instance: JobQueue | null = null; + private jobGenerator: any; + constructor() { + this.scheduler = new Scheduler({ + logLevel: "debug", + }); + } + + static async create(jobGenerator: IJobGenerator) { + if (!JobQueue.instance) { + const instance = new JobQueue(); + instance.jobGenerator = jobGenerator; + await instance.init(); + JobQueue.instance = instance; + } + return JobQueue.instance; + } + + static getInstance(): JobQueue | null { + return JobQueue.instance; + } + + init = async () => { + try { + this.scheduler.start(); + // Add template and jobs + this.scheduler.addTemplate("monitor-job", this.jobGenerator.generateJob()); + + // Add a cleanup job + this.scheduler.addTemplate("cleanup-job", this.jobGenerator.generateCleanupJob()); + await this.scheduler.addJob({ + id: "cleanup-orphaned-checks", + template: "cleanup-job", + repeat: 24 * 60 * 60 * 1000, // 24 hours + active: true, + }); + + const monitors = await Monitor.find(); + for (const monitor of monitors) { + this.addJob(monitor); + } + + return true; + } catch (error) { + console.error(error); + return false; + } + }; + + addJob = async (monitor: IMonitor) => { + try { + return await this.scheduler?.addJob({ + id: monitor._id.toString(), + template: "monitor-job", + repeat: monitor.interval, + active: monitor.isActive, + data: monitor, + }); + } catch (error) { + console.error(error); + return false; + } + }; + + pauseJob = async (monitor: IMonitor) => { + try { + return await this.scheduler?.pauseJob(monitor._id.toString()); + } catch (error) { + console.error(error); + return false; + } + }; + + resumeJob = async (monitor: IMonitor) => { + try { + return await this.scheduler.resumeJob(monitor._id.toString()); + } catch (error) { + console.error(error); + return false; + } + }; + + updateJob = async (monitor: IMonitor) => { + try { + return await this.scheduler.updateJob(monitor._id.toString(), { + repeat: monitor.interval, + data: monitor, + }); + } catch (error) { + console.error(error); + return false; + } + }; + + deleteJob = async (monitor: IMonitor) => { + try { + this.scheduler?.removeJob(monitor._id.toString()); + return true; + } catch (error) { + console.error(error); + return false; + } + }; + + getMetrics = async (): Promise => { + try { + const jobs = await this.scheduler.getJobs(); + const metrics: IJobMetrics = jobs.reduce( + (acc, job) => { + if (!job.data) return acc; + + acc.totalRuns += job.runCount || 0; + acc.totalFailures += job.failCount || 0; + acc.jobs++; + + // Check if job is currently failing (has recent failures) + const hasFailures = job.failCount && job.failCount > 0; + const isCurrentlyFailing = hasFailures && job.lastFailedAt && (!job.lastRunAt || job.lastFailedAt > job.lastRunAt); + + if (isCurrentlyFailing) { + acc.failingJobs++; + } + + if (job.lockedAt) { + acc.activeJobs++; + } + + if (hasFailures) { + acc.jobsWithFailures.push({ + monitorId: job.id, + monitorUrl: job.data?.url || null, + monitorType: job.data?.type || null, + failedAt: job.lastFailedAt || null, + failCount: job.failCount || null, + failReason: job.lastFailReason || null, + }); + } + return acc; + }, + { + jobs: 0, + activeJobs: 0, + failingJobs: 0, + jobsWithFailures: [], + totalRuns: 0, + totalFailures: 0, + } + ); + return metrics; + } catch (error) { + console.error(error); + return null; + } + }; + + getJobs = async (): Promise => { + try { + const jobs = await this.scheduler.getJobs(); + return jobs.map((job) => { + return { + ...job, + lastRunTook: job.lockedAt || !job.lastFinishedAt || !job.lastRunAt ? null : job.lastFinishedAt - job.lastRunAt, + }; + }); + } catch (error) { + console.error(error); + return null; + } + }; + + flush = async () => { + try { + return await this.scheduler.flushJobs(); + } catch (error) { + console.error(error); + return false; + } + }; + + shutdown = async () => { + try { + return await this.scheduler.stop(); + } catch (error) { + console.error(error); + return false; + } + }; +} diff --git a/server/src/service/v2/infrastructure/NetworkService.ts b/server/src/service/v2/infrastructure/NetworkService.ts new file mode 100644 index 000000000..90657bd2d --- /dev/null +++ b/server/src/service/v2/infrastructure/NetworkService.ts @@ -0,0 +1,193 @@ +import { Got, HTTPError } from "got"; +import ping from "ping"; +import { IMonitor } from "../../../db/v2/models/index.js"; +import { GotTimings } from "../../../db/v2/models/checks/Check.js"; +import type { Response } from "got"; +import type { ISystemInfo, ICaptureInfo, ILighthouseResult } from "../../../db/v2/models/index.js"; +import { MonitorType, MonitorStatus } from "../../../db/v2/models/monitors/Monitor.js"; +import ApiError from "../../../utils/ApiError.js"; +import { config } from "../../../config/index.js"; +export interface INetworkService { + requestHttp: (monitor: IMonitor) => Promise; + requestInfrastructure: (monitor: IMonitor) => Promise; + requestStatus: (monitor: IMonitor) => Promise; + requestPagespeed: (monitor: IMonitor) => Promise; + requestPing: (monitor: IMonitor) => Promise; +} + +export interface ICapturePayload { + data: ISystemInfo; + capture: ICaptureInfo; +} + +export interface ILighthousePayload { + lighthouseResult: ILighthouseResult; +} + +export interface StatusResponse { + monitorId: string; + type: MonitorType; + code?: number; + status: MonitorStatus; + message: string; + responseTime: number; + timings?: GotTimings; + payload?: TPayload; +} + +class NetworkService implements INetworkService { + private got: Got; + private NETWORK_ERROR: number; + constructor(got: Got) { + this.got = got; + this.NETWORK_ERROR = 5000; + } + + private buildStatusResponse = (monitor: IMonitor, response: Response | null, error: any | null): StatusResponse => { + if (error) { + const statusResponse: StatusResponse = { + monitorId: monitor._id.toString(), + type: monitor.type, + status: "down" as MonitorStatus, + code: this.NETWORK_ERROR, + message: error.message || "Network error", + responseTime: 0, + timings: { phases: {} } as GotTimings, + }; + if (error instanceof HTTPError) { + statusResponse.code = error?.response?.statusCode || this.NETWORK_ERROR; + statusResponse.message = error.message || "HTTP error"; + statusResponse.responseTime = error.timings?.phases?.total || 0; + statusResponse.timings = error.timings; + } + return statusResponse; + } + + const statusResponse: StatusResponse = { + monitorId: monitor._id.toString(), + type: monitor.type, + code: response?.statusCode || this.NETWORK_ERROR, + status: response?.ok === true ? "up" : "down", + message: response?.statusMessage || "", + responseTime: response?.timings?.phases?.total || 0, + timings: response?.timings || ({ phases: {} } as GotTimings), + }; + + return statusResponse; + }; + + requestHttp = async (monitor: IMonitor) => { + try { + const url = monitor.url; + if (!url) { + throw new Error("No URL provided"); + } + + try { + const response: Response = await this.got(url); + return this.buildStatusResponse(monitor, response, null); + } catch (error) { + return this.buildStatusResponse(monitor, null, error); + } + } catch (error) { + throw error; + } + }; + + requestInfrastructure = async (monitor: IMonitor) => { + const url = monitor.url; + if (!url) { + throw new Error("No URL provided"); + } + const secret = monitor.secret; + if (!secret) { + throw new Error("No secret provided for infrastructure monitor"); + } + + let statusResponse: StatusResponse; + try { + const response: Response | null = await this.got(url, { + headers: { Authorization: `Bearer ${secret}` }, + responseType: "json", + }); + + statusResponse = this.buildStatusResponse(monitor, response, null); + if (!response?.body) { + throw new ApiError("No payload received from infrastructure monitor", 500); + } + statusResponse.payload = response?.body; + return statusResponse; + } catch (error) { + statusResponse = this.buildStatusResponse(monitor, null, error); + } + return statusResponse; + }; + + requestPagespeed = async (monitor: IMonitor) => { + const apiKey = config.PAGESPEED_API_KEY; + if (!apiKey) { + throw new Error("No API key provided for pagespeed monitor"); + } + const url = monitor.url; + if (!url) { + throw new Error("No URL provided"); + } + + let statusResponse: StatusResponse; + + try { + const response: Response = await this.got(url); + statusResponse = this.buildStatusResponse(monitor, response, null) as StatusResponse; + } catch (error) { + statusResponse = this.buildStatusResponse(monitor, null, error); + } + + const pagespeedUrl = `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance&key=${apiKey}`; + const pagespeedResponse = await this.got(pagespeedUrl, { + responseType: "json", + }); + const payload = pagespeedResponse.body; + if (payload) { + statusResponse.payload = payload; + return statusResponse; + } else { + throw new ApiError("No payload received from pagespeed monitor", 500); + } + }; + + requestPing = async (monitor: IMonitor) => { + const response = await ping.promise.probe(monitor.url); + const status = response?.alive === true ? "up" : "down"; + + const rawTime = typeof response?.time === "string" ? parseFloat(response.time) : Number(response?.time); + const responseTime = Number.isFinite(rawTime) ? rawTime : 0; + + return { + monitorId: monitor._id.toString(), + type: monitor.type, + status: status as MonitorStatus, + message: "Ping successful", + responseTime, + timings: { phases: {} } as GotTimings, + }; + }; + + requestStatus = async (monitor: IMonitor) => { + switch (monitor?.type) { + case "http": + return await this.requestHttp(monitor); // uses GOT + case "https": + return await this.requestHttp(monitor); // uses GOT + case "infrastructure": + return await this.requestInfrastructure(monitor); // uses GOT + case "pagespeed": + return await this.requestPagespeed(monitor); // uses GOT + case "ping": + return await this.requestPing(monitor); // uses PING + default: + throw new Error("Not implemented"); + } + }; +} + +export default NetworkService; diff --git a/server/src/service/v2/infrastructure/NotificationService.ts b/server/src/service/v2/infrastructure/NotificationService.ts new file mode 100644 index 000000000..4195958bd --- /dev/null +++ b/server/src/service/v2/infrastructure/NotificationService.ts @@ -0,0 +1,58 @@ +import UserService from "../business/UserService.js"; +import { IMonitor, NotificationChannel } from "../../../db/v2/models/index.js"; +import { EmailService, SlackService, DiscordService, WebhookService } from "./NotificationServices/index.js"; +export interface INotificationService { + handleNotifications: (monitor: IMonitor) => Promise; +} + +class NotificationService implements INotificationService { + private emailService: EmailService; + private slackService: SlackService; + private discordService: DiscordService; + private webhookService: WebhookService; + private userService: UserService; + + constructor(userService: UserService) { + this.userService = userService; + this.emailService = new EmailService(userService); + this.slackService = new SlackService(); + this.discordService = new DiscordService(); + this.webhookService = new WebhookService(); + } + + handleNotifications = async (monitor: IMonitor) => { + const notificationIds = monitor.notificationChannels || []; + + if (notificationIds.length === 0) { + return; + } + + const notificationChannels = await NotificationChannel.find({ + _id: { $in: notificationIds }, + }); + + for (const channel of notificationChannels) { + // Implement sending logic based on channel.type and channel.config + let service; + switch (channel.type) { + case "email": + await this.emailService.sendMessage(this.emailService.buildAlert(monitor), channel); + break; + case "slack": + await this.slackService.sendMessage(this.slackService.buildAlert(monitor), channel); + break; + case "discord": + await this.discordService.sendMessage(this.discordService.buildAlert(monitor), channel); + break; + case "webhook": + await this.webhookService.sendMessage(this.webhookService.buildAlert(monitor), channel); + break; + default: + console.warn(`Unknown notification channel type: ${channel.type}`); + } + } + return; + }; +} + +export default NotificationService; diff --git a/server/src/service/v2/infrastructure/NotificationServices/Discord.ts b/server/src/service/v2/infrastructure/NotificationServices/Discord.ts new file mode 100644 index 000000000..c9ec30700 --- /dev/null +++ b/server/src/service/v2/infrastructure/NotificationServices/Discord.ts @@ -0,0 +1,73 @@ +import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.js"; +import { IAlert, IMessageService } from "./IMessageService.js"; +import got from "got"; +import ApiError from "../../../../utils/ApiError.js"; +class DiscordService implements IMessageService { + constructor() {} + + private toDiscordEmbeds = (alert: IAlert) => { + return { + color: alert.status === "up" ? 65280 : 16711680, + title: `Monitor name: ${alert.name}`, + description: `Status: **${alert.status}**`, + fields: [ + { + name: "Url", + value: alert.url, + }, + { + name: "Checked at", + value: alert.checkTime ? alert.checkTime.toISOString() : "N/A", + }, + { name: "Alert time", value: alert.alertTime.toISOString() }, + ...(alert.details + ? Object.entries(alert.details).map(([key, value]) => ({ + name: key, + value, + })) + : []), + ], + }; + }; + + buildAlert = (monitor: IMonitor) => { + const name = monitor?.name || "Unnamed monitor"; + const monitorStatus = monitor?.status || "unknown status"; + const url = monitor?.url || "no URL"; + const checkTime = monitor?.lastCheckedAt || null; + const alertTime = new Date(); + return { + name, + url, + status: monitorStatus, + checkTime, + alertTime, + }; + }; + + sendMessage = async (alert: IAlert, channel: INotificationChannel) => { + const notificationUrl = channel?.config?.url; + if (!notificationUrl) { + throw new ApiError("Webhook URL not configured", 400); + } + + try { + const payload = { + content: "Status Alert", + embeds: [this.toDiscordEmbeds(alert)], + }; + await got.post(notificationUrl, { json: payload }); + } catch (error) { + console.warn("Failed to send Discord message", error); + return false; + } + + return true; + }; + + testMessage = async () => { + return true; + }; +} + +export default DiscordService; diff --git a/server/src/service/v2/infrastructure/NotificationServices/Email.ts b/server/src/service/v2/infrastructure/NotificationServices/Email.ts new file mode 100644 index 000000000..d1443ce08 --- /dev/null +++ b/server/src/service/v2/infrastructure/NotificationServices/Email.ts @@ -0,0 +1,65 @@ +import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.js"; +import { IMessageService, IAlert } from "./IMessageService.js"; +import nodemailer, { Transporter } from "nodemailer"; +import { config } from "../../../../config/index.js"; +import UserService from "../../business/UserService.js"; +import ApiError from "../../../../utils/ApiError.js"; +class EmailService implements IMessageService { + private transporter: Transporter; + private userService: UserService; + + constructor(userService: UserService) { + this.userService = userService; + this.transporter = nodemailer.createTransport({ + host: config.SMTP_HOST, + port: config.SMTP_PORT, + secure: config.SMTP_PORT === 465, + auth: { + user: config.SMTP_USER, + pass: config.SMTP_PASS, + }, + }); + } + + buildAlert = (monitor: IMonitor) => { + const name = monitor?.name || "Unnamed monitor"; + const monitorStatus = monitor?.status || "unknown status"; + const url = monitor?.url || "no URL"; + const checkTime = monitor?.lastCheckedAt || null; + const alertTime = new Date(); + return { + name, + url, + status: monitorStatus, + checkTime, + alertTime, + }; + }; + + sendMessage = async (alert: string | IAlert, channel: INotificationChannel) => { + try { + const users = await this.userService.getAllUsers(); + const emails = users.map((u) => u.email).join(","); + + if (!emails || emails.length === 0) { + throw new ApiError("No user emails found", 500); + } + + await this.transporter.sendMail({ + from: `"Checkmate" <${config.SMTP_USER}>`, + to: emails, + subject: "Monitor Alert", + text: JSON.stringify(alert, null, 2), + }); + return true; + } catch (error) { + return false; + } + }; + + testMessage = async () => { + return true; + }; +} + +export default EmailService; diff --git a/server/src/service/v2/infrastructure/NotificationServices/IMessageService.ts b/server/src/service/v2/infrastructure/NotificationServices/IMessageService.ts new file mode 100644 index 000000000..dbafc59fb --- /dev/null +++ b/server/src/service/v2/infrastructure/NotificationServices/IMessageService.ts @@ -0,0 +1,15 @@ +import { IMonitor, INotificationChannel } from "../../../db/v2/models/index.js"; + +export interface IAlert { + name: string; + url: string; + status: string; + details?: Record; + checkTime: Date | null; + alertTime: Date; +} +export interface IMessageService { + buildAlert: (monitor: IMonitor) => IAlert; + sendMessage: (alert: IAlert, channel: INotificationChannel) => Promise; + testMessage: (message: string, channel: INotificationChannel) => Promise; +} diff --git a/server/src/service/v2/infrastructure/NotificationServices/Slack.ts b/server/src/service/v2/infrastructure/NotificationServices/Slack.ts new file mode 100644 index 000000000..50e2021e4 --- /dev/null +++ b/server/src/service/v2/infrastructure/NotificationServices/Slack.ts @@ -0,0 +1,116 @@ +import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.js"; +import { IAlert, IMessageService } from "./IMessageService.js"; +import got from "got"; + +class SlackService implements IMessageService { + constructor() {} + + private toSlackBlocks = (alert: IAlert) => { + return [ + { + type: "header", + text: { + type: "plain_text", + text: "Status Alert", + }, + }, + { + type: "section", + text: { + type: "mrkdwn", + text: `*Monitor name:* ${alert.name}`, + }, + }, + + { + type: "section", + text: { + type: "mrkdwn", + text: `*Status:* ${alert.status}`, + }, + }, + + { + type: "section", + text: { + type: "mrkdwn", + text: `*URL:* ${alert.url}`, + }, + }, + { + type: "section", + text: { + type: "mrkdwn", + text: `*Checked at:* ${alert?.checkTime?.toISOString() || "N/A"}`, + }, + }, + + { + type: "divider", + }, + + ...(alert.details + ? Object.entries(alert.details).map(([key, value]) => ({ + type: "section", + fields: [ + { + type: "mrkdwn", + text: `*${key}:* ${value}`, + }, + ], + })) + : []), + + { + type: "context", + elements: [ + { + type: "mrkdwn", + text: `*Alert generated at:* ${alert?.alertTime?.toISOString() || "N/A"}`, + }, + ], + }, + ]; + }; + + buildAlert = (monitor: IMonitor) => { + const name = monitor?.name || "Unnamed monitor"; + const monitorStatus = monitor?.status || "unknown status"; + const url = monitor?.url || "no URL"; + const checkTime = monitor?.lastCheckedAt || null; + const alertTime = new Date(); + return { + name, + url, + status: monitorStatus, + checkTime, + alertTime, + }; + }; + + sendMessage = async (alert: IAlert, channel: INotificationChannel) => { + const notificationUrl = channel?.config?.url; + if (!notificationUrl) { + throw new Error("Webhook URL not configured"); + } + + try { + const payload = { + text: "Status Alert", + blocks: this.toSlackBlocks(alert), + }; + await got.post(notificationUrl, { json: payload }); + } catch (error) { + console.warn("Error sending Slack message:", error); + return false; + } + + return true; + }; + + testMessage = async () => { + return true; + }; +} + +export default SlackService; diff --git a/server/src/service/v2/infrastructure/NotificationServices/Webhook.ts b/server/src/service/v2/infrastructure/NotificationServices/Webhook.ts new file mode 100644 index 000000000..b84b2b220 --- /dev/null +++ b/server/src/service/v2/infrastructure/NotificationServices/Webhook.ts @@ -0,0 +1,43 @@ +import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.js"; +import { IAlert, IMessageService } from "./IMessageService.js"; +import ApiError from "../../../../utils/ApiError.js"; +import got from "got"; +class WebhookService implements IMessageService { + constructor() {} + + buildAlert = (monitor: IMonitor) => { + const name = monitor?.name || "Unnamed monitor"; + const monitorStatus = monitor?.status || "unknown status"; + const url = monitor?.url || "no URL"; + const checkTime = monitor?.lastCheckedAt || null; + const alertTime = new Date(); + return { + name, + url, + status: monitorStatus, + checkTime, + alertTime, + }; + }; + + sendMessage = async (alert: IAlert, channel: INotificationChannel) => { + const notificationUrl = channel?.config?.url; + if (!notificationUrl) { + throw new ApiError("Webhook URL not configured", 400); + } + try { + await got.post(notificationUrl, { json: { ...alert } }); + } catch (error) { + console.warn("Failed to send webhook notification:", error); + return false; + } + + return true; + }; + + testMessage = async () => { + return true; + }; +} + +export default WebhookService; diff --git a/server/src/service/v2/infrastructure/NotificationServices/index.ts b/server/src/service/v2/infrastructure/NotificationServices/index.ts new file mode 100644 index 000000000..2b949f716 --- /dev/null +++ b/server/src/service/v2/infrastructure/NotificationServices/index.ts @@ -0,0 +1,5 @@ +export { default as DiscordService } from "./Discord.js"; +export { default as EmailService } from "./Email.js"; +export { default as SlackService } from "./Slack.js"; +export { default as WebhookService } from "./Webhook.js"; +export * from "./IMessageService.js"; diff --git a/server/src/service/v2/infrastructure/StatusService.ts b/server/src/service/v2/infrastructure/StatusService.ts new file mode 100644 index 000000000..87b0df0af --- /dev/null +++ b/server/src/service/v2/infrastructure/StatusService.ts @@ -0,0 +1,100 @@ +import { IMonitor, IMonitorStats, MonitorStats } from "../../../db/v2/models/index.js"; +import { StatusResponse } from "./NetworkService.js"; +import ApiError from "../../../utils/ApiError.js"; + +const MAX_LATEST_CHECKS = 25; +export interface IStatusService { + updateMonitorStatus: (monitor: IMonitor, status: StatusResponse) => Promise; + + calculateAvgResponseTime: (stats: IMonitorStats, statusResponse: StatusResponse) => number; + + updateMonitorStats: (monitor: IMonitor, status: StatusResponse, statusChanged: boolean) => Promise; +} + +export type StatusChangeResult = [updatedMonitor: IMonitor, statusChanged: boolean]; + +class StatusService implements IStatusService { + updateMonitorStatus = async (monitor: IMonitor, statusResponse: StatusResponse): Promise => { + const newStatus = statusResponse.status; + monitor.lastCheckedAt = new Date(); + + // Store latest checks for display + monitor.latestChecks = monitor.latestChecks || []; + monitor.latestChecks.push({ + status: newStatus, + responseTime: statusResponse.responseTime, + checkedAt: monitor.lastCheckedAt, + }); + while (monitor.latestChecks.length > MAX_LATEST_CHECKS) { + monitor.latestChecks.shift(); + } + + // Update monitor status + if (monitor.status === "initializing") { + monitor.status = newStatus; + return [await monitor.save(), true]; + } else { + const { n } = monitor; + const latestChecks = monitor.latestChecks.slice(-n); + // Return early if not enough statuses to evaluate + if (latestChecks.length < n) { + return [await monitor.save(), false]; + } + + // If all different than current status, update status + const allDifferent = latestChecks.every((check) => check.status !== monitor.status); + if (allDifferent && monitor.status !== newStatus) { + monitor.status = newStatus; + } + return [await monitor.save(), allDifferent]; + } + }; + + calculateAvgResponseTime = (stats: IMonitorStats, statusResponse: StatusResponse): number => { + let avgResponseTime = stats.avgResponseTime; + // Set initial + if (avgResponseTime === 0) { + avgResponseTime = statusResponse.responseTime; + } else { + avgResponseTime = (avgResponseTime * (stats.totalChecks - 1) + statusResponse.responseTime) / stats.totalChecks; + } + return avgResponseTime; + }; + + updateMonitorStats = async (monitor: IMonitor, statusResponse: StatusResponse, statusChanged: boolean) => { + const stats = await MonitorStats.findOne({ monitorId: monitor._id }); + if (!stats) { + throw new ApiError("MonitorStats not found", 500); + } + + // Update check counts + stats.totalChecks += 1; + stats.totalUpChecks += statusResponse.status === "up" ? 1 : 0; + stats.totalDownChecks += statusResponse.status === "down" ? 1 : 0; + + // Update streak + if (!statusChanged) { + stats.currentStreak += 1; + } else { + stats.currentStreak = 1; + stats.currentStreakStatus = statusResponse.status; + stats.currentStreakStartedAt = Date.now(); + } + + // Update time stamps + stats.lastCheckTimestamp = Date.now(); + stats.timeOfLastFailure = statusResponse.status === "down" ? Date.now() : stats.timeOfLastFailure; + + // Update stats that need updated check counts + stats.avgResponseTime = this.calculateAvgResponseTime(stats, statusResponse); + stats.uptimePercentage = stats.totalUpChecks / stats.totalChecks; + + // Other + stats.lastResponseTime = statusResponse.responseTime; + stats.maxResponseTime = Math.max(stats.maxResponseTime, statusResponse.responseTime); + + return await stats.save(); + }; +} + +export default StatusService; diff --git a/server/src/utils/ApiError.ts b/server/src/utils/ApiError.ts new file mode 100644 index 000000000..313d01c2a --- /dev/null +++ b/server/src/utils/ApiError.ts @@ -0,0 +1,15 @@ +class ApiError extends Error { + public status: number; + + constructor(message: string, status: number = 500) { + super(message); + this.status = status; + this.name = this.constructor.name; + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor); + } + } +} + +export default ApiError; diff --git a/server/src/utils/JWTUtils.ts b/server/src/utils/JWTUtils.ts new file mode 100644 index 000000000..4eebb78a0 --- /dev/null +++ b/server/src/utils/JWTUtils.ts @@ -0,0 +1,22 @@ +import jwt from "jsonwebtoken"; +import { AuthResult } from "../service/v2/business/AuthService.js"; + +const encode = (data: AuthResult): string => { + const secret = process.env.JWT_SECRET; + if (!secret) { + throw new Error("JWT_SECRET is not defined"); + } + const token = jwt.sign(data, secret, { expiresIn: "99d" }); + return token; +}; + +const decode = (token: string): AuthResult => { + const secret = process.env.JWT_SECRET; + if (!secret) { + throw new Error("JWT_SECRET is not defined"); + } + const decoded = jwt.verify(token, secret) as AuthResult; + return decoded; +}; + +export { encode, decode }; From 66e5441bdf6d8cea0af92d94d074705473cb5a5c Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Sep 2025 15:23:20 -0700 Subject: [PATCH 016/103] fix import path --- .../v2/infrastructure/NotificationServices/IMessageService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/service/v2/infrastructure/NotificationServices/IMessageService.ts b/server/src/service/v2/infrastructure/NotificationServices/IMessageService.ts index dbafc59fb..f00a20549 100644 --- a/server/src/service/v2/infrastructure/NotificationServices/IMessageService.ts +++ b/server/src/service/v2/infrastructure/NotificationServices/IMessageService.ts @@ -1,4 +1,4 @@ -import { IMonitor, INotificationChannel } from "../../../db/v2/models/index.js"; +import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.js"; export interface IAlert { name: string; From 76f4e974db348a2e6248f1824719e0157baa37d6 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 24 Sep 2025 14:20:30 -0700 Subject: [PATCH 017/103] dev types --- server/package-lock.json | 19 +++++++++++++++++++ server/package.json | 1 + server/src/routes/v2/auth.ts | 0 3 files changed, 20 insertions(+) create mode 100644 server/src/routes/v2/auth.ts diff --git a/server/package-lock.json b/server/package-lock.json index 92d258433..2a522ae64 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -45,6 +45,7 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/jsonwebtoken": "9.0.10", "@types/nodemailer": "7.0.1", "@types/ping": "0.4.4", "c8": "10.1.3", @@ -3072,6 +3073,24 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.3.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", diff --git a/server/package.json b/server/package.json index 2bee352e0..2fdcf58cb 100755 --- a/server/package.json +++ b/server/package.json @@ -54,6 +54,7 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/jsonwebtoken": "9.0.10", "@types/nodemailer": "7.0.1", "@types/ping": "0.4.4", "c8": "10.1.3", diff --git a/server/src/routes/v2/auth.ts b/server/src/routes/v2/auth.ts new file mode 100644 index 000000000..e69de29bb From 6d0c56094837a2597cb55f776e7f96d642730e4a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 24 Sep 2025 15:55:09 -0700 Subject: [PATCH 018/103] add v2 routes, controllers --- server/src/config/controllers.js | 18 +- server/src/config/routes.js | 24 + server/src/config/services.js | 73 ++- server/src/controllers/v2/AuthController.ts | 150 ++++++ server/src/controllers/v2/InviteController.ts | 62 +++ .../controllers/v2/MaintenanceController.ts | 96 ++++ .../src/controllers/v2/MonitorController.ts | 157 ++++++ .../v2/NotificationChannelController.ts | 96 ++++ server/src/controllers/v2/QueueController.ts | 29 ++ server/src/middleware/v2/VerifyPermissions.ts | 77 +++ server/src/middleware/v2/VerifyToken.ts | 21 + server/src/routes/v2/auth.ts | 33 ++ server/src/routes/v2/invite.ts | 30 ++ server/src/routes/v2/maintenance.ts | 34 ++ server/src/routes/v2/monitors.ts | 34 ++ server/src/routes/v2/notificationChannels.ts | 34 ++ server/src/routes/v2/queue.ts | 24 + server/src/service/v2/business/AuthService.ts | 4 + .../src/service/v2/business/CheckService.ts | 3 + .../src/service/v2/business/InviteService.ts | 2 + .../service/v2/business/MaintenanceService.ts | 3 + .../src/service/v2/business/MonitorService.ts | 468 ++++++++++++++++++ .../v2/business/MonitorStatsService.ts | 3 + .../v2/business/NotificationChannelService.ts | 4 + .../src/service/v2/business/QueueService.ts | 3 + server/src/service/v2/business/UserService.ts | 2 + .../service/v2/infrastructure/JobGenerator.ts | 2 + .../src/service/v2/infrastructure/JobQueue.ts | 4 + .../v2/infrastructure/NetworkService.ts | 6 +- .../v2/infrastructure/NotificationService.ts | 3 + .../NotificationServices/Discord.ts | 3 + .../NotificationServices/Email.ts | 3 + .../NotificationServices/Slack.ts | 2 + .../NotificationServices/Webhook.ts | 4 + .../v2/infrastructure/StatusService.ts | 2 + server/src/types/express.d.ts | 10 + server/src/types/index.ts | 1 + .../src/types/monitor-response-with-checks.ts | 11 + 38 files changed, 1532 insertions(+), 3 deletions(-) create mode 100644 server/src/controllers/v2/AuthController.ts create mode 100644 server/src/controllers/v2/InviteController.ts create mode 100644 server/src/controllers/v2/MaintenanceController.ts create mode 100644 server/src/controllers/v2/MonitorController.ts create mode 100644 server/src/controllers/v2/NotificationChannelController.ts create mode 100644 server/src/controllers/v2/QueueController.ts create mode 100644 server/src/middleware/v2/VerifyPermissions.ts create mode 100644 server/src/middleware/v2/VerifyToken.ts create mode 100644 server/src/routes/v2/invite.ts create mode 100644 server/src/routes/v2/maintenance.ts create mode 100644 server/src/routes/v2/monitors.ts create mode 100644 server/src/routes/v2/notificationChannels.ts create mode 100644 server/src/routes/v2/queue.ts create mode 100644 server/src/service/v2/business/MonitorService.ts create mode 100644 server/src/types/express.d.ts create mode 100644 server/src/types/index.ts create mode 100644 server/src/types/monitor-response-with-checks.ts diff --git a/server/src/config/controllers.js b/server/src/config/controllers.js index 5c54579eb..78cd7091b 100644 --- a/server/src/config/controllers.js +++ b/server/src/config/controllers.js @@ -2,7 +2,7 @@ import { createCommonDependencies } from "../controllers/v1/baseController.js"; // Services -// Controllers +// V1 Controllers import MonitorController from "../controllers/v1/monitorController.js"; import AuthController from "../controllers/v1/authController.js"; import SettingsController from "../controllers/v1/settingsController.js"; @@ -15,10 +15,18 @@ import StatusPageController from "../controllers/v1/statusPageController.js"; import NotificationController from "../controllers/v1/notificationController.js"; import DiagnosticController from "../controllers/v1/diagnosticController.js"; +// V2 Controllers +import AuthControllerV2 from "../controllers/v2/AuthController.js"; +import InviteControllerV2 from "../controllers/v2/InviteController.js"; +import MaintenanceControllerV2 from "../controllers/v2/MaintenanceController.js"; +import MonitorControllerV2 from "../controllers/v2/MonitorController.js"; +import NotificationChannelControllerV2 from "../controllers/v2/NotificationChannelController.js"; +import QueueControllerV2 from "../controllers/v2/QueueController.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, @@ -62,5 +70,13 @@ export const initializeControllers = (services) => { diagnosticService: services.diagnosticService, }); + //V2 + controllers.authControllerV2 = new AuthControllerV2(services.authServiceV2, services.inviteServiceV2); + controllers.inviteControllerV2 = new InviteControllerV2(services.inviteServiceV2); + controllers.maintenanceControllerV2 = new MaintenanceControllerV2(services.maintenanceServiceV2); + controllers.monitorControllerV2 = new MonitorControllerV2(services.monitorServiceV2); + controllers.notificationChannelControllerV2 = new NotificationChannelControllerV2(services.notificationChannelServiceV2); + controllers.queueControllerV2 = new QueueControllerV2(services.jobQueueV2); + return controllers; }; diff --git a/server/src/config/routes.js b/server/src/config/routes.js index 40bf8e994..13b369262 100644 --- a/server/src/config/routes.js +++ b/server/src/config/routes.js @@ -13,7 +13,16 @@ import LogRoutes from "../routes/v1/logRoutes.js"; import DiagnosticRoutes from "../routes/v1//diagnosticRoute.js"; import NotificationRoutes from "../routes/v1/notificationRoute.js"; +//V2 +import AuthRoutesV2 from "../routes/v2/auth.js"; +import InviteRoutesV2 from "../routes/v2/invite.js"; +import MaintenanceRoutesV2 from "../routes/v2/maintenance.js"; +import MonitorRoutesV2 from "../routes/v2/monitors.js"; +import NotificationChannelRoutesV2 from "../routes/v2/notificationChannels.js"; +import QueueRoutesV2 from "../routes/v2/queue.js"; + export const setupRoutes = (app, controllers) => { + // V1 const authRoutes = new AuthRoutes(controllers.authController); const monitorRoutes = new MonitorRoutes(controllers.monitorController); const settingsRoutes = new SettingsRoutes(controllers.settingsController); @@ -37,4 +46,19 @@ export const setupRoutes = (app, controllers) => { app.use("/api/v1/status-page", statusPageRoutes.getRouter()); app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter()); app.use("/api/v1/diagnostic", verifyJWT, diagnosticRoutes.getRouter()); + + // V2 + const authRoutesV2 = new AuthRoutesV2(controllers.authControllerV2); + const inviteRoutesV2 = new InviteRoutesV2(controllers.inviteControllerV2); + const maintenanceRoutesV2 = new MaintenanceRoutesV2(controllers.maintenanceControllerV2); + const monitorRoutesV2 = new MonitorRoutesV2(controllers.monitorControllerV2); + const notificationChannelRoutesV2 = new NotificationChannelRoutesV2(controllers.notificationChannelControllerV2); + const queueRoutesV2 = new QueueRoutesV2(controllers.queueControllerV2); + + app.use("/api/v2/auth", authApiLimiter, authRoutesV2.getRouter()); + app.use("/api/v2/invite", inviteRoutesV2.getRouter()); + app.use("/api/v2/maintenance", maintenanceRoutesV2.getRouter()); + app.use("/api/v2/monitors", monitorRoutesV2.getRouter()); + app.use("/api/v2/notification-channels", notificationChannelRoutesV2.getRouter()); + app.use("/api/v2/queue", queueRoutesV2.getRouter()); }; diff --git a/server/src/config/services.js b/server/src/config/services.js index 22f978071..ec07c5e15 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -61,13 +61,35 @@ import AppSettings from "../db/v1/models/AppSettings.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 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"; +// V2 Business +import AuthServiceV2 from "../service/v2/business/AuthService.js"; +import CheckServiceV2 from "../service/v2/business/CheckService.js"; +import InviteServiceV2 from "../service/v2/business/InviteService.js"; +import MaintenanceServiceV2 from "../service/v2/business/MaintenanceService.js"; +import MonitorServiceV2 from "../service/v2/business/MonitorService.js"; +import MonitorStatsServiceV2 from "../service/v2/business/MonitorStatsService.js"; +import NotificationChannelServiceV2 from "../service/v2/business/NotificationChannelService.js"; +import QueueServiceV2 from "../service/v2/business/QueueService.js"; +import UserServiceV2 from "../service/v2/business/UserService.js"; + +// V2 Infra +import DiscordServiceV2 from "../service/v2/infrastructure/NotificationServices/Discord.js"; +import EmailServiceV2 from "../service/v2/infrastructure/NotificationServices/Email.js"; +import SlackServiceV2 from "../service/v2/infrastructure/NotificationServices/Slack.js"; +import WebhookServiceV2 from "../service/v2/infrastructure/NotificationServices/Webhook.js"; +import JobGeneratorV2 from "../service/v2/infrastructure/JobGenerator.js"; +import JobQueueV2 from "../service/v2/infrastructure/JobQueue.js"; +import NetworkServiceV2 from "../service/v2/infrastructure/NetworkService.js"; +import NotificationServiceV2 from "../service/v2/infrastructure/NotificationService.js"; +import StatusServiceV2 from "../service/v2/infrastructure/StatusService.js"; + export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); ServiceRegistry.instance = serviceRegistry; @@ -208,7 +230,37 @@ export const initializeServices = async ({ logger, envSettings, settingsService games, }); + // V2 Services + const jobQueueV2 = new JobQueueV2(); + const authServiceV2 = new AuthServiceV2(jobQueueV2); + const checkServiceV2 = new CheckServiceV2(); + const inviteServiceV2 = new InviteServiceV2(); + const maintenanceServiceV2 = new MaintenanceServiceV2(); + const monitorServiceV2 = new MonitorServiceV2(jobQueueV2); + const monitorStatsServiceV2 = new MonitorStatsServiceV2(); + const notificationChannelServiceV2 = new NotificationChannelServiceV2(); + const queueServiceV2 = new QueueServiceV2(jobQueueV2); + const userServiceV2 = new UserServiceV2(); + + // V2 Infra + const discordServiceV2 = new DiscordServiceV2(); + const emailServiceV2 = new EmailServiceV2(userServiceV2); + const slackServiceV2 = new SlackServiceV2(); + const webhookServiceV2 = new WebhookServiceV2(); + const networkServiceV2 = new NetworkServiceV2(); + const statusServiceV2 = new StatusServiceV2(); + const notificationServiceV2 = new NotificationServiceV2(userServiceV2); + const jobGeneratorV2 = new JobGeneratorV2( + networkServiceV2, + checkServiceV2, + monitorStatsServiceV2, + statusServiceV2, + notificationServiceV2, + maintenanceServiceV2 + ); + const services = { + //v1 settingsService, translationService, stringService, @@ -227,6 +279,25 @@ export const initializeServices = async ({ logger, envSettings, settingsService monitorService, errorService, logger, + //v2 + jobQueueV2, + authServiceV2, + checkServiceV2, + inviteServiceV2, + maintenanceServiceV2, + monitorServiceV2, + monitorStatsServiceV2, + notificationChannelServiceV2, + queueServiceV2, + userServiceV2, + discordServiceV2, + emailServiceV2, + slackServiceV2, + webhookServiceV2, + networkServiceV2, + statusServiceV2, + notificationServiceV2, + jobGeneratorV2, }; Object.values(services).forEach((service) => { diff --git a/server/src/controllers/v2/AuthController.ts b/server/src/controllers/v2/AuthController.ts new file mode 100644 index 000000000..3afed5312 --- /dev/null +++ b/server/src/controllers/v2/AuthController.ts @@ -0,0 +1,150 @@ +import { Request, Response, NextFunction } from "express"; +import { encode, decode } from "../../utils/JWTUtils.js"; +import AuthService from "../../service/v2/business/AuthService.js"; +import ApiError from "../../utils/ApiError.js"; +import InviteService from "../../service/v2/business/InviteService.js"; +import { IInvite } from "../../db/v2/models/index.js"; + +class AuthController { + private authService: AuthService; + private inviteService: InviteService; + constructor(authService: AuthService, inviteService: InviteService) { + this.authService = authService; + this.inviteService = inviteService; + } + + register = async (req: Request, res: Response, next: NextFunction) => { + try { + const { email, firstName, lastName, password } = req.body; + + if (!email || !firstName || !lastName || !password) { + throw new Error("Email, firstName, lastName, and password are required"); + } + + const result = await this.authService.register({ + email, + firstName, + lastName, + password, + }); + + const token = encode(result); + + res.cookie("token", token, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week + }); + + res.status(201).json({ + message: "User created successfully", + }); + } catch (error) { + next(error); + } + }; + + registerWithInvite = async (req: Request, res: Response, next: NextFunction) => { + try { + const token = req.params.token; + if (!token) { + throw new ApiError("Invite token is required", 400); + } + + const invite: IInvite = await this.inviteService.get(token); + + const { firstName, lastName, password } = req.body; + const email = invite?.email; + const roles = invite?.roles; + + if (!email || !firstName || !lastName || !password || !roles || roles.length === 0) { + throw new Error("Email, firstName, lastName, password, and roles are required"); + } + + const result = await this.authService.registerWithInvite({ + email, + firstName, + lastName, + password, + roles, + }); + + if (!result) { + throw new Error("Registration failed"); + } + + await this.inviteService.delete(invite._id.toString()); + + const jwt = encode(result); + + res.cookie("token", jwt, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week + }); + + res.status(201).json({ message: "User created successfully" }); + } catch (error) { + next(error); + } + }; + + login = async (req: Request, res: Response, next: NextFunction) => { + try { + const { email, password } = req.body; + // Validation + if (!email || !password) { + return res.status(400).json({ message: "Email and password are required" }); + } + const result = await this.authService.login({ email, password }); + + const token = encode(result); + + res.cookie("token", token, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week + }); + + res.status(200).json({ + message: "Login successful", + }); + } catch (error) { + next(error); + } + }; + + logout = (req: Request, res: Response) => { + res.clearCookie("token", { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + }); + res.status(200).json({ message: "Logout successful" }); + }; + + me = (req: Request, res: Response, next: NextFunction) => { + return res.status(200).json({ message: "OK" }); + }; + + cleanup = async (req: Request, res: Response) => { + try { + await this.authService.cleanup(); + res.status(200).json({ message: "Cleanup successful" }); + } catch (error) {} + }; + + cleanMonitors = async (req: Request, res: Response) => { + try { + await this.authService.cleanMonitors(); + res.status(200).json({ message: "Monitors cleanup successful" }); + } catch (error) { + res.status(500).json({ message: "Internal server error" }); + } + }; +} + +export default AuthController; diff --git a/server/src/controllers/v2/InviteController.ts b/server/src/controllers/v2/InviteController.ts new file mode 100644 index 000000000..4d72bf8d4 --- /dev/null +++ b/server/src/controllers/v2/InviteController.ts @@ -0,0 +1,62 @@ +import { Request, Response, NextFunction } from "express"; +import InviteService from "../../service/v2/business/InviteService.js"; + +class InviteController { + private inviteService: InviteService; + constructor(inviteService: InviteService) { + this.inviteService = inviteService; + } + + create = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + const invite = await this.inviteService.create(tokenizedUser, req.body); + res.status(201).json({ message: "OK", data: invite }); + } catch (error: any) { + next(error); + } + }; + + getAll = async (req: Request, res: Response, next: NextFunction) => { + try { + const invites = await this.inviteService.getAll(); + res.status(200).json({ + message: "OK", + data: invites, + }); + } catch (error) { + next(error); + } + }; + + get = async (req: Request, res: Response, next: NextFunction) => { + try { + const token = req.params.token; + if (!token) { + return res.status(400).json({ message: "Token parameter is required" }); + } + const invite = await this.inviteService.get(token); + res.status(200).json({ message: "OK", data: invite }); + } catch (error) { + next(error); + } + }; + + delete = async (req: Request, res: Response, next: NextFunction) => { + try { + const id = req.params.id; + if (!id) { + return res.status(400).json({ message: "ID parameter is required" }); + } + await this.inviteService.delete(id); + res.status(204).json({ message: "OK" }); + } catch (error: any) { + next(error); + } + }; +} + +export default InviteController; diff --git a/server/src/controllers/v2/MaintenanceController.ts b/server/src/controllers/v2/MaintenanceController.ts new file mode 100644 index 000000000..7c6be11d5 --- /dev/null +++ b/server/src/controllers/v2/MaintenanceController.ts @@ -0,0 +1,96 @@ +import { Request, Response, NextFunction } from "express"; +import MaintenanceService from "../../service/v2/business/MaintenanceService.js"; + +class MaintenanceController { + private maintenanceService: MaintenanceService; + constructor(maintenanceService: MaintenanceService) { + this.maintenanceService = maintenanceService; + } + + create = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + const maintenance = await this.maintenanceService.create(tokenizedUser, req.body); + res.status(201).json({ message: "OK", data: maintenance }); + } catch (error) { + next(error); + } + }; + + getAll = async (req: Request, res: Response, next: NextFunction) => { + try { + const maintenances = await this.maintenanceService.getAll(); + res.status(200).json({ + message: "OK", + data: maintenances, + }); + } catch (error) { + next(error); + } + }; + + toggleActive = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + const id = req.params.id; + if (!id) { + return res.status(400).json({ message: "ID parameter is required" }); + } + const maintenance = await this.maintenanceService.toggleActive(tokenizedUser, id); + res.status(200).json({ message: "OK", data: maintenance }); + } catch (error) { + next(error); + } + }; + + update = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + const id = req.params.id; + if (!id) { + return res.status(400).json({ message: "ID parameter is required" }); + } + const updatedMaintenance = await this.maintenanceService.update(tokenizedUser, id, req.body); + res.status(200).json({ message: "OK", data: updatedMaintenance }); + } catch (error) { + next(error); + } + }; + + get = async (req: Request, res: Response, next: NextFunction) => { + try { + const id = req.params.id; + if (!id) { + return res.status(400).json({ message: "ID parameter is required" }); + } + const maintenance = await this.maintenanceService.get(id); + res.status(200).json({ message: "OK", data: maintenance }); + } catch (error) { + next(error); + } + }; + + delete = async (req: Request, res: Response, next: NextFunction) => { + try { + const id = req.params.id; + if (!id) { + return res.status(400).json({ message: "ID parameter is required" }); + } + await this.maintenanceService.delete(id); + res.status(204).json({ message: "OK" }); + } catch (error) { + next(error); + } + }; +} + +export default MaintenanceController; diff --git a/server/src/controllers/v2/MonitorController.ts b/server/src/controllers/v2/MonitorController.ts new file mode 100644 index 000000000..c9a4ce7da --- /dev/null +++ b/server/src/controllers/v2/MonitorController.ts @@ -0,0 +1,157 @@ +import { Request, Response, NextFunction } from "express"; +import ApiError from "../../utils/ApiError.js"; +import MonitorService from "../../service/v2/business/MonitorService.js"; +import { MonitorType } from "../../db/v2/models/monitors/Monitor.js"; +class MonitorController { + private monitorService: MonitorService; + constructor(monitorService: MonitorService) { + this.monitorService = monitorService; + } + + create = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + + const monitor = await this.monitorService.create(tokenizedUser, req.body); + res.status(201).json({ + message: "Monitor created successfully", + data: monitor, + }); + } catch (error) { + next(error); + } + }; + + get = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + + const id = req.params.id; + if (!id) { + throw new ApiError("Monitor ID is required", 400); + } + + const range = req.query.range; + if (!range || typeof range !== "string") throw new ApiError("Range query parameter is required", 400); + + let monitor; + + const status = req.query.status; + if (status && typeof status !== "string") { + throw new ApiError("Status query parameter must be a string", 400); + } + + if (req.query.embedChecks === "true") { + monitor = await this.monitorService.getEmbedChecks(id, range, status); + } else { + monitor = await this.monitorService.get(id); + } + + res.status(200).json({ + message: "Monitor retrieved successfully", + data: monitor, + }); + } catch (error) { + next(error); + } + }; + + getAll = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + + let monitors; + if (req.query.embedChecks === "true") { + const page = Math.max(1, Number(req.query.page) || 1); + const limit = Math.max(1, Number(req.query.limit) || 10); + const type: MonitorType[] = req.query.type as MonitorType[]; + + monitors = await this.monitorService.getAllEmbedChecks(page, limit, type); + } else { + monitors = await this.monitorService.getAll(); + } + + res.status(200).json({ + message: "Monitors retrieved successfully", + data: monitors, + }); + } catch (error) { + next(error); + } + }; + + toggleActive = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + + const id = req.params.id; + if (!id) { + throw new ApiError("Monitor ID is required", 400); + } + + const monitor = await this.monitorService.toggleActive(id, tokenizedUser); + res.status(200).json({ + message: "Monitor paused/unpaused successfully", + data: monitor, + }); + } catch (error) { + next(error); + } + }; + + update = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + + const id = req.params.id; + if (!id) { + throw new ApiError("Monitor ID is required", 400); + } + + const monitor = await this.monitorService.update(tokenizedUser, id, req.body); + res.status(200).json({ + message: "Monitor updated successfully", + data: monitor, + }); + } catch (error) { + next(error); + } + }; + + delete = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + const id = req.params.id; + if (!id) { + throw new ApiError("Monitor ID is required", 400); + } + await this.monitorService.delete(id); + + res.status(200).json({ + message: "Monitor deleted successfully", + }); + } catch (error) { + next(error); + } + }; +} + +export default MonitorController; diff --git a/server/src/controllers/v2/NotificationChannelController.ts b/server/src/controllers/v2/NotificationChannelController.ts new file mode 100644 index 000000000..e13f572f4 --- /dev/null +++ b/server/src/controllers/v2/NotificationChannelController.ts @@ -0,0 +1,96 @@ +import { Request, Response, NextFunction } from "express"; +import NotificationService from "../../service/v2/business/NotificationChannelService.js"; + +class NotificationChannelController { + private notificationService: NotificationService; + constructor(notificationService: NotificationService) { + this.notificationService = notificationService; + } + + create = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + const channel = await this.notificationService.create(tokenizedUser, req.body); + res.status(201).json({ message: "OK", data: channel }); + } catch (error) { + next(error); + } + }; + + getAll = async (req: Request, res: Response, next: NextFunction) => { + try { + const notificationChannels = await this.notificationService.getAll(); + res.status(200).json({ + message: "OK", + data: notificationChannels, + }); + } catch (error) { + next(error); + } + }; + + toggleActive = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + const id = req.params.id; + if (!id) { + return res.status(400).json({ message: "ID parameter is required" }); + } + const notificationChannel = await this.notificationService.toggleActive(tokenizedUser, id); + res.status(200).json({ message: "OK", data: notificationChannel }); + } catch (error) { + next(error); + } + }; + + update = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + const id = req.params.id; + if (!id) { + return res.status(400).json({ message: "ID parameter is required" }); + } + const updatedChannel = await this.notificationService.update(tokenizedUser, id, req.body); + res.status(200).json({ message: "OK", data: updatedChannel }); + } catch (error) { + next(error); + } + }; + + get = async (req: Request, res: Response, next: NextFunction) => { + try { + const id = req.params.id; + if (!id) { + return res.status(400).json({ message: "ID parameter is required" }); + } + const notificationChannel = await this.notificationService.get(id); + res.status(200).json({ message: "OK", data: notificationChannel }); + } catch (error) { + next(error); + } + }; + + delete = async (req: Request, res: Response, next: NextFunction) => { + try { + const id = req.params.id; + if (!id) { + return res.status(400).json({ message: "ID parameter is required" }); + } + await this.notificationService.delete(id); + res.status(204).json({ message: "OK" }); + } catch (error) { + next(error); + } + }; +} + +export default NotificationChannelController; diff --git a/server/src/controllers/v2/QueueController.ts b/server/src/controllers/v2/QueueController.ts new file mode 100644 index 000000000..510703894 --- /dev/null +++ b/server/src/controllers/v2/QueueController.ts @@ -0,0 +1,29 @@ +import { Request, Response, NextFunction } from "express"; +import QueueService from "../../service/v2/business/QueueService.js"; +class QueueController { + private queueService: QueueService; + constructor(queueService: QueueService) { + this.queueService = queueService; + } + + getJobs = async (req: Request, res: Response, next: NextFunction) => { + try { + const jobs = await this.queueService.getJobs(); + res.status(200).json({ message: "ok", data: jobs }); + } catch (error) { + next(error); + } + }; + + getMetrics = async (req: Request, res: Response, next: NextFunction) => { + const metrics = await this.queueService.getMetrics(); + res.status(200).json({ message: "ok", data: metrics }); + }; + + flush = async (req: Request, res: Response, next: NextFunction) => { + const result = await this.queueService.flush(); + res.status(200).json({ message: "ok", flushed: result }); + }; +} + +export default QueueController; diff --git a/server/src/middleware/v2/VerifyPermissions.ts b/server/src/middleware/v2/VerifyPermissions.ts new file mode 100644 index 000000000..64d573e00 --- /dev/null +++ b/server/src/middleware/v2/VerifyPermissions.ts @@ -0,0 +1,77 @@ +import { Request, Response, NextFunction } from "express"; +import ApiError from "../../utils/ApiError.js"; +import { User, IUser, Role, IRole } from "../../db/v2/models/index.js"; + +const rolesCache = new Map(); +// const CACHE_TTL = 30 * 60 * 1000; // 30 minutes +const CACHE_TTL = 1; // 30 minutes +const MAX_CACHE_SIZE = 1000; + +const getCachedRoles = async (userId: string) => { + if (rolesCache.size >= MAX_CACHE_SIZE) { + const oldestKey = rolesCache.keys().next().value; + if (!oldestKey) return null; + rolesCache.delete(oldestKey); + } + + const cached = rolesCache.get(userId); + + if (cached && Date.now() - cached.timestamp < CACHE_TTL) { + return cached.roles; + } + + const user: IUser | null = await User.findById(userId); + if (!user) { + return null; + } + + const roles = await Role.find({ _id: { $in: user.roles } }); + rolesCache.set(userId, { roles, timestamp: Date.now() }); + return roles; +}; + +const hasPermission = (roles: IRole[], requiredPermissions: string[]) => { + const userPermissions = [...new Set(roles.flatMap((role) => role.permissions))]; + + if (userPermissions.includes("*")) return true; + + const matches = (requiredPermission: string, userPermission: string) => { + if (userPermission === requiredPermission) return true; + if (userPermission.endsWith(".*")) { + const prefix = userPermission.slice(0, -2); + return requiredPermission.startsWith(prefix + "."); + } + return false; + }; + + return requiredPermissions.every((requiredPermission) => { + return userPermissions.some((userPermission) => matches(requiredPermission, userPermission)); + }); +}; + +const verifyPermission = (resourceActions: string[]) => { + return async (req: Request, res: Response, next: NextFunction) => { + const tokenizedUser = req.user; + + if (!tokenizedUser) { + throw new ApiError("No user", 400); + } + + const userId = tokenizedUser.sub; + if (!userId) { + throw new ApiError("No user ID", 400); + } + + const userRoles = await getCachedRoles(userId); + if (!userRoles) { + throw new ApiError("User roles not found", 400); + } + const allowed = hasPermission(userRoles, resourceActions); + if (!allowed) { + throw new ApiError("Insufficient permissions", 403); + } + next(); + }; +}; + +export { verifyPermission }; diff --git a/server/src/middleware/v2/VerifyToken.ts b/server/src/middleware/v2/VerifyToken.ts new file mode 100644 index 000000000..7dabd51cc --- /dev/null +++ b/server/src/middleware/v2/VerifyToken.ts @@ -0,0 +1,21 @@ +import { Request, Response, NextFunction } from "express"; +import { decode } from "../../utils/JWTUtils.js"; +import ApiError from "../../utils/ApiError.js"; +const verifyToken = (req: Request, res: Response, next: NextFunction) => { + const token = req.cookies.token; + + if (!token) { + const error = new ApiError("No token provided", 401); + return next(error); + } + + try { + const decoded = decode(token); + req.user = decoded; + next(); + } catch (error) { + next(error); + } +}; + +export { verifyToken }; diff --git a/server/src/routes/v2/auth.ts b/server/src/routes/v2/auth.ts index e69de29bb..919d0fd76 100644 --- a/server/src/routes/v2/auth.ts +++ b/server/src/routes/v2/auth.ts @@ -0,0 +1,33 @@ +import { Router } from "express"; + +import express from "express"; +import AuthController from "../../controllers/v2/AuthController.js"; +import { verifyToken } from "../../middleware/v2/VerifyToken.js"; + +const router = express.Router(); + +class AuthRoutes { + private controller: AuthController; + private router: Router; + constructor(authController: AuthController) { + this.controller = authController; + this.router = Router(); + this.initRoutes(); + } + + initRoutes = () => { + this.router.post("/register", this.controller.register); + this.router.post("/register/invite/:token", this.controller.registerWithInvite); + this.router.post("/login", this.controller.login); + this.router.post("/logout", this.controller.logout); + this.router.get("/me", verifyToken, this.controller.me); + this.router.post("/cleanup", this.controller.cleanup); + this.router.post("/cleanup-monitors", this.controller.cleanMonitors); + }; + + getRouter() { + return this.router; + } +} + +export default AuthRoutes; diff --git a/server/src/routes/v2/invite.ts b/server/src/routes/v2/invite.ts new file mode 100644 index 000000000..8066d0c77 --- /dev/null +++ b/server/src/routes/v2/invite.ts @@ -0,0 +1,30 @@ +import { Router } from "express"; +import InviteController from "../../controllers/v2/InviteController.js"; +import { verifyToken } from "../../middleware/v2/VerifyToken.js"; +import { verifyPermission } from "../../middleware/v2/VerifyPermissions.js"; + +class InviteRoutes { + private router; + private controller; + constructor(inviteController: InviteController) { + this.router = Router(); + this.controller = inviteController; + this.initRoutes(); + } + + initRoutes = () => { + this.router.post("/", verifyToken, verifyPermission(["invite.create"]), this.controller.create); + + this.router.get("/", verifyToken, verifyPermission(["invite.view"]), this.controller.getAll); + + this.router.get("/:token", verifyToken, verifyPermission(["invite.view"]), this.controller.get); + + this.router.delete("/:id", verifyToken, verifyPermission(["invite.delete"]), this.controller.delete); + }; + + getRouter() { + return this.router; + } +} + +export default InviteRoutes; diff --git a/server/src/routes/v2/maintenance.ts b/server/src/routes/v2/maintenance.ts new file mode 100644 index 000000000..62bc5f1cf --- /dev/null +++ b/server/src/routes/v2/maintenance.ts @@ -0,0 +1,34 @@ +import { Router } from "express"; +import MaintenanceController from "../../controllers/v2/MaintenanceController.js"; +import { verifyToken } from "../../middleware/v2/VerifyToken.js"; +import { verifyPermission } from "../../middleware/v2/VerifyPermissions.js"; + +class MaintenanceRoutes { + private router; + private controller; + constructor(maintenanceController: MaintenanceController) { + this.router = Router(); + this.controller = maintenanceController; + this.initRoutes(); + } + + initRoutes = () => { + this.router.post("/", verifyToken, verifyPermission(["maintenance.create"]), this.controller.create); + + this.router.get("/", verifyToken, verifyPermission(["maintenance.view"]), this.controller.getAll); + + this.router.patch("/:id/active", verifyToken, verifyPermission(["maintenance.update"]), this.controller.toggleActive); + + this.router.patch("/:id", verifyToken, verifyPermission(["maintenance.update"]), this.controller.update); + + this.router.get("/:id", verifyToken, verifyPermission(["maintenance.view"]), this.controller.get); + + this.router.delete("/:id", verifyToken, verifyPermission(["maintenance.delete"]), this.controller.delete); + }; + + getRouter() { + return this.router; + } +} + +export default MaintenanceRoutes; diff --git a/server/src/routes/v2/monitors.ts b/server/src/routes/v2/monitors.ts new file mode 100644 index 000000000..eb218f4c6 --- /dev/null +++ b/server/src/routes/v2/monitors.ts @@ -0,0 +1,34 @@ +import { Router } from "express"; +import MonitorController from "../../controllers/v2/MonitorController.js"; +import { verifyToken } from "../../middleware/v2/VerifyToken.js"; +import { verifyPermission } from "../../middleware/v2/VerifyPermissions.js"; + +class MonitorRoutes { + private router; + private controller; + constructor(monitorController: MonitorController) { + this.router = Router(); + this.controller = monitorController; + this.initRoutes(); + } + + initRoutes = () => { + this.router.post("/", verifyToken, verifyPermission(["monitors.create"]), this.controller.create); + + this.router.get("/", verifyToken, verifyPermission(["monitors.view"]), this.controller.getAll); + + this.router.patch("/:id/active", verifyToken, verifyPermission(["monitors.update"]), this.controller.toggleActive); + + this.router.patch("/:id", verifyToken, verifyPermission(["monitors.update"]), this.controller.update); + + this.router.get("/:id", verifyToken, verifyPermission(["monitors.view"]), this.controller.get); + + this.router.delete("/:id", verifyToken, verifyPermission(["monitors.delete"]), this.controller.delete); + }; + + getRouter() { + return this.router; + } +} + +export default MonitorRoutes; diff --git a/server/src/routes/v2/notificationChannels.ts b/server/src/routes/v2/notificationChannels.ts new file mode 100644 index 000000000..1db32902a --- /dev/null +++ b/server/src/routes/v2/notificationChannels.ts @@ -0,0 +1,34 @@ +import { Router } from "express"; +import NotificationController from "../../controllers/v2/NotificationChannelController.js"; +import { verifyToken } from "../../middleware/v2/VerifyToken.js"; +import { verifyPermission } from "../../middleware/v2/VerifyPermissions.js"; + +class NotificationChannelRoutes { + private router; + private controller; + constructor(notificationController: NotificationController) { + this.router = Router(); + this.controller = notificationController; + this.initRoutes(); + } + + initRoutes = () => { + this.router.post("/", verifyToken, verifyPermission(["notifications.create"]), this.controller.create); + + this.router.get("/", verifyToken, verifyPermission(["notifications.view"]), this.controller.getAll); + + this.router.patch("/:id/active", verifyToken, verifyPermission(["notifications.update"]), this.controller.toggleActive); + + this.router.patch("/:id", verifyToken, verifyPermission(["notifications.update"]), this.controller.update); + + this.router.get("/:id", verifyToken, verifyPermission(["notifications.view"]), this.controller.get); + + this.router.delete("/:id", verifyToken, verifyPermission(["notifications.delete"]), this.controller.delete); + }; + + getRouter() { + return this.router; + } +} + +export default NotificationChannelRoutes; diff --git a/server/src/routes/v2/queue.ts b/server/src/routes/v2/queue.ts new file mode 100644 index 000000000..428776e61 --- /dev/null +++ b/server/src/routes/v2/queue.ts @@ -0,0 +1,24 @@ +import QueueController from "../../controllers/v2/QueueController.js"; +import { Router } from "express"; + +class QueueRoutes { + private router; + private controller; + constructor(queueController: QueueController) { + this.router = Router(); + this.controller = queueController; + this.initRoutes(); + } + + initRoutes() { + this.router.get("/jobs", this.controller.getJobs); + this.router.get("/metrics", this.controller.getMetrics); + this.router.post("/flush", this.controller.flush); + } + + getRouter() { + return this.router; + } +} + +export default QueueRoutes; diff --git a/server/src/service/v2/business/AuthService.ts b/server/src/service/v2/business/AuthService.ts index cf49a71c6..67ece226e 100644 --- a/server/src/service/v2/business/AuthService.ts +++ b/server/src/service/v2/business/AuthService.ts @@ -4,6 +4,8 @@ import ApiError from "../../../utils/ApiError.js"; import { Types } from "mongoose"; import { IJobQueue } from "../infrastructure/JobQueue.js"; +const SERVICE_NAME = "AuthServiceV2"; + export const PERMISSIONS = { users: { all: "users.*", @@ -94,6 +96,8 @@ export interface IAuthService { } class AuthService implements IAuthService { + static SERVICE_NAME = SERVICE_NAME; + private jobQueue: IJobQueue; constructor(jobQueue: IJobQueue) { this.jobQueue = jobQueue; diff --git a/server/src/service/v2/business/CheckService.ts b/server/src/service/v2/business/CheckService.ts index 5c68891a9..7187fb5a3 100644 --- a/server/src/service/v2/business/CheckService.ts +++ b/server/src/service/v2/business/CheckService.ts @@ -6,12 +6,15 @@ import { StatusResponse } from "../infrastructure/NetworkService.js"; import type { ICapturePayload, ILighthousePayload } from "../infrastructure/NetworkService.js"; import mongoose from "mongoose"; +const SERVICE_NAME = "CheckServiceV2"; export interface ICheckService { buildCheck: (statusResponse: StatusResponse, type: MonitorType) => Promise; cleanupOrphanedChecks: () => Promise; } class CheckService implements ICheckService { + static SERVICE_NAME = SERVICE_NAME; + private isCapturePayload = (payload: any): payload is ICapturePayload => { if (!payload || typeof payload !== "object") return false; diff --git a/server/src/service/v2/business/InviteService.ts b/server/src/service/v2/business/InviteService.ts index 1c954d75f..b06b2a2c2 100644 --- a/server/src/service/v2/business/InviteService.ts +++ b/server/src/service/v2/business/InviteService.ts @@ -2,6 +2,7 @@ import crypto from "node:crypto"; import { ITokenizedUser, IInvite, Invite } from "../../../db/v2/models/index.js"; import ApiError from "../../../utils/ApiError.js"; +const SERVICE_NAME = "InviteServiceV2"; export interface IInviteService { create: (tokenizedUser: ITokenizedUser, invite: IInvite) => Promise<{ token: string }>; getAll: () => Promise; @@ -10,6 +11,7 @@ export interface IInviteService { } class InviteService implements IInviteService { + static SERVICE_NAME = SERVICE_NAME; constructor() {} create = async (tokenizedUser: ITokenizedUser, inviteData: IInvite) => { diff --git a/server/src/service/v2/business/MaintenanceService.ts b/server/src/service/v2/business/MaintenanceService.ts index ef8c8152f..55b2d787b 100644 --- a/server/src/service/v2/business/MaintenanceService.ts +++ b/server/src/service/v2/business/MaintenanceService.ts @@ -1,6 +1,8 @@ import { ITokenizedUser, IMaintenance, Maintenance } from "../../../db/v2/models/index.js"; import ApiError from "../../../utils/ApiError.js"; +const SERVICE_NAME = "MaintenanceServiceV2"; + export interface IMaintenanceService { create: ( tokenizedUser: ITokenizedUser, @@ -18,6 +20,7 @@ export interface IMaintenanceService { type MaintenanceCache = Map; class MaintenanceService implements IMaintenanceService { + static SERVICE_NAME = SERVICE_NAME; private maintenanceCache: MaintenanceCache; private lastRefresh: number; private CACHE_TTL_MS = 60 * 1000; diff --git a/server/src/service/v2/business/MonitorService.ts b/server/src/service/v2/business/MonitorService.ts new file mode 100644 index 000000000..61fc8b114 --- /dev/null +++ b/server/src/service/v2/business/MonitorService.ts @@ -0,0 +1,468 @@ +import mongoose from "mongoose"; + +import { IMonitor, Monitor, ITokenizedUser, MonitorStats, Check } from "../../../db/v2/models/index.js"; +import ApiError from "../../../utils/ApiError.js"; +import { IJobQueue } from "../infrastructure/JobQueue.js"; +import { MonitorWithChecksResponse } from "../../../types/index.js"; +import { MonitorStatus, MonitorType } from "../../../db/v2/models/monitors/Monitor.js"; + +const SERVICE_NAME = "MonitorServiceV2"; + +export interface IMonitorService { + create: (tokenizedUser: ITokenizedUser, monitorData: IMonitor) => Promise; + getAll: () => Promise; + getAllEmbedChecks: (page: number, limit: number, type: MonitorType[]) => Promise; + get: (monitorId: string) => Promise; + getEmbedChecks: (monitorId: string, range: string, status?: string) => Promise; + toggleActive: (monitorId: string, tokenizedUser: ITokenizedUser) => Promise; + update: (tokenizedUser: ITokenizedUser, monitorId: string, updateData: Partial) => Promise; + delete: (monitorId: string) => Promise; +} + +class MonitorService implements IMonitorService { + static SERVICE_NAME = SERVICE_NAME; + private jobQueue: IJobQueue; + constructor(jobQueue: IJobQueue) { + this.jobQueue = jobQueue; + } + + create = async (tokenizedUser: ITokenizedUser, monitorData: IMonitor) => { + const monitor = await Monitor.create({ + ...monitorData, + createdBy: tokenizedUser.sub, + updatedBy: tokenizedUser.sub, + }); + await MonitorStats.create({ + monitorId: monitor._id, + currentStreakStartedAt: Date.now(), + }); + await this.jobQueue.addJob(monitor); + return monitor; + }; + + getAll = async () => { + return Monitor.find(); + }; + + getAllEmbedChecks = async (page: number, limit: number, type: MonitorType[] = []) => { + const skip = (page - 1) * limit; + let find = {}; + if (type.length > 0) find = { type: { $in: type } }; + const monitors = await Monitor.find(find).skip(skip).limit(limit); + return monitors; + }; + + get = async (monitorId: string) => { + const monitor = await Monitor.findById(monitorId); + if (!monitor) { + throw new ApiError("Monitor not found", 404); + } + return monitor; + }; + + private getStartDate(range: string): Date { + const now = new Date(); + switch (range) { + case "30m": + return new Date(now.getTime() - 30 * 60 * 1000); + case "24h": + return new Date(now.getTime() - 24 * 60 * 60 * 1000); + case "7d": + return new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); + case "30d": + return new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); + default: + throw new ApiError("Invalid range parameter", 400); + } + } + + private getDateFormat(range: string): string { + switch (range) { + case "30m": + return "%Y-%m-%dT%H:%M:00Z"; + case "24h": + case "7d": + return "%Y-%m-%dT%H:00:00Z"; + case "30d": + return "%Y-%m-%d"; + default: + throw new ApiError("Invalid range parameter", 400); + } + } + + private getBaseGroup = (dateFormat: string): Record => { + return { + _id: { $dateToString: { format: dateFormat, date: "$createdAt" } }, + count: { $sum: 1 }, + avgResponseTime: { $avg: "$responseTime" }, + }; + }; + + private getBaseProjection = (): object => { + return { status: 1, responseTime: 1, createdAt: 1 }; + }; + + private getPageSpeedGroup = (dateFormat: string): Record => { + return { + _id: { $dateToString: { format: dateFormat, date: "$createdAt" } }, + count: { $sum: 1 }, + avgResponseTime: { $avg: "$responseTime" }, + accessibility: { $avg: "$lighthouse.accessibility" }, + bestPractices: { $avg: "$lighthouse.bestPractices" }, + seo: { $avg: "$lighthouse.seo" }, + performance: { $avg: "$lighthouse.performance" }, + cls: { $avg: "$lighthouse.audits.cls.score" }, + si: { $avg: "$lighthouse.audits.si.score" }, + fcp: { $avg: "$lighthouse.audits.fcp.score" }, + lcp: { $avg: "$lighthouse.audits.lcp.score" }, + tbt: { $avg: "$lighthouse.audits.tbt.score" }, + }; + }; + + private getPageSpeedProjection = (): object => { + const projectStage: any = { status: 1, responseTime: 1, createdAt: 1 }; + projectStage["lighthouse.accessibility"] = 1; + projectStage["lighthouse.seo"] = 1; + projectStage["lighthouse.bestPractices"] = 1; + projectStage["lighthouse.performance"] = 1; + projectStage["lighthouse.audits.cls.score"] = 1; + projectStage["lighthouse.audits.si.score"] = 1; + projectStage["lighthouse.audits.fcp.score"] = 1; + projectStage["lighthouse.audits.lcp.score"] = 1; + projectStage["lighthouse.audits.tbt.score"] = 1; + return projectStage; + }; + + private getInfraGroup = (dateFormat: string): Record => { + return { + _id: { $dateToString: { format: dateFormat, date: "$createdAt" } }, + count: { $sum: 1 }, + avgResponseTime: { $avg: "$responseTime" }, + physicalCores: { $last: "$system.cpu.physical_core" }, + logicalCores: { $last: "$system.cpu.logical_core" }, + frequency: { $avg: "$system.cpu.frequency" }, + currentFrequency: { $last: "$system.cpu.current_frequency" }, + tempsArrays: { $push: "$system.cpu.temperature" }, + freePercent: { $avg: "$system.cpu.free_percent" }, + usedPercent: { $avg: "$system.cpu.usage_percent" }, + total_bytes: { $last: "$system.memory.total_bytes" }, + available_bytes: { $last: "$system.memory.available_bytes" }, + used_bytes: { $last: "$system.memory.used_bytes" }, + memory_usage_percent: { $avg: "$system.memory.usage_percent" }, + disksArray: { $push: "$system.disk" }, + os: { $last: "$system.host.os" }, + platform: { $last: "$system.host.platform" }, + kernel_version: { $last: "$system.host.kernel_version" }, + pretty_name: { $last: "$system.host.pretty_name" }, + netsArray: { $push: "$system.net" }, + }; + }; + + private getInfraProjection = (): object => { + const projectStage: any = { status: 1, responseTime: 1, createdAt: 1 }; + projectStage["system.cpu.physical_core"] = 1; + projectStage["system.cpu.logical_core"] = 1; + projectStage["system.cpu.frequency"] = 1; + projectStage["system.cpu.current_frequency"] = 1; + projectStage["system.cpu.temperature"] = 1; + projectStage["system.cpu.free_percent"] = 1; + projectStage["system.cpu.usage_percent"] = 1; + projectStage["system.memory.total_bytes"] = 1; + projectStage["system.memory.available_bytes"] = 1; + projectStage["system.memory.used_bytes"] = 1; + projectStage["system.memory.usage_percent"] = 1; + projectStage["system.disk"] = 1; + projectStage["system.host.os"] = 1; + projectStage["system.host.platform"] = 1; + projectStage["system.host.kernel_version"] = 1; + projectStage["system.host.pretty_name"] = 1; + projectStage["system.net"] = 1; + return projectStage; + }; + + private getFinalProjection = (type: string): object => { + if (type === "pagespeed") { + return { + _id: 1, + count: 1, + avgResponseTime: 1, + accessibility: "$accessibility", + seo: "$seo", + bestPractices: "$bestPractices", + performance: "$performance", + cls: "$cls", + si: "$si", + fcp: "$fcp", + lcp: "$lcp", + tbt: "$tbt", + }; + } + + if (type === "infrastructure") { + return { + _id: 1, + count: 1, + avgResponseTime: 1, + cpu: { + physicalCores: "$physicalCores", + logicalCores: "$logicalCores", + frequency: "$frequency", + currentFrequency: "$currentFrequency", + temperatures: { + $map: { + input: { + $range: [0, { $size: { $arrayElemAt: ["$tempsArrays", 0] } }], + }, + as: "idx", + in: { + $avg: { + $map: { + input: "$tempsArrays", + as: "arr", + in: { $arrayElemAt: ["$$arr", "$$idx"] }, + }, + }, + }, + }, + }, + freePercent: "$freePercent", + usedPercent: "$usedPercent", + }, + memory: { + total_bytes: "$total_bytes", + available_bytes: "$available_bytes", + used_bytes: "$used_bytes", + usage_percent: "$memory_usage_percent", + }, + disks: { + $map: { + input: { + $range: [0, { $size: { $arrayElemAt: ["$disksArray", 0] } }], + }, + as: "idx", + in: { + $let: { + vars: { + diskGroup: { + $map: { + input: "$disksArray", + as: "diskArr", + in: { $arrayElemAt: ["$$diskArr", "$$idx"] }, + }, + }, + }, + in: { + device: { $arrayElemAt: ["$$diskGroup.device", 0] }, + total_bytes: { $avg: "$$diskGroup.total_bytes" }, + free_bytes: { $avg: "$$diskGroup.free_bytes" }, + used_bytes: { $avg: "$$diskGroup.used_bytes" }, + usage_percent: { $avg: "$$diskGroup.usage_percent" }, + total_inodes: { $avg: "$$diskGroup.total_inodes" }, + free_inodes: { $avg: "$$diskGroup.free_inodes" }, + used_inodes: { $avg: "$$diskGroup.used_inodes" }, + inodes_usage_percent: { + $avg: "$$diskGroup.inodes_usage_percent", + }, + read_bytes: { $avg: "$$diskGroup.read_bytes" }, + write_bytes: { $avg: "$$diskGroup.write_bytes" }, + read_time: { $avg: "$$diskGroup.read_time" }, + write_time: { $avg: "$$diskGroup.write_time" }, + }, + }, + }, + }, + }, + host: { + os: "$os", + platform: "$platform", + kernel_version: "$kernel_version", + pretty_name: "$pretty_name", + }, + net: { + $map: { + input: { + $range: [0, { $size: { $arrayElemAt: ["$netsArray", 0] } }], + }, + as: "idx", + in: { + $let: { + vars: { + netGroup: { + $map: { + input: "$netsArray", + as: "netArr", + in: { $arrayElemAt: ["$$netArr", "$$idx"] }, + }, + }, + }, + in: { + name: { $arrayElemAt: ["$$netGroup.name", 0] }, + bytes_sent: { $avg: "$$netGroup.bytes_sent" }, + bytes_recv: { $avg: "$$netGroup.bytes_recv" }, + packets_sent: { $avg: "$$netGroup.packets_sent" }, + packets_recv: { $avg: "$$netGroup.packets_recv" }, + err_in: { $avg: "$$netGroup.err_in" }, + err_out: { $avg: "$$netGroup.err_out" }, + drop_in: { $avg: "$$netGroup.drop_in" }, + drop_out: { $avg: "$$netGroup.drop_out" }, + fifo_in: { $avg: "$$netGroup.fifo_in" }, + fifo_out: { $avg: "$$netGroup.fifo_out" }, + }, + }, + }, + }, + }, + }; + } + return {}; + }; + + getEmbedChecks = async (monitorId: string, range: string, status: string | undefined): Promise => { + const monitor = await Monitor.findById(monitorId); + if (!monitor) { + throw new ApiError("Monitor not found", 404); + } + const startDate = this.getStartDate(range); + const dateFormat = this.getDateFormat(range); + + // Build match stage + const matchStage: { + monitorId: mongoose.Types.ObjectId; + createdAt: { $gte: Date }; + status?: string; + } = { + monitorId: monitor._id, + createdAt: { $gte: startDate }, + }; + + if (status) { + matchStage.status = status; + } + + let groupClause; + + if (monitor.type === "pagespeed") { + groupClause = this.getPageSpeedGroup(dateFormat); + } else if (monitor.type === "infrastructure") { + groupClause = this.getInfraGroup(dateFormat); + } else { + groupClause = this.getBaseGroup(dateFormat); + } + + let projectStage; + if (monitor.type === "pagespeed") { + projectStage = this.getPageSpeedProjection(); + } else if (monitor.type === "infrastructure") { + projectStage = this.getInfraProjection(); + } else { + projectStage = this.getBaseProjection(); + } + + let finalProjection = {}; + if (monitor.type === "pagespeed" || monitor.type === "infrastructure") { + finalProjection = this.getFinalProjection(monitor.type); + } else { + finalProjection = { _id: 1, count: 1, avgResponseTime: 1 }; + } + + const checks = await Check.aggregate([ + { + $match: matchStage, + }, + { $sort: { createdAt: 1 } }, + { $project: projectStage }, + { $group: groupClause }, + { $sort: { _id: -1 } }, + { + $project: finalProjection, + }, + ]); + + // Get monitor stats + const monitorStats = await MonitorStats.findOne({ + monitorId: monitor._id, + }).lean(); + + if (!monitorStats) { + throw new ApiError("Monitor stats not found", 404); + } + + return { + monitor: monitor.toObject(), + checks, + stats: monitorStats, + }; + }; + + async toggleActive(id: string, tokenizedUser: ITokenizedUser) { + const pendingStatus: MonitorStatus = "initializing"; + const updatedMonitor = await Monitor.findOneAndUpdate( + { _id: id }, + [ + { + $set: { + isActive: { $not: "$isActive" }, + status: pendingStatus, + updatedBy: tokenizedUser.sub, + updatedAt: new Date(), + }, + }, + ], + { new: true } + ); + + if (!updatedMonitor) { + throw new ApiError("Monitor not found", 404); + } + + await this.jobQueue.updateJob(updatedMonitor); + + if (updatedMonitor?.isActive) { + await this.jobQueue.resumeJob(updatedMonitor); + } else { + await this.jobQueue.pauseJob(updatedMonitor); + } + return updatedMonitor; + } + + async update(tokenizedUser: ITokenizedUser, monitorId: string, updateData: Partial) { + const allowedFields: (keyof IMonitor)[] = ["name", "interval", "isActive", "n", "notificationChannels"]; + const safeUpdate: Partial = {}; + + for (const field of allowedFields) { + if (updateData[field] !== undefined) { + (safeUpdate as any)[field] = updateData[field]; + } + } + + const updatedMonitor = await Monitor.findByIdAndUpdate( + monitorId, + { + $set: { + ...safeUpdate, + updatedAt: new Date(), + updatedBy: tokenizedUser.sub, + }, + }, + { new: true, runValidators: true } + ); + + if (!updatedMonitor) { + throw new ApiError("Monitor not found", 404); + } + await this.jobQueue.updateJob(updatedMonitor); + return updatedMonitor; + } + + async delete(monitorId: string) { + const monitor = await Monitor.findById(monitorId); + if (!monitor) { + throw new ApiError("Monitor not found", 404); + } + await monitor.deleteOne(); + await this.jobQueue.deleteJob(monitor); + return true; + } +} + +export default MonitorService; diff --git a/server/src/service/v2/business/MonitorStatsService.ts b/server/src/service/v2/business/MonitorStatsService.ts index dc7d4acb3..089b43ec4 100644 --- a/server/src/service/v2/business/MonitorStatsService.ts +++ b/server/src/service/v2/business/MonitorStatsService.ts @@ -1,10 +1,13 @@ import { MonitorStats } from "../../../db/v2/models/index.js"; import { Monitor } from "../../../db/v2/models/index.js"; + +const SERVICE_NAME = "MonitorStatsServiceV2"; export interface IMonitorStatsService { cleanupOrphanedMonitorStats: () => Promise; } class MonitorStatsService implements IMonitorStatsService { + static SERVICE_NAME = SERVICE_NAME; constructor() {} async cleanupOrphanedMonitorStats() { diff --git a/server/src/service/v2/business/NotificationChannelService.ts b/server/src/service/v2/business/NotificationChannelService.ts index 35eae5b12..112efa691 100644 --- a/server/src/service/v2/business/NotificationChannelService.ts +++ b/server/src/service/v2/business/NotificationChannelService.ts @@ -1,6 +1,8 @@ import { ITokenizedUser, INotificationChannel, NotificationChannel, Monitor } from "../../../db/v2/models/index.js"; import ApiError from "../../../utils/ApiError.js"; +const SERVICE_NAME = "NotificationChannelServiceV2"; + export interface INotificationChannelService { create: ( tokenizedUser: ITokenizedUser, @@ -15,6 +17,8 @@ export interface INotificationChannelService { } class NotificationChannelService implements INotificationChannelService { + static SERVICE_NAME = SERVICE_NAME; + constructor() {} create = async (tokenizedUser: ITokenizedUser, notificationChannelData: INotificationChannel) => { diff --git a/server/src/service/v2/business/QueueService.ts b/server/src/service/v2/business/QueueService.ts index 20069072e..620544ba7 100644 --- a/server/src/service/v2/business/QueueService.ts +++ b/server/src/service/v2/business/QueueService.ts @@ -1,6 +1,9 @@ import { IJobQueue } from "../infrastructure/JobQueue.js"; +const SERVICE_NAME = "QueueServiceV2"; + class QueueService { + static SERVICE_NAME = SERVICE_NAME; private jobQueue: IJobQueue; constructor(jobQueue: IJobQueue) { diff --git a/server/src/service/v2/business/UserService.ts b/server/src/service/v2/business/UserService.ts index 46d221759..09c50dcbc 100644 --- a/server/src/service/v2/business/UserService.ts +++ b/server/src/service/v2/business/UserService.ts @@ -1,10 +1,12 @@ import { IUser, User } from "../../../db/v2/models/index.js"; +const SERVICE_NAME = "UserServiceV2"; export interface IUserService { getAllUsers(): Promise; } class UserService implements IUserService { + static SERVICE_NAME = SERVICE_NAME; async getAllUsers(): Promise { return await User.find(); } diff --git a/server/src/service/v2/infrastructure/JobGenerator.ts b/server/src/service/v2/infrastructure/JobGenerator.ts index 4f2beaf71..69b716920 100644 --- a/server/src/service/v2/infrastructure/JobGenerator.ts +++ b/server/src/service/v2/infrastructure/JobGenerator.ts @@ -7,12 +7,14 @@ import { INotificationService } from "./NotificationService.js"; import { IMaintenanceService } from "../business/MaintenanceService.js"; import ApiError from "../../../utils/ApiError.js"; +const SERVICE_NAME = "JobGeneratorV2"; export interface IJobGenerator { generateJob: () => (Monitor: IMonitor) => Promise; generateCleanupJob: () => () => Promise; } class JobGenerator implements IJobGenerator { + static SERVICE_NAME = SERVICE_NAME; private networkService: INetworkService; private checkService: ICheckService; private monitorStatsService: IMonitorStatsService; diff --git a/server/src/service/v2/infrastructure/JobQueue.ts b/server/src/service/v2/infrastructure/JobQueue.ts index af415d3f7..85479d8e5 100644 --- a/server/src/service/v2/infrastructure/JobQueue.ts +++ b/server/src/service/v2/infrastructure/JobQueue.ts @@ -2,6 +2,8 @@ import { IJob } from "super-simple-scheduler/dist/job/job.js"; import { Monitor, IMonitor } from "../../../db/v2/models/index.js"; import Scheduler from "super-simple-scheduler"; import { IJobGenerator } from "./JobGenerator.js"; + +const SERVICE_NAME = "JobQueueV2"; export interface IJobMetrics { jobs: number; activeJobs: number; @@ -36,6 +38,8 @@ export interface IJobQueue { } export default class JobQueue implements IJobQueue { + static SERVICE_NAME = SERVICE_NAME; + private scheduler: Scheduler; private static instance: JobQueue | null = null; private jobGenerator: any; diff --git a/server/src/service/v2/infrastructure/NetworkService.ts b/server/src/service/v2/infrastructure/NetworkService.ts index 90657bd2d..ab8427d84 100644 --- a/server/src/service/v2/infrastructure/NetworkService.ts +++ b/server/src/service/v2/infrastructure/NetworkService.ts @@ -1,4 +1,5 @@ import { Got, HTTPError } from "got"; +import got from "got"; import ping from "ping"; import { IMonitor } from "../../../db/v2/models/index.js"; import { GotTimings } from "../../../db/v2/models/checks/Check.js"; @@ -7,6 +8,8 @@ import type { ISystemInfo, ICaptureInfo, ILighthouseResult } from "../../../db/v import { MonitorType, MonitorStatus } from "../../../db/v2/models/monitors/Monitor.js"; import ApiError from "../../../utils/ApiError.js"; import { config } from "../../../config/index.js"; + +const SERVICE_NAME = "NetworkServiceV2"; export interface INetworkService { requestHttp: (monitor: IMonitor) => Promise; requestInfrastructure: (monitor: IMonitor) => Promise; @@ -36,9 +39,10 @@ export interface StatusResponse { } class NetworkService implements INetworkService { + static SERVICE_NAME = SERVICE_NAME; private got: Got; private NETWORK_ERROR: number; - constructor(got: Got) { + constructor() { this.got = got; this.NETWORK_ERROR = 5000; } diff --git a/server/src/service/v2/infrastructure/NotificationService.ts b/server/src/service/v2/infrastructure/NotificationService.ts index 4195958bd..2cc5e75d8 100644 --- a/server/src/service/v2/infrastructure/NotificationService.ts +++ b/server/src/service/v2/infrastructure/NotificationService.ts @@ -1,11 +1,14 @@ import UserService from "../business/UserService.js"; import { IMonitor, NotificationChannel } from "../../../db/v2/models/index.js"; import { EmailService, SlackService, DiscordService, WebhookService } from "./NotificationServices/index.js"; + +const SERVICE_NAME = "NotificationServiceV2"; export interface INotificationService { handleNotifications: (monitor: IMonitor) => Promise; } class NotificationService implements INotificationService { + static SERVICE_NAME = SERVICE_NAME; private emailService: EmailService; private slackService: SlackService; private discordService: DiscordService; diff --git a/server/src/service/v2/infrastructure/NotificationServices/Discord.ts b/server/src/service/v2/infrastructure/NotificationServices/Discord.ts index c9ec30700..c3a5d375a 100644 --- a/server/src/service/v2/infrastructure/NotificationServices/Discord.ts +++ b/server/src/service/v2/infrastructure/NotificationServices/Discord.ts @@ -2,7 +2,10 @@ import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.j import { IAlert, IMessageService } from "./IMessageService.js"; import got from "got"; import ApiError from "../../../../utils/ApiError.js"; + +const SERVICE_NAME = "DiscordServiceV2"; class DiscordService implements IMessageService { + static SERVICE_NAME = SERVICE_NAME; constructor() {} private toDiscordEmbeds = (alert: IAlert) => { diff --git a/server/src/service/v2/infrastructure/NotificationServices/Email.ts b/server/src/service/v2/infrastructure/NotificationServices/Email.ts index d1443ce08..4bed9a156 100644 --- a/server/src/service/v2/infrastructure/NotificationServices/Email.ts +++ b/server/src/service/v2/infrastructure/NotificationServices/Email.ts @@ -4,7 +4,10 @@ import nodemailer, { Transporter } from "nodemailer"; import { config } from "../../../../config/index.js"; import UserService from "../../business/UserService.js"; import ApiError from "../../../../utils/ApiError.js"; + +const SERVICE_NAME = "EmailServiceV2"; class EmailService implements IMessageService { + static SERVICE_NAME = SERVICE_NAME; private transporter: Transporter; private userService: UserService; diff --git a/server/src/service/v2/infrastructure/NotificationServices/Slack.ts b/server/src/service/v2/infrastructure/NotificationServices/Slack.ts index 50e2021e4..0be56ca56 100644 --- a/server/src/service/v2/infrastructure/NotificationServices/Slack.ts +++ b/server/src/service/v2/infrastructure/NotificationServices/Slack.ts @@ -2,7 +2,9 @@ import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.j import { IAlert, IMessageService } from "./IMessageService.js"; import got from "got"; +const SERVICE_NAME = "SlackServiceV2"; class SlackService implements IMessageService { + static SERVICE_NAME = SERVICE_NAME; constructor() {} private toSlackBlocks = (alert: IAlert) => { diff --git a/server/src/service/v2/infrastructure/NotificationServices/Webhook.ts b/server/src/service/v2/infrastructure/NotificationServices/Webhook.ts index b84b2b220..5dd89b16d 100644 --- a/server/src/service/v2/infrastructure/NotificationServices/Webhook.ts +++ b/server/src/service/v2/infrastructure/NotificationServices/Webhook.ts @@ -2,7 +2,11 @@ import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.j import { IAlert, IMessageService } from "./IMessageService.js"; import ApiError from "../../../../utils/ApiError.js"; import got from "got"; + +const SERVICE_NAME = "WebhookServiceV2"; class WebhookService implements IMessageService { + static SERVICE_NAME = SERVICE_NAME; + constructor() {} buildAlert = (monitor: IMonitor) => { diff --git a/server/src/service/v2/infrastructure/StatusService.ts b/server/src/service/v2/infrastructure/StatusService.ts index 87b0df0af..f2881b160 100644 --- a/server/src/service/v2/infrastructure/StatusService.ts +++ b/server/src/service/v2/infrastructure/StatusService.ts @@ -2,6 +2,7 @@ import { IMonitor, IMonitorStats, MonitorStats } from "../../../db/v2/models/ind import { StatusResponse } from "./NetworkService.js"; import ApiError from "../../../utils/ApiError.js"; +const SERVICE_NAME = "StatusServiceV2"; const MAX_LATEST_CHECKS = 25; export interface IStatusService { updateMonitorStatus: (monitor: IMonitor, status: StatusResponse) => Promise; @@ -14,6 +15,7 @@ export interface IStatusService { export type StatusChangeResult = [updatedMonitor: IMonitor, statusChanged: boolean]; class StatusService implements IStatusService { + static SERVICE_NAME = SERVICE_NAME; updateMonitorStatus = async (monitor: IMonitor, statusResponse: StatusResponse): Promise => { const newStatus = statusResponse.status; monitor.lastCheckedAt = new Date(); diff --git a/server/src/types/express.d.ts b/server/src/types/express.d.ts new file mode 100644 index 000000000..7c78a053c --- /dev/null +++ b/server/src/types/express.d.ts @@ -0,0 +1,10 @@ +import { ITokenizedUser } from "../db/models/index.ts"; + +declare global { + namespace Express { + interface Request { + user?: ITokenizedUser; + resource?: any; + } + } +} diff --git a/server/src/types/index.ts b/server/src/types/index.ts new file mode 100644 index 000000000..6e708b934 --- /dev/null +++ b/server/src/types/index.ts @@ -0,0 +1 @@ +export type { MonitorWithChecksResponse } from "./monitor-response-with-checks.js"; diff --git a/server/src/types/monitor-response-with-checks.ts b/server/src/types/monitor-response-with-checks.ts new file mode 100644 index 000000000..660ce0035 --- /dev/null +++ b/server/src/types/monitor-response-with-checks.ts @@ -0,0 +1,11 @@ +import { IMonitor, IMonitorStats } from "../db/v2/models/index.js"; + +export interface MonitorWithChecksResponse { + monitor: IMonitor; + checks: Array<{ + _id: string; + count: number; + avgResponseTime: number; + }>; + stats: IMonitorStats; +} From 811a6bf2225fb9808a5d34a333d453386f03bf4a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 25 Sep 2025 09:38:41 -0700 Subject: [PATCH 019/103] create and start v2 queue --- server/src/config/services.js | 10 ++++------ server/src/service/v1/infrastructure/bufferService.js | 10 +++++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/server/src/config/services.js b/server/src/config/services.js index ec07c5e15..c1679868d 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -231,18 +231,12 @@ export const initializeServices = async ({ logger, envSettings, settingsService }); // V2 Services - const jobQueueV2 = new JobQueueV2(); - const authServiceV2 = new AuthServiceV2(jobQueueV2); const checkServiceV2 = new CheckServiceV2(); const inviteServiceV2 = new InviteServiceV2(); const maintenanceServiceV2 = new MaintenanceServiceV2(); - const monitorServiceV2 = new MonitorServiceV2(jobQueueV2); const monitorStatsServiceV2 = new MonitorStatsServiceV2(); const notificationChannelServiceV2 = new NotificationChannelServiceV2(); - const queueServiceV2 = new QueueServiceV2(jobQueueV2); const userServiceV2 = new UserServiceV2(); - - // V2 Infra const discordServiceV2 = new DiscordServiceV2(); const emailServiceV2 = new EmailServiceV2(userServiceV2); const slackServiceV2 = new SlackServiceV2(); @@ -258,6 +252,10 @@ export const initializeServices = async ({ logger, envSettings, settingsService notificationServiceV2, maintenanceServiceV2 ); + const jobQueueV2 = await JobQueueV2.create(jobGeneratorV2); + const authServiceV2 = new AuthServiceV2(jobQueueV2); + const monitorServiceV2 = new MonitorServiceV2(jobQueueV2); + const queueServiceV2 = new QueueServiceV2(jobQueueV2); const services = { //v1 diff --git a/server/src/service/v1/infrastructure/bufferService.js b/server/src/service/v1/infrastructure/bufferService.js index ed48cc654..459d25af3 100755 --- a/server/src/service/v1/infrastructure/bufferService.js +++ b/server/src/service/v1/infrastructure/bufferService.js @@ -64,11 +64,11 @@ class BufferService { }); } this.buffer = []; - this.logger.debug({ - message: `Flushed ${items} items`, - service: this.SERVICE_NAME, - method: "flushBuffer", - }); + // this.logger.debug({ + // message: `Flushed ${items} items`, + // service: this.SERVICE_NAME, + // method: "flushBuffer", + // }); items = 0; } } From 5957479d03e0ad84c8dca84eae95d64fffe2bc9e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 25 Sep 2025 09:46:11 -0700 Subject: [PATCH 020/103] express type --- server/package-lock.json | 98 ++++++++++++++++++++++++++++++++++++++++ server/package.json | 1 + 2 files changed, 99 insertions(+) diff --git a/server/package-lock.json b/server/package-lock.json index 2a522ae64..8c9eac34c 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -45,6 +45,7 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/express": "5.0.3", "@types/jsonwebtoken": "9.0.10", "@types/nodemailer": "7.0.1", "@types/ping": "0.4.4", @@ -3046,6 +3047,27 @@ "node": ">=10.13.0" } }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -3053,12 +3075,44 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/express": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", + "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", + "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "license": "MIT" }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -3084,6 +3138,13 @@ "@types/node": "*" } }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -3118,12 +3179,49 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/relateurl": { "version": "0.2.33", "resolved": "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.33.tgz", "integrity": "sha512-bTQCKsVbIdzLqZhLkF5fcJQreE4y1ro4DIyVrlDNSCJRRwHhB8Z+4zXXa8jN6eDvc2HbRsEYgbvrnGvi54EpSw==", "license": "MIT" }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", diff --git a/server/package.json b/server/package.json index 2fdcf58cb..3bd79722a 100755 --- a/server/package.json +++ b/server/package.json @@ -54,6 +54,7 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/express": "5.0.3", "@types/jsonwebtoken": "9.0.10", "@types/nodemailer": "7.0.1", "@types/ping": "0.4.4", From 4d89213e1c46228521d9e918cff3bb06887881b4 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 25 Sep 2025 10:11:41 -0700 Subject: [PATCH 021/103] Add typescript config, move to v1 dir --- client/package.json | 4 +- client/src/Components/ActionsMenu/index.jsx | 2 +- .../src/Components/MonitorActions/index.jsx | 2 +- .../MonitorDetailsControlHeader/index.jsx | 6 +- .../MonitorDetailsControlHeader/status.jsx | 2 +- .../src/Components/PageStateWrapper/index.jsx | 2 +- client/src/Components/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 | 6 +- client/src/Hooks/{ => v1}/settingsHooks.js | 4 +- 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 | 4 +- .../Account/EditUser/hooks/editUser.js | 6 +- .../Pages/{ => v1}/Account/EditUser/index.jsx | 14 ++--- .../components/AddMemberMenu/index.jsx | 0 .../AddTeamMember/hooks/useAddTeamMember.jsx | 2 +- .../components/AddTeamMember/index.jsx | 18 +++--- .../Account/components/PasswordPanel.jsx | 14 ++--- .../Account/components/ProfilePanel.jsx | 18 +++--- .../Account/components/RoleTable/index.jsx | 4 +- .../{ => v1}/Account/components/TeamPanel.jsx | 22 +++---- client/src/Pages/{ => v1}/Account/index.jsx | 8 +-- client/src/Pages/{ => v1}/Auth/CheckEmail.jsx | 12 ++-- .../Pages/{ => v1}/Auth/ForgotPassword.jsx | 16 ++--- .../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 | 16 ++--- .../{ => v1}/Auth/NewPasswordConfirmed.jsx | 10 ++-- .../Pages/{ => v1}/Auth/Register/index.jsx | 14 ++--- .../Pages/{ => v1}/Auth/SetNewPassword.jsx | 22 +++---- .../{ => v1}/Auth/components/AuthHeader.jsx | 6 +- .../Auth/components/AuthPageWrapper.jsx | 6 +- .../Auth/components/PasswordTooltip.jsx | 2 +- .../Auth/hooks/usePasswordFeedback.jsx | 2 +- .../Auth/hooks/useValidatePassword.jsx | 2 +- client/src/Pages/{ => v1}/Auth/index.css | 0 .../Components/IncidentTable/index.jsx | 22 +++---- .../Components/OptionsHeader/index.jsx | 4 +- .../Components/OptionsHeader/skeleton.jsx | 0 .../Components/StatusBoxes/StatusBox.jsx | 10 ++-- .../Components/StatusBoxes/index.jsx | 4 +- .../Components/StatusBoxes/skeleton.jsx | 0 client/src/Pages/{ => v1}/Incidents/index.jsx | 18 +++--- .../Create/Components/CustomAlertsSection.jsx | 6 +- .../Components/CustomThreshold/index.jsx | 4 +- .../Components/MonitorActionButtons.jsx | 2 +- .../Create/Components/MonitorStatusHeader.jsx | 4 +- .../hooks/useInfrastructureMonitorForm.jsx | 0 .../Create/hooks/useInfrastructureSubmit.jsx | 2 +- .../hooks/useValidateInfrastructureForm.jsx | 4 +- .../{ => v1}/Infrastructure/Create/index.jsx | 32 +++++----- .../AreaChartBoxes/InfraAreaChart.jsx | 6 +- .../Components/AreaChartBoxes/index.jsx | 8 +-- .../Components/AreaChartBoxes/skeleton.jsx | 0 .../Components/BaseContainer/index.jsx | 2 +- .../Details/Components/GaugeBoxes/Gauge.jsx | 4 +- .../Details/Components/GaugeBoxes/index.jsx | 6 +- .../Components/GaugeBoxes/skeleton.jsx | 0 .../Components/NetworkStats/NetworkCharts.jsx | 6 +- .../NetworkStats/NetworkStatBoxes.jsx | 6 +- .../Details/Components/NetworkStats/index.jsx | 8 +-- .../Components/NetworkStats/skeleton.jsx | 0 .../Details/Components/StatusBoxes/index.jsx | 8 +-- .../Details/Hooks/useHardwareUtils.jsx | 0 .../{ => v1}/Infrastructure/Details/index.jsx | 22 +++---- .../Monitors/Components/Filters/index.jsx | 2 +- .../Components/MonitorsTable/index.jsx | 16 ++--- .../Components/MonitorsTableMenu/index.jsx | 10 ++-- .../Infrastructure/Monitors/index.jsx | 22 +++---- .../src/Pages/{ => v1}/Integrations/index.css | 0 .../src/Pages/{ => v1}/Integrations/index.jsx | 6 +- .../Diagnostics/components/gauges/index.jsx | 4 +- .../Diagnostics/components/stats/index.jsx | 4 +- .../Pages/{ => v1}/Logs/Diagnostics/index.jsx | 12 ++-- .../{ => v1}/Logs/Diagnostics/utils/utils.js | 0 client/src/Pages/{ => v1}/Logs/Logs/index.jsx | 4 +- .../Queue/components/FailedJobTable/index.jsx | 4 +- .../Logs/Queue/components/JobTable/index.jsx | 6 +- .../Logs/Queue/components/Metrics/index.jsx | 4 +- .../Queue/components/MetricsTable/index.jsx | 2 +- .../src/Pages/{ => v1}/Logs/Queue/index.jsx | 8 +-- client/src/Pages/{ => v1}/Logs/index.jsx | 8 +-- .../Components/ConfigSelect/index.jsx | 2 +- .../Components/MonitorList/index.jsx | 0 .../Components/MonitorsConfig/index.jsx | 4 +- .../hooks/useMaintenanceActions.jsx | 4 +- .../hooks/useMaintenanceData.jsx | 4 +- .../Maintenance/CreateMaintenance/index.css | 0 .../Maintenance/CreateMaintenance/index.jsx | 26 ++++---- .../MaintenanceTable/ActionsMenu/index.jsx | 10 ++-- .../Maintenance/MaintenanceTable/index.jsx | 12 ++-- .../src/Pages/{ => v1}/Maintenance/index.css | 0 .../src/Pages/{ => v1}/Maintenance/index.jsx | 8 +-- client/src/Pages/{ => v1}/NotFound/index.jsx | 2 +- .../Notifications/components/ActionMenu.jsx | 0 .../{ => v1}/Notifications/create/index.jsx | 18 +++--- .../Pages/{ => v1}/Notifications/index.jsx | 10 ++-- .../src/Pages/{ => v1}/Notifications/utils.js | 0 .../Pages/{ => v1}/PageSpeed/Create/index.css | 0 .../Pages/{ => v1}/PageSpeed/Create/index.jsx | 32 +++++----- .../{ => v1}/PageSpeed/Create/skeleton.jsx | 0 .../Details/Components/Charts/AreaChart.jsx | 2 +- .../Components/Charts/AreaChartLegend.jsx | 6 +- .../Details/Components/Charts/PieChart.jsx | 0 .../Components/Charts/PieChartLegend.jsx | 4 +- .../Components/PageSpeedAreaChart/index.jsx | 10 ++-- .../PageSpeedAreaChart/skeleton.jsx | 0 .../Components/PageSpeedStatusBoxes/index.jsx | 6 +- .../Components/PerformanceReport/index.jsx | 10 ++-- .../Components/PerformanceReport/skeleton.jsx | 0 .../{ => v1}/PageSpeed/Details/index.jsx | 18 +++--- .../Monitors/Components/Card/index.jsx | 10 ++-- .../Monitors/Components/MonitorGrid/index.jsx | 2 +- .../{ => v1}/PageSpeed/Monitors/index.jsx | 18 +++--- .../src/Pages/{ => v1}/ServerUnreachable.jsx | 14 ++--- .../Pages/{ => v1}/Settings/SettingsAbout.jsx | 4 +- .../Settings/SettingsDemoMonitors.jsx | 4 +- .../Pages/{ => v1}/Settings/SettingsEmail.jsx | 12 ++-- .../Settings/SettingsGlobalThresholds.jsx | 4 +- .../{ => v1}/Settings/SettingsPagespeed.jsx | 6 +- .../Pages/{ => v1}/Settings/SettingsStats.jsx | 6 +- .../{ => v1}/Settings/SettingsTimeZone.jsx | 6 +- .../Pages/{ => v1}/Settings/SettingsUI.jsx | 4 +- .../Pages/{ => v1}/Settings/SettingsURL.jsx | 4 +- client/src/Pages/{ => v1}/Settings/index.jsx | 30 +++++----- .../Create/Components/MonitorList/index.jsx | 0 .../Create/Components/Progress/index.jsx | 4 +- .../Create/Components/Skeleton/index.jsx | 0 .../Create/Components/Tabs/ConfigStack.jsx | 2 +- .../Create/Components/Tabs/Content.jsx | 8 +-- .../Create/Components/Tabs/Settings.jsx | 16 ++--- .../Create/Components/Tabs/index.jsx | 6 +- .../Create/Hooks/useCreateStatusPage.jsx | 4 +- .../Create/Hooks/useMonitorsFetch.jsx | 4 +- .../{ => v1}/StatusPage/Create/index.jsx | 24 ++++---- .../Status/Components/AdminLink/index.jsx | 0 .../Components/ControlsHeader/index.jsx | 6 +- .../Status/Components/MonitorsList/index.jsx | 8 +-- .../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 | 20 +++---- .../Components/StatusPagesTable/index.jsx | 4 +- .../StatusPages/Hooks/useStatusPagesFetch.jsx | 4 +- .../{ => v1}/StatusPage/StatusPages/index.jsx | 12 ++-- .../{ => v1}/Uptime/BulkImport/Upload.jsx | 0 .../{ => v1}/Uptime/BulkImport/index.jsx | 10 ++-- .../Pages/{ => v1}/Uptime/Create/index.css | 0 .../Pages/{ => v1}/Uptime/Create/index.jsx | 32 +++++----- .../Pages/{ => v1}/Uptime/Create/skeleton.jsx | 0 .../Details/Components/ChartBoxes/index.jsx | 18 +++--- .../Components/ChartBoxes/skeleton.jsx | 0 .../Components/Charts/CustomLabels.jsx | 2 +- .../Components/Charts/DownBarChart.jsx | 2 +- .../Components/Charts/ResponseGaugeChart.jsx | 0 .../Components/Charts/ResponseTimeChart.jsx | 8 +-- .../Charts/ResponseTimeChartSkeleton.jsx | 0 .../Details/Components/Charts/UpBarChart.jsx | 2 +- .../Components/ResponseTable/index.jsx | 14 ++--- .../Components/ResponseTable/skeleton.jsx | 0 .../Components/UptimeStatusBoxes/index.jsx | 8 +-- .../Details/Hooks/useCertificateFetch.jsx | 6 +- .../Pages/{ => v1}/Uptime/Details/index.jsx | 24 ++++---- .../Monitors/Components/Filter/index.jsx | 2 +- .../Components/LoadingSpinner/index.jsx | 0 .../Components/SearchComponent/index.jsx | 4 +- .../Monitors/Components/Skeleton/index.jsx | 0 .../Monitors/Components/StatusBoxes/index.jsx | 4 +- .../Components/StatusBoxes/skeleton.jsx | 0 .../Components/StatusBoxes/statusBox.jsx | 6 +- .../Components/UptimeDataTable/index.jsx | 16 ++--- .../Uptime/Monitors/Hooks/useDebounce.jsx | 0 .../Pages/{ => v1}/Uptime/Monitors/index.jsx | 26 ++++---- client/src/Routes/index.jsx | 60 +++++++++---------- client/tsconfig.app.json | 32 ++++++++++ client/tsconfig.json | 14 +++++ client/tsconfig.node.json | 27 +++++++++ client/{vite.config.js => vite.config.ts} | 6 +- 187 files changed, 708 insertions(+), 637 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 (95%) 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 (92%) rename client/src/Pages/{ => v1}/About/index.jsx (67%) rename client/src/Pages/{ => v1}/Account/EditUser/hooks/editUser.js (91%) rename client/src/Pages/{ => v1}/Account/EditUser/index.jsx (86%) 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 (91%) rename client/src/Pages/{ => v1}/Account/components/PasswordPanel.jsx (92%) rename client/src/Pages/{ => v1}/Account/components/ProfilePanel.jsx (94%) rename client/src/Pages/{ => v1}/Account/components/RoleTable/index.jsx (87%) rename client/src/Pages/{ => v1}/Account/components/TeamPanel.jsx (92%) rename client/src/Pages/{ => v1}/Account/index.jsx (91%) rename client/src/Pages/{ => v1}/Auth/CheckEmail.jsx (93%) rename client/src/Pages/{ => v1}/Auth/ForgotPassword.jsx (91%) 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 (82%) rename client/src/Pages/{ => v1}/Auth/NewPasswordConfirmed.jsx (90%) rename client/src/Pages/{ => v1}/Auth/Register/index.jsx (94%) rename client/src/Pages/{ => v1}/Auth/SetNewPassword.jsx (91%) rename client/src/Pages/{ => v1}/Auth/components/AuthHeader.jsx (81%) rename client/src/Pages/{ => v1}/Auth/components/AuthPageWrapper.jsx (92%) rename client/src/Pages/{ => v1}/Auth/components/PasswordTooltip.jsx (98%) 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 (88%) rename client/src/Pages/{ => v1}/Incidents/Components/OptionsHeader/index.jsx (96%) 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 (92%) rename client/src/Pages/{ => v1}/Incidents/Components/StatusBoxes/skeleton.jsx (100%) rename client/src/Pages/{ => v1}/Incidents/index.jsx (81%) rename client/src/Pages/{ => v1}/Infrastructure/Create/Components/CustomAlertsSection.jsx (91%) rename client/src/Pages/{ => v1}/Infrastructure/Create/Components/CustomThreshold/index.jsx (95%) rename client/src/Pages/{ => v1}/Infrastructure/Create/Components/MonitorActionButtons.jsx (96%) rename client/src/Pages/{ => v1}/Infrastructure/Create/Components/MonitorStatusHeader.jsx (91%) rename client/src/Pages/{ => v1}/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx (100%) rename client/src/Pages/{ => v1}/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx (98%) rename client/src/Pages/{ => v1}/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx (85%) rename client/src/Pages/{ => v1}/Infrastructure/Create/index.jsx (92%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx (79%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/AreaChartBoxes/index.jsx (93%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx (100%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/BaseContainer/index.jsx (94%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx (93%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/GaugeBoxes/index.jsx (93%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx (100%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx (93%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx (90%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/NetworkStats/index.jsx (90%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/NetworkStats/skeleton.jsx (100%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/StatusBoxes/index.jsx (90%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Hooks/useHardwareUtils.jsx (100%) rename client/src/Pages/{ => v1}/Infrastructure/Details/index.jsx (79%) rename client/src/Pages/{ => v1}/Infrastructure/Monitors/Components/Filters/index.jsx (96%) rename client/src/Pages/{ => v1}/Infrastructure/Monitors/Components/MonitorsTable/index.jsx (86%) rename client/src/Pages/{ => v1}/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx (93%) rename client/src/Pages/{ => v1}/Infrastructure/Monitors/index.jsx (80%) rename client/src/Pages/{ => v1}/Integrations/index.css (100%) rename client/src/Pages/{ => v1}/Integrations/index.jsx (94%) rename client/src/Pages/{ => v1}/Logs/Diagnostics/components/gauges/index.jsx (96%) rename client/src/Pages/{ => v1}/Logs/Diagnostics/components/stats/index.jsx (94%) rename client/src/Pages/{ => v1}/Logs/Diagnostics/index.jsx (82%) rename client/src/Pages/{ => v1}/Logs/Diagnostics/utils/utils.js (100%) rename client/src/Pages/{ => v1}/Logs/Logs/index.jsx (94%) rename client/src/Pages/{ => v1}/Logs/Queue/components/FailedJobTable/index.jsx (92%) rename client/src/Pages/{ => v1}/Logs/Queue/components/JobTable/index.jsx (92%) rename client/src/Pages/{ => v1}/Logs/Queue/components/Metrics/index.jsx (84%) rename client/src/Pages/{ => v1}/Logs/Queue/components/MetricsTable/index.jsx (94%) rename client/src/Pages/{ => v1}/Logs/Queue/index.jsx (86%) rename client/src/Pages/{ => v1}/Logs/index.jsx (84%) rename client/src/Pages/{ => v1}/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx (92%) rename client/src/Pages/{ => v1}/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx (100%) rename client/src/Pages/{ => v1}/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx (93%) 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 (93%) rename client/src/Pages/{ => v1}/Maintenance/MaintenanceTable/ActionsMenu/index.jsx (91%) rename client/src/Pages/{ => v1}/Maintenance/MaintenanceTable/index.jsx (92%) rename client/src/Pages/{ => v1}/Maintenance/index.css (100%) rename client/src/Pages/{ => v1}/Maintenance/index.jsx (91%) 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 (92%) rename client/src/Pages/{ => v1}/Notifications/index.jsx (89%) 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 (91%) 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 (84%) rename client/src/Pages/{ => v1}/PageSpeed/Details/Components/Charts/PieChart.jsx (100%) rename client/src/Pages/{ => v1}/PageSpeed/Details/Components/Charts/PieChartLegend.jsx (92%) rename client/src/Pages/{ => v1}/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx (75%) rename client/src/Pages/{ => v1}/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx (100%) rename client/src/Pages/{ => v1}/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx (76%) rename client/src/Pages/{ => v1}/PageSpeed/Details/Components/PerformanceReport/index.jsx (78%) rename client/src/Pages/{ => v1}/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx (100%) rename client/src/Pages/{ => v1}/PageSpeed/Details/index.jsx (81%) rename client/src/Pages/{ => v1}/PageSpeed/Monitors/Components/Card/index.jsx (95%) rename client/src/Pages/{ => v1}/PageSpeed/Monitors/Components/MonitorGrid/index.jsx (88%) rename client/src/Pages/{ => v1}/PageSpeed/Monitors/index.jsx (69%) rename client/src/Pages/{ => v1}/ServerUnreachable.jsx (90%) rename client/src/Pages/{ => v1}/Settings/SettingsAbout.jsx (88%) rename client/src/Pages/{ => v1}/Settings/SettingsDemoMonitors.jsx (95%) rename client/src/Pages/{ => v1}/Settings/SettingsEmail.jsx (94%) rename client/src/Pages/{ => v1}/Settings/SettingsGlobalThresholds.jsx (94%) rename client/src/Pages/{ => v1}/Settings/SettingsPagespeed.jsx (91%) rename client/src/Pages/{ => v1}/Settings/SettingsStats.jsx (92%) rename client/src/Pages/{ => v1}/Settings/SettingsTimeZone.jsx (90%) rename client/src/Pages/{ => v1}/Settings/SettingsUI.jsx (91%) rename client/src/Pages/{ => v1}/Settings/SettingsURL.jsx (90%) rename client/src/Pages/{ => v1}/Settings/index.jsx (89%) rename client/src/Pages/{ => v1}/StatusPage/Create/Components/MonitorList/index.jsx (100%) rename client/src/Pages/{ => v1}/StatusPage/Create/Components/Progress/index.jsx (83%) rename client/src/Pages/{ => v1}/StatusPage/Create/Components/Skeleton/index.jsx (100%) rename client/src/Pages/{ => v1}/StatusPage/Create/Components/Tabs/ConfigStack.jsx (91%) rename client/src/Pages/{ => v1}/StatusPage/Create/Components/Tabs/Content.jsx (90%) rename client/src/Pages/{ => v1}/StatusPage/Create/Components/Tabs/Settings.jsx (87%) rename client/src/Pages/{ => v1}/StatusPage/Create/Components/Tabs/index.jsx (93%) 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 (92%) rename client/src/Pages/{ => v1}/StatusPage/Status/Components/AdminLink/index.jsx (100%) rename client/src/Pages/{ => v1}/StatusPage/Status/Components/ControlsHeader/index.jsx (92%) rename client/src/Pages/{ => v1}/StatusPage/Status/Components/MonitorsList/index.jsx (82%) 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 (85%) rename client/src/Pages/{ => v1}/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx (93%) rename client/src/Pages/{ => v1}/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx (86%) rename client/src/Pages/{ => v1}/StatusPage/StatusPages/index.jsx (70%) rename client/src/Pages/{ => v1}/Uptime/BulkImport/Upload.jsx (100%) rename client/src/Pages/{ => v1}/Uptime/BulkImport/index.jsx (89%) rename client/src/Pages/{ => v1}/Uptime/Create/index.css (100%) rename client/src/Pages/{ => v1}/Uptime/Create/index.jsx (94%) rename client/src/Pages/{ => v1}/Uptime/Create/skeleton.jsx (100%) rename client/src/Pages/{ => v1}/Uptime/Details/Components/ChartBoxes/index.jsx (86%) 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 (97%) rename client/src/Pages/{ => v1}/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx (100%) rename client/src/Pages/{ => v1}/Uptime/Details/Components/Charts/ResponseTimeChart.jsx (60%) rename client/src/Pages/{ => v1}/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx (100%) rename client/src/Pages/{ => v1}/Uptime/Details/Components/Charts/UpBarChart.jsx (98%) rename client/src/Pages/{ => v1}/Uptime/Details/Components/ResponseTable/index.jsx (76%) rename client/src/Pages/{ => v1}/Uptime/Details/Components/ResponseTable/skeleton.jsx (100%) rename client/src/Pages/{ => v1}/Uptime/Details/Components/UptimeStatusBoxes/index.jsx (85%) rename client/src/Pages/{ => v1}/Uptime/Details/Hooks/useCertificateFetch.jsx (84%) rename client/src/Pages/{ => v1}/Uptime/Details/index.jsx (83%) rename client/src/Pages/{ => v1}/Uptime/Monitors/Components/Filter/index.jsx (98%) rename client/src/Pages/{ => v1}/Uptime/Monitors/Components/LoadingSpinner/index.jsx (100%) rename client/src/Pages/{ => v1}/Uptime/Monitors/Components/SearchComponent/index.jsx (89%) rename client/src/Pages/{ => v1}/Uptime/Monitors/Components/Skeleton/index.jsx (100%) rename client/src/Pages/{ => v1}/Uptime/Monitors/Components/StatusBoxes/index.jsx (91%) 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 (90%) rename client/src/Pages/{ => v1}/Uptime/Monitors/Hooks/useDebounce.jsx (100%) rename client/src/Pages/{ => v1}/Uptime/Monitors/index.jsx (86%) create mode 100644 client/tsconfig.app.json create mode 100644 client/tsconfig.json create mode 100644 client/tsconfig.node.json rename client/{vite.config.js => vite.config.ts} (63%) diff --git a/client/package.json b/client/package.json index 3108f7121..9ebc99647 100644 --- a/client/package.json +++ b/client/package.json @@ -5,8 +5,8 @@ "type": "module", "scripts": { "dev": "vite", - "build": "vite build", - "build-dev": "vite build --mode development", + "build": "tsc -b && vite build", + "build-dev": "tsc -b && vite build --mode development", "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", "format": "prettier --write .", diff --git a/client/src/Components/ActionsMenu/index.jsx b/client/src/Components/ActionsMenu/index.jsx index 1b590a471..78d86e1b2 100644 --- a/client/src/Components/ActionsMenu/index.jsx +++ b/client/src/Components/ActionsMenu/index.jsx @@ -12,7 +12,7 @@ import { useNavigate } from "react-router-dom"; import { createToast } from "../../Utils/toastUtils"; import PropTypes from "prop-types"; -import { usePauseMonitor, useDeleteMonitor } from "../../Hooks/monitorHooks"; +import { usePauseMonitor, useDeleteMonitor } from "../../Hooks/v1/monitorHooks.js"; const ActionsMenu = ({ monitor, diff --git a/client/src/Components/MonitorActions/index.jsx b/client/src/Components/MonitorActions/index.jsx index ac5d89a47..9e88e26c5 100644 --- a/client/src/Components/MonitorActions/index.jsx +++ b/client/src/Components/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"; -import { useExportMonitors } from "../../Hooks/monitorHooks"; +import { useExportMonitors } from "../../Hooks/v1/monitorHooks.js"; const MonitorActions = ({ isLoading }) => { const [open, setOpen] = React.useState(false); diff --git a/client/src/Components/MonitorDetailsControlHeader/index.jsx b/client/src/Components/MonitorDetailsControlHeader/index.jsx index 4674a1fb7..cd56394f4 100644 --- a/client/src/Components/MonitorDetailsControlHeader/index.jsx +++ b/client/src/Components/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/monitorHooks"; -import { useSendTestEmail } from "../../Hooks/useSendTestEmail"; +import { usePauseMonitor } from "../../Hooks/v1/monitorHooks.js"; +import { useSendTestEmail } from "../../Hooks/v1/useSendTestEmail.js"; import { useTranslation } from "react-i18next"; -import { useTestAllNotifications } from "../../Hooks/useNotifications"; +import { useTestAllNotifications } from "../../Hooks/v1/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/MonitorDetailsControlHeader/status.jsx b/client/src/Components/MonitorDetailsControlHeader/status.jsx index 65671a609..2cf749861 100644 --- a/client/src/Components/MonitorDetailsControlHeader/status.jsx +++ b/client/src/Components/MonitorDetailsControlHeader/status.jsx @@ -7,7 +7,7 @@ import Dot from "../../Components/Dot"; import { formatDurationRounded } from "../../Utils/timeUtils"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; -import { useMonitorUtils } from "../../Hooks/useMonitorUtils"; +import { useMonitorUtils } from "../../Hooks/v1/useMonitorUtils.js"; import { formatMonitorUrl } from "../../Utils/utils"; /** * Status component displays the status information of a monitor. diff --git a/client/src/Components/PageStateWrapper/index.jsx b/client/src/Components/PageStateWrapper/index.jsx index f971b2c9c..c335fcbb3 100644 --- a/client/src/Components/PageStateWrapper/index.jsx +++ b/client/src/Components/PageStateWrapper/index.jsx @@ -3,7 +3,7 @@ import Fallback from "../Fallback"; import { Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import { useIsAdmin } from "../../Hooks/useIsAdmin"; +import { useIsAdmin } from "../../Hooks/v1/useIsAdmin.js"; import PropTypes from "prop-types"; const PageStateWrapper = ({ networkError, diff --git a/client/src/Components/StatBox/index.jsx b/client/src/Components/StatBox/index.jsx index 7d093de60..15eb61f74 100644 --- a/client/src/Components/StatBox/index.jsx +++ b/client/src/Components/StatBox/index.jsx @@ -2,7 +2,7 @@ import { Stack, Typography } from "@mui/material"; import Image from "../Image"; import { useTheme } from "@mui/material/styles"; import PropTypes from "prop-types"; -import { useMonitorUtils } from "../../Hooks/useMonitorUtils"; +import { useMonitorUtils } from "../../Hooks/v1/useMonitorUtils.js"; /** * StatBox Component diff --git a/client/src/Hooks/checkHooks.js b/client/src/Hooks/v1/checkHooks.js similarity index 97% rename from client/src/Hooks/checkHooks.js rename to client/src/Hooks/v1/checkHooks.js index bdb1a73b7..96fd1fe1c 100644 --- a/client/src/Hooks/checkHooks.js +++ b/client/src/Hooks/v1/checkHooks.js @@ -1,6 +1,6 @@ import { useState, useEffect } from "react"; -import { networkService } from "../main"; -import { createToast } from "../Utils/toastUtils"; +import { networkService } from "../../main.jsx"; +import { createToast } from "../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; const useFetchChecksTeam = ({ diff --git a/client/src/Hooks/inviteHooks.js b/client/src/Hooks/v1/inviteHooks.js similarity index 97% rename from client/src/Hooks/inviteHooks.js rename to client/src/Hooks/v1/inviteHooks.js index fb577272a..b6a0851af 100644 --- a/client/src/Hooks/inviteHooks.js +++ b/client/src/Hooks/v1/inviteHooks.js @@ -1,5 +1,5 @@ import { useState } from "react"; -import { networkService } from "../main"; +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/logHooks.js b/client/src/Hooks/v1/logHooks.js similarity index 96% rename from client/src/Hooks/logHooks.js rename to client/src/Hooks/v1/logHooks.js index 989870eea..ff4d13872 100644 --- a/client/src/Hooks/logHooks.js +++ b/client/src/Hooks/v1/logHooks.js @@ -1,6 +1,6 @@ import { useState, useEffect } from "react"; -import { networkService } from "../main"; -import { createToast } from "../Utils/toastUtils"; +import { networkService } from "../../main.jsx"; +import { createToast } from "../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; const useFetchLogs = () => { diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/v1/monitorHooks.js similarity index 98% rename from client/src/Hooks/monitorHooks.js rename to client/src/Hooks/v1/monitorHooks.js index eb1e7acdc..e6b4b5261 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/v1/monitorHooks.js @@ -1,8 +1,8 @@ import { useEffect, useState } from "react"; -import { networkService } from "../main"; -import { createToast } from "../Utils/toastUtils"; +import { networkService } from "../../main.jsx"; +import { createToast } from "../../Utils/toastUtils.jsx"; import { useTheme } from "@emotion/react"; -import { useMonitorUtils } from "./useMonitorUtils"; +import { useMonitorUtils } from "./useMonitorUtils.js"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Hooks/settingsHooks.js b/client/src/Hooks/v1/settingsHooks.js similarity index 95% rename from client/src/Hooks/settingsHooks.js rename to client/src/Hooks/v1/settingsHooks.js index 98a21c1fa..787e46ff9 100644 --- a/client/src/Hooks/settingsHooks.js +++ b/client/src/Hooks/v1/settingsHooks.js @@ -1,6 +1,6 @@ import { useState, useEffect } from "react"; -import { networkService } from "../main"; -import { createToast } from "../Utils/toastUtils"; +import { networkService } from "../../main.jsx"; +import { createToast } from "../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; const useFetchSettings = ({ setSettingsData, setIsApiKeySet, setIsEmailPasswordSet }) => { diff --git a/client/src/Hooks/useIsAdmin.js b/client/src/Hooks/v1/useIsAdmin.js similarity index 100% rename from client/src/Hooks/useIsAdmin.js rename to client/src/Hooks/v1/useIsAdmin.js diff --git a/client/src/Hooks/useMonitorUtils.js b/client/src/Hooks/v1/useMonitorUtils.js similarity index 100% rename from client/src/Hooks/useMonitorUtils.js rename to client/src/Hooks/v1/useMonitorUtils.js diff --git a/client/src/Hooks/useNotifications.js b/client/src/Hooks/v1/useNotifications.js similarity index 96% rename from client/src/Hooks/useNotifications.js rename to client/src/Hooks/v1/useNotifications.js index 539725958..d1b4a6652 100644 --- a/client/src/Hooks/useNotifications.js +++ b/client/src/Hooks/v1/useNotifications.js @@ -1,9 +1,9 @@ import { useState, useEffect, useCallback } from "react"; -import { createToast } from "../Utils/toastUtils"; -import { networkService } from "../main"; +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/Notifications/utils"; +import { NOTIFICATION_TYPES } from "../../Pages/v1/Notifications/utils.js"; const useCreateNotification = () => { const navigate = useNavigate(); diff --git a/client/src/Hooks/useSendTestEmail.js b/client/src/Hooks/v1/useSendTestEmail.js similarity index 93% rename from client/src/Hooks/useSendTestEmail.js rename to client/src/Hooks/v1/useSendTestEmail.js index e280b1f99..0be77954a 100644 --- a/client/src/Hooks/useSendTestEmail.js +++ b/client/src/Hooks/v1/useSendTestEmail.js @@ -1,7 +1,7 @@ import { useState } from "react"; -import { networkService } from "../main"; +import { networkService } from "../../main.jsx"; import { useSelector } from "react-redux"; -import { createToast } from "../Utils/toastUtils"; +import { createToast } from "../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; const useSendTestEmail = () => { diff --git a/client/src/Hooks/userHooks.js b/client/src/Hooks/v1/userHooks.js similarity index 92% rename from client/src/Hooks/userHooks.js rename to client/src/Hooks/v1/userHooks.js index 8be32a082..71b156594 100644 --- a/client/src/Hooks/userHooks.js +++ b/client/src/Hooks/v1/userHooks.js @@ -1,6 +1,6 @@ import { useEffect, useState, useCallback } from "react"; -import { networkService } from "../main"; -import { createToast } from "../Utils/toastUtils"; +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/About/index.jsx b/client/src/Pages/v1/About/index.jsx similarity index 67% rename from client/src/Pages/About/index.jsx rename to client/src/Pages/v1/About/index.jsx index 97040b2e3..0e68089d3 100644 --- a/client/src/Pages/About/index.jsx +++ b/client/src/Pages/v1/About/index.jsx @@ -1,6 +1,6 @@ import React from "react"; -import AppBar from "../../Components/Common/AppBar"; -import Footer from "../../Components/Common/Footer"; +import AppBar from "../../../Components/Common/AppBar.jsx"; +import Footer from "../../../Components/Common/Footer.jsx"; import { useTranslation } from "react-i18next"; const AboutUs = () => { diff --git a/client/src/Pages/Account/EditUser/hooks/editUser.js b/client/src/Pages/v1/Account/EditUser/hooks/editUser.js similarity index 91% rename from client/src/Pages/Account/EditUser/hooks/editUser.js rename to client/src/Pages/v1/Account/EditUser/hooks/editUser.js index 2ffa914a0..00660aead 100644 --- a/client/src/Pages/Account/EditUser/hooks/editUser.js +++ b/client/src/Pages/v1/Account/EditUser/hooks/editUser.js @@ -1,8 +1,8 @@ import { useState, useEffect } from "react"; -import { ROLES } from "../../../../Utils/roleUtils"; -import { editUserValidation } from "../../../../Validation/validation"; +import { ROLES } from "../../../../../Utils/roleUtils.js"; +import { editUserValidation } from "../../../../../Validation/validation.js"; import Joi from "joi"; -import { createToast } from "../../../../Utils/toastUtils"; +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/Account/EditUser/index.jsx b/client/src/Pages/v1/Account/EditUser/index.jsx similarity index 86% rename from client/src/Pages/Account/EditUser/index.jsx rename to client/src/Pages/v1/Account/EditUser/index.jsx index e1eeead9d..658de5144 100644 --- a/client/src/Pages/Account/EditUser/index.jsx +++ b/client/src/Pages/v1/Account/EditUser/index.jsx @@ -2,19 +2,19 @@ import Stack from "@mui/material/Stack"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import TextInput from "../../../Components/Inputs/TextInput"; -import Search from "../../../Components/Inputs/Search"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import Search from "../../../../Components/Inputs/Search/index.jsx"; import Button from "@mui/material/Button"; -import RoleTable from "../components/RoleTable"; +import RoleTable from "../components/RoleTable/index.jsx"; // Utils import { useParams } from "react-router-dom"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import { useGetUser, useEditUser } from "../../../Hooks/userHooks"; -import { EDITABLE_ROLES, ROLES } from "../../../Utils/roleUtils"; -import { useEditUserForm, useValidateEditUserForm } from "./hooks/editUser"; +import { useGetUser, useEditUser } from "../../../../Hooks/v1/userHooks.js"; +import { EDITABLE_ROLES, ROLES } from "../../../../Utils/roleUtils.js"; +import { useEditUserForm, useValidateEditUserForm } from "./hooks/editUser.js"; const EditUser = () => { const { userId } = useParams(); diff --git a/client/src/Pages/Account/components/AddMemberMenu/index.jsx b/client/src/Pages/v1/Account/components/AddMemberMenu/index.jsx similarity index 100% rename from client/src/Pages/Account/components/AddMemberMenu/index.jsx rename to client/src/Pages/v1/Account/components/AddMemberMenu/index.jsx diff --git a/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx b/client/src/Pages/v1/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx similarity index 93% rename from client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx rename to client/src/Pages/v1/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx index e42c504b2..58db019a3 100644 --- a/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx +++ b/client/src/Pages/v1/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { newOrChangedCredentials } from "../../../../../Validation/validation"; +import { newOrChangedCredentials } from "../../../../../../Validation/validation.js"; import { useTranslation } from "react-i18next"; const useAddTeamMember = () => { const { t } = useTranslation(); diff --git a/client/src/Pages/Account/components/AddTeamMember/index.jsx b/client/src/Pages/v1/Account/components/AddTeamMember/index.jsx similarity index 91% rename from client/src/Pages/Account/components/AddTeamMember/index.jsx rename to client/src/Pages/v1/Account/components/AddTeamMember/index.jsx index 2df8fb226..e17ed433b 100644 --- a/client/src/Pages/Account/components/AddTeamMember/index.jsx +++ b/client/src/Pages/v1/Account/components/AddTeamMember/index.jsx @@ -1,16 +1,16 @@ import { Button, Stack } from "@mui/material"; -import { GenericDialog } from "../../../../Components/Dialog/genericDialog"; -import TextInput from "../../../../Components/Inputs/TextInput"; -import Select from "../../../../Components/Inputs/Select"; -import { useGetInviteToken } from "../../../../Hooks/inviteHooks"; +import { GenericDialog } from "../../../../../Components/Dialog/genericDialog.jsx"; +import TextInput from "../../../../../Components/Inputs/TextInput/index.jsx"; +import Select from "../../../../../Components/Inputs/Select/index.jsx"; +import { useGetInviteToken } from "../../../../../Hooks/v1/inviteHooks.js"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import { createToast } from "../../../../Utils/toastUtils"; +import { createToast } from "../../../../../Utils/toastUtils.jsx"; import { useState } from "react"; -import PasswordTooltip from "../../../Auth/components/PasswordTooltip"; -import useAddTeamMember from "./hooks/useAddTeamMember"; -import usePasswordFeedback from "../../../Auth/hooks/usePasswordFeedback"; -import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments"; +import PasswordTooltip from "../../../Auth/components/PasswordTooltip.jsx"; +import useAddTeamMember from "./hooks/useAddTeamMember.jsx"; +import usePasswordFeedback from "../../../Auth/hooks/usePasswordFeedback.jsx"; +import { PasswordEndAdornment } from "../../../../../Components/Inputs/TextInput/Adornments/index.jsx"; import PropTypes from "prop-types"; const INITIAL_FORM_STATE = { diff --git a/client/src/Pages/Account/components/PasswordPanel.jsx b/client/src/Pages/v1/Account/components/PasswordPanel.jsx similarity index 92% rename from client/src/Pages/Account/components/PasswordPanel.jsx rename to client/src/Pages/v1/Account/components/PasswordPanel.jsx index dabd67706..11961bf58 100644 --- a/client/src/Pages/Account/components/PasswordPanel.jsx +++ b/client/src/Pages/v1/Account/components/PasswordPanel.jsx @@ -2,14 +2,14 @@ import TabPanel from "@mui/lab/TabPanel"; import { useState } from "react"; import { useTheme } from "@emotion/react"; import { Box, Stack, Typography, Button } from "@mui/material"; -import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adornments"; -import TextInput from "../../../Components/Inputs/TextInput"; -import { newOrChangedCredentials } from "../../../Validation/validation"; -import Alert from "../../../Components/Alert"; -import { update } from "../../../Features/Auth/authSlice"; +import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments/index.jsx"; +import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import { newOrChangedCredentials } from "../../../../Validation/validation.js"; +import Alert from "../../../../Components/Alert/index.jsx"; +import { update } from "../../../../Features/Auth/authSlice.js"; import { useDispatch, useSelector } from "react-redux"; -import { createToast } from "../../../Utils/toastUtils"; -import { getTouchedFieldErrors } from "../../../Validation/error"; +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/Account/components/ProfilePanel.jsx b/client/src/Pages/v1/Account/components/ProfilePanel.jsx similarity index 94% rename from client/src/Pages/Account/components/ProfilePanel.jsx rename to client/src/Pages/v1/Account/components/ProfilePanel.jsx index 7c73e7aa3..8e607f4bd 100644 --- a/client/src/Pages/Account/components/ProfilePanel.jsx +++ b/client/src/Pages/v1/Account/components/ProfilePanel.jsx @@ -2,16 +2,16 @@ import { useTheme } from "@emotion/react"; import { useState } from "react"; import TabPanel from "@mui/lab/TabPanel"; import { Box, Button, Divider, Stack, Typography } from "@mui/material"; -import Avatar from "../../../Components/Avatar"; -import TextInput from "../../../Components/Inputs/TextInput"; -import ImageUpload from "../../../Components/Inputs/ImageUpload"; -import { newOrChangedCredentials } from "../../../Validation/validation"; +import Avatar from "../../../../Components/Avatar/index.jsx"; +import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import ImageUpload from "../../../../Components/Inputs/ImageUpload/index.jsx"; +import { newOrChangedCredentials } from "../../../../Validation/validation.js"; import { useDispatch, useSelector } from "react-redux"; -import { clearAuthState, deleteUser, update } from "../../../Features/Auth/authSlice"; -import { createToast } from "../../../Utils/toastUtils"; -import { logger } from "../../../Utils/Logger"; -import { GenericDialog } from "../../../Components/Dialog/genericDialog"; -import Dialog from "../../../Components/Dialog"; +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/Dialog/genericDialog.jsx"; +import Dialog from "../../../../Components/Dialog/index.jsx"; import { useTranslation } from "react-i18next"; /** diff --git a/client/src/Pages/Account/components/RoleTable/index.jsx b/client/src/Pages/v1/Account/components/RoleTable/index.jsx similarity index 87% rename from client/src/Pages/Account/components/RoleTable/index.jsx rename to client/src/Pages/v1/Account/components/RoleTable/index.jsx index 9d6ba3a16..c42be7bcd 100644 --- a/client/src/Pages/Account/components/RoleTable/index.jsx +++ b/client/src/Pages/v1/Account/components/RoleTable/index.jsx @@ -1,9 +1,9 @@ import Typography from "@mui/material/Typography"; -import DataTable from "../../../../Components/Table"; +import DataTable from "../../../../../Components/Table/index.jsx"; import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded"; import { useTranslation } from "react-i18next"; -import { ROLES } from "../../../../Utils/roleUtils"; +import { ROLES } from "../../../../../Utils/roleUtils.js"; const RoleTable = ({ roles, handleDeleteRole }) => { const { t } = useTranslation(); diff --git a/client/src/Pages/Account/components/TeamPanel.jsx b/client/src/Pages/v1/Account/components/TeamPanel.jsx similarity index 92% rename from client/src/Pages/Account/components/TeamPanel.jsx rename to client/src/Pages/v1/Account/components/TeamPanel.jsx index fc1768642..8964cd277 100644 --- a/client/src/Pages/Account/components/TeamPanel.jsx +++ b/client/src/Pages/v1/Account/components/TeamPanel.jsx @@ -3,18 +3,18 @@ import TabPanel from "@mui/lab/TabPanel"; import { Button, ButtonGroup, Stack, Typography } from "@mui/material"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import TextInput from "../../../Components/Inputs/TextInput"; -import { newOrChangedCredentials } from "../../../Validation/validation"; -import { networkService } from "../../../main"; -import { createToast } from "../../../Utils/toastUtils"; -import Select from "../../../Components/Inputs/Select"; -import { GenericDialog } from "../../../Components/Dialog/genericDialog"; -import AddTeamMember from "../components/AddTeamMember"; -import DataTable from "../../../Components/Table"; -import { useGetInviteToken } from "../../../Hooks/inviteHooks"; +import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import { newOrChangedCredentials } from "../../../../Validation/validation.js"; +import { networkService } from "../../../../main.jsx"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; +import Select from "../../../../Components/Inputs/Select/index.jsx"; +import { GenericDialog } from "../../../../Components/Dialog/genericDialog.jsx"; +import AddTeamMember from "./AddTeamMember/index.jsx"; +import DataTable from "../../../../Components/Table/index.jsx"; +import { useGetInviteToken } from "../../../../Hooks/v1/inviteHooks.js"; import { useNavigate } from "react-router-dom"; -import { useIsSuperAdmin } from "../../../Hooks/useIsAdmin"; -import AddMemberMenu from "./AddMemberMenu"; +import { useIsSuperAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; +import AddMemberMenu from "./AddMemberMenu/index.jsx"; /** * TeamPanel component manages the organization and team members, * providing functionalities like renaming the organization, managing team members, diff --git a/client/src/Pages/Account/index.jsx b/client/src/Pages/v1/Account/index.jsx similarity index 91% rename from client/src/Pages/Account/index.jsx rename to client/src/Pages/v1/Account/index.jsx index 37c64aca6..a496858b7 100644 --- a/client/src/Pages/Account/index.jsx +++ b/client/src/Pages/v1/Account/index.jsx @@ -3,11 +3,11 @@ import PropTypes from "prop-types"; import { useNavigate } from "react-router-dom"; import { useSelector } from "react-redux"; import { Box, Tab, useTheme } from "@mui/material"; -import CustomTabList from "../../Components/Tab"; +import CustomTabList from "../../../Components/Tab/index.jsx"; import TabContext from "@mui/lab/TabContext"; -import ProfilePanel from "./components/ProfilePanel"; -import PasswordPanel from "./components/PasswordPanel"; -import TeamPanel from "./components/TeamPanel"; +import ProfilePanel from "./components/ProfilePanel.jsx"; +import PasswordPanel from "./components/PasswordPanel.jsx"; +import TeamPanel from "./components/TeamPanel.jsx"; import { useTranslation } from "react-i18next"; /** diff --git a/client/src/Pages/Auth/CheckEmail.jsx b/client/src/Pages/v1/Auth/CheckEmail.jsx similarity index 93% rename from client/src/Pages/Auth/CheckEmail.jsx rename to client/src/Pages/v1/Auth/CheckEmail.jsx index e1f5e43ed..95217bcb5 100644 --- a/client/src/Pages/Auth/CheckEmail.jsx +++ b/client/src/Pages/v1/Auth/CheckEmail.jsx @@ -3,13 +3,13 @@ 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"; -import { forgotPassword } from "../../Features/Auth/authSlice"; +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 IconBox from "../../Components/IconBox"; +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/IconBox/index.jsx"; import "./index.css"; const CheckEmail = () => { diff --git a/client/src/Pages/Auth/ForgotPassword.jsx b/client/src/Pages/v1/Auth/ForgotPassword.jsx similarity index 91% rename from client/src/Pages/Auth/ForgotPassword.jsx rename to client/src/Pages/v1/Auth/ForgotPassword.jsx index 2a887756e..9836f5d77 100644 --- a/client/src/Pages/Auth/ForgotPassword.jsx +++ b/client/src/Pages/v1/Auth/ForgotPassword.jsx @@ -1,16 +1,16 @@ import { Box, Stack, Typography, Button } from "@mui/material"; import { useTheme } from "@emotion/react"; -import { createToast } from "../../Utils/toastUtils"; +import { createToast } from "../../../Utils/toastUtils.jsx"; import { useDispatch, useSelector } from "react-redux"; -import { forgotPassword } from "../../Features/Auth/authSlice"; +import { forgotPassword } from "../../../Features/Auth/authSlice.js"; import { useEffect, useState } from "react"; -import { newOrChangedCredentials } from "../../Validation/validation"; +import { newOrChangedCredentials } from "../../../Validation/validation.js"; import { useNavigate } from "react-router-dom"; -import TextInput from "../../Components/Inputs/TextInput"; -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/IconBox"; +import TextInput from "../../../Components/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 IconBox from "../../../Components/IconBox/index.jsx"; import { Trans, useTranslation } from "react-i18next"; import "./index.css"; diff --git a/client/src/Pages/Auth/Login/hooks/useLoadingSubmit.jsx b/client/src/Pages/v1/Auth/Login/hooks/useLoadingSubmit.jsx similarity index 100% rename from client/src/Pages/Auth/Login/hooks/useLoadingSubmit.jsx rename to client/src/Pages/v1/Auth/Login/hooks/useLoadingSubmit.jsx diff --git a/client/src/Pages/Auth/Login/hooks/useLoginForm.jsx b/client/src/Pages/v1/Auth/Login/hooks/useLoginForm.jsx similarity index 100% rename from client/src/Pages/Auth/Login/hooks/useLoginForm.jsx rename to client/src/Pages/v1/Auth/Login/hooks/useLoginForm.jsx diff --git a/client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx b/client/src/Pages/v1/Auth/Login/hooks/useLoginSubmit.jsx similarity index 87% rename from client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx rename to client/src/Pages/v1/Auth/Login/hooks/useLoginSubmit.jsx index 6bc5716ff..0eed9bd07 100644 --- a/client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx +++ b/client/src/Pages/v1/Auth/Login/hooks/useLoginSubmit.jsx @@ -1,7 +1,7 @@ import { useDispatch } from "react-redux"; -import { login } from "../../../../Features/Auth/authSlice"; +import { login } from "../../../../../Features/Auth/authSlice.js"; import { useNavigate } from "react-router-dom"; -import { createToast } from "../../../../Utils/toastUtils"; +import { createToast } from "../../../../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; const useLoginSubmit = () => { diff --git a/client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx b/client/src/Pages/v1/Auth/Login/hooks/useValidateLoginForm.jsx similarity index 90% rename from client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx rename to client/src/Pages/v1/Auth/Login/hooks/useValidateLoginForm.jsx index e5d045be8..ca9bef879 100644 --- a/client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx +++ b/client/src/Pages/v1/Auth/Login/hooks/useValidateLoginForm.jsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { loginCredentials } from "../../../../Validation/validation"; +import { loginCredentials } from "../../../../../Validation/validation.js"; const useValidateLoginForm = () => { const [errors, setErrors] = useState({ diff --git a/client/src/Pages/Auth/Login/index.jsx b/client/src/Pages/v1/Auth/Login/index.jsx similarity index 82% rename from client/src/Pages/Auth/Login/index.jsx rename to client/src/Pages/v1/Auth/Login/index.jsx index d06ecef6d..901edcd8b 100644 --- a/client/src/Pages/Auth/Login/index.jsx +++ b/client/src/Pages/v1/Auth/Login/index.jsx @@ -1,17 +1,17 @@ // Components import Stack from "@mui/material/Stack"; import Button from "@mui/material/Button"; -import TextInput from "../../../Components/Inputs/TextInput"; -import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adornments"; -import TextLink from "../../../Components/TextLink"; -import AuthPageWrapper from "../components/AuthPageWrapper"; +import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments/index.jsx"; +import TextLink from "../../../../Components/TextLink/index.jsx"; +import AuthPageWrapper from "../components/AuthPageWrapper.jsx"; // Utils import { useTheme } from "@mui/material/styles"; import { useTranslation } from "react-i18next"; -import useLoginForm from "./hooks/useLoginForm"; -import useValidateLoginForm from "./hooks/useValidateLoginForm"; -import useLoginSubmit from "./hooks/useLoginSubmit"; -import useLoadingSubmit from "./hooks/useLoadingSubmit"; +import useLoginForm from "./hooks/useLoginForm.jsx"; +import useValidateLoginForm from "./hooks/useValidateLoginForm.jsx"; +import useLoginSubmit from "./hooks/useLoginSubmit.jsx"; +import useLoadingSubmit from "./hooks/useLoadingSubmit.jsx"; const Login = () => { // Hooks diff --git a/client/src/Pages/Auth/NewPasswordConfirmed.jsx b/client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx similarity index 90% rename from client/src/Pages/Auth/NewPasswordConfirmed.jsx rename to client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx index 7ace77abc..2bede1ae4 100644 --- a/client/src/Pages/Auth/NewPasswordConfirmed.jsx +++ b/client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx @@ -2,11 +2,11 @@ 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"; -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/IconBox"; +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/IconBox/index.jsx"; import { Trans, useTranslation } from "react-i18next"; import "./index.css"; diff --git a/client/src/Pages/Auth/Register/index.jsx b/client/src/Pages/v1/Auth/Register/index.jsx similarity index 94% rename from client/src/Pages/Auth/Register/index.jsx rename to client/src/Pages/v1/Auth/Register/index.jsx index 2b45d2d15..a903602d4 100644 --- a/client/src/Pages/Auth/Register/index.jsx +++ b/client/src/Pages/v1/Auth/Register/index.jsx @@ -1,10 +1,10 @@ // Components import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import TextInput from "../../../Components/Inputs/TextInput"; +import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; import Button from "@mui/material/Button"; import Box from "@mui/material/Box"; -import PasswordTooltip from "../components/PasswordTooltip"; +import PasswordTooltip from "../components/PasswordTooltip.jsx"; // Utils import { useTheme } from "@emotion/react"; @@ -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"; -import { newOrChangedCredentials } from "../../../Validation/validation"; -import { register } from "../../../Features/Auth/authSlice"; -import AuthPageWrapper from "../components/AuthPageWrapper"; -import { createToast } from "../../../Utils/toastUtils"; +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 PropTypes from "prop-types"; const getFeedbackStatus = (form, errors, field, criteria) => { diff --git a/client/src/Pages/Auth/SetNewPassword.jsx b/client/src/Pages/v1/Auth/SetNewPassword.jsx similarity index 91% rename from client/src/Pages/Auth/SetNewPassword.jsx rename to client/src/Pages/v1/Auth/SetNewPassword.jsx index b4899f47c..e9df89d88 100644 --- a/client/src/Pages/Auth/SetNewPassword.jsx +++ b/client/src/Pages/v1/Auth/SetNewPassword.jsx @@ -4,18 +4,18 @@ 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"; -import { createToast } from "../../Utils/toastUtils"; -import { newOrChangedCredentials } from "../../Validation/validation"; -import Check from "../../Components/Check/Check"; -import TextInput from "../../Components/Inputs/TextInput"; -import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments"; -import IconBox from "../../Components/IconBox"; -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 { setNewPassword } from "../../../Features/Auth/authSlice.js"; +import { createToast } from "../../../Utils/toastUtils.jsx"; +import { newOrChangedCredentials } from "../../../Validation/validation.js"; +import Check from "../../../Components/Check/Check.jsx"; +import TextInput from "../../../Components/Inputs/TextInput/index.jsx"; +import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adornments/index.jsx"; +import IconBox from "../../../Components/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 "./index.css"; -import { useValidatePassword } from "./hooks/useValidatePassword"; +import { useValidatePassword } from "./hooks/useValidatePassword.jsx"; import { Trans, useTranslation } from "react-i18next"; const SetNewPassword = () => { diff --git a/client/src/Pages/Auth/components/AuthHeader.jsx b/client/src/Pages/v1/Auth/components/AuthHeader.jsx similarity index 81% rename from client/src/Pages/Auth/components/AuthHeader.jsx rename to client/src/Pages/v1/Auth/components/AuthHeader.jsx index ce454c065..067c47d92 100644 --- a/client/src/Pages/Auth/components/AuthHeader.jsx +++ b/client/src/Pages/v1/Auth/components/AuthHeader.jsx @@ -1,9 +1,9 @@ // 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"; -import ThemeSwitch from "../../../Components/ThemeSwitch"; +import Logo from "../../../../assets/icons/checkmate-icon.svg?react"; +import LanguageSelector from "../../../../Components/LanguageSelector.jsx"; +import ThemeSwitch from "../../../../Components/ThemeSwitch/index.jsx"; // Utils import { useTheme } from "@mui/material/styles"; diff --git a/client/src/Pages/Auth/components/AuthPageWrapper.jsx b/client/src/Pages/v1/Auth/components/AuthPageWrapper.jsx similarity index 92% rename from client/src/Pages/Auth/components/AuthPageWrapper.jsx rename to client/src/Pages/v1/Auth/components/AuthPageWrapper.jsx index b83be19c5..7e358e6ab 100644 --- a/client/src/Pages/Auth/components/AuthPageWrapper.jsx +++ b/client/src/Pages/v1/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 "../components/AuthHeader"; +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/Auth/components/PasswordTooltip.jsx b/client/src/Pages/v1/Auth/components/PasswordTooltip.jsx similarity index 98% rename from client/src/Pages/Auth/components/PasswordTooltip.jsx rename to client/src/Pages/v1/Auth/components/PasswordTooltip.jsx index bc2f28ec7..ff3c2f385 100644 --- a/client/src/Pages/Auth/components/PasswordTooltip.jsx +++ b/client/src/Pages/v1/Auth/components/PasswordTooltip.jsx @@ -1,4 +1,4 @@ -import Check from "../../../Components/Check/Check"; +import Check from "../../../../Components/Check/Check.jsx"; import Stack from "@mui/material/Stack"; import { Tooltip, useTheme } from "@mui/material"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/Auth/hooks/usePasswordFeedback.jsx b/client/src/Pages/v1/Auth/hooks/usePasswordFeedback.jsx similarity index 96% rename from client/src/Pages/Auth/hooks/usePasswordFeedback.jsx rename to client/src/Pages/v1/Auth/hooks/usePasswordFeedback.jsx index 4159e6166..3c199e25f 100644 --- a/client/src/Pages/Auth/hooks/usePasswordFeedback.jsx +++ b/client/src/Pages/v1/Auth/hooks/usePasswordFeedback.jsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { newOrChangedCredentials } from "../../../Validation/validation"; +import { newOrChangedCredentials } from "../../../../Validation/validation.js"; const usePasswordFeedback = () => { const [feedback, setFeedback] = useState({}); diff --git a/client/src/Pages/Auth/hooks/useValidatePassword.jsx b/client/src/Pages/v1/Auth/hooks/useValidatePassword.jsx similarity index 96% rename from client/src/Pages/Auth/hooks/useValidatePassword.jsx rename to client/src/Pages/v1/Auth/hooks/useValidatePassword.jsx index 102d8d73e..231b8eeb8 100644 --- a/client/src/Pages/Auth/hooks/useValidatePassword.jsx +++ b/client/src/Pages/v1/Auth/hooks/useValidatePassword.jsx @@ -1,5 +1,5 @@ import { useMemo, useState } from "react"; -import { newOrChangedCredentials } from "../../../Validation/validation"; +import { newOrChangedCredentials } from "../../../../Validation/validation.js"; const getFeedbackStatus = (form, errors, field, criteria) => { const fieldErrors = errors[field]; diff --git a/client/src/Pages/Auth/index.css b/client/src/Pages/v1/Auth/index.css similarity index 100% rename from client/src/Pages/Auth/index.css rename to client/src/Pages/v1/Auth/index.css diff --git a/client/src/Pages/Incidents/Components/IncidentTable/index.jsx b/client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx similarity index 88% rename from client/src/Pages/Incidents/Components/IncidentTable/index.jsx rename to client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx index df9174290..e204d7167 100644 --- a/client/src/Pages/Incidents/Components/IncidentTable/index.jsx +++ b/client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx @@ -1,26 +1,26 @@ //Components import Stack from "@mui/material/Stack"; -import DataTable from "../../../../Components/Table"; +import DataTable from "../../../../../Components/Table/index.jsx"; import Table from "@mui/material/Table"; import TableBody from "@mui/material/TableBody"; import TableRow from "@mui/material/TableRow"; import TableCell from "@mui/material/TableCell"; -import TableSkeleton from "../../../../Components/Table/skeleton"; -import Pagination from "../../../../Components/Table/TablePagination"; -import { StatusLabel } from "../../../../Components/Label"; -import { HttpStatusLabel } from "../../../../Components/HttpStatusLabel"; -import GenericFallback from "../../../../Components/GenericFallback"; -import NetworkError from "../../../../Components/GenericFallback/NetworkError"; +import TableSkeleton from "../../../../../Components/Table/skeleton.jsx"; +import Pagination from "../../../../../Components/Table/TablePagination/index.jsx"; +import { StatusLabel } from "../../../../../Components/Label/index.jsx"; +import { HttpStatusLabel } from "../../../../../Components/HttpStatusLabel/index.jsx"; +import GenericFallback from "../../../../../Components/GenericFallback/index.jsx"; +import NetworkError from "../../../../../Components/GenericFallback/NetworkError.jsx"; //Utils -import { formatDateWithTz } from "../../../../Utils/timeUtils"; +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/checkHooks"; -import { useFetchChecksByMonitor } from "../../../../Hooks/checkHooks"; -import { useResolveIncident } from "../../../../Hooks/checkHooks"; +import { useFetchChecksTeam } from "../../../../../Hooks/v1/checkHooks.js"; +import { useFetchChecksByMonitor } from "../../../../../Hooks/v1/checkHooks.js"; +import { useResolveIncident } from "../../../../../Hooks/v1/checkHooks.js"; import { Button, Typography, useTheme } from "@mui/material"; import { lighten } from "@mui/material/styles"; diff --git a/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx b/client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx similarity index 96% rename from client/src/Pages/Incidents/Components/OptionsHeader/index.jsx rename to client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx index 259880223..b1bde1175 100644 --- a/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx +++ b/client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx @@ -1,11 +1,11 @@ // Components import { Stack, Typography, Button, ButtonGroup } from "@mui/material"; -import Select from "../../../../Components/Inputs/Select"; +import Select from "../../../../../Components/Inputs/Select/index.jsx"; import PropTypes from "prop-types"; //Utils import { useTheme } from "@emotion/react"; -import SkeletonLayout from "./skeleton"; +import SkeletonLayout from "./skeleton.jsx"; import { useTranslation } from "react-i18next"; const OptionsHeader = ({ diff --git a/client/src/Pages/Incidents/Components/OptionsHeader/skeleton.jsx b/client/src/Pages/v1/Incidents/Components/OptionsHeader/skeleton.jsx similarity index 100% rename from client/src/Pages/Incidents/Components/OptionsHeader/skeleton.jsx rename to client/src/Pages/v1/Incidents/Components/OptionsHeader/skeleton.jsx diff --git a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx b/client/src/Pages/v1/Incidents/Components/StatusBoxes/StatusBox.jsx similarity index 87% rename from client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx rename to client/src/Pages/v1/Incidents/Components/StatusBoxes/StatusBox.jsx index 7b20e3d51..cd7078e6c 100644 --- a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx +++ b/client/src/Pages/v1/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/Incidents/Components/StatusBoxes/index.jsx b/client/src/Pages/v1/Incidents/Components/StatusBoxes/index.jsx similarity index 92% rename from client/src/Pages/Incidents/Components/StatusBoxes/index.jsx rename to client/src/Pages/v1/Incidents/Components/StatusBoxes/index.jsx index 6ba2f1820..3bcd3fd3a 100644 --- a/client/src/Pages/Incidents/Components/StatusBoxes/index.jsx +++ b/client/src/Pages/v1/Incidents/Components/StatusBoxes/index.jsx @@ -1,9 +1,9 @@ import PropTypes from "prop-types"; import { Stack } from "@mui/material"; -import StatusBox from "./StatusBox"; +import StatusBox from "./StatusBox.jsx"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import SkeletonLayout from "./skeleton"; +import SkeletonLayout from "./skeleton.jsx"; const StatusBoxes = ({ isLoading, summary }) => { const theme = useTheme(); diff --git a/client/src/Pages/Incidents/Components/StatusBoxes/skeleton.jsx b/client/src/Pages/v1/Incidents/Components/StatusBoxes/skeleton.jsx similarity index 100% rename from client/src/Pages/Incidents/Components/StatusBoxes/skeleton.jsx rename to client/src/Pages/v1/Incidents/Components/StatusBoxes/skeleton.jsx diff --git a/client/src/Pages/Incidents/index.jsx b/client/src/Pages/v1/Incidents/index.jsx similarity index 81% rename from client/src/Pages/Incidents/index.jsx rename to client/src/Pages/v1/Incidents/index.jsx index 7696c453b..b9281f97c 100644 --- a/client/src/Pages/Incidents/index.jsx +++ b/client/src/Pages/v1/Incidents/index.jsx @@ -1,19 +1,19 @@ // Components import { Stack } from "@mui/material"; -import Breadcrumbs from "../../Components/Breadcrumbs"; -import GenericFallback from "../../Components/GenericFallback"; -import IncidentTable from "./Components/IncidentTable"; -import OptionsHeader from "./Components/OptionsHeader"; -import StatusBoxes from "./Components/StatusBoxes"; +import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx"; +import GenericFallback from "../../../Components/GenericFallback/index.jsx"; +import IncidentTable from "./Components/IncidentTable/index.jsx"; +import OptionsHeader from "./Components/OptionsHeader/index.jsx"; +import StatusBoxes from "./Components/StatusBoxes/index.jsx"; import { Box, Button } from "@mui/material"; //Utils import { useTheme } from "@emotion/react"; -import { useFetchMonitorsByTeamId } from "../../Hooks/monitorHooks"; -import { useFetchChecksSummaryByTeamId } from "../../Hooks/checkHooks"; -import { useAcknowledgeChecks } from "../../Hooks/checkHooks"; +import { useFetchMonitorsByTeamId } from "../../../Hooks/v1/monitorHooks.js"; +import { useFetchChecksSummaryByTeamId } from "../../../Hooks/v1/checkHooks.js"; +import { useAcknowledgeChecks } from "../../../Hooks/v1/checkHooks.js"; import { useState, useEffect } from "react"; -import NetworkError from "../../Components/GenericFallback/NetworkError"; +import NetworkError from "../../../Components/GenericFallback/NetworkError.jsx"; import { useTranslation } from "react-i18next"; import { useParams } from "react-router-dom"; diff --git a/client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx b/client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx similarity index 91% rename from client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx rename to client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx index 5a6f5e981..299331a79 100644 --- a/client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx @@ -1,7 +1,7 @@ -import ConfigBox from "../../../../Components/ConfigBox"; +import ConfigBox from "../../../../../Components/ConfigBox/index.jsx"; import { Box, Stack, Typography } from "@mui/material"; -import { CustomThreshold } from "../Components/CustomThreshold"; -import { capitalizeFirstLetter } from "../../../../Utils/stringUtils"; +import { CustomThreshold } from "./CustomThreshold/index.jsx"; +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/Infrastructure/Create/Components/CustomThreshold/index.jsx b/client/src/Pages/v1/Infrastructure/Create/Components/CustomThreshold/index.jsx similarity index 95% rename from client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx rename to client/src/Pages/v1/Infrastructure/Create/Components/CustomThreshold/index.jsx index 865698f44..4edc0b766 100644 --- a/client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/Components/CustomThreshold/index.jsx @@ -1,6 +1,6 @@ import { Box, Stack, Typography } from "@mui/material"; -import TextInput from "../../../../../Components/Inputs/TextInput"; -import Checkbox from "../../../../../Components/Inputs/Checkbox"; +import TextInput from "../../../../../../Components/Inputs/TextInput/index.jsx"; +import Checkbox from "../../../../../../Components/Inputs/Checkbox/index.jsx"; import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; diff --git a/client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx b/client/src/Pages/v1/Infrastructure/Create/Components/MonitorActionButtons.jsx similarity index 96% rename from client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx rename to client/src/Pages/v1/Infrastructure/Create/Components/MonitorActionButtons.jsx index becda3e3a..16df8ad01 100644 --- a/client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/Components/MonitorActionButtons.jsx @@ -4,7 +4,7 @@ import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline"; import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded"; -import Dialog from "../../../../Components/Dialog"; +import Dialog from "../../../../../Components/Dialog/index.jsx"; import PropTypes from "prop-types"; const MonitorActionButtons = ({ monitor, isBusy, handlePause, handleRemove }) => { diff --git a/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx b/client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx similarity index 91% rename from client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx rename to client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx index 2ed48bf64..4313c34c0 100644 --- a/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx @@ -1,8 +1,8 @@ import { Box, Stack, Tooltip, Typography } from "@mui/material"; -import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils"; +import { useMonitorUtils } from "../../../../../Hooks/v1/useMonitorUtils.js"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import PulseDot from "../../../../Components/Animated/PulseDot"; +import PulseDot from "../../../../../Components/Animated/PulseDot.jsx"; import PropTypes from "prop-types"; const MonitorStatusHeader = ({ monitor, infrastructureMonitor }) => { const theme = useTheme(); diff --git a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx b/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx similarity index 100% rename from client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx rename to client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx diff --git a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx b/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx similarity index 98% rename from client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx rename to client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx index 9b3ee0d04..0fd5c0081 100644 --- a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx @@ -1,4 +1,4 @@ -import { useCreateMonitor, useUpdateMonitor } from "../../../../Hooks/monitorHooks"; +import { useCreateMonitor, useUpdateMonitor } from "../../../../../Hooks/v1/monitorHooks.js"; const useInfrastructureSubmit = () => { const [createMonitor, isCreating] = useCreateMonitor(); const [updateMonitor, isUpdating] = useUpdateMonitor(); diff --git a/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx b/client/src/Pages/v1/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx similarity index 85% rename from client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx rename to client/src/Pages/v1/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx index 080f441c4..73097d0d8 100644 --- a/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx @@ -1,6 +1,6 @@ import { useState } from "react"; -import { infrastructureMonitorValidation } from "../../../../Validation/validation"; -import { createToast } from "../../../../Utils/toastUtils"; +import { infrastructureMonitorValidation } from "../../../../../Validation/validation.js"; +import { createToast } from "../../../../../Utils/toastUtils.jsx"; const useValidateInfrastructureForm = () => { const [errors, setErrors] = useState({}); diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/v1/Infrastructure/Create/index.jsx similarity index 92% rename from client/src/Pages/Infrastructure/Create/index.jsx rename to client/src/Pages/v1/Infrastructure/Create/index.jsx index a9d16c926..06ecdff6c 100644 --- a/client/src/Pages/Infrastructure/Create/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/index.jsx @@ -1,18 +1,18 @@ //Components -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import ConfigBox from "../../../Components/ConfigBox"; -import FieldWrapper from "../../../Components/Inputs/FieldWrapper"; -import Link from "../../../Components/Link"; -import Select from "../../../Components/Inputs/Select"; -import TextInput from "../../../Components/Inputs/TextInput"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import ConfigBox from "../../../../Components/ConfigBox/index.jsx"; +import FieldWrapper from "../../../../Components/Inputs/FieldWrapper/index.jsx"; +import Link from "../../../../Components/Link/index.jsx"; +import Select from "../../../../Components/Inputs/Select/index.jsx"; +import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material"; -import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments"; -import MonitorStatusHeader from "./Components/MonitorStatusHeader"; -import MonitorActionButtons from "./Components/MonitorActionButtons"; -import CustomAlertsSection from "./Components/CustomAlertsSection"; +import { HttpAdornment } from "../../../../Components/Inputs/TextInput/Adornments/index.jsx"; +import MonitorStatusHeader from "./Components/MonitorStatusHeader.jsx"; +import MonitorActionButtons from "./Components/MonitorActionButtons.jsx"; +import CustomAlertsSection from "./Components/CustomAlertsSection.jsx"; // Utils -import NotificationsConfig from "../../../Components/NotificationConfig"; -import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications"; +import NotificationsConfig from "../../../../Components/NotificationConfig/index.jsx"; +import { useGetNotificationsByTeamId } from "../../../../Hooks/v1/useNotifications.js"; import { useParams } from "react-router-dom"; import { useState, useEffect } from "react"; import { useTheme } from "@emotion/react"; @@ -22,10 +22,10 @@ import { useFetchGlobalSettings, useFetchHardwareMonitorById, usePauseMonitor, -} from "../../../Hooks/monitorHooks"; -import useInfrastructureMonitorForm from "./hooks/useInfrastructureMonitorForm"; -import useValidateInfrastructureForm from "./hooks/useValidateInfrastructureForm"; -import useInfrastructureSubmit from "./hooks/useInfrastructureSubmit"; +} from "../../../../Hooks/v1/monitorHooks.js"; +import useInfrastructureMonitorForm from "./hooks/useInfrastructureMonitorForm.jsx"; +import useValidateInfrastructureForm from "./hooks/useValidateInfrastructureForm.jsx"; +import useInfrastructureSubmit from "./hooks/useInfrastructureSubmit.jsx"; const CreateInfrastructureMonitor = () => { const { monitorId } = useParams(); diff --git a/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx similarity index 79% rename from client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx index 9544bcf2a..47f33a5e6 100644 --- a/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx @@ -1,10 +1,10 @@ // Components import { Typography } from "@mui/material"; -import BaseContainer from "../BaseContainer"; -import AreaChart from "../../../../../Components/Charts/AreaChart"; +import BaseContainer from "../BaseContainer/index.jsx"; +import AreaChart from "../../../../../../Components/Charts/AreaChart/index.jsx"; // Utils import { useTheme } from "@emotion/react"; -import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; const InfraAreaChart = ({ config }) => { const theme = useTheme(); const { getDimensions } = useHardwareUtils(); diff --git a/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/index.jsx similarity index 93% rename from client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/index.jsx index 0eb3aa36d..3dfb4edd8 100644 --- a/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/index.jsx @@ -1,7 +1,7 @@ // Components import { Stack } from "@mui/material"; -import InfraAreaChart from "./InfraAreaChart"; -import SkeletonLayout from "./skeleton"; +import InfraAreaChart from "./InfraAreaChart.jsx"; +import SkeletonLayout from "./skeleton.jsx"; // Utils import { @@ -9,9 +9,9 @@ import { TzTick, InfrastructureTooltip, TemperatureTooltip, -} from "../../../../../Components/Charts/Utils/chartUtils"; +} from "../../../../../../Components/Charts/Utils/chartUtils.jsx"; import { useTheme } from "@emotion/react"; -import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; import { useTranslation } from "react-i18next"; const AreaChartBoxes = ({ shouldRender, monitor, dateRange }) => { const theme = useTheme(); diff --git a/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx similarity index 100% rename from client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx diff --git a/client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/BaseContainer/index.jsx similarity index 94% rename from client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/BaseContainer/index.jsx index 246769751..dd73d1a65 100644 --- a/client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/BaseContainer/index.jsx @@ -11,7 +11,7 @@ import { Box } from "@mui/material"; // Utils import { useTheme } from "@emotion/react"; -import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; import PropTypes from "prop-types"; const BaseContainer = ({ children, sx = {} }) => { diff --git a/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx similarity index 93% rename from client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx index 70d36bd00..5c55119ad 100644 --- a/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx @@ -1,6 +1,6 @@ // Components -import CustomGauge from "../../../../../Components/Charts/CustomGauge"; -import BaseContainer from "../BaseContainer"; +import CustomGauge from "../../../../../../Components/Charts/CustomGauge/index.jsx"; +import BaseContainer from "../BaseContainer/index.jsx"; import { Stack, Typography, Box } from "@mui/material"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx similarity index 93% rename from client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx index 3c1c68f8b..432881e7e 100644 --- a/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx @@ -1,11 +1,11 @@ // Components import { Stack } from "@mui/material"; -import Gauge from "./Gauge"; -import SkeletonLayout from "./skeleton"; +import Gauge from "./Gauge.jsx"; +import SkeletonLayout from "./skeleton.jsx"; import PropTypes from "prop-types"; // Utils -import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx similarity index 100% rename from client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx similarity index 93% rename from client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx index 9157a3432..5cfdba350 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx @@ -1,15 +1,15 @@ import PropTypes from "prop-types"; import { Stack, Typography } from "@mui/material"; -import InfraAreaChart from "../../../../../Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart"; +import InfraAreaChart from "../AreaChartBoxes/InfraAreaChart.jsx"; import { TzTick, InfrastructureTooltip, NetworkTick, -} from "../../../../../Components/Charts/Utils/chartUtils"; +} from "../../../../../../Components/Charts/Utils/chartUtils.jsx"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; const NetworkCharts = ({ ethernetData, dateRange }) => { const theme = useTheme(); diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx similarity index 90% rename from client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx index 98ba90dae..52ccf0805 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx @@ -1,9 +1,9 @@ import PropTypes from "prop-types"; -import StatusBoxes from "../../../../../Components/StatusBoxes"; -import StatBox from "../../../../../Components/StatBox"; +import StatusBoxes from "../../../../../../Components/StatusBoxes/index.jsx"; +import StatBox from "../../../../../../Components/StatBox/index.jsx"; import { Typography } from "@mui/material"; import { useTranslation } from "react-i18next"; -import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; function formatNumber(num) { return num != null ? num.toLocaleString() : "0"; diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/index.jsx similarity index 90% rename from client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/index.jsx index 41d2b0a87..e0d6986e1 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/index.jsx @@ -3,10 +3,10 @@ import { useState, useEffect } from "react"; import { Box } from "@mui/material"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; -import Select from "../../../../../Components/Inputs/Select"; -import NetworkStatBoxes from "./NetworkStatBoxes"; -import NetworkCharts from "./NetworkCharts"; -import MonitorTimeFrameHeader from "../../../../../Components/MonitorTimeFrameHeader"; +import Select from "../../../../../../Components/Inputs/Select/index.jsx"; +import NetworkStatBoxes from "./NetworkStatBoxes.jsx"; +import NetworkCharts from "./NetworkCharts.jsx"; +import MonitorTimeFrameHeader from "../../../../../../Components/MonitorTimeFrameHeader/index.jsx"; const getAvailableInterfaces = (net) => { return (net || []).map((iface) => iface.name).filter(Boolean); diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/skeleton.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/skeleton.jsx similarity index 100% rename from client/src/Pages/Infrastructure/Details/Components/NetworkStats/skeleton.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/skeleton.jsx diff --git a/client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx similarity index 90% rename from client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx index 6c11e7c82..47862449e 100644 --- a/client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx @@ -1,11 +1,11 @@ // Components import { Stack, Typography } from "@mui/material"; -import StatusBoxes from "../../../../../Components/StatusBoxes"; -import StatBox from "../../../../../Components/StatBox"; +import StatusBoxes from "../../../../../../Components/StatusBoxes/index.jsx"; +import StatBox from "../../../../../../Components/StatBox/index.jsx"; //Utils -import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils"; -import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; +import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; import { useTranslation } from "react-i18next"; const InfraStatBoxes = ({ shouldRender, monitor }) => { diff --git a/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx b/client/src/Pages/v1/Infrastructure/Details/Hooks/useHardwareUtils.jsx similarity index 100% rename from client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx rename to client/src/Pages/v1/Infrastructure/Details/Hooks/useHardwareUtils.jsx diff --git a/client/src/Pages/Infrastructure/Details/index.jsx b/client/src/Pages/v1/Infrastructure/Details/index.jsx similarity index 79% rename from client/src/Pages/Infrastructure/Details/index.jsx rename to client/src/Pages/v1/Infrastructure/Details/index.jsx index a16f3906b..04691d985 100644 --- a/client/src/Pages/Infrastructure/Details/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/index.jsx @@ -1,20 +1,20 @@ // Components import { Stack, Typography, Tab } from "@mui/material"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import MonitorDetailsControlHeader from "../../../Components/MonitorDetailsControlHeader"; -import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader"; -import StatusBoxes from "./Components/StatusBoxes"; -import GaugeBoxes from "./Components/GaugeBoxes"; -import AreaChartBoxes from "./Components/AreaChartBoxes"; -import GenericFallback from "../../../Components/GenericFallback"; -import NetworkStats from "./Components/NetworkStats"; -import CustomTabList from "../../../Components/Tab"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import MonitorDetailsControlHeader from "../../../../Components/MonitorDetailsControlHeader/index.jsx"; +import MonitorTimeFrameHeader from "../../../../Components/MonitorTimeFrameHeader/index.jsx"; +import StatusBoxes from "./Components/StatusBoxes/index.jsx"; +import GaugeBoxes from "./Components/GaugeBoxes/index.jsx"; +import AreaChartBoxes from "./Components/AreaChartBoxes/index.jsx"; +import GenericFallback from "../../../../Components/GenericFallback/index.jsx"; +import NetworkStats from "./Components/NetworkStats/index.jsx"; +import CustomTabList from "../../../../Components/Tab/index.jsx"; import TabContext from "@mui/lab/TabContext"; // Utils import { useTheme } from "@emotion/react"; -import { useIsAdmin } from "../../../Hooks/useIsAdmin"; -import { useFetchHardwareMonitorById } from "../../../Hooks/monitorHooks"; +import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; +import { useFetchHardwareMonitorById } from "../../../../Hooks/v1/monitorHooks.js"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { useParams } from "react-router-dom"; diff --git a/client/src/Pages/Infrastructure/Monitors/Components/Filters/index.jsx b/client/src/Pages/v1/Infrastructure/Monitors/Components/Filters/index.jsx similarity index 96% rename from client/src/Pages/Infrastructure/Monitors/Components/Filters/index.jsx rename to client/src/Pages/v1/Infrastructure/Monitors/Components/Filters/index.jsx index 3c2511c2f..bef632e88 100644 --- a/client/src/Pages/Infrastructure/Monitors/Components/Filters/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Monitors/Components/Filters/index.jsx @@ -1,6 +1,6 @@ import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; -import FilterHeader from "../../../../../Components/FilterHeader"; +import FilterHeader from "../../../../../../Components/FilterHeader/index.jsx"; import { useMemo } from "react"; import { Box, Button } from "@mui/material"; import ClearIcon from "@mui/icons-material/Clear"; diff --git a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx b/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx similarity index 86% rename from client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx rename to client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx index 7ee603ef7..801a9bc6a 100644 --- a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx @@ -1,18 +1,18 @@ // Components import { Box } from "@mui/material"; -import DataTable from "../../../../../Components/Table"; -import Host from "../../../../../Components/Host"; -import { StatusLabel } from "../../../../../Components/Label"; +import DataTable from "../../../../../../Components/Table/index.jsx"; +import Host from "../../../../../../Components/Host/index.jsx"; +import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; import { Stack } from "@mui/material"; -import { InfrastructureMenu } from "../MonitorsTableMenu"; -import LoadingSpinner from "../../../../Uptime/Monitors/Components/LoadingSpinner"; +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 CustomGauge from "../../../../../Components/Charts/CustomGauge"; +import CPUChipIcon from "../../../../../../assets/icons/cpu-chip.svg?react"; +import CustomGauge from "../../../../../../Components/Charts/CustomGauge/index.jsx"; // Utils import { useTheme } from "@emotion/react"; -import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils"; +import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; import { useNavigate } from "react-router-dom"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx b/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx similarity index 93% rename from client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx rename to client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx index 147f1c297..c2270c691 100644 --- a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx +++ b/client/src/Pages/v1/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"; +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/Dialog"; -import { networkService } from "../../../../../Utils/NetworkService.js"; -import { usePauseMonitor } from "../../../../../Hooks/monitorHooks"; +import Dialog from "../../../../../../Components/Dialog/index.jsx"; +import { networkService } from "../../../../../../Utils/NetworkService.js"; +import { usePauseMonitor } from "../../../../../../Hooks/v1/monitorHooks.js"; import { useTranslation } from "react-i18next"; /** diff --git a/client/src/Pages/Infrastructure/Monitors/index.jsx b/client/src/Pages/v1/Infrastructure/Monitors/index.jsx similarity index 80% rename from client/src/Pages/Infrastructure/Monitors/index.jsx rename to client/src/Pages/v1/Infrastructure/Monitors/index.jsx index bfd2270fe..7fbfa1167 100644 --- a/client/src/Pages/Infrastructure/Monitors/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Monitors/index.jsx @@ -1,21 +1,21 @@ // Components import { Stack } from "@mui/material"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import MonitorCountHeader from "../../../Components/MonitorCountHeader"; -import MonitorCreateHeader from "../../../Components/MonitorCreateHeader"; -import MonitorsTable from "./Components/MonitorsTable"; -import Pagination from "../../..//Components/Table/TablePagination"; -import PageStateWrapper from "../../../Components/PageStateWrapper"; -import Filter from "./Components/Filters"; -import SearchComponent from "../../Uptime/Monitors/Components/SearchComponent"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import MonitorCountHeader from "../../../../Components/MonitorCountHeader/index.jsx"; +import MonitorCreateHeader from "../../../../Components/MonitorCreateHeader/index.jsx"; +import MonitorsTable from "./Components/MonitorsTable/index.jsx"; +import Pagination from "../../../../Components/Table/TablePagination/index.jsx"; +import PageStateWrapper from "../../../../Components/PageStateWrapper/index.jsx"; +import Filter from "./Components/Filters/index.jsx"; +import SearchComponent from "../../Uptime/Monitors/Components/SearchComponent/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { useEffect, useState } from "react"; -import { useIsAdmin } from "../../../Hooks/useIsAdmin"; +import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; import { useTranslation } from "react-i18next"; -import { useFetchMonitorsByTeamId } from "../../../Hooks/monitorHooks"; +import { useFetchMonitorsByTeamId } from "../../../../Hooks/v1/monitorHooks.js"; import { useDispatch, useSelector } from "react-redux"; -import { setRowsPerPage } from "../../../Features/UI/uiSlice"; +import { setRowsPerPage } from "../../../../Features/UI/uiSlice.js"; // Constants const TYPES = ["hardware"]; const BREADCRUMBS = [{ name: `infrastructure`, path: "/infrastructure" }]; diff --git a/client/src/Pages/Integrations/index.css b/client/src/Pages/v1/Integrations/index.css similarity index 100% rename from client/src/Pages/Integrations/index.css rename to client/src/Pages/v1/Integrations/index.css diff --git a/client/src/Pages/Integrations/index.jsx b/client/src/Pages/v1/Integrations/index.jsx similarity index 94% rename from client/src/Pages/Integrations/index.jsx rename to client/src/Pages/v1/Integrations/index.jsx index fe0faa5cf..dde6b52ee 100644 --- a/client/src/Pages/Integrations/index.jsx +++ b/client/src/Pages/v1/Integrations/index.jsx @@ -1,9 +1,9 @@ import PropTypes from "prop-types"; import { Stack, Typography, Grid, Button } from "@mui/material"; import { useTheme } from "@emotion/react"; -import Discord from "../../assets/icons/discord-icon.svg?react"; -import Slack from "../../assets/icons/slack-icon.svg?react"; -import Zapier from "../../assets/icons/zapier-icon.svg?react"; +import Discord from "../../../assets/icons/discord-icon.svg?react"; +import Slack from "../../../assets/icons/slack-icon.svg?react"; +import Zapier from "../../../assets/icons/zapier-icon.svg?react"; import { useTranslation } from "react-i18next"; import "./index.css"; diff --git a/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx b/client/src/Pages/v1/Logs/Diagnostics/components/gauges/index.jsx similarity index 96% rename from client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx rename to client/src/Pages/v1/Logs/Diagnostics/components/gauges/index.jsx index 5bb227b7c..082be2587 100644 --- a/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx +++ b/client/src/Pages/v1/Logs/Diagnostics/components/gauges/index.jsx @@ -1,11 +1,11 @@ import Stack from "@mui/material/Stack"; -import CustomGauge from "../../../../../Components/Charts/CustomGauge"; +import CustomGauge from "../../../../../../Components/Charts/CustomGauge/index.jsx"; import Typography from "@mui/material/Typography"; // Utils import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; -import { getPercentage, formatBytes } from "../../utils/utils"; +import { getPercentage, formatBytes } from "../../utils/utils.js"; import { useTranslation } from "react-i18next"; import { Box } from "@mui/material"; diff --git a/client/src/Pages/Logs/Diagnostics/components/stats/index.jsx b/client/src/Pages/v1/Logs/Diagnostics/components/stats/index.jsx similarity index 94% rename from client/src/Pages/Logs/Diagnostics/components/stats/index.jsx rename to client/src/Pages/v1/Logs/Diagnostics/components/stats/index.jsx index 103d1c698..b99f34506 100644 --- a/client/src/Pages/Logs/Diagnostics/components/stats/index.jsx +++ b/client/src/Pages/v1/Logs/Diagnostics/components/stats/index.jsx @@ -7,8 +7,8 @@ import CircularProgress from "@mui/material/CircularProgress"; import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; -import { getHumanReadableDuration } from "../../../../../Utils/timeUtils"; -import { formatBytes } from "../../utils/utils"; +import { getHumanReadableDuration } from "../../../../../../Utils/timeUtils.js"; +import { formatBytes } from "../../utils/utils.js"; import { useTranslation } from "react-i18next"; const StatsCard = ({ title, value, unit = "", isLoading }) => { diff --git a/client/src/Pages/Logs/Diagnostics/index.jsx b/client/src/Pages/v1/Logs/Diagnostics/index.jsx similarity index 82% rename from client/src/Pages/Logs/Diagnostics/index.jsx rename to client/src/Pages/v1/Logs/Diagnostics/index.jsx index 72ee47f48..d7a1a8a7c 100644 --- a/client/src/Pages/Logs/Diagnostics/index.jsx +++ b/client/src/Pages/v1/Logs/Diagnostics/index.jsx @@ -1,14 +1,14 @@ import Stack from "@mui/material/Stack"; import Box from "@mui/material/Box"; -import Gauges from "./components/gauges"; +import Gauges from "./components/gauges/index.jsx"; import Button from "@mui/material/Button"; -import StatBox from "../../../Components/StatBox"; -import StatusBoxes from "../../../Components/StatusBoxes"; +import StatBox from "../../../../Components/StatBox/index.jsx"; +import StatusBoxes from "../../../../Components/StatusBoxes/index.jsx"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import { useFetchDiagnostics } from "../../../Hooks/logHooks"; -import { getHumanReadableDuration } from "../../../Utils/timeUtils"; -import { formatBytes, getPercentage } from "./utils/utils"; +import { useFetchDiagnostics } from "../../../../Hooks/v1/logHooks.js"; +import { getHumanReadableDuration } from "../../../../Utils/timeUtils.js"; +import { formatBytes, getPercentage } from "./utils/utils.js"; const Diagnostics = () => { // Local state diff --git a/client/src/Pages/Logs/Diagnostics/utils/utils.js b/client/src/Pages/v1/Logs/Diagnostics/utils/utils.js similarity index 100% rename from client/src/Pages/Logs/Diagnostics/utils/utils.js rename to client/src/Pages/v1/Logs/Diagnostics/utils/utils.js diff --git a/client/src/Pages/Logs/Logs/index.jsx b/client/src/Pages/v1/Logs/Logs/index.jsx similarity index 94% rename from client/src/Pages/Logs/Logs/index.jsx rename to client/src/Pages/v1/Logs/Logs/index.jsx index b44b3fd1a..5c7ad9fa9 100644 --- a/client/src/Pages/Logs/Logs/index.jsx +++ b/client/src/Pages/v1/Logs/Logs/index.jsx @@ -1,10 +1,10 @@ import Stack from "@mui/material/Stack"; import Box from "@mui/material/Box"; -import Select from "../../../Components/Inputs/Select"; +import Select from "../../../../Components/Inputs/Select/index.jsx"; import Typography from "@mui/material/Typography"; import Divider from "@mui/material/Divider"; -import { useFetchLogs } from "../../../Hooks/logHooks"; +import { useFetchLogs } from "../../../../Hooks/v1/logHooks.js"; import { useTheme } from "@emotion/react"; import { useState } from "react"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/Logs/Queue/components/FailedJobTable/index.jsx b/client/src/Pages/v1/Logs/Queue/components/FailedJobTable/index.jsx similarity index 92% rename from client/src/Pages/Logs/Queue/components/FailedJobTable/index.jsx rename to client/src/Pages/v1/Logs/Queue/components/FailedJobTable/index.jsx index 94fc2f5c3..46be9520c 100644 --- a/client/src/Pages/Logs/Queue/components/FailedJobTable/index.jsx +++ b/client/src/Pages/v1/Logs/Queue/components/FailedJobTable/index.jsx @@ -1,10 +1,10 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import DataTable from "../../../../../Components/Table"; +import DataTable from "../../../../../../Components/Table/index.jsx"; import { useNavigate } from "react-router-dom"; import { useTheme } from "@emotion/react"; -import { TypeToPathMap } from "../../../../../Utils/monitorUtils"; +import { TypeToPathMap } from "../../../../../../Utils/monitorUtils.js"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/Logs/Queue/components/JobTable/index.jsx b/client/src/Pages/v1/Logs/Queue/components/JobTable/index.jsx similarity index 92% rename from client/src/Pages/Logs/Queue/components/JobTable/index.jsx rename to client/src/Pages/v1/Logs/Queue/components/JobTable/index.jsx index fab85ec5d..4ac4069bd 100644 --- a/client/src/Pages/Logs/Queue/components/JobTable/index.jsx +++ b/client/src/Pages/v1/Logs/Queue/components/JobTable/index.jsx @@ -1,13 +1,13 @@ import Stack from "@mui/material/Stack"; -import DataTable from "../../../../../Components/Table"; +import DataTable from "../../../../../../Components/Table/index.jsx"; import Typography from "@mui/material/Typography"; // Utils import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import { TypeToPathMap } from "../../../../../Utils/monitorUtils"; +import { TypeToPathMap } from "../../../../../../Utils/monitorUtils.js"; import { useTranslation } from "react-i18next"; -import { createHeaderFactory } from "../../../../../Components/Table/TableUtils"; +import { createHeaderFactory } from "../../../../../../Components/Table/TableUtils.js"; const JobTable = ({ jobs = [] }) => { const theme = useTheme(); diff --git a/client/src/Pages/Logs/Queue/components/Metrics/index.jsx b/client/src/Pages/v1/Logs/Queue/components/Metrics/index.jsx similarity index 84% rename from client/src/Pages/Logs/Queue/components/Metrics/index.jsx rename to client/src/Pages/v1/Logs/Queue/components/Metrics/index.jsx index b0112fa99..dd9fdcc6a 100644 --- a/client/src/Pages/Logs/Queue/components/Metrics/index.jsx +++ b/client/src/Pages/v1/Logs/Queue/components/Metrics/index.jsx @@ -1,6 +1,6 @@ import Stack from "@mui/material/Stack"; -import StatBox from "../../../../../Components/StatBox"; -import StatusBoxes from "../../../../../Components/StatusBoxes"; +import StatBox from "../../../../../../Components/StatBox/index.jsx"; +import StatusBoxes from "../../../../../../Components/StatusBoxes/index.jsx"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/Logs/Queue/components/MetricsTable/index.jsx b/client/src/Pages/v1/Logs/Queue/components/MetricsTable/index.jsx similarity index 94% rename from client/src/Pages/Logs/Queue/components/MetricsTable/index.jsx rename to client/src/Pages/v1/Logs/Queue/components/MetricsTable/index.jsx index 204b90035..58f13fd5d 100644 --- a/client/src/Pages/Logs/Queue/components/MetricsTable/index.jsx +++ b/client/src/Pages/v1/Logs/Queue/components/MetricsTable/index.jsx @@ -1,6 +1,6 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import DataTable from "../../../../../Components/Table"; +import DataTable from "../../../../../../Components/Table/index.jsx"; // Utils import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/Logs/Queue/index.jsx b/client/src/Pages/v1/Logs/Queue/index.jsx similarity index 86% rename from client/src/Pages/Logs/Queue/index.jsx rename to client/src/Pages/v1/Logs/Queue/index.jsx index fde9073fc..10eca4d3b 100644 --- a/client/src/Pages/Logs/Queue/index.jsx +++ b/client/src/Pages/v1/Logs/Queue/index.jsx @@ -1,8 +1,8 @@ // Components import Stack from "@mui/material/Stack"; -import JobTable from "./components/JobTable"; -import Metrics from "./components/Metrics"; -import FailedJobTable from "./components/FailedJobTable"; +import JobTable from "./components/JobTable/index.jsx"; +import Metrics from "./components/Metrics/index.jsx"; +import FailedJobTable from "./components/FailedJobTable/index.jsx"; import ButtonGroup from "@mui/material/ButtonGroup"; import Button from "@mui/material/Button"; import Typography from "@mui/material/Typography"; @@ -10,7 +10,7 @@ import Divider from "@mui/material/Divider"; // Utils import { useState } from "react"; -import { useFetchQueueData, useFlushQueue } from "../../../Hooks/logHooks"; +import { useFetchQueueData, useFlushQueue } from "../../../../Hooks/v1/logHooks.js"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/Logs/index.jsx b/client/src/Pages/v1/Logs/index.jsx similarity index 84% rename from client/src/Pages/Logs/index.jsx rename to client/src/Pages/v1/Logs/index.jsx index 79c89f282..c6dfe4d99 100644 --- a/client/src/Pages/Logs/index.jsx +++ b/client/src/Pages/v1/Logs/index.jsx @@ -1,10 +1,10 @@ import Stack from "@mui/material/Stack"; -import Breadcrumbs from "../../Components/Breadcrumbs"; +import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx"; import Tabs from "@mui/material/Tabs"; import Tab from "@mui/material/Tab"; -import Queue from "./Queue"; -import LogsComponent from "./Logs"; -import Diagnostics from "./Diagnostics"; +import Queue from "./Queue/index.jsx"; +import LogsComponent from "./Logs/index.jsx"; +import Diagnostics from "./Diagnostics/index.jsx"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx b/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx similarity index 92% rename from client/src/Pages/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx rename to client/src/Pages/v1/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx index 1627ef3d5..b43bcbfc7 100644 --- a/client/src/Pages/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx +++ b/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx @@ -1,4 +1,4 @@ -import Select from "../../../../../Components/Inputs/Select"; +import Select from "../../../../../../Components/Inputs/Select/index.jsx"; import PropTypes from "prop-types"; const ConfigSelect = ({ configSelection, valueSelect, onChange, ...props }) => { const getValueById = (config, id) => { diff --git a/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx b/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx similarity index 100% rename from client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx rename to client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx diff --git a/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx b/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx similarity index 93% rename from client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx rename to client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx index e792734ff..91e6a1531 100644 --- a/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx +++ b/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx @@ -1,7 +1,7 @@ import PropTypes from "prop-types"; -import Search from "../../../../../Components/Inputs/Search"; +import Search from "../../../../../../Components/Inputs/Search/index.jsx"; import { useState } from "react"; -import ConfigRow from "../../../../../Components/ConfigRow"; +import ConfigRow from "../../../../../../Components/ConfigRow/index.jsx"; /** * MonitorsConfig is a component that allows users to select and apply a maintenance window to a list of monitors. diff --git a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx b/client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx similarity index 93% rename from client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx rename to client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx index 84333a14f..a75cbe206 100644 --- a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx +++ b/client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx @@ -1,4 +1,4 @@ -import { networkService } from "../../../../main"; +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"; +} from "../../../../../Utils/timeUtils.js"; const useMaintenanceActions = () => { const MS_LOOKUP = { diff --git a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx b/client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx similarity index 95% rename from client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx rename to client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx index 8ba9b5e80..d21906e41 100644 --- a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx +++ b/client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx @@ -1,4 +1,4 @@ -import { networkService } from "../../../../main"; +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"; +} from "../../../../../Utils/timeUtils.js"; const useMaintenanceData = () => { const REVERSE_REPEAT_LOOKUP = { 0: "none", diff --git a/client/src/Pages/Maintenance/CreateMaintenance/index.css b/client/src/Pages/v1/Maintenance/CreateMaintenance/index.css similarity index 100% rename from client/src/Pages/Maintenance/CreateMaintenance/index.css rename to client/src/Pages/v1/Maintenance/CreateMaintenance/index.css diff --git a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx b/client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx similarity index 93% rename from client/src/Pages/Maintenance/CreateMaintenance/index.jsx rename to client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx index 9dbf9d4aa..da3e72d8a 100644 --- a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx +++ b/client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx @@ -2,27 +2,27 @@ import { Box, Button, Stack, Typography } from "@mui/material"; import { useSelector } from "react-redux"; import { useTheme } from "@emotion/react"; import { useEffect, useState } from "react"; -import ConfigBox from "../../../Components/ConfigBox"; +import ConfigBox from "../../../../Components/ConfigBox/index.jsx"; 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"; -import { createToast } from "../../../Utils/toastUtils"; -import MonitorList from "./Components/MonitorList"; -import ConfigSelect from "./Components/ConfigSelect"; -import useMaintenanceData from "./hooks/useMaintenanceData"; -import useMaintenanceActions from "./hooks/useMaintenanceActions"; +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"; +import useMaintenanceActions from "./hooks/useMaintenanceActions.jsx"; -import TextInput from "../../../Components/Inputs/TextInput"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import CalendarIcon from "../../../assets/icons/calendar.svg?react"; +import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import CalendarIcon from "../../../../assets/icons/calendar.svg?react"; import "./index.css"; -import Search from "../../../Components/Inputs/Search"; -import { logger } from "../../../Utils/Logger"; +import Search from "../../../../Components/Inputs/Search/index.jsx"; +import { logger } from "../../../../Utils/Logger.js"; import { useNavigate, useParams } from "react-router-dom"; -import { buildErrors, hasValidationErrors } from "../../../Validation/error"; +import { buildErrors, hasValidationErrors } from "../../../../Validation/error.js"; import { useTranslation } from "react-i18next"; import dayjs from "dayjs"; diff --git a/client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx b/client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx similarity index 91% rename from client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx rename to client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx index aa1c48433..cc51697f7 100644 --- a/client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx +++ b/client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx @@ -3,14 +3,14 @@ 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"; -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"; -import { createToast } from "../../../../Utils/toastUtils"; +import { networkService } from "../../../../../main.jsx"; +import { createToast } from "../../../../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; -import Dialog from "../../../../Components/Dialog"; +import Dialog from "../../../../../Components/Dialog/index.jsx"; const ActionsMenu = ({ /* isAdmin, */ maintenanceWindow, updateCallback }) => { maintenanceWindow; diff --git a/client/src/Pages/Maintenance/MaintenanceTable/index.jsx b/client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx similarity index 92% rename from client/src/Pages/Maintenance/MaintenanceTable/index.jsx rename to client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx index bb5842e9f..fb18cd380 100644 --- a/client/src/Pages/Maintenance/MaintenanceTable/index.jsx +++ b/client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx @@ -1,15 +1,15 @@ import PropTypes from "prop-types"; import { Box } from "@mui/material"; -import DataTable from "../../../Components/Table"; -import Pagination from "../../../Components/Table/TablePagination"; +import DataTable from "../../../../Components/Table/index.jsx"; +import Pagination from "../../../../Components/Table/TablePagination/index.jsx"; import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded"; import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded"; -import ActionsMenu from "./ActionsMenu"; +import ActionsMenu from "./ActionsMenu/index.jsx"; import { memo } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { formatDurationRounded } from "../../../Utils/timeUtils"; -import { StatusLabel } from "../../../Components/Label"; -import { setRowsPerPage } from "../../../Features/UI/uiSlice"; +import { formatDurationRounded } from "../../../../Utils/timeUtils.js"; +import { StatusLabel } from "../../../../Components/Label/index.jsx"; +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/Maintenance/index.css b/client/src/Pages/v1/Maintenance/index.css similarity index 100% rename from client/src/Pages/Maintenance/index.css rename to client/src/Pages/v1/Maintenance/index.css diff --git a/client/src/Pages/Maintenance/index.jsx b/client/src/Pages/v1/Maintenance/index.jsx similarity index 91% rename from client/src/Pages/Maintenance/index.jsx rename to client/src/Pages/v1/Maintenance/index.jsx index ee142f524..a993c153f 100644 --- a/client/src/Pages/Maintenance/index.jsx +++ b/client/src/Pages/v1/Maintenance/index.jsx @@ -2,13 +2,13 @@ import { Stack, Button } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useState, useEffect } from "react"; import "./index.css"; -import MaintenanceTable from "./MaintenanceTable"; +import MaintenanceTable from "./MaintenanceTable/index.jsx"; import { useSelector } from "react-redux"; -import { networkService } from "../../main"; -import Breadcrumbs from "../../Components/Breadcrumbs"; +import { networkService } from "../../../main.jsx"; +import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; -import PageStateWrapper from "../../Components/PageStateWrapper"; +import PageStateWrapper from "../../../Components/PageStateWrapper/index.jsx"; const Maintenance = () => { const theme = useTheme(); diff --git a/client/src/Pages/NotFound/index.jsx b/client/src/Pages/v1/NotFound/index.jsx similarity index 96% rename from client/src/Pages/NotFound/index.jsx rename to client/src/Pages/v1/NotFound/index.jsx index fdae92cb5..48825c9f6 100644 --- a/client/src/Pages/NotFound/index.jsx +++ b/client/src/Pages/v1/NotFound/index.jsx @@ -1,5 +1,5 @@ import PropTypes from "prop-types"; -import NotFoundSvg from "../../../src/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/Notifications/components/ActionMenu.jsx b/client/src/Pages/v1/Notifications/components/ActionMenu.jsx similarity index 100% rename from client/src/Pages/Notifications/components/ActionMenu.jsx rename to client/src/Pages/v1/Notifications/components/ActionMenu.jsx diff --git a/client/src/Pages/Notifications/create/index.jsx b/client/src/Pages/v1/Notifications/create/index.jsx similarity index 92% rename from client/src/Pages/Notifications/create/index.jsx rename to client/src/Pages/v1/Notifications/create/index.jsx index 6d8afe665..0d541736d 100644 --- a/client/src/Pages/Notifications/create/index.jsx +++ b/client/src/Pages/v1/Notifications/create/index.jsx @@ -1,13 +1,13 @@ // Components import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; import Button from "@mui/material/Button"; -import ConfigBox from "../../../Components/ConfigBox"; +import ConfigBox from "../../../../Components/ConfigBox/index.jsx"; import Box from "@mui/material/Box"; -import Select from "../../../Components/Inputs/Select"; -import TextInput from "../../../Components/Inputs/TextInput"; -import Dialog from "../../../Components/Dialog"; +import Select from "../../../../Components/Inputs/Select/index.jsx"; +import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import Dialog from "../../../../Components/Dialog/index.jsx"; // Utils import { useState } from "react"; @@ -18,9 +18,9 @@ import { useEditNotification, useTestNotification, useDeleteNotification, -} from "../../../Hooks/useNotifications"; -import { notificationValidation } from "../../../Validation/validation"; -import { createToast } from "../../../Utils/toastUtils"; +} from "../../../../Hooks/v1/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 { @@ -29,7 +29,7 @@ import { DESCRIPTION_MAP, LABEL_MAP, PLACEHOLDER_MAP, -} from "../utils"; +} from "../utils.js"; // Setup diff --git a/client/src/Pages/Notifications/index.jsx b/client/src/Pages/v1/Notifications/index.jsx similarity index 89% rename from client/src/Pages/Notifications/index.jsx rename to client/src/Pages/v1/Notifications/index.jsx index 910fbf1fc..5649224b6 100644 --- a/client/src/Pages/Notifications/index.jsx +++ b/client/src/Pages/v1/Notifications/index.jsx @@ -1,11 +1,11 @@ // Components import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import Breadcrumbs from "../../Components/Breadcrumbs"; +import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx"; import Button from "@mui/material/Button"; -import DataTable from "../../Components/Table"; -import ActionMenu from "./components/ActionMenu"; -import PageStateWrapper from "../../Components/PageStateWrapper"; +import DataTable from "../../../Components/Table/index.jsx"; +import ActionMenu from "./components/ActionMenu.jsx"; +import PageStateWrapper from "../../../Components/PageStateWrapper/index.jsx"; // Utils import { useState } from "react"; @@ -14,7 +14,7 @@ import { useNavigate } from "react-router-dom"; import { useGetNotificationsByTeamId, useDeleteNotification, -} from "../../Hooks/useNotifications"; +} from "../../../Hooks/v1/useNotifications.js"; import { useTranslation } from "react-i18next"; const Notifications = () => { diff --git a/client/src/Pages/Notifications/utils.js b/client/src/Pages/v1/Notifications/utils.js similarity index 100% rename from client/src/Pages/Notifications/utils.js rename to client/src/Pages/v1/Notifications/utils.js diff --git a/client/src/Pages/PageSpeed/Create/index.css b/client/src/Pages/v1/PageSpeed/Create/index.css similarity index 100% rename from client/src/Pages/PageSpeed/Create/index.css rename to client/src/Pages/v1/PageSpeed/Create/index.css diff --git a/client/src/Pages/PageSpeed/Create/index.jsx b/client/src/Pages/v1/PageSpeed/Create/index.jsx similarity index 91% rename from client/src/Pages/PageSpeed/Create/index.jsx rename to client/src/Pages/v1/PageSpeed/Create/index.jsx index 46bc7f977..7cc244848 100644 --- a/client/src/Pages/PageSpeed/Create/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Create/index.jsx @@ -1,37 +1,37 @@ // Components import { Box, Stack, Tooltip, Typography, Button, ButtonGroup } from "@mui/material"; -import ConfigBox from "../../../Components/ConfigBox"; -import Select from "../../../Components/Inputs/Select"; -import TextInput from "../../../Components/Inputs/TextInput"; +import ConfigBox from "../../../../Components/ConfigBox/index.jsx"; +import Select from "../../../../Components/Inputs/Select/index.jsx"; +import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import PulseDot from "../../../Components/Animated/PulseDot"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import PulseDot from "../../../../Components/Animated/PulseDot.jsx"; import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded"; -import SkeletonLayout from "./skeleton"; -import NotificationsConfig from "../../../Components/NotificationConfig"; -import Dialog from "../../../Components/Dialog"; -import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments"; -import Radio from "../../../Components/Inputs/Radio"; +import SkeletonLayout from "./skeleton.jsx"; +import NotificationsConfig from "../../../../Components/NotificationConfig/index.jsx"; +import Dialog from "../../../../Components/Dialog/index.jsx"; +import { HttpAdornment } from "../../../../Components/Inputs/TextInput/Adornments/index.jsx"; +import Radio from "../../../../Components/Inputs/Radio/index.jsx"; // Utils import { useState, useEffect } from "react"; import { useSelector } from "react-redux"; -import { monitorValidation } from "../../../Validation/validation"; -import { parseDomainName } from "../../../Utils/monitorUtils"; +import { monitorValidation } from "../../../../Validation/validation.js"; +import { parseDomainName } from "../../../../Utils/monitorUtils.js"; import { useTranslation } from "react-i18next"; -import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications"; +import { useGetNotificationsByTeamId } from "../../../../Hooks/v1/useNotifications.js"; import { useTheme } from "@emotion/react"; -import { createToast } from "../../../Utils/toastUtils"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; import { useParams } from "react-router"; -import { useMonitorUtils } from "../../../Hooks/useMonitorUtils"; +import { useMonitorUtils } from "../../../../Hooks/v1/useMonitorUtils.js"; import { useCreateMonitor, useFetchMonitorById, useDeleteMonitor, useUpdateMonitor, usePauseMonitor, -} from "../../../Hooks/monitorHooks"; +} from "../../../../Hooks/v1/monitorHooks.js"; const PageSpeedSetup = () => { const { monitorId } = useParams(); diff --git a/client/src/Pages/PageSpeed/Create/skeleton.jsx b/client/src/Pages/v1/PageSpeed/Create/skeleton.jsx similarity index 100% rename from client/src/Pages/PageSpeed/Create/skeleton.jsx rename to client/src/Pages/v1/PageSpeed/Create/skeleton.jsx diff --git a/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChart.jsx similarity index 99% rename from client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx rename to client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChart.jsx index 40fec0ab6..5c79cc06c 100644 --- a/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx +++ b/client/src/Pages/v1/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"; +import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js"; import { useSelector } from "react-redux"; const config = { diff --git a/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx similarity index 84% rename from client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx rename to client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx index 72811fa2a..cb6f18bd2 100644 --- a/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx @@ -1,7 +1,7 @@ import { Box, Typography, Divider } from "@mui/material"; -import Checkbox from "../../../../../Components/Inputs/Checkbox"; -import MetricsIcon from "../../../../../assets/icons/ruler-icon.svg?react"; -import LegendBox from "../../../../../Components/Charts/LegendBox"; +import Checkbox from "../../../../../../Components/Inputs/Checkbox/index.jsx"; +import MetricsIcon from "../../../../../../assets/icons/ruler-icon.svg?react"; +import LegendBox from "../../../../../../Components/Charts/LegendBox/index.jsx"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChart.jsx similarity index 100% rename from client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx rename to client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChart.jsx diff --git a/client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx similarity index 92% rename from client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx rename to client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx index 18ca0733c..fc4b190d8 100644 --- a/client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx +++ b/client/src/Pages/v1/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/Charts/LegendBox"; -import SpeedometerIcon from "../../../../../assets/icons/speedometer-icon.svg?react"; +import LegendBox from "../../../../../../Components/Charts/LegendBox/index.jsx"; +import SpeedometerIcon from "../../../../../../assets/icons/speedometer-icon.svg?react"; import PropTypes from "prop-types"; const PieChartLegend = ({ audits }) => { diff --git a/client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx similarity index 75% rename from client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx rename to client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx index 582648e23..bb45117fe 100644 --- a/client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx @@ -1,8 +1,8 @@ -import ChartBox from "../../../../../Components/Charts/ChartBox"; -import AreaChart from "../Charts/AreaChart"; -import AreaChartLegend from "../Charts/AreaChartLegend"; -import SkeletonLayout from "./skeleton"; -import ScoreIcon from "../../../../../assets/icons/monitor-graph-line.svg?react"; +import ChartBox from "../../../../../../Components/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 { Stack } from "@mui/material"; import PropTypes from "prop-types"; diff --git a/client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx similarity index 100% rename from client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx rename to client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx diff --git a/client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx similarity index 76% rename from client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx rename to client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx index cb79198e9..0811ae707 100644 --- a/client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx @@ -1,7 +1,7 @@ -import StatusBoxes from "../../../../../Components/StatusBoxes"; -import StatBox from "../../../../../Components/StatBox"; +import StatusBoxes from "../../../../../../Components/StatusBoxes/index.jsx"; +import StatBox from "../../../../../../Components/StatBox/index.jsx"; import { Typography } from "@mui/material"; -import { getHumanReadableDuration } from "../../../../../Utils/timeUtils"; +import { getHumanReadableDuration } from "../../../../../../Utils/timeUtils.js"; import { useTranslation } from "react-i18next"; const PageSpeedStatusBoxes = ({ shouldRender, monitor }) => { diff --git a/client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx similarity index 78% rename from client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx rename to client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx index 2f809ebbb..ce4704144 100644 --- a/client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx @@ -1,10 +1,10 @@ -import ChartBox from "../../../../../Components/Charts/ChartBox"; -import PerformanceIcon from "../../../../../assets/icons/performance-report.svg?react"; -import PieChart from "../Charts/PieChart"; +import ChartBox from "../../../../../../Components/Charts/ChartBox/index.jsx"; +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"; -import PieChartLegend from "../Charts/PieChartLegend"; -import SkeletonLayout from "./skeleton"; +import PieChartLegend from "../Charts/PieChartLegend.jsx"; +import SkeletonLayout from "./skeleton.jsx"; import { useTranslation } from "react-i18next"; const PerformanceReport = ({ shouldRender, audits }) => { diff --git a/client/src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx similarity index 100% rename from client/src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx rename to client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx diff --git a/client/src/Pages/PageSpeed/Details/index.jsx b/client/src/Pages/v1/PageSpeed/Details/index.jsx similarity index 81% rename from client/src/Pages/PageSpeed/Details/index.jsx rename to client/src/Pages/v1/PageSpeed/Details/index.jsx index cfc799b36..cf9652bce 100644 --- a/client/src/Pages/PageSpeed/Details/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/index.jsx @@ -1,17 +1,17 @@ // Components import { Stack, Typography } from "@mui/material"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader"; -import PageSpeedStatusBoxes from "./Components/PageSpeedStatusBoxes"; -import MonitorDetailsControlHeader from "../../../Components/MonitorDetailsControlHeader"; -import PageSpeedAreaChart from "./Components/PageSpeedAreaChart"; -import PerformanceReport from "./Components/PerformanceReport"; -import GenericFallback from "../../../Components/GenericFallback"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import MonitorTimeFrameHeader from "../../../../Components/MonitorTimeFrameHeader/index.jsx"; +import PageSpeedStatusBoxes from "./Components/PageSpeedStatusBoxes/index.jsx"; +import MonitorDetailsControlHeader from "../../../../Components/MonitorDetailsControlHeader/index.jsx"; +import PageSpeedAreaChart from "./Components/PageSpeedAreaChart/index.jsx"; +import PerformanceReport from "./Components/PerformanceReport/index.jsx"; +import GenericFallback from "../../../../Components/GenericFallback/index.jsx"; // Utils import { useTheme } from "@emotion/react"; -import { useIsAdmin } from "../../../Hooks/useIsAdmin"; +import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; import { useParams } from "react-router-dom"; -import { useFetchStatsByMonitorId } from "../../../Hooks/monitorHooks"; +import { useFetchStatsByMonitorId } from "../../../../Hooks/v1/monitorHooks.js"; import { useState } from "react"; import { useTranslation } from "react-i18next"; // Constants diff --git a/client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx b/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx similarity index 95% rename from client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx rename to client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx index 94d6b137b..db1f6e712 100644 --- a/client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx @@ -1,17 +1,17 @@ import PropTypes from "prop-types"; // import PageSpeedIcon from "../../../../assets/icons/page-speed.svg?react"; -import PageSpeedIcon from "../../../../../assets/icons/page-speed.svg?react"; -import { StatusLabel } from "../../../../../Components/Label"; +import PageSpeedIcon from "../../../../../../assets/icons/page-speed.svg?react"; +import { StatusLabel } from "../../../../../../Components/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"; -import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils"; +import { formatDateWithTz, formatDurationSplit } from "../../../../../../Utils/timeUtils.js"; +import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; import { useState } from "react"; import { useTranslation } from "react-i18next"; -import IconBox from "../../../../../Components/IconBox"; +import IconBox from "../../../../../../Components/IconBox/index.jsx"; /** * CustomToolTip displays a tooltip with formatted date and score information. * @param {Object} props diff --git a/client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx b/client/src/Pages/v1/PageSpeed/Monitors/Components/MonitorGrid/index.jsx similarity index 88% rename from client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx rename to client/src/Pages/v1/PageSpeed/Monitors/Components/MonitorGrid/index.jsx index 6266ebdc9..6b0a800fb 100644 --- a/client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Monitors/Components/MonitorGrid/index.jsx @@ -1,5 +1,5 @@ import { Grid, Grid2 } from "@mui/material"; -import Card from "../Card"; +import Card from "../Card/index.jsx"; const MonitorGrid = ({ shouldRender, monitors }) => { return ( diff --git a/client/src/Pages/PageSpeed/Monitors/index.jsx b/client/src/Pages/v1/PageSpeed/Monitors/index.jsx similarity index 69% rename from client/src/Pages/PageSpeed/Monitors/index.jsx rename to client/src/Pages/v1/PageSpeed/Monitors/index.jsx index b5b13ba6e..e568e8d82 100644 --- a/client/src/Pages/PageSpeed/Monitors/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Monitors/index.jsx @@ -1,18 +1,18 @@ // Components import { useState } from "react"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; import { Stack } from "@mui/material"; -import CreateMonitorHeader from "../../../Components/MonitorCreateHeader"; -import MonitorCountHeader from "../../../Components/MonitorCountHeader"; -import MonitorGrid from "./Components/MonitorGrid"; -import PageStateWrapper from "../../../Components/PageStateWrapper"; -import FallbackPageSpeedWarning from "../../../Components/Fallback/FallbackPageSpeedWarning"; +import CreateMonitorHeader from "../../../../Components/MonitorCreateHeader/index.jsx"; +import MonitorCountHeader from "../../../../Components/MonitorCountHeader/index.jsx"; +import MonitorGrid from "./Components/MonitorGrid/index.jsx"; +import PageStateWrapper from "../../../../Components/PageStateWrapper/index.jsx"; +import FallbackPageSpeedWarning from "../../../../Components/Fallback/FallbackPageSpeedWarning.jsx"; // Utils import { useTheme } from "@emotion/react"; -import { useIsAdmin } from "../../../Hooks/useIsAdmin"; -import { useFetchMonitorsByTeamId } from "../../../Hooks/monitorHooks"; -import { useFetchSettings } from "../../../Hooks/settingsHooks"; +import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; +import { useFetchMonitorsByTeamId } from "../../../../Hooks/v1/monitorHooks.js"; +import { useFetchSettings } from "../../../../Hooks/v1/settingsHooks.js"; // Constants const BREADCRUMBS = [{ name: `pagespeed`, path: "/pagespeed" }]; const TYPES = ["pagespeed"]; diff --git a/client/src/Pages/ServerUnreachable.jsx b/client/src/Pages/v1/ServerUnreachable.jsx similarity index 90% rename from client/src/Pages/ServerUnreachable.jsx rename to client/src/Pages/v1/ServerUnreachable.jsx index 466633144..0db697543 100644 --- a/client/src/Pages/ServerUnreachable.jsx +++ b/client/src/Pages/v1/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"; -import Alert from "../Components/Alert"; -import { createToast } from "../Utils/toastUtils"; +import { networkService } from "../../Utils/NetworkService.js"; +import Alert from "../../Components/Alert/index.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 ThemeSwitch from "../Components/ThemeSwitch"; -import LanguageSelector from "../Components/LanguageSelector"; +import Background from "../../assets/Images/background-grid.svg?react"; +import Logo from "../../assets/icons/checkmate-icon.svg?react"; +import ThemeSwitch from "../../Components/ThemeSwitch/index.jsx"; +import LanguageSelector from "../../Components/LanguageSelector.jsx"; const ServerUnreachable = () => { const theme = useTheme(); diff --git a/client/src/Pages/Settings/SettingsAbout.jsx b/client/src/Pages/v1/Settings/SettingsAbout.jsx similarity index 88% rename from client/src/Pages/Settings/SettingsAbout.jsx rename to client/src/Pages/v1/Settings/SettingsAbout.jsx index a80351393..3d5f5224e 100644 --- a/client/src/Pages/Settings/SettingsAbout.jsx +++ b/client/src/Pages/v1/Settings/SettingsAbout.jsx @@ -1,10 +1,10 @@ import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; -import ConfigBox from "../../Components/ConfigBox"; +import ConfigBox from "../../../Components/ConfigBox/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import Link from "../../Components/Link"; +import Link from "../../../Components/Link/index.jsx"; const SettingsAbout = () => { const theme = useTheme(); diff --git a/client/src/Pages/Settings/SettingsDemoMonitors.jsx b/client/src/Pages/v1/Settings/SettingsDemoMonitors.jsx similarity index 95% rename from client/src/Pages/Settings/SettingsDemoMonitors.jsx rename to client/src/Pages/v1/Settings/SettingsDemoMonitors.jsx index 13e93421e..96f907970 100644 --- a/client/src/Pages/Settings/SettingsDemoMonitors.jsx +++ b/client/src/Pages/v1/Settings/SettingsDemoMonitors.jsx @@ -1,12 +1,12 @@ import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; -import ConfigBox from "../../Components/ConfigBox"; +import ConfigBox from "../../../Components/ConfigBox/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; import { useTranslation } from "react-i18next"; -import Dialog from "../../Components/Dialog"; +import Dialog from "../../../Components/Dialog/index.jsx"; import { useState } from "react"; const SettingsDemoMonitors = ({ isAdmin, HEADER_SX, handleChange, isLoading }) => { diff --git a/client/src/Pages/Settings/SettingsEmail.jsx b/client/src/Pages/v1/Settings/SettingsEmail.jsx similarity index 94% rename from client/src/Pages/Settings/SettingsEmail.jsx rename to client/src/Pages/v1/Settings/SettingsEmail.jsx index 1f86b1984..c2389b277 100644 --- a/client/src/Pages/Settings/SettingsEmail.jsx +++ b/client/src/Pages/v1/Settings/SettingsEmail.jsx @@ -1,19 +1,19 @@ import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; -import ConfigBox from "../../Components/ConfigBox"; -import TextInput from "../../Components/Inputs/TextInput"; +import ConfigBox from "../../../Components/ConfigBox/index.jsx"; +import TextInput from "../../../Components/Inputs/TextInput/index.jsx"; import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; import { Switch } from "@mui/material"; -import TextLink from "../../Components/TextLink"; +import TextLink from "../../../Components/TextLink/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments"; -import { useSendTestEmail } from "../../Hooks/useSendTestEmail"; -import { createToast } from "../../Utils/toastUtils"; +import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adornments/index.jsx"; +import { useSendTestEmail } from "../../../Hooks/v1/useSendTestEmail.js"; +import { createToast } from "../../../Utils/toastUtils.jsx"; const SettingsEmail = ({ isAdmin, diff --git a/client/src/Pages/Settings/SettingsGlobalThresholds.jsx b/client/src/Pages/v1/Settings/SettingsGlobalThresholds.jsx similarity index 94% rename from client/src/Pages/Settings/SettingsGlobalThresholds.jsx rename to client/src/Pages/v1/Settings/SettingsGlobalThresholds.jsx index acc101ab4..e238c6475 100644 --- a/client/src/Pages/Settings/SettingsGlobalThresholds.jsx +++ b/client/src/Pages/v1/Settings/SettingsGlobalThresholds.jsx @@ -1,8 +1,8 @@ import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import ConfigBox from "../../Components/ConfigBox"; -import TextInput from "../../Components/Inputs/TextInput"; +import ConfigBox from "../../../Components/ConfigBox/index.jsx"; +import TextInput from "../../../Components/Inputs/TextInput/index.jsx"; import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; diff --git a/client/src/Pages/Settings/SettingsPagespeed.jsx b/client/src/Pages/v1/Settings/SettingsPagespeed.jsx similarity index 91% rename from client/src/Pages/Settings/SettingsPagespeed.jsx rename to client/src/Pages/v1/Settings/SettingsPagespeed.jsx index 157c75dac..83b2e23c7 100644 --- a/client/src/Pages/Settings/SettingsPagespeed.jsx +++ b/client/src/Pages/v1/Settings/SettingsPagespeed.jsx @@ -2,9 +2,9 @@ import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; -import ConfigBox from "../../Components/ConfigBox"; -import TextInput from "../../Components/Inputs/TextInput"; -import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments"; +import ConfigBox from "../../../Components/ConfigBox/index.jsx"; +import TextInput from "../../../Components/Inputs/TextInput/index.jsx"; +import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adornments/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; diff --git a/client/src/Pages/Settings/SettingsStats.jsx b/client/src/Pages/v1/Settings/SettingsStats.jsx similarity index 92% rename from client/src/Pages/Settings/SettingsStats.jsx rename to client/src/Pages/v1/Settings/SettingsStats.jsx index 740ac3a37..52f739ecf 100644 --- a/client/src/Pages/Settings/SettingsStats.jsx +++ b/client/src/Pages/v1/Settings/SettingsStats.jsx @@ -2,9 +2,9 @@ import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; -import ConfigBox from "../../Components/ConfigBox"; -import TextInput from "../../Components/Inputs/TextInput"; -import Dialog from "../../Components/Dialog"; +import ConfigBox from "../../../Components/ConfigBox/index.jsx"; +import TextInput from "../../../Components/Inputs/TextInput/index.jsx"; +import Dialog from "../../../Components/Dialog/index.jsx"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/Settings/SettingsTimeZone.jsx b/client/src/Pages/v1/Settings/SettingsTimeZone.jsx similarity index 90% rename from client/src/Pages/Settings/SettingsTimeZone.jsx rename to client/src/Pages/v1/Settings/SettingsTimeZone.jsx index ee5cf48de..59b2008f4 100644 --- a/client/src/Pages/Settings/SettingsTimeZone.jsx +++ b/client/src/Pages/v1/Settings/SettingsTimeZone.jsx @@ -1,9 +1,9 @@ import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import ConfigBox from "../../Components/ConfigBox"; -import Search from "../../Components/Inputs/Search"; -import timezones from "../../Utils/timezones.json"; +import ConfigBox from "../../../Components/ConfigBox/index.jsx"; +import Search from "../../../Components/Inputs/Search/index.jsx"; +import timezones from "../../../Utils/timezones.json"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/Settings/SettingsUI.jsx b/client/src/Pages/v1/Settings/SettingsUI.jsx similarity index 91% rename from client/src/Pages/Settings/SettingsUI.jsx rename to client/src/Pages/v1/Settings/SettingsUI.jsx index aab5f863d..91f4cea3c 100644 --- a/client/src/Pages/Settings/SettingsUI.jsx +++ b/client/src/Pages/v1/Settings/SettingsUI.jsx @@ -1,8 +1,8 @@ import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import ConfigBox from "../../Components/ConfigBox"; -import Select from "../../Components/Inputs/Select"; +import ConfigBox from "../../../Components/ConfigBox/index.jsx"; +import Select from "../../../Components/Inputs/Select/index.jsx"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/Settings/SettingsURL.jsx b/client/src/Pages/v1/Settings/SettingsURL.jsx similarity index 90% rename from client/src/Pages/Settings/SettingsURL.jsx rename to client/src/Pages/v1/Settings/SettingsURL.jsx index 24c49cad1..105658d4f 100644 --- a/client/src/Pages/Settings/SettingsURL.jsx +++ b/client/src/Pages/v1/Settings/SettingsURL.jsx @@ -1,8 +1,8 @@ import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import ConfigBox from "../../Components/ConfigBox"; -import Select from "../../Components/Inputs/Select"; +import ConfigBox from "../../../Components/ConfigBox/index.jsx"; +import Select from "../../../Components/Inputs/Select/index.jsx"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/Settings/index.jsx b/client/src/Pages/v1/Settings/index.jsx similarity index 89% rename from client/src/Pages/Settings/index.jsx rename to client/src/Pages/v1/Settings/index.jsx index 92f58b4c0..d27685ed6 100644 --- a/client/src/Pages/Settings/index.jsx +++ b/client/src/Pages/v1/Settings/index.jsx @@ -1,31 +1,31 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import Breadcrumbs from "../../Components/Breadcrumbs"; -import SettingsTimeZone from "./SettingsTimeZone"; -import SettingsUI from "./SettingsUI"; -import SettingsURL from "./SettingsURL"; -import SettingsPagespeed from "./SettingsPagespeed"; -import SettingsDemoMonitors from "./SettingsDemoMonitors"; -import SettingsAbout from "./SettingsAbout"; -import SettingsEmail from "./SettingsEmail"; -import SettingsGlobalThresholds from "./SettingsGlobalThresholds"; +import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx"; +import SettingsTimeZone from "./SettingsTimeZone.jsx"; +import SettingsUI from "./SettingsUI.jsx"; +import SettingsURL from "./SettingsURL.jsx"; +import SettingsPagespeed from "./SettingsPagespeed.jsx"; +import SettingsDemoMonitors from "./SettingsDemoMonitors.jsx"; +import SettingsAbout from "./SettingsAbout.jsx"; +import SettingsEmail from "./SettingsEmail.jsx"; +import SettingsGlobalThresholds from "./SettingsGlobalThresholds.jsx"; import Button from "@mui/material/Button"; // Utils -import { settingsValidation } from "../../Validation/validation"; +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"; -import SettingsStats from "./SettingsStats"; +import { setTimezone, setMode, setLanguage, setShowURL } from "../../../Features/UI/uiSlice.js"; +import SettingsStats from "./SettingsStats.jsx"; -import { useFetchSettings, useSaveSettings } from "../../Hooks/settingsHooks"; -import { useIsAdmin } from "../../Hooks/useIsAdmin"; +import { useFetchSettings, useSaveSettings } from "../../../Hooks/v1/settingsHooks.js"; +import { useIsAdmin } from "../../../Hooks/v1/useIsAdmin.js"; import { useAddDemoMonitors, useDeleteAllMonitors, useDeleteMonitorStats, -} from "../../Hooks/monitorHooks"; +} from "../../../Hooks/v1/monitorHooks.js"; // Constants const BREADCRUMBS = [{ name: `Settings`, path: "/settings" }]; diff --git a/client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx b/client/src/Pages/v1/StatusPage/Create/Components/MonitorList/index.jsx similarity index 100% rename from client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx rename to client/src/Pages/v1/StatusPage/Create/Components/MonitorList/index.jsx diff --git a/client/src/Pages/StatusPage/Create/Components/Progress/index.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx similarity index 83% rename from client/src/Pages/StatusPage/Create/Components/Progress/index.jsx rename to client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx index 14c4ce0f5..c9f7d5f8f 100644 --- a/client/src/Pages/StatusPage/Create/Components/Progress/index.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx @@ -1,8 +1,8 @@ import { Button, Box } from "@mui/material"; -import ProgressUpload from "../../../../../Components/ProgressBars"; +import ProgressUpload from "../../../../../../Components/ProgressBars/index.jsx"; import ImageIcon from "@mui/icons-material/Image"; import { useTranslation } from "react-i18next"; -import { formatBytes } from "../../../../../Utils/fileUtils"; +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/StatusPage/Create/Components/Skeleton/index.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Skeleton/index.jsx similarity index 100% rename from client/src/Pages/StatusPage/Create/Components/Skeleton/index.jsx rename to client/src/Pages/v1/StatusPage/Create/Components/Skeleton/index.jsx diff --git a/client/src/Pages/StatusPage/Create/Components/Tabs/ConfigStack.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx similarity index 91% rename from client/src/Pages/StatusPage/Create/Components/Tabs/ConfigStack.jsx rename to client/src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx index e313f0e85..fb5278c4e 100644 --- a/client/src/Pages/StatusPage/Create/Components/Tabs/ConfigStack.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx @@ -1,5 +1,5 @@ import { Stack, Typography } from "@mui/material"; -import ConfigBox from "../../../../../Components/ConfigBox"; +import ConfigBox from "../../../../../../Components/ConfigBox/index.jsx"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx similarity index 90% rename from client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx rename to client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx index 327a2fb0a..23e713c5a 100644 --- a/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx @@ -1,14 +1,14 @@ // Components import { Stack, Typography } from "@mui/material"; import { TabPanel } from "@mui/lab"; -import MonitorList from "../MonitorList"; -import Search from "../../../../../Components/Inputs/Search"; -import Checkbox from "../../../../../Components/Inputs/Checkbox"; +import MonitorList from "../MonitorList/index.jsx"; +import Search from "../../../../../../Components/Inputs/Search/index.jsx"; +import Checkbox from "../../../../../../Components/Inputs/Checkbox/index.jsx"; // Utils import { useState } from "react"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import ConfigStack from "./ConfigStack"; +import ConfigStack from "./ConfigStack.jsx"; const Content = ({ tabValue, form, diff --git a/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx similarity index 87% rename from client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx rename to client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx index dd4345158..a4612e3da 100644 --- a/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx @@ -1,17 +1,17 @@ // Components import { Stack, Typography } from "@mui/material"; import { TabPanel } from "@mui/lab"; -import ConfigBox from "../../../../../Components/ConfigBox"; -import Checkbox from "../../../../../Components/Inputs/Checkbox"; -import TextInput from "../../../../../Components/Inputs/TextInput"; -import Search from "../../../../../Components/Inputs/Search"; -import ImageUpload from "../../../../../Components/Inputs/ImageUpload"; -import ColorPicker from "../../../../../Components/Inputs/ColorPicker"; -import Progress from "../Progress"; +import ConfigBox from "../../../../../../Components/ConfigBox/index.jsx"; +import Checkbox from "../../../../../../Components/Inputs/Checkbox/index.jsx"; +import TextInput from "../../../../../../Components/Inputs/TextInput/index.jsx"; +import Search from "../../../../../../Components/Inputs/Search/index.jsx"; +import ImageUpload from "../../../../../../Components/Inputs/ImageUpload/index.jsx"; +import ColorPicker from "../../../../../../Components/Inputs/ColorPicker/index.jsx"; +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/StatusPage/Create/Components/Tabs/index.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx similarity index 93% rename from client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx rename to client/src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx index 60d86e1c9..971b8d85c 100644 --- a/client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx @@ -1,12 +1,12 @@ // Components import { TabContext } from "@mui/lab"; import { Tab } from "@mui/material"; -import Settings from "./Settings"; -import Content from "./Content"; +import Settings from "./Settings.jsx"; +import Content from "./Content.jsx"; // Utils import PropTypes from "prop-types"; -import CustomTabList from "../../../../../Components/Tab"; +import CustomTabList from "../../../../../../Components/Tab/index.jsx"; const Tabs = ({ isCreate, diff --git a/client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx b/client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx similarity index 83% rename from client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx rename to client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx index a2ac5f55d..808598052 100644 --- a/client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx @@ -1,6 +1,6 @@ import { useState } from "react"; -import { networkService } from "../../../../main"; -import { createToast } from "../../../../Utils/toastUtils"; +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/StatusPage/Create/Hooks/useMonitorsFetch.jsx b/client/src/Pages/v1/StatusPage/Create/Hooks/useMonitorsFetch.jsx similarity index 88% rename from client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx rename to client/src/Pages/v1/StatusPage/Create/Hooks/useMonitorsFetch.jsx index f380bed9a..2a7db202a 100644 --- a/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Hooks/useMonitorsFetch.jsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; -import { networkService } from "../../../../main"; +import { networkService } from "../../../../../main.jsx"; import { useSelector } from "react-redux"; -import { createToast } from "../../../../Utils/toastUtils"; +import { createToast } from "../../../../../Utils/toastUtils.jsx"; const useMonitorsFetch = () => { const { user } = useSelector((state) => state.auth); diff --git a/client/src/Pages/StatusPage/Create/index.jsx b/client/src/Pages/v1/StatusPage/Create/index.jsx similarity index 92% rename from client/src/Pages/StatusPage/Create/index.jsx rename to client/src/Pages/v1/StatusPage/Create/index.jsx index d4cbc0fd5..674a158ac 100644 --- a/client/src/Pages/StatusPage/Create/index.jsx +++ b/client/src/Pages/v1/StatusPage/Create/index.jsx @@ -1,23 +1,23 @@ // Components import { Stack, Button, Typography } from "@mui/material"; -import Tabs from "./Components/Tabs"; -import GenericFallback from "../../../Components/GenericFallback"; -import SkeletonLayout from "./Components/Skeleton"; -import Dialog from "../../../Components/Dialog"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; +import Tabs from "./Components/Tabs/index.jsx"; +import GenericFallback from "../../../../Components/GenericFallback/index.jsx"; +import SkeletonLayout from "./Components/Skeleton/index.jsx"; +import Dialog from "../../../../Components/Dialog/index.jsx"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; //Utils import { useTheme } from "@emotion/react"; import { useState, useEffect, useRef, useCallback } from "react"; -import { statusPageValidation } from "../../../Validation/validation"; -import { buildErrors } from "../../../Validation/error"; -import { useMonitorsFetch } from "./Hooks/useMonitorsFetch"; -import { useCreateStatusPage } from "./Hooks/useCreateStatusPage"; -import { createToast } from "../../../Utils/toastUtils"; +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 { useNavigate } from "react-router-dom"; -import { useStatusPageFetch } from "../Status/Hooks/useStatusPageFetch"; +import { useStatusPageFetch } from "../Status/Hooks/useStatusPageFetch.jsx"; import { useParams } from "react-router-dom"; import { useTranslation } from "react-i18next"; -import { useStatusPageDelete } from "../Status/Hooks/useStatusPageDelete"; +import { useStatusPageDelete } from "../Status/Hooks/useStatusPageDelete.jsx"; //Constants const ERROR_TAB_MAPPING = [ ["companyName", "url", "timezone", "color", "isPublished", "logo"], diff --git a/client/src/Pages/StatusPage/Status/Components/AdminLink/index.jsx b/client/src/Pages/v1/StatusPage/Status/Components/AdminLink/index.jsx similarity index 100% rename from client/src/Pages/StatusPage/Status/Components/AdminLink/index.jsx rename to client/src/Pages/v1/StatusPage/Status/Components/AdminLink/index.jsx diff --git a/client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx b/client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx similarity index 92% rename from client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx rename to client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx index beac3b0d2..fa5ed71f1 100644 --- a/client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx +++ b/client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx @@ -1,8 +1,8 @@ // Components import { Box, Stack, Typography, Button } from "@mui/material"; -import Image from "../../../../../Components/Image"; -import SettingsIcon from "../../../../../assets/icons/settings-bold.svg?react"; -import ThemeSwitch from "../../../../../Components/ThemeSwitch"; +import Image from "../../../../../../Components/Image/index.jsx"; +import SettingsIcon from "../../../../../../assets/icons/settings-bold.svg?react"; +import ThemeSwitch from "../../../../../../Components/ThemeSwitch/index.jsx"; import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward"; //Utils import { useTheme } from "@mui/material/styles"; diff --git a/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx b/client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx similarity index 82% rename from client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx rename to client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx index 545974c57..fd04dae66 100644 --- a/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx +++ b/client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx @@ -1,12 +1,12 @@ // Components import { Stack, Box } from "@mui/material"; -import Host from "../../../../../Components/Host"; -import StatusPageBarChart from "../../../../../Components/Charts/StatusPageBarChart"; -import { StatusLabel } from "../../../../../Components/Label"; +import Host from "../../../../../../Components/Host/index.jsx"; +import StatusPageBarChart from "../../../../../../Components/Charts/StatusPageBarChart/index.jsx"; +import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; //Utils import { useTheme } from "@mui/material/styles"; -import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils"; +import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; import PropTypes from "prop-types"; import { useSelector } from "react-redux"; diff --git a/client/src/Pages/StatusPage/Status/Components/Skeleton/index.jsx b/client/src/Pages/v1/StatusPage/Status/Components/Skeleton/index.jsx similarity index 100% rename from client/src/Pages/StatusPage/Status/Components/Skeleton/index.jsx rename to client/src/Pages/v1/StatusPage/Status/Components/Skeleton/index.jsx diff --git a/client/src/Pages/StatusPage/Status/Components/StatusBar/index.jsx b/client/src/Pages/v1/StatusPage/Status/Components/StatusBar/index.jsx similarity index 100% rename from client/src/Pages/StatusPage/Status/Components/StatusBar/index.jsx rename to client/src/Pages/v1/StatusPage/Status/Components/StatusBar/index.jsx diff --git a/client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx b/client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageDelete.jsx similarity index 89% rename from client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx rename to client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageDelete.jsx index 2c39c70b3..038a771ba 100644 --- a/client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx +++ b/client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageDelete.jsx @@ -1,6 +1,6 @@ import { useState } from "react"; -import { networkService } from "../../../../Utils/NetworkService"; -import { createToast } from "../../../../Utils/toastUtils"; +import { networkService } from "../../../../../Utils/NetworkService.js"; +import { createToast } from "../../../../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; /** diff --git a/client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx b/client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx similarity index 87% rename from client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx rename to client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx index ae9c7b579..506082887 100644 --- a/client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx +++ b/client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx @@ -1,9 +1,9 @@ import { useEffect, useState, useCallback } from "react"; -import { networkService } from "../../../../main"; +import { networkService } from "../../../../../main.jsx"; import { useSelector } from "react-redux"; -import { createToast } from "../../../../Utils/toastUtils"; +import { createToast } from "../../../../../Utils/toastUtils.jsx"; import { useTheme } from "@emotion/react"; -import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils"; +import { useMonitorUtils } from "../../../../../Hooks/v1/useMonitorUtils.js"; const useStatusPageFetch = (isCreate = false, url) => { const [isLoading, setIsLoading] = useState(true); diff --git a/client/src/Pages/StatusPage/Status/index.jsx b/client/src/Pages/v1/StatusPage/Status/index.jsx similarity index 85% rename from client/src/Pages/StatusPage/Status/index.jsx rename to client/src/Pages/v1/StatusPage/Status/index.jsx index a77f2a380..48fc44fc9 100644 --- a/client/src/Pages/StatusPage/Status/index.jsx +++ b/client/src/Pages/v1/StatusPage/Status/index.jsx @@ -1,18 +1,18 @@ // Components import { Typography, Stack } from "@mui/material"; -import GenericFallback from "../../../Components/GenericFallback"; -import AdminLink from "./Components/AdminLink"; -import ControlsHeader from "./Components/ControlsHeader"; -import SkeletonLayout from "./Components/Skeleton"; -import StatusBar from "./Components/StatusBar"; -import MonitorsList from "./Components/MonitorsList"; -import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx"; -import TextLink from "../../../Components/TextLink"; +import GenericFallback from "../../../../Components/GenericFallback/index.jsx"; +import AdminLink from "./Components/AdminLink/index.jsx"; +import ControlsHeader from "./Components/ControlsHeader/index.jsx"; +import SkeletonLayout from "./Components/Skeleton/index.jsx"; +import StatusBar from "./Components/StatusBar/index.jsx"; +import MonitorsList from "./Components/MonitorsList/index.jsx"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import TextLink from "../../../../Components/TextLink/index.jsx"; // Utils -import { useStatusPageFetch } from "./Hooks/useStatusPageFetch"; +import { useStatusPageFetch } from "./Hooks/useStatusPageFetch.jsx"; import { useTheme } from "@emotion/react"; -import { useIsAdmin } from "../../../Hooks/useIsAdmin"; +import { useIsAdmin } from "../../../../Hooks/v1/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/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx b/client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx similarity index 93% rename from client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx rename to client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx index 04d7a6cfb..d8d68fb2e 100644 --- a/client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx +++ b/client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx @@ -1,7 +1,7 @@ -import DataTable from "../../../../../Components/Table"; +import DataTable from "../../../../../../Components/Table/index.jsx"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import { StatusLabel } from "../../../../../Components/Label"; +import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward"; import { Stack, Typography } from "@mui/material"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx b/client/src/Pages/v1/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx similarity index 86% rename from client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx rename to client/src/Pages/v1/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx index b236368c8..89df1b84d 100644 --- a/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx +++ b/client/src/Pages/v1/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx @@ -1,7 +1,7 @@ import { useState, useEffect } from "react"; -import { networkService } from "../../../../main"; +import { networkService } from "../../../../../main.jsx"; import { useSelector } from "react-redux"; -import { createToast } from "../../../../Utils/toastUtils"; +import { createToast } from "../../../../../Utils/toastUtils.jsx"; const useStatusPagesFetch = () => { const { user } = useSelector((state) => state.auth); diff --git a/client/src/Pages/StatusPage/StatusPages/index.jsx b/client/src/Pages/v1/StatusPage/StatusPages/index.jsx similarity index 70% rename from client/src/Pages/StatusPage/StatusPages/index.jsx rename to client/src/Pages/v1/StatusPage/StatusPages/index.jsx index fa26c2b81..8e0c89ce3 100644 --- a/client/src/Pages/StatusPage/StatusPages/index.jsx +++ b/client/src/Pages/v1/StatusPage/StatusPages/index.jsx @@ -1,13 +1,13 @@ // Components import { Stack } from "@mui/material"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import MonitorCreateHeader from "../../../Components/MonitorCreateHeader"; -import StatusPagesTable from "./Components/StatusPagesTable"; -import PageStateWrapper from "../../../Components/PageStateWrapper"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import MonitorCreateHeader from "../../../../Components/MonitorCreateHeader/index.jsx"; +import StatusPagesTable from "./Components/StatusPagesTable/index.jsx"; +import PageStateWrapper from "../../../../Components/PageStateWrapper/index.jsx"; // Utils import { useTheme } from "@emotion/react"; -import { useStatusPagesFetch } from "./Hooks/useStatusPagesFetch"; -import { useIsAdmin } from "../../../Hooks/useIsAdmin"; +import { useStatusPagesFetch } from "./Hooks/useStatusPagesFetch.jsx"; +import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; const BREADCRUMBS = [{ name: `Status Pages`, path: "" }]; const StatusPages = () => { diff --git a/client/src/Pages/Uptime/BulkImport/Upload.jsx b/client/src/Pages/v1/Uptime/BulkImport/Upload.jsx similarity index 100% rename from client/src/Pages/Uptime/BulkImport/Upload.jsx rename to client/src/Pages/v1/Uptime/BulkImport/Upload.jsx diff --git a/client/src/Pages/Uptime/BulkImport/index.jsx b/client/src/Pages/v1/Uptime/BulkImport/index.jsx similarity index 89% rename from client/src/Pages/Uptime/BulkImport/index.jsx rename to client/src/Pages/v1/Uptime/BulkImport/index.jsx index 9490b4eab..412ba0f97 100644 --- a/client/src/Pages/Uptime/BulkImport/index.jsx +++ b/client/src/Pages/v1/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"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import ConfigBox from "../../../Components/ConfigBox"; -import UploadFile from "./Upload"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import ConfigBox from "../../../../Components/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/monitorHooks"; +import { useCreateBulkMonitors } from "../../../../Hooks/v1/monitorHooks.js"; const BulkImport = () => { const theme = useTheme(); diff --git a/client/src/Pages/Uptime/Create/index.css b/client/src/Pages/v1/Uptime/Create/index.css similarity index 100% rename from client/src/Pages/Uptime/Create/index.css rename to client/src/Pages/v1/Uptime/Create/index.css diff --git a/client/src/Pages/Uptime/Create/index.jsx b/client/src/Pages/v1/Uptime/Create/index.jsx similarity index 94% rename from client/src/Pages/Uptime/Create/index.jsx rename to client/src/Pages/v1/Uptime/Create/index.jsx index 3f905fa11..6bb2a3b33 100644 --- a/client/src/Pages/Uptime/Create/index.jsx +++ b/client/src/Pages/v1/Uptime/Create/index.jsx @@ -9,31 +9,31 @@ import { Tooltip, Typography, } from "@mui/material"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import TextInput from "../../../Components/Inputs/TextInput"; -import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments"; -import Radio from "../../../Components/Inputs/Radio"; -import Select from "../../../Components/Inputs/Select"; -import ConfigBox from "../../../Components/ConfigBox"; -import NotificationsConfig from "../../../Components/NotificationConfig"; -import Checkbox from "../../../Components/Inputs/Checkbox"; -import Dialog from "../../../Components/Dialog"; -import PulseDot from "../../../Components/Animated/PulseDot"; -import SkeletonLayout from "./skeleton"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import { HttpAdornment } from "../../../../Components/Inputs/TextInput/Adornments/index.jsx"; +import Radio from "../../../../Components/Inputs/Radio/index.jsx"; +import Select from "../../../../Components/Inputs/Select/index.jsx"; +import ConfigBox from "../../../../Components/ConfigBox/index.jsx"; +import NotificationsConfig from "../../../../Components/NotificationConfig/index.jsx"; +import Checkbox from "../../../../Components/Inputs/Checkbox/index.jsx"; +import Dialog from "../../../../Components/Dialog/index.jsx"; +import PulseDot from "../../../../Components/Animated/PulseDot.jsx"; +import SkeletonLayout from "./skeleton.jsx"; // Utils 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"; -import { createToast } from "../../../Utils/toastUtils"; +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/useMonitorUtils"; -import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications"; +import { useMonitorUtils } from "../../../../Hooks/v1/useMonitorUtils.js"; +import { useGetNotificationsByTeamId } from "../../../../Hooks/v1/useNotifications.js"; import { useParams } from "react-router-dom"; import { useCreateMonitor, @@ -42,7 +42,7 @@ import { usePauseMonitor, useFetchMonitorById, useFetchMonitorGames, -} from "../../../Hooks/monitorHooks"; +} from "../../../../Hooks/v1/monitorHooks.js"; /** * Create page renders monitor creation or configuration views. diff --git a/client/src/Pages/Uptime/Create/skeleton.jsx b/client/src/Pages/v1/Uptime/Create/skeleton.jsx similarity index 100% rename from client/src/Pages/Uptime/Create/skeleton.jsx rename to client/src/Pages/v1/Uptime/Create/skeleton.jsx diff --git a/client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx b/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx similarity index 86% rename from client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx rename to client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx index b60a4dd04..c6b76c289 100644 --- a/client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx +++ b/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx @@ -1,15 +1,15 @@ // Components import { Stack, Typography, Box } from "@mui/material"; -import ChartBox from "../../../../../Components/Charts/ChartBox"; -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"; -import DownBarChart from "../Charts/DownBarChart"; -import ResponseGaugeChart from "../Charts/ResponseGaugeChart"; -import SkeletonLayout from "./skeleton"; +import ChartBox from "../../../../../../Components/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 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"; +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/Uptime/Details/Components/ChartBoxes/skeleton.jsx b/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/skeleton.jsx similarity index 100% rename from client/src/Pages/Uptime/Details/Components/ChartBoxes/skeleton.jsx rename to client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/skeleton.jsx diff --git a/client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/CustomLabels.jsx similarity index 93% rename from client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx rename to client/src/Pages/v1/Uptime/Details/Components/Charts/CustomLabels.jsx index 8df33953f..c9c1ff58d 100644 --- a/client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx +++ b/client/src/Pages/v1/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"; +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/Uptime/Details/Components/Charts/DownBarChart.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/DownBarChart.jsx similarity index 97% rename from client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx rename to client/src/Pages/v1/Uptime/Details/Components/Charts/DownBarChart.jsx index 662abc831..c5e08ecca 100644 --- a/client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx +++ b/client/src/Pages/v1/Uptime/Details/Components/Charts/DownBarChart.jsx @@ -2,7 +2,7 @@ import { memo, useState } from "react"; import { useTheme } from "@mui/material"; import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts"; import PropTypes from "prop-types"; -import CustomLabels from "./CustomLabels"; +import CustomLabels from "./CustomLabels.jsx"; const DownBarChart = memo(({ groupedDownChecks = [], type, onBarHover }) => { const theme = useTheme(); diff --git a/client/src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx similarity index 100% rename from client/src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx rename to client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx diff --git a/client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx similarity index 60% rename from client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx rename to client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx index e2d8dea80..f306dfd76 100644 --- a/client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx +++ b/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx @@ -1,7 +1,7 @@ -import ChartBox from "../../../../../Components/Charts/ChartBox"; -import MonitorDetailsAreaChart from "../../../../../Components/Charts/MonitorDetailsAreaChart"; -import ResponseTimeIcon from "../../../../../assets/icons/response-time-icon.svg?react"; -import SkeletonLayout from "./ResponseTimeChartSkeleton"; +import ChartBox from "../../../../../../Components/Charts/ChartBox/index.jsx"; +import MonitorDetailsAreaChart from "../../../../../../Components/Charts/MonitorDetailsAreaChart/index.jsx"; +import ResponseTimeIcon from "../../../../../../assets/icons/response-time-icon.svg?react"; +import SkeletonLayout from "./ResponseTimeChartSkeleton.jsx"; import PropTypes from "prop-types"; const ResponseTImeChart = ({ isLoading = false, groupedChecks = [], dateRange }) => { diff --git a/client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx similarity index 100% rename from client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx rename to client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx diff --git a/client/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/UpBarChart.jsx similarity index 98% rename from client/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx rename to client/src/Pages/v1/Uptime/Details/Components/Charts/UpBarChart.jsx index 317147b74..8f1af8912 100644 --- a/client/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx +++ b/client/src/Pages/v1/Uptime/Details/Components/Charts/UpBarChart.jsx @@ -2,7 +2,7 @@ import { memo, useState } from "react"; import { useTheme } from "@mui/material"; import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts"; import PropTypes from "prop-types"; -import CustomLabels from "./CustomLabels"; +import CustomLabels from "./CustomLabels.jsx"; const getThemeColor = (responseTime) => { if (responseTime < 200) { diff --git a/client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx b/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx similarity index 76% rename from client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx rename to client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx index 4d482aa07..cc5745a26 100644 --- a/client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx +++ b/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx @@ -1,12 +1,12 @@ -import ChartBox from "../../../../../Components/Charts/ChartBox"; +import ChartBox from "../../../../../../Components/Charts/ChartBox/index.jsx"; import PropTypes from "prop-types"; -import HistoryIcon from "../../../../../assets/icons/history-icon.svg?react"; -import Table from "../../../../../Components/Table"; -import TablePagination from "../../../../../Components/Table/TablePagination"; -import { StatusLabel } from "../../../../../Components/Label"; +import HistoryIcon from "../../../../../../assets/icons/history-icon.svg?react"; +import Table from "../../../../../../Components/Table/index.jsx"; +import TablePagination from "../../../../../../Components/Table/TablePagination/index.jsx"; +import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; import { useTranslation } from "react-i18next"; -import { formatDateWithTz } from "../../../../../Utils/timeUtils"; -import SkeletonLayout from "./skeleton"; +import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js"; +import SkeletonLayout from "./skeleton.jsx"; const ResponseTable = ({ isLoading = false, checks = [], diff --git a/client/src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx b/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/skeleton.jsx similarity index 100% rename from client/src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx rename to client/src/Pages/v1/Uptime/Details/Components/ResponseTable/skeleton.jsx diff --git a/client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx b/client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx similarity index 85% rename from client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx rename to client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx index abeb251fe..f0988466c 100644 --- a/client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx +++ b/client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx @@ -1,11 +1,11 @@ -import StatusBoxes from "../../../../../Components/StatusBoxes"; -import StatBox from "../../../../../Components/StatBox"; +import StatusBoxes from "../../../../../../Components/StatusBoxes/index.jsx"; +import StatBox from "../../../../../../Components/StatBox/index.jsx"; import PropTypes from "prop-types"; -import { getHumanReadableDuration } from "../../../../../Utils/timeUtils"; +import { getHumanReadableDuration } from "../../../../../../Utils/timeUtils.js"; import { useTheme } from "@mui/material/styles"; import { Typography } from "@mui/material"; -import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils"; +import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; const UptimeStatusBoxes = ({ isLoading = false, diff --git a/client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx b/client/src/Pages/v1/Uptime/Details/Hooks/useCertificateFetch.jsx similarity index 84% rename from client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx rename to client/src/Pages/v1/Uptime/Details/Hooks/useCertificateFetch.jsx index ad0db6f70..f85111f97 100644 --- a/client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx +++ b/client/src/Pages/v1/Uptime/Details/Hooks/useCertificateFetch.jsx @@ -1,7 +1,7 @@ -import { logger } from "../../../../Utils/Logger"; +import { logger } from "../../../../../Utils/Logger.js"; import { useEffect, useState } from "react"; -import { networkService } from "../../../../main"; -import { formatDateWithTz } from "../../../../Utils/timeUtils"; +import { networkService } from "../../../../../main.jsx"; +import { formatDateWithTz } from "../../../../../Utils/timeUtils.js"; const useCertificateFetch = ({ monitor, diff --git a/client/src/Pages/Uptime/Details/index.jsx b/client/src/Pages/v1/Uptime/Details/index.jsx similarity index 83% rename from client/src/Pages/Uptime/Details/index.jsx rename to client/src/Pages/v1/Uptime/Details/index.jsx index c72a5756f..4e773dc2b 100644 --- a/client/src/Pages/Uptime/Details/index.jsx +++ b/client/src/Pages/v1/Uptime/Details/index.jsx @@ -1,12 +1,12 @@ // Components -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import MonitorDetailsControlHeader from "../../../Components/MonitorDetailsControlHeader"; -import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader"; -import ChartBoxes from "./Components/ChartBoxes"; -import ResponseTimeChart from "./Components/Charts/ResponseTimeChart"; -import ResponseTable from "./Components/ResponseTable"; -import UptimeStatusBoxes from "./Components/UptimeStatusBoxes"; -import GenericFallback from "../../../Components/GenericFallback"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import MonitorDetailsControlHeader from "../../../../Components/MonitorDetailsControlHeader/index.jsx"; +import MonitorTimeFrameHeader from "../../../../Components/MonitorTimeFrameHeader/index.jsx"; +import ChartBoxes from "./Components/ChartBoxes/index.jsx"; +import ResponseTimeChart from "./Components/Charts/ResponseTimeChart.jsx"; +import ResponseTable from "./Components/ResponseTable/index.jsx"; +import UptimeStatusBoxes from "./Components/UptimeStatusBoxes/index.jsx"; +import GenericFallback from "../../../../Components/GenericFallback/index.jsx"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; @@ -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/useIsAdmin"; -import { useFetchUptimeMonitorById } from "../../../Hooks/monitorHooks"; -import useCertificateFetch from "./Hooks/useCertificateFetch"; -import { useFetchChecksByMonitor } from "../../../Hooks/checkHooks"; +import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; +import { useFetchUptimeMonitorById } from "../../../../Hooks/v1/monitorHooks.js"; +import useCertificateFetch from "./Hooks/useCertificateFetch.jsx"; +import { useFetchChecksByMonitor } from "../../../../Hooks/v1/checkHooks.js"; import { useTranslation } from "react-i18next"; // Constants diff --git a/client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx similarity index 98% rename from client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx rename to client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx index 0efb06b6c..85c4a8540 100644 --- a/client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx +++ b/client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx @@ -1,6 +1,6 @@ import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; -import FilterHeader from "../../../../../Components/FilterHeader"; +import FilterHeader from "../../../../../../Components/FilterHeader/index.jsx"; import { useMemo } from "react"; import { Box, Button } from "@mui/material"; import ClearIcon from "@mui/icons-material/Clear"; diff --git a/client/src/Pages/Uptime/Monitors/Components/LoadingSpinner/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/LoadingSpinner/index.jsx similarity index 100% rename from client/src/Pages/Uptime/Monitors/Components/LoadingSpinner/index.jsx rename to client/src/Pages/v1/Uptime/Monitors/Components/LoadingSpinner/index.jsx diff --git a/client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx similarity index 89% rename from client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx rename to client/src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx index 0c6eec854..3339102a2 100644 --- a/client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx +++ b/client/src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx @@ -1,7 +1,7 @@ import { useState } from "react"; -import Search from "../../../../../Components/Inputs/Search"; +import Search from "../../../../../../Components/Inputs/Search/index.jsx"; import { Box } from "@mui/material"; -import useDebounce from "../../Hooks/useDebounce"; +import useDebounce from "../../Hooks/useDebounce.jsx"; import { useEffect, useRef } from "react"; import PropTypes from "prop-types"; diff --git a/client/src/Pages/Uptime/Monitors/Components/Skeleton/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/Skeleton/index.jsx similarity index 100% rename from client/src/Pages/Uptime/Monitors/Components/Skeleton/index.jsx rename to client/src/Pages/v1/Uptime/Monitors/Components/Skeleton/index.jsx diff --git a/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/index.jsx similarity index 91% rename from client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx rename to client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/index.jsx index 9cd29c4ca..5cba9e3b1 100644 --- a/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx +++ b/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/index.jsx @@ -1,9 +1,9 @@ import PropTypes from "prop-types"; import { Stack } from "@mui/material"; -import StatusBox from "./statusBox"; +import StatusBox from "./statusBox.jsx"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import SkeletonLayout from "./skeleton"; +import SkeletonLayout from "./skeleton.jsx"; const StatusBoxes = ({ shouldRender, monitorsSummary }) => { const theme = useTheme(); diff --git a/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx similarity index 100% rename from client/src/Pages/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx rename to client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx diff --git a/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx similarity index 90% rename from client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx rename to client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx index 78d555409..c745e7935 100644 --- a/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx +++ b/client/src/Pages/v1/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/Uptime/Monitors/Components/UptimeDataTable/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx similarity index 90% rename from client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx rename to client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx index 63a40f85a..575647eab 100644 --- a/client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx +++ b/client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx @@ -1,19 +1,19 @@ // Components import { Box, Stack } from "@mui/material"; -import DataTable from "../../../../../Components/Table"; +import DataTable from "../../../../../../Components/Table/index.jsx"; import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded"; import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded"; -import Host from "../../../../../Components/Host"; -import { StatusLabel } from "../../../../../Components/Label"; -import BarChart from "../../../../../Components/Charts/BarChart"; -import ActionsMenu from "../../../../../Components/ActionsMenu"; +import Host from "../../../../../../Components/Host/index.jsx"; +import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; +import BarChart from "../../../../../../Components/Charts/BarChart/index.jsx"; +import ActionsMenu from "../../../../../../Components/ActionsMenu/index.jsx"; -import LoadingSpinner from "../LoadingSpinner"; -import TableSkeleton from "../../../../../Components/Table/skeleton"; +import LoadingSpinner from "../LoadingSpinner/index.jsx"; +import TableSkeleton from "../../../../../../Components/Table/skeleton.jsx"; // Utils import { useTheme } from "@emotion/react"; -import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils"; +import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; import { useNavigate } from "react-router-dom"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/Uptime/Monitors/Hooks/useDebounce.jsx b/client/src/Pages/v1/Uptime/Monitors/Hooks/useDebounce.jsx similarity index 100% rename from client/src/Pages/Uptime/Monitors/Hooks/useDebounce.jsx rename to client/src/Pages/v1/Uptime/Monitors/Hooks/useDebounce.jsx diff --git a/client/src/Pages/Uptime/Monitors/index.jsx b/client/src/Pages/v1/Uptime/Monitors/index.jsx similarity index 86% rename from client/src/Pages/Uptime/Monitors/index.jsx rename to client/src/Pages/v1/Uptime/Monitors/index.jsx index 7f28c1cd7..dae0bc2d5 100644 --- a/client/src/Pages/Uptime/Monitors/index.jsx +++ b/client/src/Pages/v1/Uptime/Monitors/index.jsx @@ -4,32 +4,32 @@ // 2a.List of monitors must have the total number of monitors that match. // Components -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import Greeting from "../../../Utils/greeting"; -import StatusBoxes from "./Components/StatusBoxes"; -import UptimeDataTable from "./Components/UptimeDataTable"; -import Pagination from "../../../Components/Table/TablePagination"; -import CreateMonitorHeader from "../../../Components/MonitorCreateHeader"; -import SearchComponent from "./Components/SearchComponent"; -import Filter from "./Components/Filter"; -import PageStateWrapper from "../../../Components/PageStateWrapper"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.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/Table/TablePagination/index.jsx"; +import CreateMonitorHeader from "../../../../Components/MonitorCreateHeader/index.jsx"; +import SearchComponent from "./Components/SearchComponent/index.jsx"; +import Filter from "./Components/Filter/index.jsx"; +import PageStateWrapper from "../../../../Components/PageStateWrapper/index.jsx"; -import MonitorCountHeader from "../../../Components/MonitorCountHeader"; +import MonitorCountHeader from "../../../../Components/MonitorCountHeader/index.jsx"; // MUI Components import { Stack, Box, Button } from "@mui/material"; // Utils import { useState, useCallback, useEffect } from "react"; -import { useIsAdmin } from "../../../Hooks/useIsAdmin"; +import { useIsAdmin } from "../../../../Hooks/v1/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"; +import { setRowsPerPage } from "../../../../Features/UI/uiSlice.js"; import PropTypes from "prop-types"; import { useFetchMonitorsWithSummary, useFetchMonitorsWithChecks, -} from "../../../Hooks/monitorHooks"; +} from "../../../../Hooks/v1/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 7ddcd7e3c..0de847cb7 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -1,57 +1,57 @@ import { Navigate, Route, Routes as LibRoutes } from "react-router"; import HomeLayout from "../Components/Layouts/HomeLayout"; -import NotFound from "../Pages/NotFound"; +import NotFound from "../Pages/v1/NotFound"; // Auth -import AuthLogin from "../Pages/Auth/Login"; -import AuthRegister from "../Pages/Auth/Register/"; -import AuthForgotPassword from "../Pages/Auth/ForgotPassword"; -import AuthCheckEmail from "../Pages/Auth/CheckEmail"; -import AuthSetNewPassword from "../Pages/Auth/SetNewPassword"; -import AuthNewPasswordConfirmed from "../Pages/Auth/NewPasswordConfirmed"; +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"; // Uptime -import Uptime from "../Pages/Uptime/Monitors"; -import UptimeDetails from "../Pages/Uptime/Details"; -import UptimeCreate from "../Pages/Uptime/Create"; +import Uptime from "../Pages/v1/Uptime/Monitors"; +import UptimeDetails from "../Pages/v1/Uptime/Details"; +import UptimeCreate from "../Pages/v1/Uptime/Create"; // PageSpeed -import PageSpeed from "../Pages/PageSpeed/Monitors"; -import PageSpeedDetails from "../Pages/PageSpeed/Details"; -import PageSpeedCreate from "../Pages/PageSpeed/Create"; +import PageSpeed from "../Pages/v1/PageSpeed/Monitors"; +import PageSpeedDetails from "../Pages/v1/PageSpeed/Details"; +import PageSpeedCreate from "../Pages/v1/PageSpeed/Create"; // Infrastructure -import Infrastructure from "../Pages/Infrastructure/Monitors"; -import InfrastructureCreate from "../Pages/Infrastructure/Create"; -import InfrastructureDetails from "../Pages/Infrastructure/Details"; +import Infrastructure from "../Pages/v1/Infrastructure/Monitors"; +import InfrastructureCreate from "../Pages/v1/Infrastructure/Create"; +import InfrastructureDetails from "../Pages/v1/Infrastructure/Details"; // Server Status -import ServerUnreachable from "../Pages/ServerUnreachable"; +import ServerUnreachable from "../Pages/v1/ServerUnreachable.jsx"; // Incidents -import Incidents from "../Pages/Incidents"; +import Incidents from "../Pages/v1/Incidents"; // Status pages -import CreateStatus from "../Pages/StatusPage/Create"; -import StatusPages from "../Pages/StatusPage/StatusPages"; -import Status from "../Pages/StatusPage/Status"; +import CreateStatus from "../Pages/v1/StatusPage/Create"; +import StatusPages from "../Pages/v1/StatusPage/StatusPages"; +import Status from "../Pages/v1/StatusPage/Status"; -import Notifications from "../Pages/Notifications"; -import CreateNotifications from "../Pages/Notifications/create"; +import Notifications from "../Pages/v1/Notifications"; +import CreateNotifications from "../Pages/v1/Notifications/create"; // Settings -import Account from "../Pages/Account"; -import EditUser from "../Pages/Account/EditUser"; -import Settings from "../Pages/Settings"; +import Account from "../Pages/v1/Account"; +import EditUser from "../Pages/v1/Account/EditUser"; +import Settings from "../Pages/v1/Settings"; -import Maintenance from "../Pages/Maintenance"; +import Maintenance from "../Pages/v1/Maintenance"; import ProtectedRoute from "../Components/ProtectedRoute"; import RoleProtectedRoute from "../Components/RoleProtectedRoute"; -import CreateNewMaintenanceWindow from "../Pages/Maintenance/CreateMaintenance"; +import CreateNewMaintenanceWindow from "../Pages/v1/Maintenance/CreateMaintenance"; import withAdminCheck from "../Components/HOC/withAdminCheck"; -import BulkImport from "../Pages/Uptime/BulkImport"; -import Logs from "../Pages/Logs"; +import BulkImport from "../Pages/v1/Uptime/BulkImport"; +import Logs from "../Pages/v1/Logs"; const Routes = () => { const AdminCheckedRegister = withAdminCheck(AuthRegister); diff --git a/client/tsconfig.app.json b/client/tsconfig.app.json new file mode 100644 index 000000000..f44839e9f --- /dev/null +++ b/client/tsconfig.app.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + "paths": { + "@/*": ["./src/*"] + }, + "allowJs": true, + "checkJs": false + }, + "include": ["src"] +} diff --git a/client/tsconfig.json b/client/tsconfig.json new file mode 100644 index 000000000..a1a0f47d0 --- /dev/null +++ b/client/tsconfig.json @@ -0,0 +1,14 @@ +{ + "files": [], + "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }], + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Bundler", + "skipLibCheck": true, + "paths": { + "@/*": ["./src/*"] + }, + "allowJs": true, + "checkJs": false + } +} diff --git a/client/tsconfig.node.json b/client/tsconfig.node.json new file mode 100644 index 000000000..bbd18d2b2 --- /dev/null +++ b/client/tsconfig.node.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + "allowJs": true, + "checkJs": false + }, + "include": ["vite.config.ts"] +} diff --git a/client/vite.config.js b/client/vite.config.ts similarity index 63% rename from client/vite.config.js rename to client/vite.config.ts index 07b5de748..66630e88c 100644 --- a/client/vite.config.js +++ b/client/vite.config.ts @@ -1,10 +1,8 @@ -import { defineConfig, loadEnv } from "vite"; +import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import svgr from "vite-plugin-svgr"; -import { execSync } from "child_process"; -export default defineConfig(({ mode }) => { - const env = loadEnv(mode, process.cwd(), ""); +export default defineConfig(({}) => { let version = "3.2.0"; return { From 17b84cbe8ac03e26b38447098ebfbd55a2197a79 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 25 Sep 2025 10:19:08 -0700 Subject: [PATCH 022/103] format --- client/src/Pages/v1/Account/components/ProfilePanel.jsx | 6 +++++- .../Create/hooks/useInfrastructureSubmit.jsx | 5 ++++- .../Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx | 5 ++++- client/src/Pages/v1/Settings/index.jsx | 7 ++++++- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/client/src/Pages/v1/Account/components/ProfilePanel.jsx b/client/src/Pages/v1/Account/components/ProfilePanel.jsx index 8e607f4bd..1ce1d34da 100644 --- a/client/src/Pages/v1/Account/components/ProfilePanel.jsx +++ b/client/src/Pages/v1/Account/components/ProfilePanel.jsx @@ -7,7 +7,11 @@ import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; import ImageUpload from "../../../../Components/Inputs/ImageUpload/index.jsx"; import { newOrChangedCredentials } from "../../../../Validation/validation.js"; import { useDispatch, useSelector } from "react-redux"; -import { clearAuthState, deleteUser, update } from "../../../../Features/Auth/authSlice.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/Dialog/genericDialog.jsx"; diff --git a/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx b/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx index 0fd5c0081..8a97486c7 100644 --- a/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx @@ -1,4 +1,7 @@ -import { useCreateMonitor, useUpdateMonitor } from "../../../../../Hooks/v1/monitorHooks.js"; +import { + useCreateMonitor, + useUpdateMonitor, +} from "../../../../../Hooks/v1/monitorHooks.js"; const useInfrastructureSubmit = () => { const [createMonitor, isCreating] = useCreateMonitor(); const [updateMonitor, isUpdating] = useUpdateMonitor(); diff --git a/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx b/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx index db1f6e712..5857a7a7e 100644 --- a/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx @@ -7,7 +7,10 @@ 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 { + formatDateWithTz, + formatDurationSplit, +} from "../../../../../../Utils/timeUtils.js"; import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; import { useState } from "react"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Settings/index.jsx b/client/src/Pages/v1/Settings/index.jsx index d27685ed6..d1788eddf 100644 --- a/client/src/Pages/v1/Settings/index.jsx +++ b/client/src/Pages/v1/Settings/index.jsx @@ -16,7 +16,12 @@ 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"; From e29f7af85894c57d2e4f34a274b69acd621ca264 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 25 Sep 2025 10:21:31 -0700 Subject: [PATCH 023/103] add client build check --- .github/workflows/check-build.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml index 8417b823f..5ce8c9c26 100644 --- a/.github/workflows/check-build.yml +++ b/.github/workflows/check-build.yml @@ -4,6 +4,23 @@ on: workflow_dispatch: jobs: + build-client: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install client dependencies + working-directory: client + run: npm install + + - name: Check client build + working-directory: client + run: npm run build build-server: runs-on: ubuntu-latest steps: From 28ef465b3a5bd19b5eabd72002baa02bc3f3ffcc Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 25 Sep 2025 10:22:47 -0700 Subject: [PATCH 024/103] update action name --- .github/workflows/check-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml index 5ce8c9c26..4d2de7041 100644 --- a/.github/workflows/check-build.yml +++ b/.github/workflows/check-build.yml @@ -1,4 +1,4 @@ -name: Build Check ( Server) +name: Build Check (Client & Server) on: pull_request: workflow_dispatch: From cb1e10f0a95fd671ad8fc227dccd754adcd6d655 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 25 Sep 2025 16:06:52 -0700 Subject: [PATCH 025/103] add v2 login page --- client/package-lock.json | 65 +++++++++- client/package.json | 6 +- .../Components/Inputs/TextInput/indexV2.tsx | 6 + client/src/Hooks/v2/UseApi.tsx | 52 ++++++++ client/src/Pages/v2/Auth/Login.tsx | 113 ++++++++++++++++++ client/src/Routes/index.jsx | 15 ++- client/src/Utils/ApiClient.ts | 21 ++++ client/src/Utils/Theme/constants.js | 1 - client/src/Utils/Theme/v2/palette.ts | 82 +++++++++++++ client/src/Utils/Theme/v2/theme.ts | 52 ++++++++ client/src/types/env.d.ts | 9 ++ client/src/types/mui.d.ts | 20 ++++ client/vite.config.ts | 6 + server/src/app.js | 2 +- 14 files changed, 445 insertions(+), 5 deletions(-) create mode 100644 client/src/Components/Inputs/TextInput/indexV2.tsx create mode 100644 client/src/Hooks/v2/UseApi.tsx create mode 100644 client/src/Pages/v2/Auth/Login.tsx create mode 100644 client/src/Utils/ApiClient.ts create mode 100644 client/src/Utils/Theme/v2/palette.ts create mode 100644 client/src/Utils/Theme/v2/theme.ts create mode 100644 client/src/types/env.d.ts create mode 100644 client/src/types/mui.d.ts diff --git a/client/package-lock.json b/client/package-lock.json index ee1ab1bc1..de297943a 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -11,6 +11,7 @@ "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@hello-pangea/dnd": "^18.0.0", + "@hookform/resolvers": "5.2.2", "@mui/icons-material": "6.4.11", "@mui/lab": "6.0.0-dev.240424162023-9968b4889d", "@mui/material": "6.4.11", @@ -26,6 +27,7 @@ "mui-color-input": "^6.0.0", "react": "18.3.1", "react-dom": "^18.2.0", + "react-hook-form": "7.63.0", "react-i18next": "^15.4.0", "react-icons": "5.5.0", "react-redux": "9.2.0", @@ -34,7 +36,9 @@ "react-toastify": "^10.0.5", "recharts": "2.15.2", "redux-persist": "6.0.0", - "vite-plugin-svgr": "^4.2.0" + "swr": "2.3.6", + "vite-plugin-svgr": "^4.2.0", + "zod": "4.1.11" }, "devDependencies": { "@types/react": "^18.2.66", @@ -653,6 +657,18 @@ "react-dom": "^18.0.0 || ^19.0.0" } }, + "node_modules/@hookform/resolvers": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.2.tgz", + "integrity": "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==", + "license": "MIT", + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "react-hook-form": "^7.55.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -2991,6 +3007,15 @@ "node": ">=0.4.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -5343,6 +5368,22 @@ "react": "^18.3.1" } }, + "node_modules/react-hook-form": { + "version": "7.63.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.63.0.tgz", + "integrity": "sha512-ZwueDMvUeucovM2VjkCf7zIHcs1aAlDimZu2Hvel5C5907gUzMpm4xCrQXtRzCvsBqFjonB4m3x4LzCFI1ZKWA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-i18next": { "version": "15.5.1", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.1.tgz", @@ -6141,6 +6182,19 @@ "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", "license": "MIT" }, + "node_modules/swr": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.6.tgz", + "integrity": "sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/text-segmentation": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", @@ -6639,6 +6693,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.11.tgz", + "integrity": "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/client/package.json b/client/package.json index 9ebc99647..03d0cf6bc 100644 --- a/client/package.json +++ b/client/package.json @@ -16,6 +16,7 @@ "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@hello-pangea/dnd": "^18.0.0", + "@hookform/resolvers": "5.2.2", "@mui/icons-material": "6.4.11", "@mui/lab": "6.0.0-dev.240424162023-9968b4889d", "@mui/material": "6.4.11", @@ -31,6 +32,7 @@ "mui-color-input": "^6.0.0", "react": "18.3.1", "react-dom": "^18.2.0", + "react-hook-form": "7.63.0", "react-i18next": "^15.4.0", "react-icons": "5.5.0", "react-redux": "9.2.0", @@ -39,7 +41,9 @@ "react-toastify": "^10.0.5", "recharts": "2.15.2", "redux-persist": "6.0.0", - "vite-plugin-svgr": "^4.2.0" + "swr": "2.3.6", + "vite-plugin-svgr": "^4.2.0", + "zod": "4.1.11" }, "unusedDepencies": { "@solana/wallet-adapter-base": "0.9.25", diff --git a/client/src/Components/Inputs/TextInput/indexV2.tsx b/client/src/Components/Inputs/TextInput/indexV2.tsx new file mode 100644 index 000000000..bc1f144b6 --- /dev/null +++ b/client/src/Components/Inputs/TextInput/indexV2.tsx @@ -0,0 +1,6 @@ +import TextField from "@mui/material/TextField"; +import Box from "@mui/material/Box"; +import type { TextFieldProps } from "@mui/material"; +export const TextInput = (props: TextFieldProps) => { + return ; +}; diff --git a/client/src/Hooks/v2/UseApi.tsx b/client/src/Hooks/v2/UseApi.tsx new file mode 100644 index 000000000..fdb4e8a24 --- /dev/null +++ b/client/src/Hooks/v2/UseApi.tsx @@ -0,0 +1,52 @@ +import { useState } from "react"; +import useSWR from "swr"; +import type { SWRConfiguration } from "swr"; +import type { AxiosRequestConfig } from "axios"; +import { get, post } from "@/Utils/ApiClient"; // your axios wrapper + +// Generic fetcher for GET requests +const fetcher = async (url: string, config?: AxiosRequestConfig) => { + const res = await get(url, config); + return res.data; +}; + +export const useGet = ( + url: string, + axiosConfig?: AxiosRequestConfig, + swrConfig?: SWRConfiguration +) => { + const { data, error, isLoading, mutate } = useSWR( + url, + (url) => fetcher(url, axiosConfig), + swrConfig + ); + + return { + response: data ?? null, + loading: isLoading, + error: error?.message ?? null, + refetch: mutate, + }; +}; + +export const usePost = (endpoint: string) => { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const postFn = async (body: B, config?: AxiosRequestConfig): Promise => { + setLoading(true); + setError(null); + + try { + const res = await post(endpoint, body, config); + return res.data; + } catch (err: any) { + setError(err?.message ?? "Unknown error"); + return null; + } finally { + setLoading(false); + } + }; + + return { post: postFn, loading, error }; +}; diff --git a/client/src/Pages/v2/Auth/Login.tsx b/client/src/Pages/v2/Auth/Login.tsx new file mode 100644 index 000000000..d1d4ec014 --- /dev/null +++ b/client/src/Pages/v2/Auth/Login.tsx @@ -0,0 +1,113 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import { useTranslation } from "react-i18next"; +import { useForm, Controller } from "react-hook-form"; +import { useTheme } from "@mui/material/styles"; + +import { TextInput } from "@/Components/Inputs/TextInput/indexV2.tsx"; +import Button from "@mui/material/Button"; +import Stack from "@mui/material/Stack"; +import { usePost } from "@/Hooks/v2/UseApi"; +const schema = z.object({ + email: z.email("Invalid email address"), + password: z.string().min(6, "Password must be at least 6 characters"), +}); + +type FormData = z.infer; +type LoginData = { + username: string; + password: string; +}; + +const Login = () => { + const { t } = useTranslation(); + const theme = useTheme(); + const { post, loading, error } = usePost("/auth/login"); + + const { + handleSubmit, + control, + formState: { errors }, + } = useForm({ + resolver: zodResolver(schema), // ⬅️ connect Zod + defaultValues: { + email: "", + password: "", + }, + }); + + const onSubmit = async (data: FormData) => { + const result = await post(data); + if (result) { + console.log("Login successful:", result); + } else { + console.error("Login failed:", error); + } + }; + + return ( + + + ( + + )} + /> + ( + + )} + /> + + + + ); +}; + +export default Login; diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index 0de847cb7..06a4b1920 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -1,9 +1,12 @@ +import { useSelector } from "react-redux"; +import { lightTheme, darkTheme } from "@/Utils/Theme/v2/theme"; import { Navigate, Route, Routes as LibRoutes } from "react-router"; +import { ThemeProvider } from "@emotion/react"; import HomeLayout from "../Components/Layouts/HomeLayout"; import NotFound from "../Pages/v1/NotFound"; - // Auth import AuthLogin from "../Pages/v1/Auth/Login"; +import AuthLoginV2 from "@/Pages/v2/Auth/Login"; import AuthRegister from "../Pages/v1/Auth/Register/"; import AuthForgotPassword from "../Pages/v1/Auth/ForgotPassword"; import AuthCheckEmail from "../Pages/v1/Auth/CheckEmail"; @@ -54,6 +57,8 @@ import BulkImport from "../Pages/v1/Uptime/BulkImport"; import Logs from "../Pages/v1/Logs"; const Routes = () => { + const mode = useSelector((state) => state.ui.mode); + const v2Theme = mode === "light" ? lightTheme : darkTheme; const AdminCheckedRegister = withAdminCheck(AuthRegister); return ( @@ -214,6 +219,14 @@ const Routes = () => { path="/login" element={} /> + + + + } + /> ( + url: string, + config: AxiosRequestConfig = {} +): Promise> => api.get(url, config); + +export const post = ( + url: string, + data: any, + config: AxiosRequestConfig = {} +): Promise> => api.post(url, data, config); + +export default api; diff --git a/client/src/Utils/Theme/constants.js b/client/src/Utils/Theme/constants.js index 9df6665d0..45217fdeb 100644 --- a/client/src/Utils/Theme/constants.js +++ b/client/src/Utils/Theme/constants.js @@ -100,7 +100,6 @@ const newColors = { blueGray900: "#515151", blueBlueWave: "#1570EF", lightBlueWave: "#CDE2FF", - /* I changed green 100 and green 700. Need to change red and warning as well, and refactor the object following the structure */ green100: "#67cd78", green200: "#4B9B77", green400: "#079455", diff --git a/client/src/Utils/Theme/v2/palette.ts b/client/src/Utils/Theme/v2/palette.ts new file mode 100644 index 000000000..2875f750d --- /dev/null +++ b/client/src/Utils/Theme/v2/palette.ts @@ -0,0 +1,82 @@ +import { lighten, darken } from "@mui/material/styles"; + +const typographyBase = 13; + +export const typographyLevels = { + base: typographyBase, + xs: `${(typographyBase - 4) / 16}rem`, + s: `${(typographyBase - 2) / 16}rem`, + m: `${typographyBase / 16}rem`, + l: `${(typographyBase + 2) / 16}rem`, + xl: `${(typographyBase + 10) / 16}rem`, +}; + +const colors = { + offWhite: "#FEFEFE", + offBlack: "#131315", + gray0: "#FDFDFD", + gray10: "#F4F4FF", + gray50: "#F9F9F9", + gray100: "#F3F3F3", + gray200: "#EFEFEF", + gray250: "#DADADA", + gray500: "#A2A3A3", + gray900: "#1c1c1c", + blueGray50: "#E8F0FE", + blueGray500: "#475467", + blueGray600: "#344054", + blueGray800: "#1C2130", + blueGray900: "#515151", + blueBlueWave: "#1570EF", + lightBlueWave: "#CDE2FF", + green100: "#67cd78", + green200: "#4B9B77", + green400: "#079455", + green700: "#026513", + orange100: "#FD8F22", + orange200: "#D69A5D", + orange600: "#9B734B", + orange700: "#884605", + red100: "#F27C7C", + red400: "#D92020", + red600: "#9B4B4B", + red700: "#980303", +}; + +export const lightPalette = { + accent: { + main: colors.blueBlueWave, + light: lighten(colors.blueBlueWave, 0.2), + dark: darken(colors.blueBlueWave, 0.2), + contrastText: colors.offWhite, + }, + primary: { + main: colors.offWhite, + contrastText: colors.blueGray800, + contrastTextSecondary: colors.blueGray600, + }, + secondary: { + main: colors.gray200, + light: colors.lightBlueWave, + contrastText: colors.blueGray600, + }, +}; + +export const darkPalette = { + accent: { + main: colors.blueBlueWave, + light: lighten(colors.blueBlueWave, 0.2), + dark: darken(colors.blueBlueWave, 0.2), + contrastText: colors.offWhite, + }, + primary: { + main: colors.offBlack, + contrastText: colors.blueGray50, + contrastTextSecondary: colors.gray200, + }, + secondary: { + main: "#313131", + light: colors.lightBlueWave, + contrastText: colors.gray200, + }, +}; diff --git a/client/src/Utils/Theme/v2/theme.ts b/client/src/Utils/Theme/v2/theme.ts new file mode 100644 index 000000000..ce81ea2fd --- /dev/null +++ b/client/src/Utils/Theme/v2/theme.ts @@ -0,0 +1,52 @@ +import { createTheme } from "@mui/material"; +import { lightPalette, darkPalette, typographyLevels } from "./palette"; +const fontFamilyPrimary = '"Inter" , sans-serif'; + +export const theme = (mode: string, palette: any) => + createTheme({ + spacing: 2, + palette: { + mode: mode, + ...palette, + }, + typography: { + fontFamily: fontFamilyPrimary, + fontSize: typographyLevels.base, + }, + + components: { + MuiFormLabel: { + styleOverrides: { + root: ({ theme }) => ({ + fontSize: typographyLevels.base, + "&.Mui-focused": { + color: theme.palette.secondary.contrastText, + }, + }), + }, + }, + MuiInputLabel: { + styleOverrides: { + root: ({ theme }) => ({ + top: `-${theme.spacing(4)}`, + "&.MuiInputLabel-shrink": { + top: 0, + }, + }), + }, + }, + MuiTextField: { + styleOverrides: { + root: ({ theme }) => ({ + "& .MuiOutlinedInput-root": { + height: 34, + fontSize: typographyLevels.base, + }, + }), + }, + }, + }, + }); + +export const lightTheme = createTheme(theme("light", lightPalette)); +export const darkTheme = createTheme(theme("dark", darkPalette)); diff --git a/client/src/types/env.d.ts b/client/src/types/env.d.ts new file mode 100644 index 000000000..e2bce23dd --- /dev/null +++ b/client/src/types/env.d.ts @@ -0,0 +1,9 @@ +/// + +interface ImportMetaEnv { + readonly VITE_APP_API_V2_BASE_URL?: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/client/src/types/mui.d.ts b/client/src/types/mui.d.ts new file mode 100644 index 000000000..851ffea70 --- /dev/null +++ b/client/src/types/mui.d.ts @@ -0,0 +1,20 @@ +import "@mui/material/Button"; + +declare module "@mui/material/styles" { + interface Palette { + accent: Palette["primary"]; + } + interface PaletteOptions { + accent?: PaletteOptions["primary"]; + } + + interface PaletteColor { + contrastTextSecondary?: string; + } +} + +declare module "@mui/material/Button" { + interface ButtonPropsColorOverrides { + accent: true; + } +} diff --git a/client/vite.config.ts b/client/vite.config.ts index 66630e88c..a99c1a254 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import svgr from "vite-plugin-svgr"; +import path from "path"; export default defineConfig(({}) => { let version = "3.2.0"; @@ -8,6 +9,11 @@ export default defineConfig(({}) => { return { base: "/", plugins: [svgr(), react()], + resolve: { + alias: { + "@": path.resolve(__dirname, "src"), + }, + }, optimizeDeps: { include: ["@mui/material/Tooltip", "@emotion/styled"], }, diff --git a/server/src/app.js b/server/src/app.js index 253baee88..4578de896 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -27,7 +27,7 @@ export const createApp = ({ services, controllers, envSettings, frontendPath, op cors({ origin: allowedOrigin, methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS", - allowedHeaders: "*", + allowedHeaders: ["Content-Type", "Authorization"], credentials: true, }) ); From 0c5b9a863b5531417c2fce6b25df4ac9ea400b77 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 26 Sep 2025 14:16:59 -0700 Subject: [PATCH 026/103] add loading --- client/src/Pages/v2/Auth/Login.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/Pages/v2/Auth/Login.tsx b/client/src/Pages/v2/Auth/Login.tsx index d1d4ec014..6a76fdf76 100644 --- a/client/src/Pages/v2/Auth/Login.tsx +++ b/client/src/Pages/v2/Auth/Login.tsx @@ -99,6 +99,7 @@ const Login = () => { /> + {error && {error}} + + + ); +}; + +export default Register; diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index 06a4b1920..39959bc1c 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -8,6 +8,7 @@ import NotFound from "../Pages/v1/NotFound"; import AuthLogin from "../Pages/v1/Auth/Login"; import AuthLoginV2 from "@/Pages/v2/Auth/Login"; import AuthRegister from "../Pages/v1/Auth/Register/"; +import AuthRegisterV2 from "@/Pages/v2/Auth/Register"; import AuthForgotPassword from "../Pages/v1/Auth/ForgotPassword"; import AuthCheckEmail from "../Pages/v1/Auth/CheckEmail"; import AuthSetNewPassword from "../Pages/v1/Auth/SetNewPassword"; @@ -232,6 +233,14 @@ const Routes = () => { path="/register" element={} /> + + + + } + /> Date: Fri, 26 Sep 2025 15:31:55 -0700 Subject: [PATCH 033/103] move v2 routes to v2 router --- client/src/Routes/index.jsx | 27 +++++++-------------------- client/src/Routes/v2router.tsx | 29 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 20 deletions(-) create mode 100644 client/src/Routes/v2router.tsx diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index 39959bc1c..2e4fd9c50 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -1,14 +1,11 @@ import { useSelector } from "react-redux"; import { lightTheme, darkTheme } from "@/Utils/Theme/v2/theme"; import { Navigate, Route, Routes as LibRoutes } from "react-router"; -import { ThemeProvider } from "@emotion/react"; import HomeLayout from "../Components/Layouts/HomeLayout"; import NotFound from "../Pages/v1/NotFound"; // Auth import AuthLogin from "../Pages/v1/Auth/Login"; -import AuthLoginV2 from "@/Pages/v2/Auth/Login"; import AuthRegister from "../Pages/v1/Auth/Register/"; -import AuthRegisterV2 from "@/Pages/v2/Auth/Register"; import AuthForgotPassword from "../Pages/v1/Auth/ForgotPassword"; import AuthCheckEmail from "../Pages/v1/Auth/CheckEmail"; import AuthSetNewPassword from "../Pages/v1/Auth/SetNewPassword"; @@ -57,12 +54,18 @@ import withAdminCheck from "../Components/HOC/withAdminCheck"; import BulkImport from "../Pages/v1/Uptime/BulkImport"; import Logs from "../Pages/v1/Logs"; +import V2Routes from "@/Routes/v2router"; + const Routes = () => { const mode = useSelector((state) => state.ui.mode); - const v2Theme = mode === "light" ? lightTheme : darkTheme; const AdminCheckedRegister = withAdminCheck(AuthRegister); return ( + } + /> + { path="/login" element={} /> - - - - } - /> } /> - - - - } - /> { + const v2Theme = mode === "light" ? lightTheme : darkTheme; + + return ( + + + } + /> + } + /> + + + ); +}; + +export default V2Routes; From 1d5f2427ed692658b9d86111eec95334fc087fbf Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 26 Sep 2025 16:01:05 -0700 Subject: [PATCH 034/103] add root layout --- .../src/Components/v2/Layouts/RootLayout.tsx | 50 +++++++++++++++++++ client/src/Routes/index.jsx | 27 +++------- client/src/Routes/v2router.tsx | 39 +++++++++++++++ 3 files changed, 96 insertions(+), 20 deletions(-) create mode 100644 client/src/Components/v2/Layouts/RootLayout.tsx create mode 100644 client/src/Routes/v2router.tsx diff --git a/client/src/Components/v2/Layouts/RootLayout.tsx b/client/src/Components/v2/Layouts/RootLayout.tsx new file mode 100644 index 000000000..5f78600f4 --- /dev/null +++ b/client/src/Components/v2/Layouts/RootLayout.tsx @@ -0,0 +1,50 @@ +import { useState, useEffect } from "react"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import { useTheme } from "@emotion/react"; + +import { Outlet } from "react-router"; +import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; + +const COLLAPSED_WIDTH = 50; +const EXPANDED_WIDTH = 250; + +const SideBar = () => { + const theme = useTheme(); + const isSmall = useMediaQuery(theme.breakpoints.down("md")); + + const [collapsed, setCollapsed] = useState(false); + useEffect(() => { + setCollapsed(isSmall); + }, [isSmall]); + return ( + + setCollapsed(!collapsed)} + > + Sidebar Content + + + ); +}; + +const RootLayout = ({ mode = "light" }) => { + return ( + + + + + ); +}; + +export default RootLayout; diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index 39959bc1c..2e4fd9c50 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -1,14 +1,11 @@ import { useSelector } from "react-redux"; import { lightTheme, darkTheme } from "@/Utils/Theme/v2/theme"; import { Navigate, Route, Routes as LibRoutes } from "react-router"; -import { ThemeProvider } from "@emotion/react"; import HomeLayout from "../Components/Layouts/HomeLayout"; import NotFound from "../Pages/v1/NotFound"; // Auth import AuthLogin from "../Pages/v1/Auth/Login"; -import AuthLoginV2 from "@/Pages/v2/Auth/Login"; import AuthRegister from "../Pages/v1/Auth/Register/"; -import AuthRegisterV2 from "@/Pages/v2/Auth/Register"; import AuthForgotPassword from "../Pages/v1/Auth/ForgotPassword"; import AuthCheckEmail from "../Pages/v1/Auth/CheckEmail"; import AuthSetNewPassword from "../Pages/v1/Auth/SetNewPassword"; @@ -57,12 +54,18 @@ import withAdminCheck from "../Components/HOC/withAdminCheck"; import BulkImport from "../Pages/v1/Uptime/BulkImport"; import Logs from "../Pages/v1/Logs"; +import V2Routes from "@/Routes/v2router"; + const Routes = () => { const mode = useSelector((state) => state.ui.mode); - const v2Theme = mode === "light" ? lightTheme : darkTheme; const AdminCheckedRegister = withAdminCheck(AuthRegister); return ( + } + /> + { path="/login" element={} /> - - - - } - /> } /> - - - - } - /> { + const v2Theme = mode === "light" ? lightTheme : darkTheme; + + return ( + + + } + /> + } + /> + } + > + Test Page} + /> + + + + ); +}; + +export default V2Routes; From 4d254b004dd8440ec08bbfc01f04ffcbd229e190 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sat, 27 Sep 2025 14:38:14 -0700 Subject: [PATCH 035/103] add gitignore --- .gitignore | 1 + client/src/Components/v2/Layouts/RootLayout.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 172702b40..997dff9c5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +.idea .vscode .VSCodeCounter \ No newline at end of file diff --git a/client/src/Components/v2/Layouts/RootLayout.tsx b/client/src/Components/v2/Layouts/RootLayout.tsx index 5f78600f4..4082242ef 100644 --- a/client/src/Components/v2/Layouts/RootLayout.tsx +++ b/client/src/Components/v2/Layouts/RootLayout.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from "react"; import useMediaQuery from "@mui/material/useMediaQuery"; -import { useTheme } from "@emotion/react"; +import { useTheme } from "@mui/material/styles"; import { Outlet } from "react-router"; import Stack from "@mui/material/Stack"; From 09fbc6b0b267406ffa6cf256cc2a09fdbe6b4294 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sat, 27 Sep 2025 14:48:05 -0700 Subject: [PATCH 036/103] remove unused mode --- client/src/Components/v2/Layouts/RootLayout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/Components/v2/Layouts/RootLayout.tsx b/client/src/Components/v2/Layouts/RootLayout.tsx index 4082242ef..be2f42e30 100644 --- a/client/src/Components/v2/Layouts/RootLayout.tsx +++ b/client/src/Components/v2/Layouts/RootLayout.tsx @@ -35,7 +35,7 @@ const SideBar = () => { ); }; -const RootLayout = ({ mode = "light" }) => { +const RootLayout = () => { return ( Date: Sat, 27 Sep 2025 14:54:55 -0700 Subject: [PATCH 037/103] move components to v1 and v2 dirs --- client/src/App.jsx | 2 +- .../Components/{ => v1}/ActionsMenu/index.jsx | 8 ++-- .../src/Components/{ => v1}/Alert/index.css | 0 .../src/Components/{ => v1}/Alert/index.jsx | 0 .../Components/{ => v1}/Animated/PulseDot.jsx | 0 .../Components/{ => v1}/ArrowLeft/index.jsx | 6 +-- .../Components/{ => v1}/ArrowRight/index.jsx | 4 +- .../src/Components/{ => v1}/Avatar/index.css | 0 .../src/Components/{ => v1}/Avatar/index.jsx | 0 .../Components/{ => v1}/Breadcrumbs/index.css | 0 .../Components/{ => v1}/Breadcrumbs/index.jsx | 2 +- .../{ => v1}/Buttons/RoundGradientButton.jsx | 0 .../{ => v1}/Charts/AreaChart/index.jsx | 2 +- .../{ => v1}/Charts/BarChart/index.css | 0 .../{ => v1}/Charts/BarChart/index.jsx | 2 +- .../{ => v1}/Charts/ChartBox/EmptyView.jsx | 2 +- .../{ => v1}/Charts/ChartBox/index.jsx | 4 +- .../{ => v1}/Charts/CustomGauge/index.css | 0 .../{ => v1}/Charts/CustomGauge/index.jsx | 0 .../Charts/DePINStatusPageBarChart/index.jsx | 2 +- .../{ => v1}/Charts/LegendBox/index.jsx | 2 +- .../Charts/MonitorDetailsAreaChart/index.css | 0 .../Charts/MonitorDetailsAreaChart/index.jsx | 4 +- .../Charts/StatusPageBarChart/index.jsx | 2 +- .../Charts/Utils/chartUtilFunctions.js | 0 .../{ => v1}/Charts/Utils/chartUtils.jsx | 4 +- .../{ => v1}/Charts/Utils/gradientUtils.jsx | 0 .../src/Components/{ => v1}/Check/Check.jsx | 4 +- .../src/Components/{ => v1}/Check/check.css | 0 .../{ => v1}/CircularCount/index.jsx | 0 .../src/Components/{ => v1}/Common/AppBar.jsx | 2 +- .../src/Components/{ => v1}/Common/Footer.jsx | 0 .../Components/{ => v1}/ConfigBox/index.jsx | 0 .../Components/{ => v1}/ConfigRow/index.jsx | 2 +- .../{ => v1}/Dialog/genericDialog.jsx | 0 .../src/Components/{ => v1}/Dialog/index.jsx | 2 +- client/src/Components/{ => v1}/Dot/index.jsx | 0 .../Fallback/FallBackActionButtons.jsx | 0 .../{ => v1}/Fallback/FallbackBackground.jsx | 6 +-- .../{ => v1}/Fallback/FallbackCheckList.jsx | 2 +- .../{ => v1}/Fallback/FallbackContainer.jsx | 0 .../Fallback/FallbackPageSpeedWarning.jsx | 2 +- .../{ => v1}/Fallback/FallbackTitle.jsx | 0 .../Components/{ => v1}/Fallback/index.css | 0 .../Components/{ => v1}/Fallback/index.jsx | 10 ++--- .../{ => v1}/FilterHeader/index.jsx | 0 .../{ => v1}/GenericFallback/NetworkError.jsx | 0 .../{ => v1}/GenericFallback/index.jsx | 6 +-- .../{ => v1}/HOC/withAdminCheck.jsx | 4 +- .../src/Components/{ => v1}/Heading/index.jsx | 0 client/src/Components/{ => v1}/Host/index.jsx | 2 +- .../{ => v1}/HttpStatusLabel/index.jsx | 2 +- .../Components/{ => v1}/I18nLoader/index.jsx | 2 +- .../src/Components/{ => v1}/IconBox/index.jsx | 0 .../src/Components/{ => v1}/Image/index.jsx | 0 .../src/Components/{ => v1}/InfoBox/index.jsx | 4 +- .../{ => v1}/Inputs/Checkbox/index.css | 0 .../{ => v1}/Inputs/Checkbox/index.jsx | 4 +- .../{ => v1}/Inputs/ColorPicker/index.jsx | 0 .../{ => v1}/Inputs/FieldWrapper/index.jsx | 0 .../{ => v1}/Inputs/ImageUpload/index.jsx | 4 +- .../{ => v1}/Inputs/Radio/index.css | 0 .../{ => v1}/Inputs/Radio/index.jsx | 2 +- .../{ => v1}/Inputs/Search/index.jsx | 4 +- .../{ => v1}/Inputs/Select/index.css | 0 .../{ => v1}/Inputs/Select/index.jsx | 2 +- .../Inputs/TextInput/Adornments/index.jsx | 2 +- .../{ => v1}/Inputs/TextInput/index.jsx | 2 +- .../src/Components/{ => v1}/Label/index.css | 0 .../src/Components/{ => v1}/Label/index.jsx | 0 .../{ => v1}/Layouts/AppLayout/index.jsx | 2 +- .../{ => v1}/Layouts/HomeLayout/index.css | 0 .../{ => v1}/Layouts/HomeLayout/index.jsx | 2 +- client/src/Components/{ => v1}/Link/index.jsx | 0 client/src/Components/{ => v1}/Link/link.css | 0 .../{ => v1}/MonitorActions/index.jsx | 4 +- .../{ => v1}/MonitorCountHeader/index.jsx | 2 +- .../{ => v1}/MonitorCountHeader/skeleton.jsx | 0 .../{ => v1}/MonitorCreateHeader/index.jsx | 2 +- .../{ => v1}/MonitorCreateHeader/skeleton.jsx | 0 .../MonitorDetailsControlHeader/index.jsx | 10 ++--- .../MonitorDetailsControlHeader/skeleton.jsx | 0 .../MonitorDetailsControlHeader/status.jsx | 10 ++--- .../{ => v1}/MonitorTimeFrameHeader/index.jsx | 2 +- .../MonitorTimeFrameHeader/skeleton.jsx | 0 .../{ => v1}/NotificationConfig/index.jsx | 2 +- .../NotificationIntegrationModal.jsx | 6 +-- .../Components/TabComponent.jsx | 4 +- .../Components/TabPanel.jsx | 0 .../Hooks/useNotification.js | 4 +- .../{ => v1}/PageStateWrapper/index.jsx | 6 +-- .../{ => v1}/ProgressBars/index.css | 0 .../{ => v1}/ProgressBars/index.jsx | 0 .../{ => v1}/ProgressStepper/index.css | 0 .../{ => v1}/ProgressStepper/index.jsx | 0 .../{ => v1}/ProtectedRoute/index.jsx | 0 .../{ => v1}/RoleProtectedRoute/index.jsx | 0 .../{ => v1}/ShareComponent/index.jsx | 0 .../Sidebar/components/authFooter.jsx | 8 ++-- .../Sidebar/components/collapseButton.jsx | 6 +-- .../{ => v1}/Sidebar/components/logo.jsx | 0 .../{ => v1}/Sidebar/components/navItem.jsx | 0 .../src/Components/{ => v1}/Sidebar/index.jsx | 42 +++++++++---------- .../{ => v1}/Skeletons/FullPage/index.jsx | 0 .../{ => v1}/StandardContainer/index.jsx | 0 .../Components/{ => v1}/StarPrompt/index.jsx | 2 +- .../src/Components/{ => v1}/StatBox/index.jsx | 4 +- .../Components/{ => v1}/StatusBoxes/index.jsx | 2 +- .../{ => v1}/StatusBoxes/skeleton.jsx | 0 .../Components/{ => v1}/Subheader/index.jsx | 0 client/src/Components/{ => v1}/Tab/index.jsx | 0 .../Table/TablePagination/Actions/index.jsx | 4 +- .../{ => v1}/Table/TablePagination/index.jsx | 4 +- .../Components/{ => v1}/Table/TableUtils.js | 0 .../src/Components/{ => v1}/Table/index.jsx | 2 +- .../Components/{ => v1}/Table/skeleton.jsx | 0 .../Components/{ => v1}/TextLink/index.jsx | 0 .../{ => v1}/ThemeSwitch/SunAndMoonIcon.jsx | 0 .../Components/{ => v1}/ThemeSwitch/index.css | 0 .../Components/{ => v1}/ThemeSwitch/index.jsx | 4 +- client/src/Components/{ => v1}/Toast/body.jsx | 0 .../src/Components/{ => v1}/Toast/index.jsx | 2 +- .../indexV2.tsx => v2/Inputs/TextInput.tsx} | 0 client/src/Pages/v1/About/index.jsx | 4 +- .../src/Pages/v1/Account/EditUser/index.jsx | 6 +-- .../components/AddTeamMember/index.jsx | 8 ++-- .../v1/Account/components/PasswordPanel.jsx | 6 +-- .../v1/Account/components/ProfilePanel.jsx | 10 ++--- .../v1/Account/components/RoleTable/index.jsx | 2 +- .../Pages/v1/Account/components/TeamPanel.jsx | 8 ++-- client/src/Pages/v1/Account/index.jsx | 2 +- client/src/Pages/v1/Auth/CheckEmail.jsx | 2 +- client/src/Pages/v1/Auth/ForgotPassword.jsx | 4 +- client/src/Pages/v1/Auth/Login/index.jsx | 6 +-- .../Pages/v1/Auth/NewPasswordConfirmed.jsx | 2 +- client/src/Pages/v1/Auth/Register/index.jsx | 2 +- client/src/Pages/v1/Auth/SetNewPassword.jsx | 8 ++-- .../Pages/v1/Auth/components/AuthHeader.jsx | 2 +- .../v1/Auth/components/PasswordTooltip.jsx | 2 +- .../Components/IncidentTable/index.jsx | 14 +++---- .../Components/OptionsHeader/index.jsx | 2 +- client/src/Pages/v1/Incidents/index.jsx | 6 +-- .../Create/Components/CustomAlertsSection.jsx | 2 +- .../Components/CustomThreshold/index.jsx | 4 +- .../Components/MonitorActionButtons.jsx | 2 +- .../Create/Components/MonitorStatusHeader.jsx | 2 +- .../Pages/v1/Infrastructure/Create/index.jsx | 16 +++---- .../AreaChartBoxes/InfraAreaChart.jsx | 2 +- .../Components/AreaChartBoxes/index.jsx | 2 +- .../Details/Components/GaugeBoxes/Gauge.jsx | 2 +- .../Components/NetworkStats/NetworkCharts.jsx | 2 +- .../NetworkStats/NetworkStatBoxes.jsx | 4 +- .../Details/Components/NetworkStats/index.jsx | 4 +- .../Details/Components/StatusBoxes/index.jsx | 4 +- .../Pages/v1/Infrastructure/Details/index.jsx | 10 ++--- .../Monitors/Components/Filters/index.jsx | 2 +- .../Components/MonitorsTable/index.jsx | 8 ++-- .../Components/MonitorsTableMenu/index.jsx | 2 +- .../v1/Infrastructure/Monitors/index.jsx | 10 ++--- .../Diagnostics/components/gauges/index.jsx | 2 +- .../src/Pages/v1/Logs/Diagnostics/index.jsx | 4 +- client/src/Pages/v1/Logs/Logs/index.jsx | 2 +- .../Queue/components/FailedJobTable/index.jsx | 2 +- .../Logs/Queue/components/JobTable/index.jsx | 4 +- .../Logs/Queue/components/Metrics/index.jsx | 4 +- .../Queue/components/MetricsTable/index.jsx | 2 +- client/src/Pages/v1/Logs/index.jsx | 2 +- .../Components/ConfigSelect/index.jsx | 2 +- .../Components/MonitorsConfig/index.jsx | 4 +- .../Maintenance/CreateMaintenance/index.jsx | 8 ++-- .../MaintenanceTable/ActionsMenu/index.jsx | 2 +- .../v1/Maintenance/MaintenanceTable/index.jsx | 6 +-- client/src/Pages/v1/Maintenance/index.jsx | 4 +- .../Pages/v1/Notifications/create/index.jsx | 10 ++--- client/src/Pages/v1/Notifications/index.jsx | 6 +-- .../src/Pages/v1/PageSpeed/Create/index.jsx | 18 ++++---- .../Components/Charts/AreaChartLegend.jsx | 4 +- .../Components/Charts/PieChartLegend.jsx | 2 +- .../Components/PageSpeedAreaChart/index.jsx | 2 +- .../Components/PageSpeedStatusBoxes/index.jsx | 4 +- .../Components/PerformanceReport/index.jsx | 2 +- .../src/Pages/v1/PageSpeed/Details/index.jsx | 8 ++-- .../Monitors/Components/Card/index.jsx | 4 +- .../src/Pages/v1/PageSpeed/Monitors/index.jsx | 10 ++--- client/src/Pages/v1/ServerUnreachable.jsx | 4 +- .../src/Pages/v1/Settings/SettingsAbout.jsx | 4 +- .../v1/Settings/SettingsDemoMonitors.jsx | 4 +- .../src/Pages/v1/Settings/SettingsEmail.jsx | 8 ++-- .../v1/Settings/SettingsGlobalThresholds.jsx | 4 +- .../Pages/v1/Settings/SettingsPagespeed.jsx | 6 +-- .../src/Pages/v1/Settings/SettingsStats.jsx | 6 +-- .../Pages/v1/Settings/SettingsTimeZone.jsx | 4 +- client/src/Pages/v1/Settings/SettingsUI.jsx | 4 +- client/src/Pages/v1/Settings/SettingsURL.jsx | 4 +- client/src/Pages/v1/Settings/index.jsx | 2 +- .../Create/Components/Progress/index.jsx | 2 +- .../Create/Components/Tabs/ConfigStack.jsx | 2 +- .../Create/Components/Tabs/Content.jsx | 4 +- .../Create/Components/Tabs/Settings.jsx | 12 +++--- .../Create/Components/Tabs/index.jsx | 2 +- .../src/Pages/v1/StatusPage/Create/index.jsx | 6 +-- .../Components/ControlsHeader/index.jsx | 4 +- .../Status/Components/MonitorsList/index.jsx | 6 +-- .../src/Pages/v1/StatusPage/Status/index.jsx | 6 +-- .../Components/StatusPagesTable/index.jsx | 4 +- .../Pages/v1/StatusPage/StatusPages/index.jsx | 6 +-- .../src/Pages/v1/Uptime/BulkImport/index.jsx | 4 +- client/src/Pages/v1/Uptime/Create/index.jsx | 20 ++++----- .../Details/Components/ChartBoxes/index.jsx | 2 +- .../Components/Charts/ResponseTimeChart.jsx | 4 +- .../Components/ResponseTable/index.jsx | 8 ++-- .../Components/UptimeStatusBoxes/index.jsx | 4 +- client/src/Pages/v1/Uptime/Details/index.jsx | 8 ++-- .../Monitors/Components/Filter/index.jsx | 2 +- .../Components/SearchComponent/index.jsx | 2 +- .../Components/UptimeDataTable/index.jsx | 12 +++--- client/src/Pages/v1/Uptime/Monitors/index.jsx | 10 ++--- client/src/Pages/v2/Auth/Login.tsx | 2 +- client/src/Pages/v2/Auth/Register.tsx | 2 +- client/src/Routes/index.jsx | 8 ++-- client/src/Utils/toastUtils.jsx | 2 +- client/src/main.jsx | 2 +- 222 files changed, 370 insertions(+), 370 deletions(-) rename client/src/Components/{ => v1}/ActionsMenu/index.jsx (94%) rename client/src/Components/{ => v1}/Alert/index.css (100%) rename client/src/Components/{ => v1}/Alert/index.jsx (100%) rename client/src/Components/{ => v1}/Animated/PulseDot.jsx (100%) rename client/src/Components/{ => v1}/ArrowLeft/index.jsx (70%) rename client/src/Components/{ => v1}/ArrowRight/index.jsx (73%) rename client/src/Components/{ => v1}/Avatar/index.css (100%) rename client/src/Components/{ => v1}/Avatar/index.jsx (100%) rename client/src/Components/{ => v1}/Breadcrumbs/index.css (100%) rename client/src/Components/{ => v1}/Breadcrumbs/index.jsx (97%) rename client/src/Components/{ => v1}/Buttons/RoundGradientButton.jsx (100%) rename client/src/Components/{ => v1}/Charts/AreaChart/index.jsx (98%) rename client/src/Components/{ => v1}/Charts/BarChart/index.css (100%) rename client/src/Components/{ => v1}/Charts/BarChart/index.jsx (98%) rename client/src/Components/{ => v1}/Charts/ChartBox/EmptyView.jsx (98%) rename client/src/Components/{ => v1}/Charts/ChartBox/index.jsx (95%) rename client/src/Components/{ => v1}/Charts/CustomGauge/index.css (100%) rename client/src/Components/{ => v1}/Charts/CustomGauge/index.jsx (100%) rename client/src/Components/{ => v1}/Charts/DePINStatusPageBarChart/index.jsx (98%) rename client/src/Components/{ => v1}/Charts/LegendBox/index.jsx (95%) rename client/src/Components/{ => v1}/Charts/MonitorDetailsAreaChart/index.css (100%) rename client/src/Components/{ => v1}/Charts/MonitorDetailsAreaChart/index.jsx (97%) rename client/src/Components/{ => v1}/Charts/StatusPageBarChart/index.jsx (98%) rename client/src/Components/{ => v1}/Charts/Utils/chartUtilFunctions.js (100%) rename client/src/Components/{ => v1}/Charts/Utils/chartUtils.jsx (98%) rename client/src/Components/{ => v1}/Charts/Utils/gradientUtils.jsx (100%) rename client/src/Components/{ => v1}/Check/Check.jsx (93%) rename client/src/Components/{ => v1}/Check/check.css (100%) rename client/src/Components/{ => v1}/CircularCount/index.jsx (100%) rename client/src/Components/{ => v1}/Common/AppBar.jsx (99%) rename client/src/Components/{ => v1}/Common/Footer.jsx (100%) rename client/src/Components/{ => v1}/ConfigBox/index.jsx (100%) rename client/src/Components/{ => v1}/ConfigRow/index.jsx (96%) rename client/src/Components/{ => v1}/Dialog/genericDialog.jsx (100%) rename client/src/Components/{ => v1}/Dialog/index.jsx (95%) rename client/src/Components/{ => v1}/Dot/index.jsx (100%) rename client/src/Components/{ => v1}/Fallback/FallBackActionButtons.jsx (100%) rename client/src/Components/{ => v1}/Fallback/FallbackBackground.jsx (78%) rename client/src/Components/{ => v1}/Fallback/FallbackCheckList.jsx (95%) rename client/src/Components/{ => v1}/Fallback/FallbackContainer.jsx (100%) rename client/src/Components/{ => v1}/Fallback/FallbackPageSpeedWarning.jsx (97%) rename client/src/Components/{ => v1}/Fallback/FallbackTitle.jsx (100%) rename client/src/Components/{ => v1}/Fallback/index.css (100%) rename client/src/Components/{ => v1}/Fallback/index.jsx (87%) rename client/src/Components/{ => v1}/FilterHeader/index.jsx (100%) rename client/src/Components/{ => v1}/GenericFallback/NetworkError.jsx (100%) rename client/src/Components/{ => v1}/GenericFallback/index.jsx (89%) rename client/src/Components/{ => v1}/HOC/withAdminCheck.jsx (91%) rename client/src/Components/{ => v1}/Heading/index.jsx (100%) rename client/src/Components/{ => v1}/Host/index.jsx (97%) rename client/src/Components/{ => v1}/HttpStatusLabel/index.jsx (96%) rename client/src/Components/{ => v1}/I18nLoader/index.jsx (88%) rename client/src/Components/{ => v1}/IconBox/index.jsx (100%) rename client/src/Components/{ => v1}/Image/index.jsx (100%) rename client/src/Components/{ => v1}/InfoBox/index.jsx (88%) rename client/src/Components/{ => v1}/Inputs/Checkbox/index.css (100%) rename client/src/Components/{ => v1}/Inputs/Checkbox/index.jsx (95%) rename client/src/Components/{ => v1}/Inputs/ColorPicker/index.jsx (100%) rename client/src/Components/{ => v1}/Inputs/FieldWrapper/index.jsx (100%) rename client/src/Components/{ => v1}/Inputs/ImageUpload/index.jsx (98%) rename client/src/Components/{ => v1}/Inputs/Radio/index.css (100%) rename client/src/Components/{ => v1}/Inputs/Radio/index.jsx (96%) rename client/src/Components/{ => v1}/Inputs/Search/index.jsx (98%) rename client/src/Components/{ => v1}/Inputs/Select/index.css (100%) rename client/src/Components/{ => v1}/Inputs/Select/index.jsx (98%) rename client/src/Components/{ => v1}/Inputs/TextInput/Adornments/index.jsx (96%) rename client/src/Components/{ => v1}/Inputs/TextInput/index.jsx (98%) rename client/src/Components/{ => v1}/Label/index.css (100%) rename client/src/Components/{ => v1}/Label/index.jsx (100%) rename client/src/Components/{ => v1}/Layouts/AppLayout/index.jsx (91%) rename client/src/Components/{ => v1}/Layouts/HomeLayout/index.css (100%) rename client/src/Components/{ => v1}/Layouts/HomeLayout/index.jsx (88%) rename client/src/Components/{ => v1}/Link/index.jsx (100%) rename client/src/Components/{ => v1}/Link/link.css (100%) rename client/src/Components/{ => v1}/MonitorActions/index.jsx (95%) rename client/src/Components/{ => v1}/MonitorCountHeader/index.jsx (95%) rename client/src/Components/{ => v1}/MonitorCountHeader/skeleton.jsx (100%) rename client/src/Components/{ => v1}/MonitorCreateHeader/index.jsx (94%) rename client/src/Components/{ => v1}/MonitorCreateHeader/skeleton.jsx (100%) rename client/src/Components/{ => v1}/MonitorDetailsControlHeader/index.jsx (92%) rename client/src/Components/{ => v1}/MonitorDetailsControlHeader/skeleton.jsx (100%) rename client/src/Components/{ => v1}/MonitorDetailsControlHeader/status.jsx (83%) rename client/src/Components/{ => v1}/MonitorTimeFrameHeader/index.jsx (97%) rename client/src/Components/{ => v1}/MonitorTimeFrameHeader/skeleton.jsx (100%) rename client/src/Components/{ => v1}/NotificationConfig/index.jsx (98%) rename client/src/Components/{ => v1}/NotificationIntegrationModal/Components/NotificationIntegrationModal.jsx (98%) rename client/src/Components/{ => v1}/NotificationIntegrationModal/Components/TabComponent.jsx (95%) rename client/src/Components/{ => v1}/NotificationIntegrationModal/Components/TabPanel.jsx (100%) rename client/src/Components/{ => v1}/NotificationIntegrationModal/Hooks/useNotification.js (96%) rename client/src/Components/{ => v1}/PageStateWrapper/index.jsx (90%) rename client/src/Components/{ => v1}/ProgressBars/index.css (100%) rename client/src/Components/{ => v1}/ProgressBars/index.jsx (100%) rename client/src/Components/{ => v1}/ProgressStepper/index.css (100%) rename client/src/Components/{ => v1}/ProgressStepper/index.jsx (100%) rename client/src/Components/{ => v1}/ProtectedRoute/index.jsx (100%) rename client/src/Components/{ => v1}/RoleProtectedRoute/index.jsx (100%) rename client/src/Components/{ => v1}/ShareComponent/index.jsx (100%) rename client/src/Components/{ => v1}/Sidebar/components/authFooter.jsx (96%) rename client/src/Components/{ => v1}/Sidebar/components/collapseButton.jsx (88%) rename client/src/Components/{ => v1}/Sidebar/components/logo.jsx (100%) rename client/src/Components/{ => v1}/Sidebar/components/navItem.jsx (100%) rename client/src/Components/{ => v1}/Sidebar/index.jsx (75%) rename client/src/Components/{ => v1}/Skeletons/FullPage/index.jsx (100%) rename client/src/Components/{ => v1}/StandardContainer/index.jsx (100%) rename client/src/Components/{ => v1}/StarPrompt/index.jsx (97%) rename client/src/Components/{ => v1}/StatBox/index.jsx (97%) rename client/src/Components/{ => v1}/StatusBoxes/index.jsx (93%) rename client/src/Components/{ => v1}/StatusBoxes/skeleton.jsx (100%) rename client/src/Components/{ => v1}/Subheader/index.jsx (100%) rename client/src/Components/{ => v1}/Tab/index.jsx (100%) rename client/src/Components/{ => v1}/Table/TablePagination/Actions/index.jsx (95%) rename client/src/Components/{ => v1}/Table/TablePagination/index.jsx (96%) rename client/src/Components/{ => v1}/Table/TableUtils.js (100%) rename client/src/Components/{ => v1}/Table/index.jsx (99%) rename client/src/Components/{ => v1}/Table/skeleton.jsx (100%) rename client/src/Components/{ => v1}/TextLink/index.jsx (100%) rename client/src/Components/{ => v1}/ThemeSwitch/SunAndMoonIcon.jsx (100%) rename client/src/Components/{ => v1}/ThemeSwitch/index.css (100%) rename client/src/Components/{ => v1}/ThemeSwitch/index.jsx (91%) rename client/src/Components/{ => v1}/Toast/body.jsx (100%) rename client/src/Components/{ => v1}/Toast/index.jsx (98%) rename client/src/Components/{Inputs/TextInput/indexV2.tsx => v2/Inputs/TextInput.tsx} (100%) diff --git a/client/src/App.jsx b/client/src/App.jsx index b599890f1..04f0d5b65 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -10,7 +10,7 @@ import { logger } from "./Utils/Logger"; // Import the logger import { networkService } from "./main"; import { Routes } from "./Routes"; import WalletProvider from "./Components/WalletProvider"; -import AppLayout from "./Components/Layouts/AppLayout"; +import AppLayout from "@/Components/v1/Layouts/AppLayout"; function App() { const mode = useSelector((state) => state.ui.mode); diff --git a/client/src/Components/ActionsMenu/index.jsx b/client/src/Components/v1/ActionsMenu/index.jsx similarity index 94% rename from client/src/Components/ActionsMenu/index.jsx rename to client/src/Components/v1/ActionsMenu/index.jsx index 78d86e1b2..229c7c875 100644 --- a/client/src/Components/ActionsMenu/index.jsx +++ b/client/src/Components/v1/ActionsMenu/index.jsx @@ -2,17 +2,17 @@ import IconButton from "@mui/material/IconButton"; import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; -import Settings from "../../assets/icons/settings-bold.svg?react"; -import Dialog from "../../Components/Dialog"; +import Settings from "../../../assets/icons/settings-bold.svg?react"; +import Dialog from "../Dialog/index.jsx"; // Utils import { useState } from "react"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import { createToast } from "../../Utils/toastUtils"; +import { createToast } from "../../../Utils/toastUtils.jsx"; import PropTypes from "prop-types"; -import { usePauseMonitor, useDeleteMonitor } from "../../Hooks/v1/monitorHooks.js"; +import { usePauseMonitor, useDeleteMonitor } from "../../../Hooks/v1/monitorHooks.js"; const ActionsMenu = ({ monitor, diff --git a/client/src/Components/Alert/index.css b/client/src/Components/v1/Alert/index.css similarity index 100% rename from client/src/Components/Alert/index.css rename to client/src/Components/v1/Alert/index.css diff --git a/client/src/Components/Alert/index.jsx b/client/src/Components/v1/Alert/index.jsx similarity index 100% rename from client/src/Components/Alert/index.jsx rename to client/src/Components/v1/Alert/index.jsx diff --git a/client/src/Components/Animated/PulseDot.jsx b/client/src/Components/v1/Animated/PulseDot.jsx similarity index 100% rename from client/src/Components/Animated/PulseDot.jsx rename to client/src/Components/v1/Animated/PulseDot.jsx diff --git a/client/src/Components/ArrowLeft/index.jsx b/client/src/Components/v1/ArrowLeft/index.jsx similarity index 70% rename from client/src/Components/ArrowLeft/index.jsx rename to client/src/Components/v1/ArrowLeft/index.jsx index 7909441f3..a327d6bb7 100644 --- a/client/src/Components/ArrowLeft/index.jsx +++ b/client/src/Components/v1/ArrowLeft/index.jsx @@ -1,6 +1,6 @@ -import LeftArrow from "../../assets/icons/left-arrow.svg?react"; -import LeftArrowDouble from "../../assets/icons/left-arrow-double.svg?react"; -import LeftArrowLong from "../../assets/icons/left-arrow-long.svg?react"; +import LeftArrow from "../../../assets/icons/left-arrow.svg?react"; +import LeftArrowDouble from "../../../assets/icons/left-arrow-double.svg?react"; +import LeftArrowLong from "../../../assets/icons/left-arrow-long.svg?react"; import PropTypes from "prop-types"; const ArrowLeft = ({ type, color = "#667085", ...props }) => { diff --git a/client/src/Components/ArrowRight/index.jsx b/client/src/Components/v1/ArrowRight/index.jsx similarity index 73% rename from client/src/Components/ArrowRight/index.jsx rename to client/src/Components/v1/ArrowRight/index.jsx index 335bc19f2..08a2639b0 100644 --- a/client/src/Components/ArrowRight/index.jsx +++ b/client/src/Components/v1/ArrowRight/index.jsx @@ -1,5 +1,5 @@ -import RightArrow from "../../assets/icons/right-arrow.svg?react"; -import RightArrowDouble from "../../assets/icons/right-arrow-double.svg?react"; +import RightArrow from "../../../assets/icons/right-arrow.svg?react"; +import RightArrowDouble from "../../../assets/icons/right-arrow-double.svg?react"; import PropTypes from "prop-types"; const ArrowRight = ({ type, color = "#667085", ...props }) => { diff --git a/client/src/Components/Avatar/index.css b/client/src/Components/v1/Avatar/index.css similarity index 100% rename from client/src/Components/Avatar/index.css rename to client/src/Components/v1/Avatar/index.css diff --git a/client/src/Components/Avatar/index.jsx b/client/src/Components/v1/Avatar/index.jsx similarity index 100% rename from client/src/Components/Avatar/index.jsx rename to client/src/Components/v1/Avatar/index.jsx diff --git a/client/src/Components/Breadcrumbs/index.css b/client/src/Components/v1/Breadcrumbs/index.css similarity index 100% rename from client/src/Components/Breadcrumbs/index.css rename to client/src/Components/v1/Breadcrumbs/index.css diff --git a/client/src/Components/Breadcrumbs/index.jsx b/client/src/Components/v1/Breadcrumbs/index.jsx similarity index 97% rename from client/src/Components/Breadcrumbs/index.jsx rename to client/src/Components/v1/Breadcrumbs/index.jsx index f1af89790..34e5d5e95 100644 --- a/client/src/Components/Breadcrumbs/index.jsx +++ b/client/src/Components/v1/Breadcrumbs/index.jsx @@ -2,7 +2,7 @@ import PropTypes from "prop-types"; import { Box, Breadcrumbs as MUIBreadcrumbs } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import ArrowRight from "../ArrowRight"; +import ArrowRight from "../ArrowRight/index.jsx"; import "./index.css"; /** diff --git a/client/src/Components/Buttons/RoundGradientButton.jsx b/client/src/Components/v1/Buttons/RoundGradientButton.jsx similarity index 100% rename from client/src/Components/Buttons/RoundGradientButton.jsx rename to client/src/Components/v1/Buttons/RoundGradientButton.jsx diff --git a/client/src/Components/Charts/AreaChart/index.jsx b/client/src/Components/v1/Charts/AreaChart/index.jsx similarity index 98% rename from client/src/Components/Charts/AreaChart/index.jsx rename to client/src/Components/v1/Charts/AreaChart/index.jsx index e1f1832e5..dd5a900d6 100644 --- a/client/src/Components/Charts/AreaChart/index.jsx +++ b/client/src/Components/v1/Charts/AreaChart/index.jsx @@ -61,7 +61,7 @@ import { Tooltip, ResponsiveContainer, } from "recharts"; -import { createGradient } from "../Utils/gradientUtils"; +import { createGradient } from "../Utils/gradientUtils.jsx"; import PropTypes from "prop-types"; import { useTheme } from "@mui/material"; import { useId } from "react"; diff --git a/client/src/Components/Charts/BarChart/index.css b/client/src/Components/v1/Charts/BarChart/index.css similarity index 100% rename from client/src/Components/Charts/BarChart/index.css rename to client/src/Components/v1/Charts/BarChart/index.css diff --git a/client/src/Components/Charts/BarChart/index.jsx b/client/src/Components/v1/Charts/BarChart/index.jsx similarity index 98% rename from client/src/Components/Charts/BarChart/index.jsx rename to client/src/Components/v1/Charts/BarChart/index.jsx index 375773b94..2bf8e1798 100644 --- a/client/src/Components/Charts/BarChart/index.jsx +++ b/client/src/Components/v1/Charts/BarChart/index.jsx @@ -1,6 +1,6 @@ import { useTheme } from "@emotion/react"; import { Box, Stack, Tooltip, Typography } from "@mui/material"; -import { formatDateWithTz } from "../../../Utils/timeUtils"; +import { formatDateWithTz } from "../../../../Utils/timeUtils.js"; import { useEffect, useState } from "react"; import "./index.css"; import { useSelector } from "react-redux"; diff --git a/client/src/Components/Charts/ChartBox/EmptyView.jsx b/client/src/Components/v1/Charts/ChartBox/EmptyView.jsx similarity index 98% rename from client/src/Components/Charts/ChartBox/EmptyView.jsx rename to client/src/Components/v1/Charts/ChartBox/EmptyView.jsx index 296197a8c..c1b60632d 100644 --- a/client/src/Components/Charts/ChartBox/EmptyView.jsx +++ b/client/src/Components/v1/Charts/ChartBox/EmptyView.jsx @@ -2,7 +2,7 @@ import { Typography, Stack } from "@mui/material"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; -import IconBox from "../../IconBox"; +import IconBox from "../../IconBox/index.jsx"; /** * `EmptyView` is a functional React component that displays an empty state view with an optional icon, header, and message. diff --git a/client/src/Components/Charts/ChartBox/index.jsx b/client/src/Components/v1/Charts/ChartBox/index.jsx similarity index 95% rename from client/src/Components/Charts/ChartBox/index.jsx rename to client/src/Components/v1/Charts/ChartBox/index.jsx index 2c13ece87..be2faf8ed 100644 --- a/client/src/Components/Charts/ChartBox/index.jsx +++ b/client/src/Components/v1/Charts/ChartBox/index.jsx @@ -1,7 +1,7 @@ import { Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; -import IconBox from "../../IconBox"; -import EmptyView from "./EmptyView"; +import IconBox from "../../IconBox/index.jsx"; +import EmptyView from "./EmptyView.jsx"; import PropTypes from "prop-types"; const ChartBox = ({ diff --git a/client/src/Components/Charts/CustomGauge/index.css b/client/src/Components/v1/Charts/CustomGauge/index.css similarity index 100% rename from client/src/Components/Charts/CustomGauge/index.css rename to client/src/Components/v1/Charts/CustomGauge/index.css diff --git a/client/src/Components/Charts/CustomGauge/index.jsx b/client/src/Components/v1/Charts/CustomGauge/index.jsx similarity index 100% rename from client/src/Components/Charts/CustomGauge/index.jsx rename to client/src/Components/v1/Charts/CustomGauge/index.jsx diff --git a/client/src/Components/Charts/DePINStatusPageBarChart/index.jsx b/client/src/Components/v1/Charts/DePINStatusPageBarChart/index.jsx similarity index 98% rename from client/src/Components/Charts/DePINStatusPageBarChart/index.jsx rename to client/src/Components/v1/Charts/DePINStatusPageBarChart/index.jsx index 65c6278f1..b37990e46 100644 --- a/client/src/Components/Charts/DePINStatusPageBarChart/index.jsx +++ b/client/src/Components/v1/Charts/DePINStatusPageBarChart/index.jsx @@ -5,7 +5,7 @@ import { Stack, Box, Tooltip, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useState, useEffect } from "react"; import PropTypes from "prop-types"; -import { formatDateWithTz } from "../../../Utils/timeUtils"; +import { formatDateWithTz } from "../../../../Utils/timeUtils.js"; import { useSelector } from "react-redux"; const PlaceholderCheck = ({ daysToShow }) => { diff --git a/client/src/Components/Charts/LegendBox/index.jsx b/client/src/Components/v1/Charts/LegendBox/index.jsx similarity index 95% rename from client/src/Components/Charts/LegendBox/index.jsx rename to client/src/Components/v1/Charts/LegendBox/index.jsx index ac14669e3..f19e09105 100644 --- a/client/src/Components/Charts/LegendBox/index.jsx +++ b/client/src/Components/v1/Charts/LegendBox/index.jsx @@ -1,6 +1,6 @@ import { Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; -import IconBox from "../../IconBox"; +import IconBox from "../../IconBox/index.jsx"; import PropTypes from "prop-types"; const LegendBox = ({ children, icon, header, sx }) => { diff --git a/client/src/Components/Charts/MonitorDetailsAreaChart/index.css b/client/src/Components/v1/Charts/MonitorDetailsAreaChart/index.css similarity index 100% rename from client/src/Components/Charts/MonitorDetailsAreaChart/index.css rename to client/src/Components/v1/Charts/MonitorDetailsAreaChart/index.css diff --git a/client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/client/src/Components/v1/Charts/MonitorDetailsAreaChart/index.jsx similarity index 97% rename from client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx rename to client/src/Components/v1/Charts/MonitorDetailsAreaChart/index.jsx index a1ead3cf9..a9ef04a53 100644 --- a/client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx +++ b/client/src/Components/v1/Charts/MonitorDetailsAreaChart/index.jsx @@ -12,11 +12,11 @@ import { Box, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useMemo, useState } from "react"; import { useSelector } from "react-redux"; -import { formatDateWithTz } from "../../../Utils/timeUtils"; +import { formatDateWithTz } from "../../../../Utils/timeUtils.js"; import { tooltipDateFormatLookup, tickDateFormatLookup, -} from "../Utils/chartUtilFunctions"; +} from "../Utils/chartUtilFunctions.js"; import "./index.css"; const CustomToolTip = ({ active, payload, label, dateRange }) => { diff --git a/client/src/Components/Charts/StatusPageBarChart/index.jsx b/client/src/Components/v1/Charts/StatusPageBarChart/index.jsx similarity index 98% rename from client/src/Components/Charts/StatusPageBarChart/index.jsx rename to client/src/Components/v1/Charts/StatusPageBarChart/index.jsx index eab0c7d1d..7da4d58ba 100644 --- a/client/src/Components/Charts/StatusPageBarChart/index.jsx +++ b/client/src/Components/v1/Charts/StatusPageBarChart/index.jsx @@ -1,6 +1,6 @@ import { useTheme } from "@emotion/react"; import { Box, Stack, Tooltip, Typography } from "@mui/material"; -import { formatDateWithTz } from "../../../Utils/timeUtils"; +import { formatDateWithTz } from "../../../../Utils/timeUtils.js"; import { useEffect, useState, forwardRef } from "react"; import { useSelector } from "react-redux"; import PropTypes from "prop-types"; diff --git a/client/src/Components/Charts/Utils/chartUtilFunctions.js b/client/src/Components/v1/Charts/Utils/chartUtilFunctions.js similarity index 100% rename from client/src/Components/Charts/Utils/chartUtilFunctions.js rename to client/src/Components/v1/Charts/Utils/chartUtilFunctions.js diff --git a/client/src/Components/Charts/Utils/chartUtils.jsx b/client/src/Components/v1/Charts/Utils/chartUtils.jsx similarity index 98% rename from client/src/Components/Charts/Utils/chartUtils.jsx rename to client/src/Components/v1/Charts/Utils/chartUtils.jsx index e24881c1c..1bab80595 100644 --- a/client/src/Components/Charts/Utils/chartUtils.jsx +++ b/client/src/Components/v1/Charts/Utils/chartUtils.jsx @@ -2,9 +2,9 @@ import PropTypes from "prop-types"; import { useSelector } from "react-redux"; import { useTheme } from "@mui/material"; import { Text } from "recharts"; -import { formatDateWithTz } from "../../../Utils/timeUtils"; +import { formatDateWithTz } from "../../../../Utils/timeUtils.js"; import { Box, Stack, Typography } from "@mui/material"; -import { tickDateFormatLookup, tooltipDateFormatLookup } from "./chartUtilFunctions"; +import { tickDateFormatLookup, tooltipDateFormatLookup } from "./chartUtilFunctions.js"; /** * Custom tick component for rendering time with timezone. * diff --git a/client/src/Components/Charts/Utils/gradientUtils.jsx b/client/src/Components/v1/Charts/Utils/gradientUtils.jsx similarity index 100% rename from client/src/Components/Charts/Utils/gradientUtils.jsx rename to client/src/Components/v1/Charts/Utils/gradientUtils.jsx diff --git a/client/src/Components/Check/Check.jsx b/client/src/Components/v1/Check/Check.jsx similarity index 93% rename from client/src/Components/Check/Check.jsx rename to client/src/Components/v1/Check/Check.jsx index 02b9fd78a..53971316b 100644 --- a/client/src/Components/Check/Check.jsx +++ b/client/src/Components/v1/Check/Check.jsx @@ -1,7 +1,7 @@ import "./check.css"; import PropTypes from "prop-types"; -import CheckGrey from "../../assets/icons/check.svg?react"; -import CheckOutlined from "../../assets/icons/check-outlined.svg?react"; +import CheckGrey from "../../../assets/icons/check.svg?react"; +import CheckOutlined from "../../../assets/icons/check-outlined.svg?react"; import { Box, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Components/Check/check.css b/client/src/Components/v1/Check/check.css similarity index 100% rename from client/src/Components/Check/check.css rename to client/src/Components/v1/Check/check.css diff --git a/client/src/Components/CircularCount/index.jsx b/client/src/Components/v1/CircularCount/index.jsx similarity index 100% rename from client/src/Components/CircularCount/index.jsx rename to client/src/Components/v1/CircularCount/index.jsx diff --git a/client/src/Components/Common/AppBar.jsx b/client/src/Components/v1/Common/AppBar.jsx similarity index 99% rename from client/src/Components/Common/AppBar.jsx rename to client/src/Components/v1/Common/AppBar.jsx index de5f0992e..1d5a38ddd 100644 --- a/client/src/Components/Common/AppBar.jsx +++ b/client/src/Components/v1/Common/AppBar.jsx @@ -10,7 +10,7 @@ import MenuItem from "@mui/material/MenuItem"; import Drawer from "@mui/material/Drawer"; import MenuIcon from "@mui/icons-material/Menu"; import CloseRoundedIcon from "@mui/icons-material/CloseRounded"; -import ThemeSwitch from "../ThemeSwitch"; +import ThemeSwitch from "../ThemeSwitch/index.jsx"; import { useTheme } from "@mui/material/styles"; import { useSelector } from "react-redux"; import { useLocation, useNavigate } from "react-router-dom"; diff --git a/client/src/Components/Common/Footer.jsx b/client/src/Components/v1/Common/Footer.jsx similarity index 100% rename from client/src/Components/Common/Footer.jsx rename to client/src/Components/v1/Common/Footer.jsx diff --git a/client/src/Components/ConfigBox/index.jsx b/client/src/Components/v1/ConfigBox/index.jsx similarity index 100% rename from client/src/Components/ConfigBox/index.jsx rename to client/src/Components/v1/ConfigBox/index.jsx diff --git a/client/src/Components/ConfigRow/index.jsx b/client/src/Components/v1/ConfigRow/index.jsx similarity index 96% rename from client/src/Components/ConfigRow/index.jsx rename to client/src/Components/v1/ConfigRow/index.jsx index 217085b6d..12fea923e 100644 --- a/client/src/Components/ConfigRow/index.jsx +++ b/client/src/Components/v1/ConfigRow/index.jsx @@ -1,7 +1,7 @@ import { Box, Stack, Typography } from "@mui/material"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; -import ConfigBox from "../ConfigBox"; +import ConfigBox from "../ConfigBox/index.jsx"; /** * ConfigRow is a styled container used to layout content in a row format with specific padding, border, and spacing. diff --git a/client/src/Components/Dialog/genericDialog.jsx b/client/src/Components/v1/Dialog/genericDialog.jsx similarity index 100% rename from client/src/Components/Dialog/genericDialog.jsx rename to client/src/Components/v1/Dialog/genericDialog.jsx diff --git a/client/src/Components/Dialog/index.jsx b/client/src/Components/v1/Dialog/index.jsx similarity index 95% rename from client/src/Components/Dialog/index.jsx rename to client/src/Components/v1/Dialog/index.jsx index 678eb8721..b8cf07439 100644 --- a/client/src/Components/Dialog/index.jsx +++ b/client/src/Components/v1/Dialog/index.jsx @@ -1,6 +1,6 @@ import PropTypes from "prop-types"; import { Button, Stack } from "@mui/material"; -import { GenericDialog } from "./genericDialog"; +import { GenericDialog } from "./genericDialog.jsx"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Components/Dot/index.jsx b/client/src/Components/v1/Dot/index.jsx similarity index 100% rename from client/src/Components/Dot/index.jsx rename to client/src/Components/v1/Dot/index.jsx diff --git a/client/src/Components/Fallback/FallBackActionButtons.jsx b/client/src/Components/v1/Fallback/FallBackActionButtons.jsx similarity index 100% rename from client/src/Components/Fallback/FallBackActionButtons.jsx rename to client/src/Components/v1/Fallback/FallBackActionButtons.jsx diff --git a/client/src/Components/Fallback/FallbackBackground.jsx b/client/src/Components/v1/Fallback/FallbackBackground.jsx similarity index 78% rename from client/src/Components/Fallback/FallbackBackground.jsx rename to client/src/Components/v1/Fallback/FallbackBackground.jsx index fe46f4064..2182082fd 100644 --- a/client/src/Components/Fallback/FallbackBackground.jsx +++ b/client/src/Components/v1/Fallback/FallbackBackground.jsx @@ -1,8 +1,8 @@ import { useTheme } from "@emotion/react"; import Box from "@mui/material/Box"; -import Background from "../../assets/Images/background-grid.svg?react"; -import OutputAnimation from "../../assets/Animations/output.gif"; -import DarkmodeOutput from "../../assets/Animations/darkmodeOutput.gif"; +import Background from "../../../assets/Images/background-grid.svg?react"; +import OutputAnimation from "../../../assets/Animations/output.gif"; +import DarkmodeOutput from "../../../assets/Animations/darkmodeOutput.gif"; import { useSelector } from "react-redux"; const FallbackBackground = () => { const theme = useTheme(); diff --git a/client/src/Components/Fallback/FallbackCheckList.jsx b/client/src/Components/v1/Fallback/FallbackCheckList.jsx similarity index 95% rename from client/src/Components/Fallback/FallbackCheckList.jsx rename to client/src/Components/v1/Fallback/FallbackCheckList.jsx index 4d13dead4..befb95185 100644 --- a/client/src/Components/Fallback/FallbackCheckList.jsx +++ b/client/src/Components/v1/Fallback/FallbackCheckList.jsx @@ -2,7 +2,7 @@ import PropTypes from "prop-types"; import { useTheme } from "@mui/material/styles"; import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; -import Check from "../Check/Check"; +import Check from "../Check/Check.jsx"; const FallbackCheckList = ({ checks, type }) => { const theme = useTheme(); return ( diff --git a/client/src/Components/Fallback/FallbackContainer.jsx b/client/src/Components/v1/Fallback/FallbackContainer.jsx similarity index 100% rename from client/src/Components/Fallback/FallbackContainer.jsx rename to client/src/Components/v1/Fallback/FallbackContainer.jsx diff --git a/client/src/Components/Fallback/FallbackPageSpeedWarning.jsx b/client/src/Components/v1/Fallback/FallbackPageSpeedWarning.jsx similarity index 97% rename from client/src/Components/Fallback/FallbackPageSpeedWarning.jsx rename to client/src/Components/v1/Fallback/FallbackPageSpeedWarning.jsx index bcec7300d..2cf52bd88 100644 --- a/client/src/Components/Fallback/FallbackPageSpeedWarning.jsx +++ b/client/src/Components/v1/Fallback/FallbackPageSpeedWarning.jsx @@ -3,7 +3,7 @@ import { useTheme } from "@mui/material/styles"; import Link from "@mui/material/Link"; import { Link as RouterLink } from "react-router-dom"; import { useTranslation } from "react-i18next"; -import Alert from "../Alert"; +import Alert from "../Alert/index.jsx"; import PropTypes from "prop-types"; const renderWarningMessage = (t) => { diff --git a/client/src/Components/Fallback/FallbackTitle.jsx b/client/src/Components/v1/Fallback/FallbackTitle.jsx similarity index 100% rename from client/src/Components/Fallback/FallbackTitle.jsx rename to client/src/Components/v1/Fallback/FallbackTitle.jsx diff --git a/client/src/Components/Fallback/index.css b/client/src/Components/v1/Fallback/index.css similarity index 100% rename from client/src/Components/Fallback/index.css rename to client/src/Components/v1/Fallback/index.css diff --git a/client/src/Components/Fallback/index.jsx b/client/src/Components/v1/Fallback/index.jsx similarity index 87% rename from client/src/Components/Fallback/index.jsx rename to client/src/Components/v1/Fallback/index.jsx index 5fbb958e7..40113f882 100644 --- a/client/src/Components/Fallback/index.jsx +++ b/client/src/Components/v1/Fallback/index.jsx @@ -2,11 +2,11 @@ import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { Box, Stack } from "@mui/material"; import "./index.css"; -import FallbackTitle from "./FallbackTitle"; -import FallbackCheckList from "./FallbackCheckList"; -import FallbackActionButtons from "./FallBackActionButtons"; -import FallbackBackground from "./FallbackBackground"; -import FallbackContainer from "./FallbackContainer"; +import FallbackTitle from "./FallbackTitle.jsx"; +import FallbackCheckList from "./FallbackCheckList.jsx"; +import FallbackActionButtons from "./FallBackActionButtons.jsx"; +import FallbackBackground from "./FallbackBackground.jsx"; +import FallbackContainer from "./FallbackContainer.jsx"; /** * Fallback component to display a fallback UI with a title, a list of checks, and a navigation button. * diff --git a/client/src/Components/FilterHeader/index.jsx b/client/src/Components/v1/FilterHeader/index.jsx similarity index 100% rename from client/src/Components/FilterHeader/index.jsx rename to client/src/Components/v1/FilterHeader/index.jsx diff --git a/client/src/Components/GenericFallback/NetworkError.jsx b/client/src/Components/v1/GenericFallback/NetworkError.jsx similarity index 100% rename from client/src/Components/GenericFallback/NetworkError.jsx rename to client/src/Components/v1/GenericFallback/NetworkError.jsx diff --git a/client/src/Components/GenericFallback/index.jsx b/client/src/Components/v1/GenericFallback/index.jsx similarity index 89% rename from client/src/Components/GenericFallback/index.jsx rename to client/src/Components/v1/GenericFallback/index.jsx index 7f6d258ea..137cdd1da 100644 --- a/client/src/Components/GenericFallback/index.jsx +++ b/client/src/Components/v1/GenericFallback/index.jsx @@ -1,8 +1,8 @@ import { useTheme } from "@emotion/react"; import { Box, Stack } from "@mui/material"; -import OutputAnimation from "../../assets/Animations/output.gif"; -import DarkmodeOutput from "../../assets/Animations/darkmodeOutput.gif"; -import Background from "../../assets/Images/background-grid.svg?react"; +import OutputAnimation from "../../../assets/Animations/output.gif"; +import DarkmodeOutput from "../../../assets/Animations/darkmodeOutput.gif"; +import Background from "../../../assets/Images/background-grid.svg?react"; import { useSelector } from "react-redux"; /** diff --git a/client/src/Components/HOC/withAdminCheck.jsx b/client/src/Components/v1/HOC/withAdminCheck.jsx similarity index 91% rename from client/src/Components/HOC/withAdminCheck.jsx rename to client/src/Components/v1/HOC/withAdminCheck.jsx index 458330a1f..8646c99b6 100644 --- a/client/src/Components/HOC/withAdminCheck.jsx +++ b/client/src/Components/v1/HOC/withAdminCheck.jsx @@ -1,8 +1,8 @@ import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; -import { logger } from "../../Utils/Logger"; -import { networkService } from "../../main"; +import { logger } from "../../../Utils/Logger.js"; +import { networkService } from "../../../main.jsx"; const withAdminCheck = (WrappedComponent) => { const WithAdminCheck = (props) => { diff --git a/client/src/Components/Heading/index.jsx b/client/src/Components/v1/Heading/index.jsx similarity index 100% rename from client/src/Components/Heading/index.jsx rename to client/src/Components/v1/Heading/index.jsx diff --git a/client/src/Components/Host/index.jsx b/client/src/Components/v1/Host/index.jsx similarity index 97% rename from client/src/Components/Host/index.jsx rename to client/src/Components/v1/Host/index.jsx index d1cf382b2..772f5b61c 100644 --- a/client/src/Components/Host/index.jsx +++ b/client/src/Components/v1/Host/index.jsx @@ -1,7 +1,7 @@ import { Stack, Typography } from "@mui/material"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; -import Dot from "../Dot"; +import Dot from "../Dot/index.jsx"; /** * Host component. * This subcomponent receives a params object and displays the host details. diff --git a/client/src/Components/HttpStatusLabel/index.jsx b/client/src/Components/v1/HttpStatusLabel/index.jsx similarity index 96% rename from client/src/Components/HttpStatusLabel/index.jsx rename to client/src/Components/v1/HttpStatusLabel/index.jsx index 52676ae7c..2253e323e 100644 --- a/client/src/Components/HttpStatusLabel/index.jsx +++ b/client/src/Components/v1/HttpStatusLabel/index.jsx @@ -1,6 +1,6 @@ import PropTypes from "prop-types"; import { useTheme } from "@mui/material"; -import { BaseLabel } from "../Label"; +import { BaseLabel } from "../Label/index.jsx"; /** * @component diff --git a/client/src/Components/I18nLoader/index.jsx b/client/src/Components/v1/I18nLoader/index.jsx similarity index 88% rename from client/src/Components/I18nLoader/index.jsx rename to client/src/Components/v1/I18nLoader/index.jsx index 7c3cbfb85..2a6a4966d 100644 --- a/client/src/Components/I18nLoader/index.jsx +++ b/client/src/Components/v1/I18nLoader/index.jsx @@ -1,4 +1,4 @@ -import i18n from "../../Utils/i18n"; +import i18n from "../../../Utils/i18n.js"; import { useSelector } from "react-redux"; import { useEffect } from "react"; const I18nLoader = () => { diff --git a/client/src/Components/IconBox/index.jsx b/client/src/Components/v1/IconBox/index.jsx similarity index 100% rename from client/src/Components/IconBox/index.jsx rename to client/src/Components/v1/IconBox/index.jsx diff --git a/client/src/Components/Image/index.jsx b/client/src/Components/v1/Image/index.jsx similarity index 100% rename from client/src/Components/Image/index.jsx rename to client/src/Components/v1/Image/index.jsx diff --git a/client/src/Components/InfoBox/index.jsx b/client/src/Components/v1/InfoBox/index.jsx similarity index 88% rename from client/src/Components/InfoBox/index.jsx rename to client/src/Components/v1/InfoBox/index.jsx index 6797d6c9c..96e67975d 100644 --- a/client/src/Components/InfoBox/index.jsx +++ b/client/src/Components/v1/InfoBox/index.jsx @@ -1,6 +1,6 @@ -import { RowContainer } from "../StandardContainer"; +import { RowContainer } from "../StandardContainer/index.jsx"; import { Stack, Typography } from "@mui/material"; -import Image from "../Image"; +import Image from "../Image/index.jsx"; import { useTheme } from "@mui/material/styles"; diff --git a/client/src/Components/Inputs/Checkbox/index.css b/client/src/Components/v1/Inputs/Checkbox/index.css similarity index 100% rename from client/src/Components/Inputs/Checkbox/index.css rename to client/src/Components/v1/Inputs/Checkbox/index.css diff --git a/client/src/Components/Inputs/Checkbox/index.jsx b/client/src/Components/v1/Inputs/Checkbox/index.jsx similarity index 95% rename from client/src/Components/Inputs/Checkbox/index.jsx rename to client/src/Components/v1/Inputs/Checkbox/index.jsx index 1497c44fb..08af49d84 100644 --- a/client/src/Components/Inputs/Checkbox/index.jsx +++ b/client/src/Components/v1/Inputs/Checkbox/index.jsx @@ -1,8 +1,8 @@ import PropTypes from "prop-types"; import { FormControlLabel, Checkbox as MuiCheckbox } from "@mui/material"; import { useTheme } from "@emotion/react"; -import CheckboxOutline from "../../../assets/icons/checkbox-outline.svg?react"; -import CheckboxFilled from "../../../assets/icons/checkbox-filled.svg?react"; +import CheckboxOutline from "../../../../assets/icons/checkbox-outline.svg?react"; +import CheckboxFilled from "../../../../assets/icons/checkbox-filled.svg?react"; import "./index.css"; /** diff --git a/client/src/Components/Inputs/ColorPicker/index.jsx b/client/src/Components/v1/Inputs/ColorPicker/index.jsx similarity index 100% rename from client/src/Components/Inputs/ColorPicker/index.jsx rename to client/src/Components/v1/Inputs/ColorPicker/index.jsx diff --git a/client/src/Components/Inputs/FieldWrapper/index.jsx b/client/src/Components/v1/Inputs/FieldWrapper/index.jsx similarity index 100% rename from client/src/Components/Inputs/FieldWrapper/index.jsx rename to client/src/Components/v1/Inputs/FieldWrapper/index.jsx diff --git a/client/src/Components/Inputs/ImageUpload/index.jsx b/client/src/Components/v1/Inputs/ImageUpload/index.jsx similarity index 98% rename from client/src/Components/Inputs/ImageUpload/index.jsx rename to client/src/Components/v1/Inputs/ImageUpload/index.jsx index a81c89393..e7415bf8f 100644 --- a/client/src/Components/Inputs/ImageUpload/index.jsx +++ b/client/src/Components/v1/Inputs/ImageUpload/index.jsx @@ -1,10 +1,10 @@ // Components import { Box, Stack, Typography } from "@mui/material"; import CloudUploadIcon from "@mui/icons-material/CloudUpload"; -import Image from "../../../Components/Image"; +import Image from "../../Image/index.jsx"; import TextField from "@mui/material/TextField"; import IconButton from "@mui/material/IconButton"; -import ProgressUpload from "../../ProgressBars"; +import ProgressUpload from "../../ProgressBars/index.jsx"; import ImageIcon from "@mui/icons-material/Image"; // Utils diff --git a/client/src/Components/Inputs/Radio/index.css b/client/src/Components/v1/Inputs/Radio/index.css similarity index 100% rename from client/src/Components/Inputs/Radio/index.css rename to client/src/Components/v1/Inputs/Radio/index.css diff --git a/client/src/Components/Inputs/Radio/index.jsx b/client/src/Components/v1/Inputs/Radio/index.jsx similarity index 96% rename from client/src/Components/Inputs/Radio/index.jsx rename to client/src/Components/v1/Inputs/Radio/index.jsx index 4c687de1e..575c1af74 100644 --- a/client/src/Components/Inputs/Radio/index.jsx +++ b/client/src/Components/v1/Inputs/Radio/index.jsx @@ -1,7 +1,7 @@ import PropTypes from "prop-types"; import { FormControlLabel, Radio as MUIRadio, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; -import RadioChecked from "../../../assets/icons/radio-checked.svg?react"; +import RadioChecked from "../../../../assets/icons/radio-checked.svg?react"; import "./index.css"; /** diff --git a/client/src/Components/Inputs/Search/index.jsx b/client/src/Components/v1/Inputs/Search/index.jsx similarity index 98% rename from client/src/Components/Inputs/Search/index.jsx rename to client/src/Components/v1/Inputs/Search/index.jsx index 7202cc2eb..7a3771d47 100644 --- a/client/src/Components/Inputs/Search/index.jsx +++ b/client/src/Components/v1/Inputs/Search/index.jsx @@ -8,10 +8,10 @@ import { Checkbox, } from "@mui/material"; import { useTheme } from "@emotion/react"; -import SearchIcon from "../../../assets/icons/search.svg?react"; +import SearchIcon from "../../../../assets/icons/search.svg?react"; import React, { useEffect } from "react"; import { useTranslation } from "react-i18next"; -import FieldWrapper from "../FieldWrapper"; +import FieldWrapper from "../FieldWrapper/index.jsx"; /** * Search component using Material UI's Autocomplete. diff --git a/client/src/Components/Inputs/Select/index.css b/client/src/Components/v1/Inputs/Select/index.css similarity index 100% rename from client/src/Components/Inputs/Select/index.css rename to client/src/Components/v1/Inputs/Select/index.css diff --git a/client/src/Components/Inputs/Select/index.jsx b/client/src/Components/v1/Inputs/Select/index.jsx similarity index 98% rename from client/src/Components/Inputs/Select/index.jsx rename to client/src/Components/v1/Inputs/Select/index.jsx index c5c488f91..37ce30dc1 100644 --- a/client/src/Components/Inputs/Select/index.jsx +++ b/client/src/Components/v1/Inputs/Select/index.jsx @@ -2,7 +2,7 @@ import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { MenuItem, Select as MuiSelect, Stack, Typography } from "@mui/material"; import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; -import FieldWrapper from "../FieldWrapper"; +import FieldWrapper from "../FieldWrapper/index.jsx"; import "./index.css"; diff --git a/client/src/Components/Inputs/TextInput/Adornments/index.jsx b/client/src/Components/v1/Inputs/TextInput/Adornments/index.jsx similarity index 96% rename from client/src/Components/Inputs/TextInput/Adornments/index.jsx rename to client/src/Components/v1/Inputs/TextInput/Adornments/index.jsx index d6af2a89e..4e4bbd2af 100644 --- a/client/src/Components/Inputs/TextInput/Adornments/index.jsx +++ b/client/src/Components/v1/Inputs/TextInput/Adornments/index.jsx @@ -4,7 +4,7 @@ import PropTypes from "prop-types"; import VisibilityOff from "@mui/icons-material/VisibilityOff"; import Visibility from "@mui/icons-material/Visibility"; import ReorderRoundedIcon from "@mui/icons-material/ReorderRounded"; -import DeleteIcon from "../../../../assets/icons/trash-bin.svg?react"; +import DeleteIcon from "../../../../../assets/icons/trash-bin.svg?react"; export const HttpAdornment = ({ https }) => { const theme = useTheme(); diff --git a/client/src/Components/Inputs/TextInput/index.jsx b/client/src/Components/v1/Inputs/TextInput/index.jsx similarity index 98% rename from client/src/Components/Inputs/TextInput/index.jsx rename to client/src/Components/v1/Inputs/TextInput/index.jsx index 469791704..7203e5f50 100644 --- a/client/src/Components/Inputs/TextInput/index.jsx +++ b/client/src/Components/v1/Inputs/TextInput/index.jsx @@ -2,7 +2,7 @@ import { Stack, TextField, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import { forwardRef, useState, cloneElement } from "react"; import PropTypes from "prop-types"; -import FieldWrapper from "../FieldWrapper"; +import FieldWrapper from "../FieldWrapper/index.jsx"; const getSx = (theme, type, maxWidth) => { const sx = { diff --git a/client/src/Components/Label/index.css b/client/src/Components/v1/Label/index.css similarity index 100% rename from client/src/Components/Label/index.css rename to client/src/Components/v1/Label/index.css diff --git a/client/src/Components/Label/index.jsx b/client/src/Components/v1/Label/index.jsx similarity index 100% rename from client/src/Components/Label/index.jsx rename to client/src/Components/v1/Label/index.jsx diff --git a/client/src/Components/Layouts/AppLayout/index.jsx b/client/src/Components/v1/Layouts/AppLayout/index.jsx similarity index 91% rename from client/src/Components/Layouts/AppLayout/index.jsx rename to client/src/Components/v1/Layouts/AppLayout/index.jsx index d13f6d879..0dbfb1e7a 100644 --- a/client/src/Components/Layouts/AppLayout/index.jsx +++ b/client/src/Components/v1/Layouts/AppLayout/index.jsx @@ -1,7 +1,7 @@ import Box from "@mui/material/Box"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; -import BackgroundSVG from "../../../assets/Images/background.svg"; +import BackgroundSVG from "../../../../assets/Images/background.svg"; import { useSelector } from "react-redux"; const AppLayout = ({ children }) => { diff --git a/client/src/Components/Layouts/HomeLayout/index.css b/client/src/Components/v1/Layouts/HomeLayout/index.css similarity index 100% rename from client/src/Components/Layouts/HomeLayout/index.css rename to client/src/Components/v1/Layouts/HomeLayout/index.css diff --git a/client/src/Components/Layouts/HomeLayout/index.jsx b/client/src/Components/v1/Layouts/HomeLayout/index.jsx similarity index 88% rename from client/src/Components/Layouts/HomeLayout/index.jsx rename to client/src/Components/v1/Layouts/HomeLayout/index.jsx index 55b612522..3e63aff37 100644 --- a/client/src/Components/Layouts/HomeLayout/index.jsx +++ b/client/src/Components/v1/Layouts/HomeLayout/index.jsx @@ -1,4 +1,4 @@ -import Sidebar from "../../Sidebar"; +import Sidebar from "../../Sidebar/index.jsx"; import { Outlet } from "react-router"; import { Stack } from "@mui/material"; diff --git a/client/src/Components/Link/index.jsx b/client/src/Components/v1/Link/index.jsx similarity index 100% rename from client/src/Components/Link/index.jsx rename to client/src/Components/v1/Link/index.jsx diff --git a/client/src/Components/Link/link.css b/client/src/Components/v1/Link/link.css similarity index 100% rename from client/src/Components/Link/link.css rename to client/src/Components/v1/Link/link.css diff --git a/client/src/Components/MonitorActions/index.jsx b/client/src/Components/v1/MonitorActions/index.jsx similarity index 95% rename from client/src/Components/MonitorActions/index.jsx rename to client/src/Components/v1/MonitorActions/index.jsx index 9e88e26c5..23cebc972 100644 --- a/client/src/Components/MonitorActions/index.jsx +++ b/client/src/Components/v1/MonitorActions/index.jsx @@ -10,8 +10,8 @@ import MenuItem from "@mui/material/MenuItem"; import MenuList from "@mui/material/MenuList"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; -import { createToast } from "../../Utils/toastUtils"; -import { useExportMonitors } from "../../Hooks/v1/monitorHooks.js"; +import { createToast } from "../../../Utils/toastUtils.jsx"; +import { useExportMonitors } from "../../../Hooks/v1/monitorHooks.js"; const MonitorActions = ({ isLoading }) => { const [open, setOpen] = React.useState(false); diff --git a/client/src/Components/MonitorCountHeader/index.jsx b/client/src/Components/v1/MonitorCountHeader/index.jsx similarity index 95% rename from client/src/Components/MonitorCountHeader/index.jsx rename to client/src/Components/v1/MonitorCountHeader/index.jsx index 7610a9d5c..eba644d26 100644 --- a/client/src/Components/MonitorCountHeader/index.jsx +++ b/client/src/Components/v1/MonitorCountHeader/index.jsx @@ -1,7 +1,7 @@ import { Stack, Typography } from "@mui/material"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; -import SkeletonLayout from "./skeleton"; +import SkeletonLayout from "./skeleton.jsx"; const MonitorCountHeader = ({ isLoading = false, diff --git a/client/src/Components/MonitorCountHeader/skeleton.jsx b/client/src/Components/v1/MonitorCountHeader/skeleton.jsx similarity index 100% rename from client/src/Components/MonitorCountHeader/skeleton.jsx rename to client/src/Components/v1/MonitorCountHeader/skeleton.jsx diff --git a/client/src/Components/MonitorCreateHeader/index.jsx b/client/src/Components/v1/MonitorCreateHeader/index.jsx similarity index 94% rename from client/src/Components/MonitorCreateHeader/index.jsx rename to client/src/Components/v1/MonitorCreateHeader/index.jsx index b3b50681c..90c12f5ec 100644 --- a/client/src/Components/MonitorCreateHeader/index.jsx +++ b/client/src/Components/v1/MonitorCreateHeader/index.jsx @@ -3,7 +3,7 @@ import { useNavigate } from "react-router-dom"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; -import MonitorActions from "../MonitorActions"; +import MonitorActions from "../MonitorActions/index.jsx"; const CreateMonitorHeader = ({ isAdmin, label, isLoading = true, path, bulkPath }) => { const navigate = useNavigate(); diff --git a/client/src/Components/MonitorCreateHeader/skeleton.jsx b/client/src/Components/v1/MonitorCreateHeader/skeleton.jsx similarity index 100% rename from client/src/Components/MonitorCreateHeader/skeleton.jsx rename to client/src/Components/v1/MonitorCreateHeader/skeleton.jsx diff --git a/client/src/Components/MonitorDetailsControlHeader/index.jsx b/client/src/Components/v1/MonitorDetailsControlHeader/index.jsx similarity index 92% rename from client/src/Components/MonitorDetailsControlHeader/index.jsx rename to client/src/Components/v1/MonitorDetailsControlHeader/index.jsx index cd56394f4..ae565f3bb 100644 --- a/client/src/Components/MonitorDetailsControlHeader/index.jsx +++ b/client/src/Components/v1/MonitorDetailsControlHeader/index.jsx @@ -1,6 +1,6 @@ import Stack from "@mui/material/Stack"; -import Status from "./status"; -import Skeleton from "./skeleton"; +import Status from "./status.jsx"; +import Skeleton from "./skeleton.jsx"; import Button from "@mui/material/Button"; import { Tooltip } from "@mui/material"; import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined"; @@ -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/v1/monitorHooks.js"; +import { useSendTestEmail } from "../../../Hooks/v1/useSendTestEmail.js"; import { useTranslation } from "react-i18next"; -import { useTestAllNotifications } from "../../Hooks/v1/useNotifications.js"; +import { useTestAllNotifications } from "../../../Hooks/v1/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/MonitorDetailsControlHeader/skeleton.jsx b/client/src/Components/v1/MonitorDetailsControlHeader/skeleton.jsx similarity index 100% rename from client/src/Components/MonitorDetailsControlHeader/skeleton.jsx rename to client/src/Components/v1/MonitorDetailsControlHeader/skeleton.jsx diff --git a/client/src/Components/MonitorDetailsControlHeader/status.jsx b/client/src/Components/v1/MonitorDetailsControlHeader/status.jsx similarity index 83% rename from client/src/Components/MonitorDetailsControlHeader/status.jsx rename to client/src/Components/v1/MonitorDetailsControlHeader/status.jsx index 2cf749861..d9b7afc40 100644 --- a/client/src/Components/MonitorDetailsControlHeader/status.jsx +++ b/client/src/Components/v1/MonitorDetailsControlHeader/status.jsx @@ -1,14 +1,14 @@ // Components import Stack from "@mui/material/Stack"; -import PulseDot from "../../Components/Animated/PulseDot"; +import PulseDot from "../Animated/PulseDot.jsx"; import Typography from "@mui/material/Typography"; -import Dot from "../../Components/Dot"; +import Dot from "../Dot/index.jsx"; // Utils -import { formatDurationRounded } from "../../Utils/timeUtils"; +import { formatDurationRounded } from "../../../Utils/timeUtils.js"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; -import { useMonitorUtils } from "../../Hooks/v1/useMonitorUtils.js"; -import { formatMonitorUrl } from "../../Utils/utils"; +import { useMonitorUtils } from "../../../Hooks/v1/useMonitorUtils.js"; +import { formatMonitorUrl } from "../../../Utils/utils.js"; /** * Status component displays the status information of a monitor. * It includes the monitor's name, URL, and check interval. diff --git a/client/src/Components/MonitorTimeFrameHeader/index.jsx b/client/src/Components/v1/MonitorTimeFrameHeader/index.jsx similarity index 97% rename from client/src/Components/MonitorTimeFrameHeader/index.jsx rename to client/src/Components/v1/MonitorTimeFrameHeader/index.jsx index 3958d507c..9d57edc41 100644 --- a/client/src/Components/MonitorTimeFrameHeader/index.jsx +++ b/client/src/Components/v1/MonitorTimeFrameHeader/index.jsx @@ -1,6 +1,6 @@ import { Stack, Typography, Button, ButtonGroup } from "@mui/material"; import { useTheme } from "@emotion/react"; -import SkeletonLayout from "./skeleton"; +import SkeletonLayout from "./skeleton.jsx"; import PropTypes from "prop-types"; const MonitorTimeFrameHeader = ({ diff --git a/client/src/Components/MonitorTimeFrameHeader/skeleton.jsx b/client/src/Components/v1/MonitorTimeFrameHeader/skeleton.jsx similarity index 100% rename from client/src/Components/MonitorTimeFrameHeader/skeleton.jsx rename to client/src/Components/v1/MonitorTimeFrameHeader/skeleton.jsx diff --git a/client/src/Components/NotificationConfig/index.jsx b/client/src/Components/v1/NotificationConfig/index.jsx similarity index 98% rename from client/src/Components/NotificationConfig/index.jsx rename to client/src/Components/v1/NotificationConfig/index.jsx index c37889eff..58096881e 100644 --- a/client/src/Components/NotificationConfig/index.jsx +++ b/client/src/Components/v1/NotificationConfig/index.jsx @@ -3,7 +3,7 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; import Divider from "@mui/material/Divider"; import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded"; -import Search from "../Inputs/Search"; +import Search from "../Inputs/Search/index.jsx"; // Utils import { useState, useEffect } from "react"; diff --git a/client/src/Components/NotificationIntegrationModal/Components/NotificationIntegrationModal.jsx b/client/src/Components/v1/NotificationIntegrationModal/Components/NotificationIntegrationModal.jsx similarity index 98% rename from client/src/Components/NotificationIntegrationModal/Components/NotificationIntegrationModal.jsx rename to client/src/Components/v1/NotificationIntegrationModal/Components/NotificationIntegrationModal.jsx index dc94ee731..2aae996f6 100644 --- a/client/src/Components/NotificationIntegrationModal/Components/NotificationIntegrationModal.jsx +++ b/client/src/Components/v1/NotificationIntegrationModal/Components/NotificationIntegrationModal.jsx @@ -14,9 +14,9 @@ import { Stack, } from "@mui/material"; import { useTheme } from "@emotion/react"; -import TabPanel from "./TabPanel"; -import TabComponent from "./TabComponent"; -import useNotifications from "../Hooks/useNotification"; +import TabPanel from "./TabPanel.jsx"; +import TabComponent from "./TabComponent.jsx"; +import useNotifications from "../Hooks/useNotification.js"; // Define constants for notification types to avoid magic values const NOTIFICATION_TYPES = { diff --git a/client/src/Components/NotificationIntegrationModal/Components/TabComponent.jsx b/client/src/Components/v1/NotificationIntegrationModal/Components/TabComponent.jsx similarity index 95% rename from client/src/Components/NotificationIntegrationModal/Components/TabComponent.jsx rename to client/src/Components/v1/NotificationIntegrationModal/Components/TabComponent.jsx index 571379c7e..87fb2c301 100644 --- a/client/src/Components/NotificationIntegrationModal/Components/TabComponent.jsx +++ b/client/src/Components/v1/NotificationIntegrationModal/Components/TabComponent.jsx @@ -2,8 +2,8 @@ import React from "react"; import { Typography, Box, Button, CircularProgress } from "@mui/material"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; -import TextInput from "../../../Components/Inputs/TextInput"; -import Checkbox from "../../../Components/Inputs/Checkbox"; +import TextInput from "../../Inputs/TextInput/index.jsx"; +import Checkbox from "../../Inputs/Checkbox/index.jsx"; const TabComponent = ({ type, diff --git a/client/src/Components/NotificationIntegrationModal/Components/TabPanel.jsx b/client/src/Components/v1/NotificationIntegrationModal/Components/TabPanel.jsx similarity index 100% rename from client/src/Components/NotificationIntegrationModal/Components/TabPanel.jsx rename to client/src/Components/v1/NotificationIntegrationModal/Components/TabPanel.jsx diff --git a/client/src/Components/NotificationIntegrationModal/Hooks/useNotification.js b/client/src/Components/v1/NotificationIntegrationModal/Hooks/useNotification.js similarity index 96% rename from client/src/Components/NotificationIntegrationModal/Hooks/useNotification.js rename to client/src/Components/v1/NotificationIntegrationModal/Hooks/useNotification.js index cdbc11598..c0712fa5f 100644 --- a/client/src/Components/NotificationIntegrationModal/Hooks/useNotification.js +++ b/client/src/Components/v1/NotificationIntegrationModal/Hooks/useNotification.js @@ -1,7 +1,7 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { networkService } from "../../../Utils/NetworkService"; -import { createToast } from "../../../Utils/toastUtils"; +import { networkService } from "../../../../Utils/NetworkService.js"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; // Define constants for notification types to avoid magic values const NOTIFICATION_TYPES = { diff --git a/client/src/Components/PageStateWrapper/index.jsx b/client/src/Components/v1/PageStateWrapper/index.jsx similarity index 90% rename from client/src/Components/PageStateWrapper/index.jsx rename to client/src/Components/v1/PageStateWrapper/index.jsx index c335fcbb3..ac97704df 100644 --- a/client/src/Components/PageStateWrapper/index.jsx +++ b/client/src/Components/v1/PageStateWrapper/index.jsx @@ -1,9 +1,9 @@ -import GenericFallback from "../GenericFallback"; -import Fallback from "../Fallback"; +import GenericFallback from "../GenericFallback/index.jsx"; +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/v1/useIsAdmin.js"; import PropTypes from "prop-types"; const PageStateWrapper = ({ networkError, diff --git a/client/src/Components/ProgressBars/index.css b/client/src/Components/v1/ProgressBars/index.css similarity index 100% rename from client/src/Components/ProgressBars/index.css rename to client/src/Components/v1/ProgressBars/index.css diff --git a/client/src/Components/ProgressBars/index.jsx b/client/src/Components/v1/ProgressBars/index.jsx similarity index 100% rename from client/src/Components/ProgressBars/index.jsx rename to client/src/Components/v1/ProgressBars/index.jsx diff --git a/client/src/Components/ProgressStepper/index.css b/client/src/Components/v1/ProgressStepper/index.css similarity index 100% rename from client/src/Components/ProgressStepper/index.css rename to client/src/Components/v1/ProgressStepper/index.css diff --git a/client/src/Components/ProgressStepper/index.jsx b/client/src/Components/v1/ProgressStepper/index.jsx similarity index 100% rename from client/src/Components/ProgressStepper/index.jsx rename to client/src/Components/v1/ProgressStepper/index.jsx diff --git a/client/src/Components/ProtectedRoute/index.jsx b/client/src/Components/v1/ProtectedRoute/index.jsx similarity index 100% rename from client/src/Components/ProtectedRoute/index.jsx rename to client/src/Components/v1/ProtectedRoute/index.jsx diff --git a/client/src/Components/RoleProtectedRoute/index.jsx b/client/src/Components/v1/RoleProtectedRoute/index.jsx similarity index 100% rename from client/src/Components/RoleProtectedRoute/index.jsx rename to client/src/Components/v1/RoleProtectedRoute/index.jsx diff --git a/client/src/Components/ShareComponent/index.jsx b/client/src/Components/v1/ShareComponent/index.jsx similarity index 100% rename from client/src/Components/ShareComponent/index.jsx rename to client/src/Components/v1/ShareComponent/index.jsx diff --git a/client/src/Components/Sidebar/components/authFooter.jsx b/client/src/Components/v1/Sidebar/components/authFooter.jsx similarity index 96% rename from client/src/Components/Sidebar/components/authFooter.jsx rename to client/src/Components/v1/Sidebar/components/authFooter.jsx index e94aeddee..2c33a2402 100644 --- a/client/src/Components/Sidebar/components/authFooter.jsx +++ b/client/src/Components/v1/Sidebar/components/authFooter.jsx @@ -3,19 +3,19 @@ import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import Tooltip from "@mui/material/Tooltip"; import IconButton from "@mui/material/IconButton"; -import Avatar from "../../Avatar"; +import Avatar from "../../Avatar/index.jsx"; import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; import Divider from "@mui/material/Divider"; -import DotsVertical from "../../../assets/icons/dots-vertical.svg?react"; -import LogoutSvg from "../../../assets/icons/logout.svg?react"; +import DotsVertical from "../../../../assets/icons/dots-vertical.svg?react"; +import LogoutSvg from "../../../../assets/icons/logout.svg?react"; import { useTheme } from "@emotion/react"; import { useSelector } from "react-redux"; import { useTranslation } from "react-i18next"; import { useState } from "react"; import { useNavigate } from "react-router"; -import { clearAuthState } from "../../../Features/Auth/authSlice"; +import { clearAuthState } from "../../../../Features/Auth/authSlice.js"; import { useDispatch } from "react-redux"; import PropTypes from "prop-types"; diff --git a/client/src/Components/Sidebar/components/collapseButton.jsx b/client/src/Components/v1/Sidebar/components/collapseButton.jsx similarity index 88% rename from client/src/Components/Sidebar/components/collapseButton.jsx rename to client/src/Components/v1/Sidebar/components/collapseButton.jsx index e7c382e5a..e3a9c91f5 100644 --- a/client/src/Components/Sidebar/components/collapseButton.jsx +++ b/client/src/Components/v1/Sidebar/components/collapseButton.jsx @@ -1,9 +1,9 @@ import IconButton from "@mui/material/IconButton"; -import ArrowRight from "../../ArrowRight"; -import ArrowLeft from "../../ArrowLeft"; +import ArrowRight from "../../ArrowRight/index.jsx"; +import ArrowLeft from "../../ArrowLeft/index.jsx"; import { useTheme } from "@mui/material/styles"; import { useDispatch } from "react-redux"; -import { toggleSidebar } from "../../../Features/UI/uiSlice"; +import { toggleSidebar } from "../../../../Features/UI/uiSlice.js"; import PropTypes from "prop-types"; const CollapseButton = ({ collapsed }) => { diff --git a/client/src/Components/Sidebar/components/logo.jsx b/client/src/Components/v1/Sidebar/components/logo.jsx similarity index 100% rename from client/src/Components/Sidebar/components/logo.jsx rename to client/src/Components/v1/Sidebar/components/logo.jsx diff --git a/client/src/Components/Sidebar/components/navItem.jsx b/client/src/Components/v1/Sidebar/components/navItem.jsx similarity index 100% rename from client/src/Components/Sidebar/components/navItem.jsx rename to client/src/Components/v1/Sidebar/components/navItem.jsx diff --git a/client/src/Components/Sidebar/index.jsx b/client/src/Components/v1/Sidebar/index.jsx similarity index 75% rename from client/src/Components/Sidebar/index.jsx rename to client/src/Components/v1/Sidebar/index.jsx index 6b7b6577c..bd7400fa6 100644 --- a/client/src/Components/Sidebar/index.jsx +++ b/client/src/Components/v1/Sidebar/index.jsx @@ -1,29 +1,29 @@ import Stack from "@mui/material/Stack"; import List from "@mui/material/List"; -import Logo from "./components/logo"; -import CollapseButton from "./components/collapseButton"; +import Logo from "./components/logo.jsx"; +import CollapseButton from "./components/collapseButton.jsx"; import Divider from "@mui/material/Divider"; -import NavItem from "./components/navItem"; -import AuthFooter from "./components/authFooter"; +import NavItem from "./components/navItem.jsx"; +import AuthFooter from "./components/authFooter.jsx"; -import StarPrompt from "../StarPrompt"; -import LockSvg from "../../assets/icons/lock.svg?react"; -import UserSvg from "../../assets/icons/user.svg?react"; -import TeamSvg from "../../assets/icons/user-two.svg?react"; -import Support from "../../assets/icons/support.svg?react"; -import Maintenance from "../../assets/icons/maintenance.svg?react"; -import Monitors from "../../assets/icons/monitors.svg?react"; -import Incidents from "../../assets/icons/incidents.svg?react"; -import Integrations from "../../assets/icons/integrations.svg?react"; -import PageSpeed from "../../assets/icons/page-speed.svg?react"; -import Settings from "../../assets/icons/settings.svg?react"; -import ChangeLog from "../../assets/icons/changeLog.svg?react"; -import Docs from "../../assets/icons/docs.svg?react"; -import StatusPages from "../../assets/icons/status-pages.svg?react"; -import Discussions from "../../assets/icons/discussions.svg?react"; -import Notifications from "../../assets/icons/notifications.svg?react"; -import Logs from "../../assets/icons/logs.svg?react"; +import StarPrompt from "../StarPrompt/index.jsx"; +import LockSvg from "../../../assets/icons/lock.svg?react"; +import UserSvg from "../../../assets/icons/user.svg?react"; +import TeamSvg from "../../../assets/icons/user-two.svg?react"; +import Support from "../../../assets/icons/support.svg?react"; +import Maintenance from "../../../assets/icons/maintenance.svg?react"; +import Monitors from "../../../assets/icons/monitors.svg?react"; +import Incidents from "../../../assets/icons/incidents.svg?react"; +import Integrations from "../../../assets/icons/integrations.svg?react"; +import PageSpeed from "../../../assets/icons/page-speed.svg?react"; +import Settings from "../../../assets/icons/settings.svg?react"; +import ChangeLog from "../../../assets/icons/changeLog.svg?react"; +import Docs from "../../../assets/icons/docs.svg?react"; +import StatusPages from "../../../assets/icons/status-pages.svg?react"; +import Discussions from "../../../assets/icons/discussions.svg?react"; +import Notifications from "../../../assets/icons/notifications.svg?react"; +import Logs from "../../../assets/icons/logs.svg?react"; // Utils import { useTheme } from "@mui/material/styles"; diff --git a/client/src/Components/Skeletons/FullPage/index.jsx b/client/src/Components/v1/Skeletons/FullPage/index.jsx similarity index 100% rename from client/src/Components/Skeletons/FullPage/index.jsx rename to client/src/Components/v1/Skeletons/FullPage/index.jsx diff --git a/client/src/Components/StandardContainer/index.jsx b/client/src/Components/v1/StandardContainer/index.jsx similarity index 100% rename from client/src/Components/StandardContainer/index.jsx rename to client/src/Components/v1/StandardContainer/index.jsx diff --git a/client/src/Components/StarPrompt/index.jsx b/client/src/Components/v1/StarPrompt/index.jsx similarity index 97% rename from client/src/Components/StarPrompt/index.jsx rename to client/src/Components/v1/StarPrompt/index.jsx index f5f130387..6151544c1 100644 --- a/client/src/Components/StarPrompt/index.jsx +++ b/client/src/Components/v1/StarPrompt/index.jsx @@ -4,7 +4,7 @@ import CloseIcon from "@mui/icons-material/Close"; import { useTheme } from "@emotion/react"; import { useSelector, useDispatch } from "react-redux"; import { useTranslation } from "react-i18next"; -import { setStarPromptOpen } from "../../Features/UI/uiSlice"; +import { setStarPromptOpen } from "../../../Features/UI/uiSlice.js"; const StarPrompt = ({ repoUrl = "https://github.com/bluewave-labs/checkmate" }) => { const theme = useTheme(); diff --git a/client/src/Components/StatBox/index.jsx b/client/src/Components/v1/StatBox/index.jsx similarity index 97% rename from client/src/Components/StatBox/index.jsx rename to client/src/Components/v1/StatBox/index.jsx index 15eb61f74..1b5f1cf6f 100644 --- a/client/src/Components/StatBox/index.jsx +++ b/client/src/Components/v1/StatBox/index.jsx @@ -1,8 +1,8 @@ import { Stack, Typography } from "@mui/material"; -import Image from "../Image"; +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/v1/useMonitorUtils.js"; /** * StatBox Component diff --git a/client/src/Components/StatusBoxes/index.jsx b/client/src/Components/v1/StatusBoxes/index.jsx similarity index 93% rename from client/src/Components/StatusBoxes/index.jsx rename to client/src/Components/v1/StatusBoxes/index.jsx index d97bb6211..a78337ba7 100644 --- a/client/src/Components/StatusBoxes/index.jsx +++ b/client/src/Components/v1/StatusBoxes/index.jsx @@ -1,6 +1,6 @@ // Components import { Stack } from "@mui/material"; -import SkeletonLayout from "./skeleton"; +import SkeletonLayout from "./skeleton.jsx"; // Utils import { useTheme } from "@mui/material/styles"; import PropTypes from "prop-types"; diff --git a/client/src/Components/StatusBoxes/skeleton.jsx b/client/src/Components/v1/StatusBoxes/skeleton.jsx similarity index 100% rename from client/src/Components/StatusBoxes/skeleton.jsx rename to client/src/Components/v1/StatusBoxes/skeleton.jsx diff --git a/client/src/Components/Subheader/index.jsx b/client/src/Components/v1/Subheader/index.jsx similarity index 100% rename from client/src/Components/Subheader/index.jsx rename to client/src/Components/v1/Subheader/index.jsx diff --git a/client/src/Components/Tab/index.jsx b/client/src/Components/v1/Tab/index.jsx similarity index 100% rename from client/src/Components/Tab/index.jsx rename to client/src/Components/v1/Tab/index.jsx diff --git a/client/src/Components/Table/TablePagination/Actions/index.jsx b/client/src/Components/v1/Table/TablePagination/Actions/index.jsx similarity index 95% rename from client/src/Components/Table/TablePagination/Actions/index.jsx rename to client/src/Components/v1/Table/TablePagination/Actions/index.jsx index dec72446b..0df62878e 100644 --- a/client/src/Components/Table/TablePagination/Actions/index.jsx +++ b/client/src/Components/v1/Table/TablePagination/Actions/index.jsx @@ -1,7 +1,7 @@ import PropTypes from "prop-types"; import { Box, Button } from "@mui/material"; -import LeftArrow from "../../../ArrowLeft"; -import RightArrow from "../../../ArrowRight"; +import LeftArrow from "../../../ArrowLeft/index.jsx"; +import RightArrow from "../../../ArrowRight/index.jsx"; import { useTheme } from "@emotion/react"; TablePaginationActions.propTypes = { diff --git a/client/src/Components/Table/TablePagination/index.jsx b/client/src/Components/v1/Table/TablePagination/index.jsx similarity index 96% rename from client/src/Components/Table/TablePagination/index.jsx rename to client/src/Components/v1/Table/TablePagination/index.jsx index 44b127d45..8a5af0cd5 100644 --- a/client/src/Components/Table/TablePagination/index.jsx +++ b/client/src/Components/v1/Table/TablePagination/index.jsx @@ -1,8 +1,8 @@ import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { Stack, TablePagination, Typography } from "@mui/material"; -import { TablePaginationActions } from "./Actions"; -import SelectorVertical from "../../../assets/icons/selector-vertical.svg?react"; +import { TablePaginationActions } from "./Actions/index.jsx"; +import SelectorVertical from "../../../../assets/icons/selector-vertical.svg?react"; Pagination.propTypes = { paginationLabel: PropTypes.string, // Label for the pagination. diff --git a/client/src/Components/Table/TableUtils.js b/client/src/Components/v1/Table/TableUtils.js similarity index 100% rename from client/src/Components/Table/TableUtils.js rename to client/src/Components/v1/Table/TableUtils.js diff --git a/client/src/Components/Table/index.jsx b/client/src/Components/v1/Table/index.jsx similarity index 99% rename from client/src/Components/Table/index.jsx rename to client/src/Components/v1/Table/index.jsx index bfafb5387..2113a2215 100644 --- a/client/src/Components/Table/index.jsx +++ b/client/src/Components/v1/Table/index.jsx @@ -8,7 +8,7 @@ import { TableRow, } from "@mui/material"; import Tooltip from "@mui/material/Tooltip"; -import SkeletonLayout from "./skeleton"; +import SkeletonLayout from "./skeleton.jsx"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Components/Table/skeleton.jsx b/client/src/Components/v1/Table/skeleton.jsx similarity index 100% rename from client/src/Components/Table/skeleton.jsx rename to client/src/Components/v1/Table/skeleton.jsx diff --git a/client/src/Components/TextLink/index.jsx b/client/src/Components/v1/TextLink/index.jsx similarity index 100% rename from client/src/Components/TextLink/index.jsx rename to client/src/Components/v1/TextLink/index.jsx diff --git a/client/src/Components/ThemeSwitch/SunAndMoonIcon.jsx b/client/src/Components/v1/ThemeSwitch/SunAndMoonIcon.jsx similarity index 100% rename from client/src/Components/ThemeSwitch/SunAndMoonIcon.jsx rename to client/src/Components/v1/ThemeSwitch/SunAndMoonIcon.jsx diff --git a/client/src/Components/ThemeSwitch/index.css b/client/src/Components/v1/ThemeSwitch/index.css similarity index 100% rename from client/src/Components/ThemeSwitch/index.css rename to client/src/Components/v1/ThemeSwitch/index.css diff --git a/client/src/Components/ThemeSwitch/index.jsx b/client/src/Components/v1/ThemeSwitch/index.jsx similarity index 91% rename from client/src/Components/ThemeSwitch/index.jsx rename to client/src/Components/v1/ThemeSwitch/index.jsx index 5efb17419..60763da90 100644 --- a/client/src/Components/ThemeSwitch/index.jsx +++ b/client/src/Components/v1/ThemeSwitch/index.jsx @@ -10,9 +10,9 @@ */ import { IconButton } from "@mui/material"; -import SunAndMoonIcon from "./SunAndMoonIcon"; +import SunAndMoonIcon from "./SunAndMoonIcon.jsx"; import { useDispatch, useSelector } from "react-redux"; -import { setMode } from "../../Features/UI/uiSlice"; +import { setMode } from "../../../Features/UI/uiSlice.js"; import "./index.css"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Components/Toast/body.jsx b/client/src/Components/v1/Toast/body.jsx similarity index 100% rename from client/src/Components/Toast/body.jsx rename to client/src/Components/v1/Toast/body.jsx diff --git a/client/src/Components/Toast/index.jsx b/client/src/Components/v1/Toast/index.jsx similarity index 98% rename from client/src/Components/Toast/index.jsx rename to client/src/Components/v1/Toast/index.jsx index 36df0e631..5d66fca3f 100644 --- a/client/src/Components/Toast/index.jsx +++ b/client/src/Components/v1/Toast/index.jsx @@ -2,7 +2,7 @@ import Stack from "@mui/material/Stack"; import IconButton from "@mui/material/IconButton"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; -import ToastBody from "./body"; +import ToastBody from "./body.jsx"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import ErrorOutlineOutlinedIcon from "@mui/icons-material/ErrorOutlineOutlined"; import WarningAmberOutlinedIcon from "@mui/icons-material/WarningAmberOutlined"; diff --git a/client/src/Components/Inputs/TextInput/indexV2.tsx b/client/src/Components/v2/Inputs/TextInput.tsx similarity index 100% rename from client/src/Components/Inputs/TextInput/indexV2.tsx rename to client/src/Components/v2/Inputs/TextInput.tsx diff --git a/client/src/Pages/v1/About/index.jsx b/client/src/Pages/v1/About/index.jsx index 0e68089d3..ff7bb6c6b 100644 --- a/client/src/Pages/v1/About/index.jsx +++ b/client/src/Pages/v1/About/index.jsx @@ -1,6 +1,6 @@ import React from "react"; -import AppBar from "../../../Components/Common/AppBar.jsx"; -import Footer from "../../../Components/Common/Footer.jsx"; +import AppBar from "@/Components/v1/Common/AppBar.jsx"; +import Footer from "@/Components/v1/Common/Footer.jsx"; import { useTranslation } from "react-i18next"; const AboutUs = () => { diff --git a/client/src/Pages/v1/Account/EditUser/index.jsx b/client/src/Pages/v1/Account/EditUser/index.jsx index 658de5144..e2e976916 100644 --- a/client/src/Pages/v1/Account/EditUser/index.jsx +++ b/client/src/Pages/v1/Account/EditUser/index.jsx @@ -2,9 +2,9 @@ import Stack from "@mui/material/Stack"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; -import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; -import Search from "../../../../Components/Inputs/Search/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; +import Search from "@/Components/v1/Inputs/Search/index.jsx"; import Button from "@mui/material/Button"; import RoleTable from "../components/RoleTable/index.jsx"; diff --git a/client/src/Pages/v1/Account/components/AddTeamMember/index.jsx b/client/src/Pages/v1/Account/components/AddTeamMember/index.jsx index e17ed433b..219658b63 100644 --- a/client/src/Pages/v1/Account/components/AddTeamMember/index.jsx +++ b/client/src/Pages/v1/Account/components/AddTeamMember/index.jsx @@ -1,7 +1,7 @@ import { Button, Stack } from "@mui/material"; -import { GenericDialog } from "../../../../../Components/Dialog/genericDialog.jsx"; -import TextInput from "../../../../../Components/Inputs/TextInput/index.jsx"; -import Select from "../../../../../Components/Inputs/Select/index.jsx"; +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 { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; @@ -10,7 +10,7 @@ import { useState } from "react"; import PasswordTooltip from "../../../Auth/components/PasswordTooltip.jsx"; import useAddTeamMember from "./hooks/useAddTeamMember.jsx"; import usePasswordFeedback from "../../../Auth/hooks/usePasswordFeedback.jsx"; -import { PasswordEndAdornment } from "../../../../../Components/Inputs/TextInput/Adornments/index.jsx"; +import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; import PropTypes from "prop-types"; const INITIAL_FORM_STATE = { diff --git a/client/src/Pages/v1/Account/components/PasswordPanel.jsx b/client/src/Pages/v1/Account/components/PasswordPanel.jsx index 11961bf58..ca2af9205 100644 --- a/client/src/Pages/v1/Account/components/PasswordPanel.jsx +++ b/client/src/Pages/v1/Account/components/PasswordPanel.jsx @@ -2,10 +2,10 @@ import TabPanel from "@mui/lab/TabPanel"; import { useState } from "react"; import { useTheme } from "@emotion/react"; import { Box, Stack, Typography, Button } from "@mui/material"; -import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments/index.jsx"; -import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +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 Alert from "../../../../Components/Alert/index.jsx"; +import Alert from "@/Components/v1/Alert/index.jsx"; import { update } from "../../../../Features/Auth/authSlice.js"; import { useDispatch, useSelector } from "react-redux"; import { createToast } from "../../../../Utils/toastUtils.jsx"; diff --git a/client/src/Pages/v1/Account/components/ProfilePanel.jsx b/client/src/Pages/v1/Account/components/ProfilePanel.jsx index 1ce1d34da..2035861a2 100644 --- a/client/src/Pages/v1/Account/components/ProfilePanel.jsx +++ b/client/src/Pages/v1/Account/components/ProfilePanel.jsx @@ -2,9 +2,9 @@ import { useTheme } from "@emotion/react"; import { useState } from "react"; import TabPanel from "@mui/lab/TabPanel"; import { Box, Button, Divider, Stack, Typography } from "@mui/material"; -import Avatar from "../../../../Components/Avatar/index.jsx"; -import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; -import ImageUpload from "../../../../Components/Inputs/ImageUpload/index.jsx"; +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 { useDispatch, useSelector } from "react-redux"; import { @@ -14,8 +14,8 @@ import { } from "../../../../Features/Auth/authSlice.js"; import { createToast } from "../../../../Utils/toastUtils.jsx"; import { logger } from "../../../../Utils/Logger.js"; -import { GenericDialog } from "../../../../Components/Dialog/genericDialog.jsx"; -import Dialog from "../../../../Components/Dialog/index.jsx"; +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/v1/Account/components/RoleTable/index.jsx index c42be7bcd..02dcaaf0d 100644 --- a/client/src/Pages/v1/Account/components/RoleTable/index.jsx +++ b/client/src/Pages/v1/Account/components/RoleTable/index.jsx @@ -1,5 +1,5 @@ import Typography from "@mui/material/Typography"; -import DataTable from "../../../../../Components/Table/index.jsx"; +import DataTable from "@/Components/v1/Table/index.jsx"; import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Account/components/TeamPanel.jsx b/client/src/Pages/v1/Account/components/TeamPanel.jsx index 8964cd277..02918eb30 100644 --- a/client/src/Pages/v1/Account/components/TeamPanel.jsx +++ b/client/src/Pages/v1/Account/components/TeamPanel.jsx @@ -3,14 +3,14 @@ import TabPanel from "@mui/lab/TabPanel"; import { Button, ButtonGroup, Stack, Typography } from "@mui/material"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +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 Select from "../../../../Components/Inputs/Select/index.jsx"; -import { GenericDialog } from "../../../../Components/Dialog/genericDialog.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/Table/index.jsx"; +import DataTable from "@/Components/v1/Table/index.jsx"; import { useGetInviteToken } from "../../../../Hooks/v1/inviteHooks.js"; import { useNavigate } from "react-router-dom"; import { useIsSuperAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; diff --git a/client/src/Pages/v1/Account/index.jsx b/client/src/Pages/v1/Account/index.jsx index a496858b7..ae16ff9d7 100644 --- a/client/src/Pages/v1/Account/index.jsx +++ b/client/src/Pages/v1/Account/index.jsx @@ -3,7 +3,7 @@ import PropTypes from "prop-types"; import { useNavigate } from "react-router-dom"; import { useSelector } from "react-redux"; import { Box, Tab, useTheme } from "@mui/material"; -import CustomTabList from "../../../Components/Tab/index.jsx"; +import CustomTabList from "@/Components/v1/Tab/index.jsx"; import TabContext from "@mui/lab/TabContext"; import ProfilePanel from "./components/ProfilePanel.jsx"; import PasswordPanel from "./components/PasswordPanel.jsx"; diff --git a/client/src/Pages/v1/Auth/CheckEmail.jsx b/client/src/Pages/v1/Auth/CheckEmail.jsx index 95217bcb5..c12a84804 100644 --- a/client/src/Pages/v1/Auth/CheckEmail.jsx +++ b/client/src/Pages/v1/Auth/CheckEmail.jsx @@ -9,7 +9,7 @@ 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 IconBox from "../../../Components/IconBox/index.jsx"; +import IconBox from "@/Components/v1/IconBox/index.jsx"; import "./index.css"; const CheckEmail = () => { diff --git a/client/src/Pages/v1/Auth/ForgotPassword.jsx b/client/src/Pages/v1/Auth/ForgotPassword.jsx index 9836f5d77..8cbd85d8a 100644 --- a/client/src/Pages/v1/Auth/ForgotPassword.jsx +++ b/client/src/Pages/v1/Auth/ForgotPassword.jsx @@ -6,11 +6,11 @@ import { forgotPassword } from "../../../Features/Auth/authSlice.js"; import { useEffect, useState } from "react"; import { newOrChangedCredentials } from "../../../Validation/validation.js"; import { useNavigate } from "react-router-dom"; -import TextInput from "../../../Components/Inputs/TextInput/index.jsx"; +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 IconBox from "../../../Components/IconBox/index.jsx"; +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/index.jsx b/client/src/Pages/v1/Auth/Login/index.jsx index 901edcd8b..7f5dcb5e4 100644 --- a/client/src/Pages/v1/Auth/Login/index.jsx +++ b/client/src/Pages/v1/Auth/Login/index.jsx @@ -1,9 +1,9 @@ // Components import Stack from "@mui/material/Stack"; import Button from "@mui/material/Button"; -import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; -import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments/index.jsx"; -import TextLink from "../../../../Components/TextLink/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; +import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; +import TextLink from "@/Components/v1/TextLink/index.jsx"; import AuthPageWrapper from "../components/AuthPageWrapper.jsx"; // Utils import { useTheme } from "@mui/material/styles"; diff --git a/client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx b/client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx index 2bede1ae4..629494098 100644 --- a/client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx +++ b/client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx @@ -6,7 +6,7 @@ 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/IconBox/index.jsx"; +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/v1/Auth/Register/index.jsx index a903602d4..d6515b85c 100644 --- a/client/src/Pages/v1/Auth/Register/index.jsx +++ b/client/src/Pages/v1/Auth/Register/index.jsx @@ -1,7 +1,7 @@ // Components import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import Button from "@mui/material/Button"; import Box from "@mui/material/Box"; import PasswordTooltip from "../components/PasswordTooltip.jsx"; diff --git a/client/src/Pages/v1/Auth/SetNewPassword.jsx b/client/src/Pages/v1/Auth/SetNewPassword.jsx index e9df89d88..45f3351f3 100644 --- a/client/src/Pages/v1/Auth/SetNewPassword.jsx +++ b/client/src/Pages/v1/Auth/SetNewPassword.jsx @@ -7,10 +7,10 @@ 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 Check from "../../../Components/Check/Check.jsx"; -import TextInput from "../../../Components/Inputs/TextInput/index.jsx"; -import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adornments/index.jsx"; -import IconBox from "../../../Components/IconBox/index.jsx"; +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"; diff --git a/client/src/Pages/v1/Auth/components/AuthHeader.jsx b/client/src/Pages/v1/Auth/components/AuthHeader.jsx index 067c47d92..a34e83d6a 100644 --- a/client/src/Pages/v1/Auth/components/AuthHeader.jsx +++ b/client/src/Pages/v1/Auth/components/AuthHeader.jsx @@ -3,7 +3,7 @@ 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 ThemeSwitch from "../../../../Components/ThemeSwitch/index.jsx"; +import ThemeSwitch from "@/Components/v1/ThemeSwitch/index.jsx"; // Utils import { useTheme } from "@mui/material/styles"; diff --git a/client/src/Pages/v1/Auth/components/PasswordTooltip.jsx b/client/src/Pages/v1/Auth/components/PasswordTooltip.jsx index ff3c2f385..2881eb001 100644 --- a/client/src/Pages/v1/Auth/components/PasswordTooltip.jsx +++ b/client/src/Pages/v1/Auth/components/PasswordTooltip.jsx @@ -1,4 +1,4 @@ -import Check from "../../../../Components/Check/Check.jsx"; +import Check from "@/Components/v1/Check/Check.jsx"; import Stack from "@mui/material/Stack"; import { Tooltip, useTheme } from "@mui/material"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx b/client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx index e204d7167..df457c0e2 100644 --- a/client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx +++ b/client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx @@ -1,16 +1,16 @@ //Components import Stack from "@mui/material/Stack"; -import DataTable from "../../../../../Components/Table/index.jsx"; +import DataTable from "@/Components/v1/Table/index.jsx"; import Table from "@mui/material/Table"; import TableBody from "@mui/material/TableBody"; import TableRow from "@mui/material/TableRow"; import TableCell from "@mui/material/TableCell"; -import TableSkeleton from "../../../../../Components/Table/skeleton.jsx"; -import Pagination from "../../../../../Components/Table/TablePagination/index.jsx"; -import { StatusLabel } from "../../../../../Components/Label/index.jsx"; -import { HttpStatusLabel } from "../../../../../Components/HttpStatusLabel/index.jsx"; -import GenericFallback from "../../../../../Components/GenericFallback/index.jsx"; -import NetworkError from "../../../../../Components/GenericFallback/NetworkError.jsx"; +import TableSkeleton from "@/Components/v1/Table/skeleton.jsx"; +import Pagination from "@/Components/v1/Table/TablePagination/index.jsx"; +import { StatusLabel } from "@/Components/v1/Label/index.jsx"; +import { HttpStatusLabel } from "@/Components/v1/HttpStatusLabel/index.jsx"; +import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; +import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx"; //Utils import { formatDateWithTz } from "../../../../../Utils/timeUtils.js"; diff --git a/client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx b/client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx index b1bde1175..c16f54f18 100644 --- a/client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx +++ b/client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx @@ -1,6 +1,6 @@ // Components import { Stack, Typography, Button, ButtonGroup } from "@mui/material"; -import Select from "../../../../../Components/Inputs/Select/index.jsx"; +import Select from "@/Components/v1/Inputs/Select/index.jsx"; import PropTypes from "prop-types"; //Utils diff --git a/client/src/Pages/v1/Incidents/index.jsx b/client/src/Pages/v1/Incidents/index.jsx index b9281f97c..3c0d7f60f 100644 --- a/client/src/Pages/v1/Incidents/index.jsx +++ b/client/src/Pages/v1/Incidents/index.jsx @@ -1,7 +1,7 @@ // Components import { Stack } from "@mui/material"; -import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx"; -import GenericFallback from "../../../Components/GenericFallback/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; import IncidentTable from "./Components/IncidentTable/index.jsx"; import OptionsHeader from "./Components/OptionsHeader/index.jsx"; import StatusBoxes from "./Components/StatusBoxes/index.jsx"; @@ -13,7 +13,7 @@ import { useFetchMonitorsByTeamId } from "../../../Hooks/v1/monitorHooks.js"; import { useFetchChecksSummaryByTeamId } from "../../../Hooks/v1/checkHooks.js"; import { useAcknowledgeChecks } from "../../../Hooks/v1/checkHooks.js"; import { useState, useEffect } from "react"; -import NetworkError from "../../../Components/GenericFallback/NetworkError.jsx"; +import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx"; import { useTranslation } from "react-i18next"; import { useParams } from "react-router-dom"; diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx b/client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx index 299331a79..5fe1d8984 100644 --- a/client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx @@ -1,4 +1,4 @@ -import ConfigBox from "../../../../../Components/ConfigBox/index.jsx"; +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"; diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/CustomThreshold/index.jsx b/client/src/Pages/v1/Infrastructure/Create/Components/CustomThreshold/index.jsx index 4edc0b766..d783f7985 100644 --- a/client/src/Pages/v1/Infrastructure/Create/Components/CustomThreshold/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/Components/CustomThreshold/index.jsx @@ -1,6 +1,6 @@ import { Box, Stack, Typography } from "@mui/material"; -import TextInput from "../../../../../../Components/Inputs/TextInput/index.jsx"; -import Checkbox from "../../../../../../Components/Inputs/Checkbox/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; +import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx"; import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/MonitorActionButtons.jsx b/client/src/Pages/v1/Infrastructure/Create/Components/MonitorActionButtons.jsx index 16df8ad01..871fe0d7b 100644 --- a/client/src/Pages/v1/Infrastructure/Create/Components/MonitorActionButtons.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/Components/MonitorActionButtons.jsx @@ -4,7 +4,7 @@ import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline"; import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded"; -import Dialog from "../../../../../Components/Dialog/index.jsx"; +import Dialog from "@/Components/v1/Dialog/index.jsx"; import PropTypes from "prop-types"; const MonitorActionButtons = ({ monitor, isBusy, handlePause, handleRemove }) => { diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx b/client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx index 4313c34c0..570f38a7e 100644 --- a/client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx @@ -2,7 +2,7 @@ import { Box, Stack, Tooltip, Typography } from "@mui/material"; import { useMonitorUtils } from "../../../../../Hooks/v1/useMonitorUtils.js"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import PulseDot from "../../../../../Components/Animated/PulseDot.jsx"; +import PulseDot from "@/Components/v1/Animated/PulseDot.jsx"; import PropTypes from "prop-types"; const MonitorStatusHeader = ({ monitor, infrastructureMonitor }) => { const theme = useTheme(); diff --git a/client/src/Pages/v1/Infrastructure/Create/index.jsx b/client/src/Pages/v1/Infrastructure/Create/index.jsx index 06ecdff6c..d4fd859ec 100644 --- a/client/src/Pages/v1/Infrastructure/Create/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/index.jsx @@ -1,17 +1,17 @@ //Components -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; -import ConfigBox from "../../../../Components/ConfigBox/index.jsx"; -import FieldWrapper from "../../../../Components/Inputs/FieldWrapper/index.jsx"; -import Link from "../../../../Components/Link/index.jsx"; -import Select from "../../../../Components/Inputs/Select/index.jsx"; -import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; +import FieldWrapper from "@/Components/v1/Inputs/FieldWrapper/index.jsx"; +import Link from "@/Components/v1/Link/index.jsx"; +import Select from "@/Components/v1/Inputs/Select/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material"; -import { HttpAdornment } from "../../../../Components/Inputs/TextInput/Adornments/index.jsx"; +import { HttpAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; import MonitorStatusHeader from "./Components/MonitorStatusHeader.jsx"; import MonitorActionButtons from "./Components/MonitorActionButtons.jsx"; import CustomAlertsSection from "./Components/CustomAlertsSection.jsx"; // Utils -import NotificationsConfig from "../../../../Components/NotificationConfig/index.jsx"; +import NotificationsConfig from "@/Components/v1/NotificationConfig/index.jsx"; import { useGetNotificationsByTeamId } from "../../../../Hooks/v1/useNotifications.js"; import { useParams } from "react-router-dom"; import { useState, useEffect } from "react"; diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx index 47f33a5e6..ee100bb0e 100644 --- a/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx @@ -1,7 +1,7 @@ // Components import { Typography } from "@mui/material"; import BaseContainer from "../BaseContainer/index.jsx"; -import AreaChart from "../../../../../../Components/Charts/AreaChart/index.jsx"; +import AreaChart from "@/Components/v1/Charts/AreaChart/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/index.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/index.jsx index 3dfb4edd8..3465683d9 100644 --- a/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/index.jsx @@ -9,7 +9,7 @@ import { TzTick, InfrastructureTooltip, TemperatureTooltip, -} from "../../../../../../Components/Charts/Utils/chartUtils.jsx"; +} from "@/Components/v1/Charts/Utils/chartUtils.jsx"; import { useTheme } from "@emotion/react"; import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx index 5c55119ad..cd61b7109 100644 --- a/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx @@ -1,5 +1,5 @@ // Components -import CustomGauge from "../../../../../../Components/Charts/CustomGauge/index.jsx"; +import CustomGauge from "@/Components/v1/Charts/CustomGauge/index.jsx"; import BaseContainer from "../BaseContainer/index.jsx"; import { Stack, Typography, Box } from "@mui/material"; // Utils diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx index 5cfdba350..d9f93311c 100644 --- a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx @@ -6,7 +6,7 @@ import { TzTick, InfrastructureTooltip, NetworkTick, -} from "../../../../../../Components/Charts/Utils/chartUtils.jsx"; +} from "@/Components/v1/Charts/Utils/chartUtils.jsx"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx index 52ccf0805..0747dfcd1 100644 --- a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx @@ -1,6 +1,6 @@ import PropTypes from "prop-types"; -import StatusBoxes from "../../../../../../Components/StatusBoxes/index.jsx"; -import StatBox from "../../../../../../Components/StatBox/index.jsx"; +import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx"; +import StatBox from "@/Components/v1/StatBox/index.jsx"; import { Typography } from "@mui/material"; import { useTranslation } from "react-i18next"; import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/index.jsx index e0d6986e1..78db333e6 100644 --- a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/index.jsx @@ -3,10 +3,10 @@ import { useState, useEffect } from "react"; import { Box } from "@mui/material"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; -import Select from "../../../../../../Components/Inputs/Select/index.jsx"; +import Select from "@/Components/v1/Inputs/Select/index.jsx"; import NetworkStatBoxes from "./NetworkStatBoxes.jsx"; import NetworkCharts from "./NetworkCharts.jsx"; -import MonitorTimeFrameHeader from "../../../../../../Components/MonitorTimeFrameHeader/index.jsx"; +import MonitorTimeFrameHeader from "@/Components/v1/MonitorTimeFrameHeader/index.jsx"; const getAvailableInterfaces = (net) => { return (net || []).map((iface) => iface.name).filter(Boolean); diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx index 47862449e..7f09e82d5 100644 --- a/client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx @@ -1,7 +1,7 @@ // Components import { Stack, Typography } from "@mui/material"; -import StatusBoxes from "../../../../../../Components/StatusBoxes/index.jsx"; -import StatBox from "../../../../../../Components/StatBox/index.jsx"; +import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx"; +import StatBox from "@/Components/v1/StatBox/index.jsx"; //Utils import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; diff --git a/client/src/Pages/v1/Infrastructure/Details/index.jsx b/client/src/Pages/v1/Infrastructure/Details/index.jsx index 04691d985..f9c51c27d 100644 --- a/client/src/Pages/v1/Infrastructure/Details/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/index.jsx @@ -1,14 +1,14 @@ // Components import { Stack, Typography, Tab } from "@mui/material"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; -import MonitorDetailsControlHeader from "../../../../Components/MonitorDetailsControlHeader/index.jsx"; -import MonitorTimeFrameHeader from "../../../../Components/MonitorTimeFrameHeader/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import MonitorDetailsControlHeader from "@/Components/v1/MonitorDetailsControlHeader/index.jsx"; +import MonitorTimeFrameHeader from "@/Components/v1/MonitorTimeFrameHeader/index.jsx"; import StatusBoxes from "./Components/StatusBoxes/index.jsx"; import GaugeBoxes from "./Components/GaugeBoxes/index.jsx"; import AreaChartBoxes from "./Components/AreaChartBoxes/index.jsx"; -import GenericFallback from "../../../../Components/GenericFallback/index.jsx"; +import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; import NetworkStats from "./Components/NetworkStats/index.jsx"; -import CustomTabList from "../../../../Components/Tab/index.jsx"; +import CustomTabList from "@/Components/v1/Tab/index.jsx"; import TabContext from "@mui/lab/TabContext"; // Utils diff --git a/client/src/Pages/v1/Infrastructure/Monitors/Components/Filters/index.jsx b/client/src/Pages/v1/Infrastructure/Monitors/Components/Filters/index.jsx index bef632e88..70bf502af 100644 --- a/client/src/Pages/v1/Infrastructure/Monitors/Components/Filters/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Monitors/Components/Filters/index.jsx @@ -1,6 +1,6 @@ import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; -import FilterHeader from "../../../../../../Components/FilterHeader/index.jsx"; +import FilterHeader from "@/Components/v1/FilterHeader/index.jsx"; import { useMemo } from "react"; import { Box, Button } from "@mui/material"; import ClearIcon from "@mui/icons-material/Clear"; diff --git a/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx b/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx index 801a9bc6a..cc2cd5f98 100644 --- a/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx @@ -1,14 +1,14 @@ // Components import { Box } from "@mui/material"; -import DataTable from "../../../../../../Components/Table/index.jsx"; -import Host from "../../../../../../Components/Host/index.jsx"; -import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; +import DataTable from "@/Components/v1/Table/index.jsx"; +import Host from "@/Components/v1/Host/index.jsx"; +import { StatusLabel } from "@/Components/v1/Label/index.jsx"; 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 CustomGauge from "../../../../../../Components/Charts/CustomGauge/index.jsx"; +import CustomGauge from "@/Components/v1/Charts/CustomGauge/index.jsx"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx b/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx index c2270c691..d3af34d00 100644 --- a/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx @@ -7,7 +7,7 @@ import { createToast } from "../../../../../../Utils/toastUtils.jsx"; import { IconButton, Menu, MenuItem } from "@mui/material"; import Settings from "../../../../../../assets/icons/settings-bold.svg?react"; import PropTypes from "prop-types"; -import Dialog from "../../../../../../Components/Dialog/index.jsx"; +import Dialog from "@/Components/v1/Dialog/index.jsx"; import { networkService } from "../../../../../../Utils/NetworkService.js"; import { usePauseMonitor } from "../../../../../../Hooks/v1/monitorHooks.js"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Infrastructure/Monitors/index.jsx b/client/src/Pages/v1/Infrastructure/Monitors/index.jsx index 7fbfa1167..6c3a657ab 100644 --- a/client/src/Pages/v1/Infrastructure/Monitors/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Monitors/index.jsx @@ -1,11 +1,11 @@ // Components import { Stack } from "@mui/material"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; -import MonitorCountHeader from "../../../../Components/MonitorCountHeader/index.jsx"; -import MonitorCreateHeader from "../../../../Components/MonitorCreateHeader/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import MonitorCountHeader from "@/Components/v1/MonitorCountHeader/index.jsx"; +import MonitorCreateHeader from "@/Components/v1/MonitorCreateHeader/index.jsx"; import MonitorsTable from "./Components/MonitorsTable/index.jsx"; -import Pagination from "../../../../Components/Table/TablePagination/index.jsx"; -import PageStateWrapper from "../../../../Components/PageStateWrapper/index.jsx"; +import Pagination from "@/Components/v1/Table/TablePagination/index.jsx"; +import PageStateWrapper from "@/Components/v1/PageStateWrapper/index.jsx"; import Filter from "./Components/Filters/index.jsx"; import SearchComponent from "../../Uptime/Monitors/Components/SearchComponent/index.jsx"; // Utils diff --git a/client/src/Pages/v1/Logs/Diagnostics/components/gauges/index.jsx b/client/src/Pages/v1/Logs/Diagnostics/components/gauges/index.jsx index 082be2587..b38837cf2 100644 --- a/client/src/Pages/v1/Logs/Diagnostics/components/gauges/index.jsx +++ b/client/src/Pages/v1/Logs/Diagnostics/components/gauges/index.jsx @@ -1,5 +1,5 @@ import Stack from "@mui/material/Stack"; -import CustomGauge from "../../../../../../Components/Charts/CustomGauge/index.jsx"; +import CustomGauge from "@/Components/v1/Charts/CustomGauge/index.jsx"; import Typography from "@mui/material/Typography"; // Utils diff --git a/client/src/Pages/v1/Logs/Diagnostics/index.jsx b/client/src/Pages/v1/Logs/Diagnostics/index.jsx index d7a1a8a7c..54feee516 100644 --- a/client/src/Pages/v1/Logs/Diagnostics/index.jsx +++ b/client/src/Pages/v1/Logs/Diagnostics/index.jsx @@ -2,8 +2,8 @@ import Stack from "@mui/material/Stack"; import Box from "@mui/material/Box"; import Gauges from "./components/gauges/index.jsx"; import Button from "@mui/material/Button"; -import StatBox from "../../../../Components/StatBox/index.jsx"; -import StatusBoxes from "../../../../Components/StatusBoxes/index.jsx"; +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"; diff --git a/client/src/Pages/v1/Logs/Logs/index.jsx b/client/src/Pages/v1/Logs/Logs/index.jsx index 5c7ad9fa9..64c4cb97a 100644 --- a/client/src/Pages/v1/Logs/Logs/index.jsx +++ b/client/src/Pages/v1/Logs/Logs/index.jsx @@ -1,6 +1,6 @@ import Stack from "@mui/material/Stack"; import Box from "@mui/material/Box"; -import Select from "../../../../Components/Inputs/Select/index.jsx"; +import Select from "@/Components/v1/Inputs/Select/index.jsx"; import Typography from "@mui/material/Typography"; import Divider from "@mui/material/Divider"; diff --git a/client/src/Pages/v1/Logs/Queue/components/FailedJobTable/index.jsx b/client/src/Pages/v1/Logs/Queue/components/FailedJobTable/index.jsx index 46be9520c..743f52639 100644 --- a/client/src/Pages/v1/Logs/Queue/components/FailedJobTable/index.jsx +++ b/client/src/Pages/v1/Logs/Queue/components/FailedJobTable/index.jsx @@ -1,6 +1,6 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import DataTable from "../../../../../../Components/Table/index.jsx"; +import DataTable from "@/Components/v1/Table/index.jsx"; import { useNavigate } from "react-router-dom"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/Logs/Queue/components/JobTable/index.jsx b/client/src/Pages/v1/Logs/Queue/components/JobTable/index.jsx index 4ac4069bd..7d309e845 100644 --- a/client/src/Pages/v1/Logs/Queue/components/JobTable/index.jsx +++ b/client/src/Pages/v1/Logs/Queue/components/JobTable/index.jsx @@ -1,5 +1,5 @@ import Stack from "@mui/material/Stack"; -import DataTable from "../../../../../../Components/Table/index.jsx"; +import DataTable from "@/Components/v1/Table/index.jsx"; import Typography from "@mui/material/Typography"; // Utils import PropTypes from "prop-types"; @@ -7,7 +7,7 @@ import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; import { TypeToPathMap } from "../../../../../../Utils/monitorUtils.js"; import { useTranslation } from "react-i18next"; -import { createHeaderFactory } from "../../../../../../Components/Table/TableUtils.js"; +import { createHeaderFactory } from "@/Components/v1/Table/TableUtils.js"; const JobTable = ({ jobs = [] }) => { const theme = useTheme(); diff --git a/client/src/Pages/v1/Logs/Queue/components/Metrics/index.jsx b/client/src/Pages/v1/Logs/Queue/components/Metrics/index.jsx index dd9fdcc6a..c4f4389c3 100644 --- a/client/src/Pages/v1/Logs/Queue/components/Metrics/index.jsx +++ b/client/src/Pages/v1/Logs/Queue/components/Metrics/index.jsx @@ -1,6 +1,6 @@ import Stack from "@mui/material/Stack"; -import StatBox from "../../../../../../Components/StatBox/index.jsx"; -import StatusBoxes from "../../../../../../Components/StatusBoxes/index.jsx"; +import StatBox from "@/Components/v1/StatBox/index.jsx"; +import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/Logs/Queue/components/MetricsTable/index.jsx b/client/src/Pages/v1/Logs/Queue/components/MetricsTable/index.jsx index 58f13fd5d..d96f37a20 100644 --- a/client/src/Pages/v1/Logs/Queue/components/MetricsTable/index.jsx +++ b/client/src/Pages/v1/Logs/Queue/components/MetricsTable/index.jsx @@ -1,6 +1,6 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import DataTable from "../../../../../../Components/Table/index.jsx"; +import DataTable from "@/Components/v1/Table/index.jsx"; // Utils import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Logs/index.jsx b/client/src/Pages/v1/Logs/index.jsx index c6dfe4d99..e5bdf623d 100644 --- a/client/src/Pages/v1/Logs/index.jsx +++ b/client/src/Pages/v1/Logs/index.jsx @@ -1,5 +1,5 @@ import Stack from "@mui/material/Stack"; -import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; import Tabs from "@mui/material/Tabs"; import Tab from "@mui/material/Tab"; import Queue from "./Queue/index.jsx"; diff --git a/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx b/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx index b43bcbfc7..8d4c0ae53 100644 --- a/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx +++ b/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx @@ -1,4 +1,4 @@ -import Select from "../../../../../../Components/Inputs/Select/index.jsx"; +import Select from "@/Components/v1/Inputs/Select/index.jsx"; import PropTypes from "prop-types"; const ConfigSelect = ({ configSelection, valueSelect, onChange, ...props }) => { const getValueById = (config, id) => { diff --git a/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx b/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx index 91e6a1531..14920d81c 100644 --- a/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx +++ b/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx @@ -1,7 +1,7 @@ import PropTypes from "prop-types"; -import Search from "../../../../../../Components/Inputs/Search/index.jsx"; +import Search from "@/Components/v1/Inputs/Search/index.jsx"; import { useState } from "react"; -import ConfigRow from "../../../../../../Components/ConfigRow/index.jsx"; +import ConfigRow from "@/Components/v1/ConfigRow/index.jsx"; /** * MonitorsConfig is a component that allows users to select and apply a maintenance window to a list of monitors. diff --git a/client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx b/client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx index da3e72d8a..d0e7b03cd 100644 --- a/client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx +++ b/client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx @@ -2,7 +2,7 @@ import { Box, Button, Stack, Typography } from "@mui/material"; import { useSelector } from "react-redux"; import { useTheme } from "@emotion/react"; import { useEffect, useState } from "react"; -import ConfigBox from "../../../../Components/ConfigBox/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { LocalizationProvider } from "@mui/x-date-pickers"; import { DatePicker } from "@mui/x-date-pickers/DatePicker"; @@ -14,11 +14,11 @@ import ConfigSelect from "./Components/ConfigSelect/index.jsx"; import useMaintenanceData from "./hooks/useMaintenanceData.jsx"; import useMaintenanceActions from "./hooks/useMaintenanceActions.jsx"; -import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.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 "./index.css"; -import Search from "../../../../Components/Inputs/Search/index.jsx"; +import Search from "@/Components/v1/Inputs/Search/index.jsx"; import { logger } from "../../../../Utils/Logger.js"; import { useNavigate, useParams } from "react-router-dom"; diff --git a/client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx b/client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx index cc51697f7..b2654da49 100644 --- a/client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx +++ b/client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx @@ -10,7 +10,7 @@ import { networkService } from "../../../../../main.jsx"; import { createToast } from "../../../../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; -import Dialog from "../../../../../Components/Dialog/index.jsx"; +import Dialog from "@/Components/v1/Dialog/index.jsx"; const ActionsMenu = ({ /* isAdmin, */ maintenanceWindow, updateCallback }) => { maintenanceWindow; diff --git a/client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx b/client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx index fb18cd380..f1d44f944 100644 --- a/client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx +++ b/client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx @@ -1,14 +1,14 @@ import PropTypes from "prop-types"; import { Box } from "@mui/material"; -import DataTable from "../../../../Components/Table/index.jsx"; -import Pagination from "../../../../Components/Table/TablePagination/index.jsx"; +import DataTable from "@/Components/v1/Table/index.jsx"; +import Pagination from "@/Components/v1/Table/TablePagination/index.jsx"; import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded"; 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 { StatusLabel } from "../../../../Components/Label/index.jsx"; +import { StatusLabel } from "@/Components/v1/Label/index.jsx"; import { setRowsPerPage } from "../../../../Features/UI/uiSlice.js"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/Maintenance/index.jsx b/client/src/Pages/v1/Maintenance/index.jsx index a993c153f..cba0e9f18 100644 --- a/client/src/Pages/v1/Maintenance/index.jsx +++ b/client/src/Pages/v1/Maintenance/index.jsx @@ -5,10 +5,10 @@ import "./index.css"; import MaintenanceTable from "./MaintenanceTable/index.jsx"; import { useSelector } from "react-redux"; import { networkService } from "../../../main.jsx"; -import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; -import PageStateWrapper from "../../../Components/PageStateWrapper/index.jsx"; +import PageStateWrapper from "@/Components/v1/PageStateWrapper/index.jsx"; const Maintenance = () => { const theme = useTheme(); diff --git a/client/src/Pages/v1/Notifications/create/index.jsx b/client/src/Pages/v1/Notifications/create/index.jsx index 0d541736d..eb2b04aa6 100644 --- a/client/src/Pages/v1/Notifications/create/index.jsx +++ b/client/src/Pages/v1/Notifications/create/index.jsx @@ -1,13 +1,13 @@ // Components import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; import Button from "@mui/material/Button"; -import ConfigBox from "../../../../Components/ConfigBox/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; import Box from "@mui/material/Box"; -import Select from "../../../../Components/Inputs/Select/index.jsx"; -import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; -import Dialog from "../../../../Components/Dialog/index.jsx"; +import Select from "@/Components/v1/Inputs/Select/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; +import Dialog from "@/Components/v1/Dialog/index.jsx"; // Utils import { useState } from "react"; diff --git a/client/src/Pages/v1/Notifications/index.jsx b/client/src/Pages/v1/Notifications/index.jsx index 5649224b6..78ec6e9bb 100644 --- a/client/src/Pages/v1/Notifications/index.jsx +++ b/client/src/Pages/v1/Notifications/index.jsx @@ -1,11 +1,11 @@ // Components import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; import Button from "@mui/material/Button"; -import DataTable from "../../../Components/Table/index.jsx"; +import DataTable from "@/Components/v1/Table/index.jsx"; import ActionMenu from "./components/ActionMenu.jsx"; -import PageStateWrapper from "../../../Components/PageStateWrapper/index.jsx"; +import PageStateWrapper from "@/Components/v1/PageStateWrapper/index.jsx"; // Utils import { useState } from "react"; diff --git a/client/src/Pages/v1/PageSpeed/Create/index.jsx b/client/src/Pages/v1/PageSpeed/Create/index.jsx index 7cc244848..cb856bd37 100644 --- a/client/src/Pages/v1/PageSpeed/Create/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Create/index.jsx @@ -1,17 +1,17 @@ // Components import { Box, Stack, Tooltip, Typography, Button, ButtonGroup } from "@mui/material"; -import ConfigBox from "../../../../Components/ConfigBox/index.jsx"; -import Select from "../../../../Components/Inputs/Select/index.jsx"; -import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; +import Select from "@/Components/v1/Inputs/Select/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; -import PulseDot from "../../../../Components/Animated/PulseDot.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import PulseDot from "@/Components/v1/Animated/PulseDot.jsx"; import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded"; import SkeletonLayout from "./skeleton.jsx"; -import NotificationsConfig from "../../../../Components/NotificationConfig/index.jsx"; -import Dialog from "../../../../Components/Dialog/index.jsx"; -import { HttpAdornment } from "../../../../Components/Inputs/TextInput/Adornments/index.jsx"; -import Radio from "../../../../Components/Inputs/Radio/index.jsx"; +import NotificationsConfig from "@/Components/v1/NotificationConfig/index.jsx"; +import Dialog from "@/Components/v1/Dialog/index.jsx"; +import { HttpAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; +import Radio from "@/Components/v1/Inputs/Radio/index.jsx"; // Utils import { useState, useEffect } from "react"; diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx index cb6f18bd2..c0da1600f 100644 --- a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx @@ -1,7 +1,7 @@ import { Box, Typography, Divider } from "@mui/material"; -import Checkbox from "../../../../../../Components/Inputs/Checkbox/index.jsx"; +import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx"; import MetricsIcon from "../../../../../../assets/icons/ruler-icon.svg?react"; -import LegendBox from "../../../../../../Components/Charts/LegendBox/index.jsx"; +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/PieChartLegend.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx index fc4b190d8..1297bd699 100644 --- a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx @@ -1,6 +1,6 @@ import { Stack, Box, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; -import LegendBox from "../../../../../../Components/Charts/LegendBox/index.jsx"; +import LegendBox from "@/Components/v1/Charts/LegendBox/index.jsx"; import SpeedometerIcon from "../../../../../../assets/icons/speedometer-icon.svg?react"; import PropTypes from "prop-types"; diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx index bb45117fe..1281a396e 100644 --- a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx @@ -1,4 +1,4 @@ -import ChartBox from "../../../../../../Components/Charts/ChartBox/index.jsx"; +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"; diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx index 0811ae707..ff265317c 100644 --- a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx @@ -1,5 +1,5 @@ -import StatusBoxes from "../../../../../../Components/StatusBoxes/index.jsx"; -import StatBox from "../../../../../../Components/StatBox/index.jsx"; +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 { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx index ce4704144..27f9e885b 100644 --- a/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx @@ -1,4 +1,4 @@ -import ChartBox from "../../../../../../Components/Charts/ChartBox/index.jsx"; +import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx"; import PerformanceIcon from "../../../../../../assets/icons/performance-report.svg?react"; import PieChart from "../Charts/PieChart.jsx"; import { Typography } from "@mui/material"; diff --git a/client/src/Pages/v1/PageSpeed/Details/index.jsx b/client/src/Pages/v1/PageSpeed/Details/index.jsx index cf9652bce..836f0ca37 100644 --- a/client/src/Pages/v1/PageSpeed/Details/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/index.jsx @@ -1,12 +1,12 @@ // Components import { Stack, Typography } from "@mui/material"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; -import MonitorTimeFrameHeader from "../../../../Components/MonitorTimeFrameHeader/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import MonitorTimeFrameHeader from "@/Components/v1/MonitorTimeFrameHeader/index.jsx"; import PageSpeedStatusBoxes from "./Components/PageSpeedStatusBoxes/index.jsx"; -import MonitorDetailsControlHeader from "../../../../Components/MonitorDetailsControlHeader/index.jsx"; +import MonitorDetailsControlHeader from "@/Components/v1/MonitorDetailsControlHeader/index.jsx"; import PageSpeedAreaChart from "./Components/PageSpeedAreaChart/index.jsx"; import PerformanceReport from "./Components/PerformanceReport/index.jsx"; -import GenericFallback from "../../../../Components/GenericFallback/index.jsx"; +import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; diff --git a/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx b/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx index 5857a7a7e..4e551526b 100644 --- a/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx @@ -1,7 +1,7 @@ import PropTypes from "prop-types"; // import PageSpeedIcon from "../../../../assets/icons/page-speed.svg?react"; import PageSpeedIcon from "../../../../../../assets/icons/page-speed.svg?react"; -import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; +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"; @@ -14,7 +14,7 @@ import { import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; import { useState } from "react"; import { useTranslation } from "react-i18next"; -import IconBox from "../../../../../../Components/IconBox/index.jsx"; +import IconBox from "@/Components/v1/IconBox/index.jsx"; /** * CustomToolTip displays a tooltip with formatted date and score information. * @param {Object} props diff --git a/client/src/Pages/v1/PageSpeed/Monitors/index.jsx b/client/src/Pages/v1/PageSpeed/Monitors/index.jsx index e568e8d82..7485a72c9 100644 --- a/client/src/Pages/v1/PageSpeed/Monitors/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Monitors/index.jsx @@ -1,12 +1,12 @@ // Components import { useState } from "react"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; import { Stack } from "@mui/material"; -import CreateMonitorHeader from "../../../../Components/MonitorCreateHeader/index.jsx"; -import MonitorCountHeader from "../../../../Components/MonitorCountHeader/index.jsx"; +import CreateMonitorHeader from "@/Components/v1/MonitorCreateHeader/index.jsx"; +import MonitorCountHeader from "@/Components/v1/MonitorCountHeader/index.jsx"; import MonitorGrid from "./Components/MonitorGrid/index.jsx"; -import PageStateWrapper from "../../../../Components/PageStateWrapper/index.jsx"; -import FallbackPageSpeedWarning from "../../../../Components/Fallback/FallbackPageSpeedWarning.jsx"; +import PageStateWrapper from "@/Components/v1/PageStateWrapper/index.jsx"; +import FallbackPageSpeedWarning from "@/Components/v1/Fallback/FallbackPageSpeedWarning.jsx"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/ServerUnreachable.jsx b/client/src/Pages/v1/ServerUnreachable.jsx index 0db697543..8230f473b 100644 --- a/client/src/Pages/v1/ServerUnreachable.jsx +++ b/client/src/Pages/v1/ServerUnreachable.jsx @@ -3,12 +3,12 @@ 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 Alert from "../../Components/Alert/index.jsx"; +import Alert from "@/Components/v1/Alert/index.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 ThemeSwitch from "../../Components/ThemeSwitch/index.jsx"; +import ThemeSwitch from "@/Components/v1/ThemeSwitch/index.jsx"; import LanguageSelector from "../../Components/LanguageSelector.jsx"; const ServerUnreachable = () => { diff --git a/client/src/Pages/v1/Settings/SettingsAbout.jsx b/client/src/Pages/v1/Settings/SettingsAbout.jsx index 3d5f5224e..6a8125af3 100644 --- a/client/src/Pages/v1/Settings/SettingsAbout.jsx +++ b/client/src/Pages/v1/Settings/SettingsAbout.jsx @@ -1,10 +1,10 @@ import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; -import ConfigBox from "../../../Components/ConfigBox/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import Link from "../../../Components/Link/index.jsx"; +import Link from "@/Components/v1/Link/index.jsx"; const SettingsAbout = () => { const theme = useTheme(); diff --git a/client/src/Pages/v1/Settings/SettingsDemoMonitors.jsx b/client/src/Pages/v1/Settings/SettingsDemoMonitors.jsx index 96f907970..47e699851 100644 --- a/client/src/Pages/v1/Settings/SettingsDemoMonitors.jsx +++ b/client/src/Pages/v1/Settings/SettingsDemoMonitors.jsx @@ -1,12 +1,12 @@ import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; -import ConfigBox from "../../../Components/ConfigBox/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; import { useTranslation } from "react-i18next"; -import Dialog from "../../../Components/Dialog/index.jsx"; +import Dialog from "@/Components/v1/Dialog/index.jsx"; import { useState } from "react"; const SettingsDemoMonitors = ({ isAdmin, HEADER_SX, handleChange, isLoading }) => { diff --git a/client/src/Pages/v1/Settings/SettingsEmail.jsx b/client/src/Pages/v1/Settings/SettingsEmail.jsx index c2389b277..7b5ad175e 100644 --- a/client/src/Pages/v1/Settings/SettingsEmail.jsx +++ b/client/src/Pages/v1/Settings/SettingsEmail.jsx @@ -1,17 +1,17 @@ import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; -import ConfigBox from "../../../Components/ConfigBox/index.jsx"; -import TextInput from "../../../Components/Inputs/TextInput/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; import { Switch } from "@mui/material"; -import TextLink from "../../../Components/TextLink/index.jsx"; +import TextLink from "@/Components/v1/TextLink/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adornments/index.jsx"; +import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; import { useSendTestEmail } from "../../../Hooks/v1/useSendTestEmail.js"; import { createToast } from "../../../Utils/toastUtils.jsx"; diff --git a/client/src/Pages/v1/Settings/SettingsGlobalThresholds.jsx b/client/src/Pages/v1/Settings/SettingsGlobalThresholds.jsx index e238c6475..ea58478e2 100644 --- a/client/src/Pages/v1/Settings/SettingsGlobalThresholds.jsx +++ b/client/src/Pages/v1/Settings/SettingsGlobalThresholds.jsx @@ -1,8 +1,8 @@ import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import ConfigBox from "../../../Components/ConfigBox/index.jsx"; -import TextInput from "../../../Components/Inputs/TextInput/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; diff --git a/client/src/Pages/v1/Settings/SettingsPagespeed.jsx b/client/src/Pages/v1/Settings/SettingsPagespeed.jsx index 83b2e23c7..b0d5d6441 100644 --- a/client/src/Pages/v1/Settings/SettingsPagespeed.jsx +++ b/client/src/Pages/v1/Settings/SettingsPagespeed.jsx @@ -2,9 +2,9 @@ import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; -import ConfigBox from "../../../Components/ConfigBox/index.jsx"; -import TextInput from "../../../Components/Inputs/TextInput/index.jsx"; -import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adornments/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; +import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; diff --git a/client/src/Pages/v1/Settings/SettingsStats.jsx b/client/src/Pages/v1/Settings/SettingsStats.jsx index 52f739ecf..b68006ab2 100644 --- a/client/src/Pages/v1/Settings/SettingsStats.jsx +++ b/client/src/Pages/v1/Settings/SettingsStats.jsx @@ -2,9 +2,9 @@ import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; -import ConfigBox from "../../../Components/ConfigBox/index.jsx"; -import TextInput from "../../../Components/Inputs/TextInput/index.jsx"; -import Dialog from "../../../Components/Dialog/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; +import Dialog from "@/Components/v1/Dialog/index.jsx"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/Settings/SettingsTimeZone.jsx b/client/src/Pages/v1/Settings/SettingsTimeZone.jsx index 59b2008f4..f684c871d 100644 --- a/client/src/Pages/v1/Settings/SettingsTimeZone.jsx +++ b/client/src/Pages/v1/Settings/SettingsTimeZone.jsx @@ -1,8 +1,8 @@ import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import ConfigBox from "../../../Components/ConfigBox/index.jsx"; -import Search from "../../../Components/Inputs/Search/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; +import Search from "@/Components/v1/Inputs/Search/index.jsx"; import timezones from "../../../Utils/timezones.json"; // Utils diff --git a/client/src/Pages/v1/Settings/SettingsUI.jsx b/client/src/Pages/v1/Settings/SettingsUI.jsx index 91f4cea3c..7129e775b 100644 --- a/client/src/Pages/v1/Settings/SettingsUI.jsx +++ b/client/src/Pages/v1/Settings/SettingsUI.jsx @@ -1,8 +1,8 @@ import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import ConfigBox from "../../../Components/ConfigBox/index.jsx"; -import Select from "../../../Components/Inputs/Select/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; +import Select from "@/Components/v1/Inputs/Select/index.jsx"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/Settings/SettingsURL.jsx b/client/src/Pages/v1/Settings/SettingsURL.jsx index 105658d4f..c52dcf317 100644 --- a/client/src/Pages/v1/Settings/SettingsURL.jsx +++ b/client/src/Pages/v1/Settings/SettingsURL.jsx @@ -1,8 +1,8 @@ import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import ConfigBox from "../../../Components/ConfigBox/index.jsx"; -import Select from "../../../Components/Inputs/Select/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; +import Select from "@/Components/v1/Inputs/Select/index.jsx"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/Settings/index.jsx b/client/src/Pages/v1/Settings/index.jsx index d1788eddf..2732e331a 100644 --- a/client/src/Pages/v1/Settings/index.jsx +++ b/client/src/Pages/v1/Settings/index.jsx @@ -1,6 +1,6 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; import SettingsTimeZone from "./SettingsTimeZone.jsx"; import SettingsUI from "./SettingsUI.jsx"; import SettingsURL from "./SettingsURL.jsx"; diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx index c9f7d5f8f..232a12f1d 100644 --- a/client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx @@ -1,5 +1,5 @@ import { Button, Box } from "@mui/material"; -import ProgressUpload from "../../../../../../Components/ProgressBars/index.jsx"; +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"; diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx index fb5278c4e..f31c456ef 100644 --- a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx @@ -1,5 +1,5 @@ import { Stack, Typography } from "@mui/material"; -import ConfigBox from "../../../../../../Components/ConfigBox/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx index 23e713c5a..d11825e69 100644 --- a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx @@ -2,8 +2,8 @@ import { Stack, Typography } from "@mui/material"; import { TabPanel } from "@mui/lab"; import MonitorList from "../MonitorList/index.jsx"; -import Search from "../../../../../../Components/Inputs/Search/index.jsx"; -import Checkbox from "../../../../../../Components/Inputs/Checkbox/index.jsx"; +import Search from "@/Components/v1/Inputs/Search/index.jsx"; +import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx"; // Utils import { useState } from "react"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx index a4612e3da..a668a9db8 100644 --- a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx @@ -1,12 +1,12 @@ // Components import { Stack, Typography } from "@mui/material"; import { TabPanel } from "@mui/lab"; -import ConfigBox from "../../../../../../Components/ConfigBox/index.jsx"; -import Checkbox from "../../../../../../Components/Inputs/Checkbox/index.jsx"; -import TextInput from "../../../../../../Components/Inputs/TextInput/index.jsx"; -import Search from "../../../../../../Components/Inputs/Search/index.jsx"; -import ImageUpload from "../../../../../../Components/Inputs/ImageUpload/index.jsx"; -import ColorPicker from "../../../../../../Components/Inputs/ColorPicker/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; +import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; +import Search from "@/Components/v1/Inputs/Search/index.jsx"; +import ImageUpload from "@/Components/v1/Inputs/ImageUpload/index.jsx"; +import ColorPicker from "@/Components/v1/Inputs/ColorPicker/index.jsx"; import Progress from "../Progress/index.jsx"; // Utils diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx index 971b8d85c..e8669c474 100644 --- a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx @@ -6,7 +6,7 @@ import Content from "./Content.jsx"; // Utils import PropTypes from "prop-types"; -import CustomTabList from "../../../../../../Components/Tab/index.jsx"; +import CustomTabList from "@/Components/v1/Tab/index.jsx"; const Tabs = ({ isCreate, diff --git a/client/src/Pages/v1/StatusPage/Create/index.jsx b/client/src/Pages/v1/StatusPage/Create/index.jsx index 674a158ac..d9fafed70 100644 --- a/client/src/Pages/v1/StatusPage/Create/index.jsx +++ b/client/src/Pages/v1/StatusPage/Create/index.jsx @@ -1,10 +1,10 @@ // Components import { Stack, Button, Typography } from "@mui/material"; import Tabs from "./Components/Tabs/index.jsx"; -import GenericFallback from "../../../../Components/GenericFallback/index.jsx"; +import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; import SkeletonLayout from "./Components/Skeleton/index.jsx"; -import Dialog from "../../../../Components/Dialog/index.jsx"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import Dialog from "@/Components/v1/Dialog/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; //Utils import { useTheme } from "@emotion/react"; import { useState, useEffect, useRef, useCallback } from "react"; diff --git a/client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx b/client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx index fa5ed71f1..c56aa40cc 100644 --- a/client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx +++ b/client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx @@ -1,8 +1,8 @@ // Components import { Box, Stack, Typography, Button } from "@mui/material"; -import Image from "../../../../../../Components/Image/index.jsx"; +import Image from "@/Components/v1/Image/index.jsx"; import SettingsIcon from "../../../../../../assets/icons/settings-bold.svg?react"; -import ThemeSwitch from "../../../../../../Components/ThemeSwitch/index.jsx"; +import ThemeSwitch from "@/Components/v1/ThemeSwitch/index.jsx"; import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward"; //Utils import { useTheme } from "@mui/material/styles"; diff --git a/client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx b/client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx index fd04dae66..59ad4d522 100644 --- a/client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx +++ b/client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx @@ -1,8 +1,8 @@ // Components import { Stack, Box } from "@mui/material"; -import Host from "../../../../../../Components/Host/index.jsx"; -import StatusPageBarChart from "../../../../../../Components/Charts/StatusPageBarChart/index.jsx"; -import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; +import Host from "@/Components/v1/Host/index.jsx"; +import StatusPageBarChart from "@/Components/v1/Charts/StatusPageBarChart/index.jsx"; +import { StatusLabel } from "@/Components/v1/Label/index.jsx"; //Utils import { useTheme } from "@mui/material/styles"; diff --git a/client/src/Pages/v1/StatusPage/Status/index.jsx b/client/src/Pages/v1/StatusPage/Status/index.jsx index 48fc44fc9..e700bbbd9 100644 --- a/client/src/Pages/v1/StatusPage/Status/index.jsx +++ b/client/src/Pages/v1/StatusPage/Status/index.jsx @@ -1,13 +1,13 @@ // Components import { Typography, Stack } from "@mui/material"; -import GenericFallback from "../../../../Components/GenericFallback/index.jsx"; +import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; import AdminLink from "./Components/AdminLink/index.jsx"; import ControlsHeader from "./Components/ControlsHeader/index.jsx"; import SkeletonLayout from "./Components/Skeleton/index.jsx"; import StatusBar from "./Components/StatusBar/index.jsx"; import MonitorsList from "./Components/MonitorsList/index.jsx"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; -import TextLink from "../../../../Components/TextLink/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import TextLink from "@/Components/v1/TextLink/index.jsx"; // Utils import { useStatusPageFetch } from "./Hooks/useStatusPageFetch.jsx"; diff --git a/client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx b/client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx index d8d68fb2e..1afe25409 100644 --- a/client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx +++ b/client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx @@ -1,7 +1,7 @@ -import DataTable from "../../../../../../Components/Table/index.jsx"; +import DataTable from "@/Components/v1/Table/index.jsx"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; +import { StatusLabel } from "@/Components/v1/Label/index.jsx"; import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward"; import { Stack, Typography } from "@mui/material"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/StatusPage/StatusPages/index.jsx b/client/src/Pages/v1/StatusPage/StatusPages/index.jsx index 8e0c89ce3..3c60fe950 100644 --- a/client/src/Pages/v1/StatusPage/StatusPages/index.jsx +++ b/client/src/Pages/v1/StatusPage/StatusPages/index.jsx @@ -1,9 +1,9 @@ // Components import { Stack } from "@mui/material"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; -import MonitorCreateHeader from "../../../../Components/MonitorCreateHeader/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import MonitorCreateHeader from "@/Components/v1/MonitorCreateHeader/index.jsx"; import StatusPagesTable from "./Components/StatusPagesTable/index.jsx"; -import PageStateWrapper from "../../../../Components/PageStateWrapper/index.jsx"; +import PageStateWrapper from "@/Components/v1/PageStateWrapper/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { useStatusPagesFetch } from "./Hooks/useStatusPagesFetch.jsx"; diff --git a/client/src/Pages/v1/Uptime/BulkImport/index.jsx b/client/src/Pages/v1/Uptime/BulkImport/index.jsx index 412ba0f97..9f3efcd67 100644 --- a/client/src/Pages/v1/Uptime/BulkImport/index.jsx +++ b/client/src/Pages/v1/Uptime/BulkImport/index.jsx @@ -6,8 +6,8 @@ import { Box, Stack, Typography, Button, Link } from "@mui/material"; //Components import { createToast } from "../../../../Utils/toastUtils.jsx"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; -import ConfigBox from "../../../../Components/ConfigBox/index.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"; diff --git a/client/src/Pages/v1/Uptime/Create/index.jsx b/client/src/Pages/v1/Uptime/Create/index.jsx index 6bb2a3b33..202f64d85 100644 --- a/client/src/Pages/v1/Uptime/Create/index.jsx +++ b/client/src/Pages/v1/Uptime/Create/index.jsx @@ -9,16 +9,16 @@ import { Tooltip, Typography, } from "@mui/material"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; -import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; -import { HttpAdornment } from "../../../../Components/Inputs/TextInput/Adornments/index.jsx"; -import Radio from "../../../../Components/Inputs/Radio/index.jsx"; -import Select from "../../../../Components/Inputs/Select/index.jsx"; -import ConfigBox from "../../../../Components/ConfigBox/index.jsx"; -import NotificationsConfig from "../../../../Components/NotificationConfig/index.jsx"; -import Checkbox from "../../../../Components/Inputs/Checkbox/index.jsx"; -import Dialog from "../../../../Components/Dialog/index.jsx"; -import PulseDot from "../../../../Components/Animated/PulseDot.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; +import { HttpAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; +import Radio from "@/Components/v1/Inputs/Radio/index.jsx"; +import Select from "@/Components/v1/Inputs/Select/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; +import NotificationsConfig from "@/Components/v1/NotificationConfig/index.jsx"; +import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx"; +import Dialog from "@/Components/v1/Dialog/index.jsx"; +import PulseDot from "@/Components/v1/Animated/PulseDot.jsx"; import SkeletonLayout from "./skeleton.jsx"; // Utils diff --git a/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx b/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx index c6b76c289..45f35a42e 100644 --- a/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx +++ b/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx @@ -1,6 +1,6 @@ // Components import { Stack, Typography, Box } from "@mui/material"; -import ChartBox from "../../../../../../Components/Charts/ChartBox/index.jsx"; +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"; diff --git a/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx index f306dfd76..daa7b4f53 100644 --- a/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx +++ b/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx @@ -1,5 +1,5 @@ -import ChartBox from "../../../../../../Components/Charts/ChartBox/index.jsx"; -import MonitorDetailsAreaChart from "../../../../../../Components/Charts/MonitorDetailsAreaChart/index.jsx"; +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 SkeletonLayout from "./ResponseTimeChartSkeleton.jsx"; import PropTypes from "prop-types"; diff --git a/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx b/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx index cc5745a26..948489ac1 100644 --- a/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx +++ b/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx @@ -1,9 +1,9 @@ -import ChartBox from "../../../../../../Components/Charts/ChartBox/index.jsx"; +import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx"; import PropTypes from "prop-types"; import HistoryIcon from "../../../../../../assets/icons/history-icon.svg?react"; -import Table from "../../../../../../Components/Table/index.jsx"; -import TablePagination from "../../../../../../Components/Table/TablePagination/index.jsx"; -import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; +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 SkeletonLayout from "./skeleton.jsx"; diff --git a/client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx b/client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx index f0988466c..a7b971839 100644 --- a/client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx +++ b/client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx @@ -1,5 +1,5 @@ -import StatusBoxes from "../../../../../../Components/StatusBoxes/index.jsx"; -import StatBox from "../../../../../../Components/StatBox/index.jsx"; +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"; diff --git a/client/src/Pages/v1/Uptime/Details/index.jsx b/client/src/Pages/v1/Uptime/Details/index.jsx index 4e773dc2b..f5e283495 100644 --- a/client/src/Pages/v1/Uptime/Details/index.jsx +++ b/client/src/Pages/v1/Uptime/Details/index.jsx @@ -1,12 +1,12 @@ // Components -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; -import MonitorDetailsControlHeader from "../../../../Components/MonitorDetailsControlHeader/index.jsx"; -import MonitorTimeFrameHeader from "../../../../Components/MonitorTimeFrameHeader/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import MonitorDetailsControlHeader from "@/Components/v1/MonitorDetailsControlHeader/index.jsx"; +import MonitorTimeFrameHeader from "@/Components/v1/MonitorTimeFrameHeader/index.jsx"; import ChartBoxes from "./Components/ChartBoxes/index.jsx"; import ResponseTimeChart from "./Components/Charts/ResponseTimeChart.jsx"; import ResponseTable from "./Components/ResponseTable/index.jsx"; import UptimeStatusBoxes from "./Components/UptimeStatusBoxes/index.jsx"; -import GenericFallback from "../../../../Components/GenericFallback/index.jsx"; +import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx index 85c4a8540..b9a6824c1 100644 --- a/client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx +++ b/client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx @@ -1,6 +1,6 @@ import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; -import FilterHeader from "../../../../../../Components/FilterHeader/index.jsx"; +import FilterHeader from "@/Components/v1/FilterHeader/index.jsx"; import { useMemo } from "react"; import { Box, Button } from "@mui/material"; import ClearIcon from "@mui/icons-material/Clear"; diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx index 3339102a2..64c546a8a 100644 --- a/client/src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx +++ b/client/src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import Search from "../../../../../../Components/Inputs/Search/index.jsx"; +import Search from "@/Components/v1/Inputs/Search/index.jsx"; import { Box } from "@mui/material"; import useDebounce from "../../Hooks/useDebounce.jsx"; import { useEffect, useRef } from "react"; diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx index 575647eab..9cf2d247d 100644 --- a/client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx +++ b/client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx @@ -1,15 +1,15 @@ // Components import { Box, Stack } from "@mui/material"; -import DataTable from "../../../../../../Components/Table/index.jsx"; +import DataTable from "@/Components/v1/Table/index.jsx"; import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded"; import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded"; -import Host from "../../../../../../Components/Host/index.jsx"; -import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; -import BarChart from "../../../../../../Components/Charts/BarChart/index.jsx"; -import ActionsMenu from "../../../../../../Components/ActionsMenu/index.jsx"; +import Host from "@/Components/v1/Host/index.jsx"; +import { StatusLabel } from "@/Components/v1/Label/index.jsx"; +import BarChart from "@/Components/v1/Charts/BarChart/index.jsx"; +import ActionsMenu from "@/Components/v1/ActionsMenu/index.jsx"; import LoadingSpinner from "../LoadingSpinner/index.jsx"; -import TableSkeleton from "../../../../../../Components/Table/skeleton.jsx"; +import TableSkeleton from "@/Components/v1/Table/skeleton.jsx"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/Uptime/Monitors/index.jsx b/client/src/Pages/v1/Uptime/Monitors/index.jsx index dae0bc2d5..cacfc23b4 100644 --- a/client/src/Pages/v1/Uptime/Monitors/index.jsx +++ b/client/src/Pages/v1/Uptime/Monitors/index.jsx @@ -4,17 +4,17 @@ // 2a.List of monitors must have the total number of monitors that match. // Components -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.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/Table/TablePagination/index.jsx"; -import CreateMonitorHeader from "../../../../Components/MonitorCreateHeader/index.jsx"; +import Pagination from "@/Components/v1/Table/TablePagination/index.jsx"; +import CreateMonitorHeader from "@/Components/v1/MonitorCreateHeader/index.jsx"; import SearchComponent from "./Components/SearchComponent/index.jsx"; import Filter from "./Components/Filter/index.jsx"; -import PageStateWrapper from "../../../../Components/PageStateWrapper/index.jsx"; +import PageStateWrapper from "@/Components/v1/PageStateWrapper/index.jsx"; -import MonitorCountHeader from "../../../../Components/MonitorCountHeader/index.jsx"; +import MonitorCountHeader from "@/Components/v1/MonitorCountHeader/index.jsx"; // MUI Components import { Stack, Box, Button } from "@mui/material"; diff --git a/client/src/Pages/v2/Auth/Login.tsx b/client/src/Pages/v2/Auth/Login.tsx index 58eec01dc..fd4a08e93 100644 --- a/client/src/Pages/v2/Auth/Login.tsx +++ b/client/src/Pages/v2/Auth/Login.tsx @@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next"; import { useForm, Controller } from "react-hook-form"; import { useTheme } from "@mui/material/styles"; -import { TextInput } from "@/Components/Inputs/TextInput/indexV2.tsx"; +import { TextInput } from "@/Components/v2/Inputs/TextInput"; import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; import { usePost } from "@/Hooks/v2/UseApi"; diff --git a/client/src/Pages/v2/Auth/Register.tsx b/client/src/Pages/v2/Auth/Register.tsx index a9043a2c9..2a02c401f 100644 --- a/client/src/Pages/v2/Auth/Register.tsx +++ b/client/src/Pages/v2/Auth/Register.tsx @@ -5,7 +5,7 @@ import { useForm, Controller } from "react-hook-form"; import { useTheme } from "@mui/material/styles"; import Typography from "@mui/material/Typography"; -import { TextInput } from "@/Components/Inputs/TextInput/indexV2.tsx"; +import { TextInput } from "@/Components/v2/Inputs/TextInput"; import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; import { usePost } from "@/Hooks/v2/UseApi"; diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index 2e4fd9c50..90dcaa3f5 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -1,7 +1,7 @@ import { useSelector } from "react-redux"; import { lightTheme, darkTheme } from "@/Utils/Theme/v2/theme"; import { Navigate, Route, Routes as LibRoutes } from "react-router"; -import HomeLayout from "../Components/Layouts/HomeLayout"; +import HomeLayout from "@/Components/v1/Layouts/HomeLayout"; import NotFound from "../Pages/v1/NotFound"; // Auth import AuthLogin from "../Pages/v1/Auth/Login"; @@ -47,10 +47,10 @@ import Settings from "../Pages/v1/Settings"; import Maintenance from "../Pages/v1/Maintenance"; -import ProtectedRoute from "../Components/ProtectedRoute"; -import RoleProtectedRoute from "../Components/RoleProtectedRoute"; +import ProtectedRoute from "../Components/v1/ProtectedRoute"; +import RoleProtectedRoute from "../Components/v1/RoleProtectedRoute"; import CreateNewMaintenanceWindow from "../Pages/v1/Maintenance/CreateMaintenance"; -import withAdminCheck from "../Components/HOC/withAdminCheck"; +import withAdminCheck from "@/Components/v1/HOC/withAdminCheck"; import BulkImport from "../Pages/v1/Uptime/BulkImport"; import Logs from "../Pages/v1/Logs"; diff --git a/client/src/Utils/toastUtils.jsx b/client/src/Utils/toastUtils.jsx index 21cf13e2b..41b2d5482 100644 --- a/client/src/Utils/toastUtils.jsx +++ b/client/src/Utils/toastUtils.jsx @@ -1,6 +1,6 @@ import PropTypes from "prop-types"; import { toast, Slide } from "react-toastify"; -import Toast from "../Components/Toast"; +import Toast from "../Components/v1/Toast"; /** * @param {object} props * @param {'info' | 'error' | 'warning'} - The variant of the alert (e.g., "info", "error"). diff --git a/client/src/main.jsx b/client/src/main.jsx index 01ecefcc6..dd42587af 100644 --- a/client/src/main.jsx +++ b/client/src/main.jsx @@ -8,7 +8,7 @@ import { PersistGate } from "redux-persist/integration/react"; import NetworkServiceProvider from "./Utils/NetworkServiceProvider.jsx"; import { networkService } from "./Utils/NetworkService"; export { networkService }; -import I18nLoader from "./Components/I18nLoader"; +import I18nLoader from "./Components/v1/I18nLoader"; ReactDOM.createRoot(document.getElementById("root")).render( From b57ed47592153337df4fc81ea64d6a43cc935606 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sat, 27 Sep 2025 14:57:55 -0700 Subject: [PATCH 038/103] remove mode prop --- client/src/Routes/v2router.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/Routes/v2router.tsx b/client/src/Routes/v2router.tsx index d450854f9..941550026 100644 --- a/client/src/Routes/v2router.tsx +++ b/client/src/Routes/v2router.tsx @@ -22,7 +22,7 @@ const V2Routes = ({ mode = "light" }) => { /> } + element={} > Date: Sat, 27 Sep 2025 15:47:56 -0700 Subject: [PATCH 039/103] Add collapse button and logo --- client/src/Components/v2/Arrows/ArrowLeft.tsx | 36 +++++++++++ .../src/Components/v2/Arrows/ArrowRight.tsx | 28 +++++++++ .../src/Components/v2/Layouts/RootLayout.tsx | 35 +---------- .../v2/Layouts/Sidebar/CollapseButton.tsx | 50 ++++++++++++++++ .../Components/v2/Layouts/Sidebar/Logo.tsx | 59 +++++++++++++++++++ .../Components/v2/Layouts/Sidebar/index.tsx | 40 +++++++++++++ client/src/Features/UI/uiSlice.js | 5 ++ client/src/Utils/Theme/v2/palette.ts | 10 ++++ client/src/Utils/Theme/v2/theme.ts | 3 + client/src/types/env.d.ts | 8 +++ client/src/types/mui.d.ts | 3 + 11 files changed, 243 insertions(+), 34 deletions(-) create mode 100644 client/src/Components/v2/Arrows/ArrowLeft.tsx create mode 100644 client/src/Components/v2/Arrows/ArrowRight.tsx create mode 100644 client/src/Components/v2/Layouts/Sidebar/CollapseButton.tsx create mode 100644 client/src/Components/v2/Layouts/Sidebar/Logo.tsx create mode 100644 client/src/Components/v2/Layouts/Sidebar/index.tsx diff --git a/client/src/Components/v2/Arrows/ArrowLeft.tsx b/client/src/Components/v2/Arrows/ArrowLeft.tsx new file mode 100644 index 000000000..01d1342c8 --- /dev/null +++ b/client/src/Components/v2/Arrows/ArrowLeft.tsx @@ -0,0 +1,36 @@ +import LeftArrow from "@/assets/icons/left-arrow.svg?react"; +import LeftArrowDouble from "@/assets/icons/left-arrow-double.svg?react"; +import LeftArrowLong from "@/assets/icons/left-arrow-long.svg?react"; + +export const ArrowLeft = ({ + type, + color = "#667085", + ...props +}: { + type?: string; + color?: string | undefined; + [key: string]: any; +}) => { + if (type === "double") { + return ( + + ); + } else if (type === "long") { + return ( + + ); + } else { + return ( + + ); + } +}; diff --git a/client/src/Components/v2/Arrows/ArrowRight.tsx b/client/src/Components/v2/Arrows/ArrowRight.tsx new file mode 100644 index 000000000..c73165f16 --- /dev/null +++ b/client/src/Components/v2/Arrows/ArrowRight.tsx @@ -0,0 +1,28 @@ +import RightArrow from "@/assets/icons/right-arrow.svg?react"; +import RightArrowDouble from "@/assets/icons/right-arrow-double.svg?react"; + +export const ArrowRight = ({ + type, + color = "#667085", + ...props +}: { + type?: string; + color?: string | undefined; + [key: string]: any; +}) => { + if (type === "double") { + return ( + + ); + } else { + return ( + + ); + } +}; diff --git a/client/src/Components/v2/Layouts/RootLayout.tsx b/client/src/Components/v2/Layouts/RootLayout.tsx index be2f42e30..dbc6f2e5e 100644 --- a/client/src/Components/v2/Layouts/RootLayout.tsx +++ b/client/src/Components/v2/Layouts/RootLayout.tsx @@ -1,39 +1,6 @@ -import { useState, useEffect } from "react"; -import useMediaQuery from "@mui/material/useMediaQuery"; -import { useTheme } from "@mui/material/styles"; - import { Outlet } from "react-router"; import Stack from "@mui/material/Stack"; -import Box from "@mui/material/Box"; - -const COLLAPSED_WIDTH = 50; -const EXPANDED_WIDTH = 250; - -const SideBar = () => { - const theme = useTheme(); - const isSmall = useMediaQuery(theme.breakpoints.down("md")); - - const [collapsed, setCollapsed] = useState(false); - useEffect(() => { - setCollapsed(isSmall); - }, [isSmall]); - return ( - - setCollapsed(!collapsed)} - > - Sidebar Content - - - ); -}; +import { SideBar } from "@/Components/v2/Layouts/Sidebar"; const RootLayout = () => { return ( diff --git a/client/src/Components/v2/Layouts/Sidebar/CollapseButton.tsx b/client/src/Components/v2/Layouts/Sidebar/CollapseButton.tsx new file mode 100644 index 000000000..564c41591 --- /dev/null +++ b/client/src/Components/v2/Layouts/Sidebar/CollapseButton.tsx @@ -0,0 +1,50 @@ +import IconButton from "@mui/material/IconButton"; +import { ArrowRight } from "@/Components/v2/Arrows/ArrowRight"; +import { ArrowLeft } from "@/Components/v2/Arrows/ArrowLeft"; +import { useTheme } from "@mui/material/styles"; +import { useDispatch } from "react-redux"; +import { toggleSidebar } from "../../../../Features/UI/uiSlice.js"; + +export const CollapseButton = ({ collapsed }: { collapsed: boolean }) => { + const theme = useTheme(); + const dispatch = useDispatch(); + const arrowIcon = collapsed ? ( + + ) : ( + + ); + + return ( + { + dispatch(toggleSidebar()); + }} + > + {arrowIcon} + + ); +}; diff --git a/client/src/Components/v2/Layouts/Sidebar/Logo.tsx b/client/src/Components/v2/Layouts/Sidebar/Logo.tsx new file mode 100644 index 000000000..6d190b99d --- /dev/null +++ b/client/src/Components/v2/Layouts/Sidebar/Logo.tsx @@ -0,0 +1,59 @@ +import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import { useTheme } from "@mui/material/styles"; +import { useNavigate } from "react-router"; +import { useTranslation } from "react-i18next"; + +export const Logo = ({ collapsed }: { collapsed: boolean }) => { + const { t } = useTranslation(); + const theme = useTheme(); + const navigate = useNavigate(); + + return ( + navigate("/")} + sx={{ cursor: "pointer" }} + > + + C + + + {" "} + + {t("common.appName")} + + + + ); +}; diff --git a/client/src/Components/v2/Layouts/Sidebar/index.tsx b/client/src/Components/v2/Layouts/Sidebar/index.tsx new file mode 100644 index 000000000..43975adfc --- /dev/null +++ b/client/src/Components/v2/Layouts/Sidebar/index.tsx @@ -0,0 +1,40 @@ +import { useEffect } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { setCollapsed } from "@/Features/UI/uiSlice"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import { useTheme } from "@mui/material/styles"; + +import { CollapseButton } from "@/Components/v2/Layouts/Sidebar/CollapseButton"; +import Stack from "@mui/material/Stack"; +import { Logo } from "@/Components/v2/Layouts/Sidebar/Logo"; + +export const COLLAPSED_WIDTH = 64; +export const EXPANDED_WIDTH = 250; + +export const SideBar = () => { + const theme = useTheme(); + const isSmall = useMediaQuery(theme.breakpoints.down("md")); + const dispatch = useDispatch(); + const collapsed = useSelector((state: any) => state.ui.sidebar.collapsed); + + useEffect(() => { + dispatch(setCollapsed({ collapsed: isSmall })); + }, [isSmall]); + + return ( + + + + + ); +}; diff --git a/client/src/Features/UI/uiSlice.js b/client/src/Features/UI/uiSlice.js index 45ba267b0..63f7ae90e 100644 --- a/client/src/Features/UI/uiSlice.js +++ b/client/src/Features/UI/uiSlice.js @@ -47,6 +47,10 @@ const uiSlice = createSlice({ toggleSidebar: (state) => { state.sidebar.collapsed = !state.sidebar.collapsed; }, + setCollapsed: (state, action) => { + const { collapsed } = action.payload; + state.sidebar.collapsed = collapsed; + }, setMode: (state, action) => { state.mode = action.payload; }, @@ -73,6 +77,7 @@ export default uiSlice.reducer; export const { setRowsPerPage, toggleSidebar, + setCollapsed, setMode, setShowURL, setGreeting, diff --git a/client/src/Utils/Theme/v2/palette.ts b/client/src/Utils/Theme/v2/palette.ts index 2875f750d..2e41a159c 100644 --- a/client/src/Utils/Theme/v2/palette.ts +++ b/client/src/Utils/Theme/v2/palette.ts @@ -54,12 +54,17 @@ export const lightPalette = { main: colors.offWhite, contrastText: colors.blueGray800, contrastTextSecondary: colors.blueGray600, + lowContrast: colors.gray250, }, secondary: { main: colors.gray200, light: colors.lightBlueWave, contrastText: colors.blueGray600, }, + tertiary: { + main: colors.gray100, + contrastText: colors.blueGray800, + }, }; export const darkPalette = { @@ -73,10 +78,15 @@ export const darkPalette = { main: colors.offBlack, contrastText: colors.blueGray50, contrastTextSecondary: colors.gray200, + lowContrast: colors.blueGray600, }, secondary: { main: "#313131", light: colors.lightBlueWave, contrastText: colors.gray200, }, + tertiary: { + main: colors.blueGray800, + contrastText: colors.gray100, + }, }; diff --git a/client/src/Utils/Theme/v2/theme.ts b/client/src/Utils/Theme/v2/theme.ts index 55d2b9bb9..77bab3e70 100644 --- a/client/src/Utils/Theme/v2/theme.ts +++ b/client/src/Utils/Theme/v2/theme.ts @@ -67,6 +67,9 @@ export const theme = (mode: string, palette: any) => }, }, }, + shape: { + borderRadius: 2, + }, }); export const lightTheme = createTheme(theme("light", lightPalette)); diff --git a/client/src/types/env.d.ts b/client/src/types/env.d.ts index e2bce23dd..74a2a29e7 100644 --- a/client/src/types/env.d.ts +++ b/client/src/types/env.d.ts @@ -7,3 +7,11 @@ interface ImportMetaEnv { interface ImportMeta { readonly env: ImportMetaEnv; } + +declare module "*.svg?react" { + import * as React from "react"; + const ReactComponent: React.FunctionComponent< + React.SVGProps & { title?: string } + >; + export default ReactComponent; +} diff --git a/client/src/types/mui.d.ts b/client/src/types/mui.d.ts index 851ffea70..5f5c1c38b 100644 --- a/client/src/types/mui.d.ts +++ b/client/src/types/mui.d.ts @@ -3,13 +3,16 @@ import "@mui/material/Button"; declare module "@mui/material/styles" { interface Palette { accent: Palette["primary"]; + tertiary: Palette["primary"]; } interface PaletteOptions { accent?: PaletteOptions["primary"]; + tertiary?: PaletteOptions["primary"]; } interface PaletteColor { contrastTextSecondary?: string; + lowContrast?: string; } } From 7f2318da01a21f47216f666ebdd8192b48fe6b2a Mon Sep 17 00:00:00 2001 From: ariretiarno Date: Tue, 30 Sep 2025 11:28:22 +0700 Subject: [PATCH 040/103] helm chart --- .../templates/client-deployment.yaml | 8 ++-- .../checkmate/templates/client-ingress.yaml | 9 ++++- .../checkmate/templates/client-service.yaml | 4 +- .../helm/checkmate/templates/mongodb-pvc.yaml | 10 ----- .../checkmate/templates/mongodb-service.yaml | 4 +- ...loyment.yaml => mongodb-statefulsets.yaml} | 21 ++++++---- .../checkmate/templates/redis-deployment.yaml | 26 ------------- .../helm/checkmate/templates/redis-pvc.yaml | 10 ----- .../checkmate/templates/redis-service.yaml | 6 ++- .../templates/redis-statefulsets.yaml | 33 ++++++++++++++++ .../templates/server-deployment.yaml | 6 +-- .../checkmate/templates/server-ingress.yaml | 21 ++++++---- .../checkmate/templates/server-service.yaml | 4 +- charts/helm/checkmate/values.yaml | 38 +++++++++++-------- 14 files changed, 107 insertions(+), 93 deletions(-) delete mode 100644 charts/helm/checkmate/templates/mongodb-pvc.yaml rename charts/helm/checkmate/templates/{mongodb-deployment.yaml => mongodb-statefulsets.yaml} (54%) delete mode 100644 charts/helm/checkmate/templates/redis-deployment.yaml delete mode 100644 charts/helm/checkmate/templates/redis-pvc.yaml create mode 100644 charts/helm/checkmate/templates/redis-statefulsets.yaml diff --git a/charts/helm/checkmate/templates/client-deployment.yaml b/charts/helm/checkmate/templates/client-deployment.yaml index 6c1f70fd7..b6e13fc7d 100644 --- a/charts/helm/checkmate/templates/client-deployment.yaml +++ b/charts/helm/checkmate/templates/client-deployment.yaml @@ -1,16 +1,16 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: client + name: checkmate-client spec: replicas: 1 selector: matchLabels: - app: client + app: checkmate-client template: metadata: labels: - app: client + app: checkmate-client spec: containers: - name: client @@ -20,3 +20,5 @@ spec: env: - name: UPTIME_APP_API_BASE_URL value: "{{ .Values.server.protocol }}://{{ .Values.server.ingress.host }}/api/v1" + - name: UPTIME_APP_CLIENT_HOST + value: "http://{{ .Values.client.ingress.host }}" diff --git a/charts/helm/checkmate/templates/client-ingress.yaml b/charts/helm/checkmate/templates/client-ingress.yaml index da059a863..014aa53b3 100644 --- a/charts/helm/checkmate/templates/client-ingress.yaml +++ b/charts/helm/checkmate/templates/client-ingress.yaml @@ -2,10 +2,15 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: client-ingress + name: checkmate-client-ingress + {{- if .Values.client.ingress.annotations }} annotations: - nginx.ingress.kubernetes.io/rewrite-target: / + {{- range $key, $value := .Values.client.ingress.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- end }} spec: + ingressClassName: {{ .Values.client.ingress.className }} rules: - host: {{ .Values.client.ingress.host }} http: diff --git a/charts/helm/checkmate/templates/client-service.yaml b/charts/helm/checkmate/templates/client-service.yaml index afb88188c..b56eaeedf 100644 --- a/charts/helm/checkmate/templates/client-service.yaml +++ b/charts/helm/checkmate/templates/client-service.yaml @@ -1,9 +1,9 @@ apiVersion: v1 kind: Service metadata: - name: client + name: checkmate-client spec: selector: - app: client + app: checkmate-client ports: - port: {{ .Values.client.port }} diff --git a/charts/helm/checkmate/templates/mongodb-pvc.yaml b/charts/helm/checkmate/templates/mongodb-pvc.yaml deleted file mode 100644 index 435b662e6..000000000 --- a/charts/helm/checkmate/templates/mongodb-pvc.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: mongodb-pvc -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: {{ .Values.persistence.mongodbSize }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/mongodb-service.yaml b/charts/helm/checkmate/templates/mongodb-service.yaml index 2911dc3eb..b96fe1a82 100644 --- a/charts/helm/checkmate/templates/mongodb-service.yaml +++ b/charts/helm/checkmate/templates/mongodb-service.yaml @@ -1,9 +1,9 @@ apiVersion: v1 kind: Service metadata: - name: mongodb + name: checkmate-mongodb spec: selector: - app: mongodb + app: checkmate-mongodb ports: - port: {{ .Values.mongodb.port }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/mongodb-deployment.yaml b/charts/helm/checkmate/templates/mongodb-statefulsets.yaml similarity index 54% rename from charts/helm/checkmate/templates/mongodb-deployment.yaml rename to charts/helm/checkmate/templates/mongodb-statefulsets.yaml index 4c906fe09..4b490d7d7 100644 --- a/charts/helm/checkmate/templates/mongodb-deployment.yaml +++ b/charts/helm/checkmate/templates/mongodb-statefulsets.yaml @@ -1,16 +1,16 @@ apiVersion: apps/v1 -kind: Deployment +kind: StatefulSet metadata: - name: mongodb + name: checkmate-mongodb spec: replicas: 1 selector: matchLabels: - app: mongodb + app: checkmate-mongodb template: metadata: labels: - app: mongodb + app: checkmate-mongodb spec: containers: - name: mongodb @@ -21,7 +21,12 @@ spec: volumeMounts: - name: mongodb-data mountPath: /data/db - volumes: - - name: mongodb-data - persistentVolumeClaim: - claimName: mongodb-pvc \ No newline at end of file + volumeClaimTemplates: + - metadata: + name: checkmate-mongo-persistent-storage + spec: + storageClassName: "gp3" + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: {{ .Values.persistence.mongodbSize }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/redis-deployment.yaml b/charts/helm/checkmate/templates/redis-deployment.yaml deleted file mode 100644 index cc9ffdaf9..000000000 --- a/charts/helm/checkmate/templates/redis-deployment.yaml +++ /dev/null @@ -1,26 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: redis -spec: - replicas: 1 - selector: - matchLabels: - app: redis - template: - metadata: - labels: - app: redis - spec: - containers: - - name: redis - image: {{ .Values.redis.image }} - ports: - - containerPort: {{ .Values.redis.port }} - volumeMounts: - - name: redis-data - mountPath: /data - volumes: - - name: redis-data - persistentVolumeClaim: - claimName: redis-pvc diff --git a/charts/helm/checkmate/templates/redis-pvc.yaml b/charts/helm/checkmate/templates/redis-pvc.yaml deleted file mode 100644 index 2f0d81960..000000000 --- a/charts/helm/checkmate/templates/redis-pvc.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: redis-pvc -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: {{ .Values.persistence.redisSize }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/redis-service.yaml b/charts/helm/checkmate/templates/redis-service.yaml index f29fa46de..2720350da 100644 --- a/charts/helm/checkmate/templates/redis-service.yaml +++ b/charts/helm/checkmate/templates/redis-service.yaml @@ -1,9 +1,11 @@ +{{- if .Values.redis.enabled }} apiVersion: v1 kind: Service metadata: - name: redis + name: checkmate-redis spec: selector: - app: redis + app: checkmate-redis ports: - port: {{ .Values.redis.port }} +{{- end }} diff --git a/charts/helm/checkmate/templates/redis-statefulsets.yaml b/charts/helm/checkmate/templates/redis-statefulsets.yaml new file mode 100644 index 000000000..252361730 --- /dev/null +++ b/charts/helm/checkmate/templates/redis-statefulsets.yaml @@ -0,0 +1,33 @@ +{{- if .Values.redis.enabled }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: checkmate-redis +spec: + replicas: 1 + selector: + matchLabels: + app: checkmate-redis + template: + metadata: + labels: + app: checkmate-redis + spec: + containers: + - name: redis + image: {{ .Values.redis.image }} + ports: + - containerPort: {{ .Values.redis.port }} + volumeMounts: + - name: redis-data + mountPath: /data + volumeClaimTemplates: + - metadata: + name: checkmate-redis-persistent-storage + spec: + storageClassName: "gp3" + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: {{ .Values.persistence.redisSize }} +{{- end }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/server-deployment.yaml b/charts/helm/checkmate/templates/server-deployment.yaml index 6f21f86f3..19affe8d8 100644 --- a/charts/helm/checkmate/templates/server-deployment.yaml +++ b/charts/helm/checkmate/templates/server-deployment.yaml @@ -1,16 +1,16 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: server + name: checkmate-server spec: replicas: 1 selector: matchLabels: - app: server + app: checkmate-server template: metadata: labels: - app: server + app: checkmate-server spec: containers: - name: server diff --git a/charts/helm/checkmate/templates/server-ingress.yaml b/charts/helm/checkmate/templates/server-ingress.yaml index c1982d451..3755c94b8 100644 --- a/charts/helm/checkmate/templates/server-ingress.yaml +++ b/charts/helm/checkmate/templates/server-ingress.yaml @@ -2,15 +2,22 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: server-ingress + name: checkmate-server-ingress + {{- if .Values.server.ingress.annotations }} annotations: - nginx.ingress.kubernetes.io/rewrite-target: / - nginx.ingress.kubernetes.io/enable-cors: "true" - nginx.ingress.kubernetes.io/cors-allow-origin: "http://{{ .Values.client.ingress.host }},https://{{ .Values.client.ingress.host }}" - nginx.ingress.kubernetes.io/cors-allow-methods: "GET, PUT, POST, DELETE, PATCH, OPTIONS" - nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" - nginx.ingress.kubernetes.io/cors-allow-credentials: "true" + {{- range $key, $value := .Values.server.ingress.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- end }} + {{/*#annotations: + #nginx.ingress.kubernetes.io/rewrite-target: / + #nginx.ingress.kubernetes.io/enable-cors: "true" + #nginx.ingress.kubernetes.io/cors-allow-origin: "http://{{ .Values.client.ingress.host }},https://{{ .Values.client.ingress.host }}" + #nginx.ingress.kubernetes.io/cors-allow-methods: "GET, PUT, POST, DELETE, PATCH, OPTIONS" + #nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" + #nginx.ingress.kubernetes.io/cors-allow-credentials: "true"*/}} spec: + ingressClassName: {{ .Values.server.ingress.className }} rules: - host: {{ .Values.server.ingress.host }} http: diff --git a/charts/helm/checkmate/templates/server-service.yaml b/charts/helm/checkmate/templates/server-service.yaml index 64494ff90..d8a7fbe86 100644 --- a/charts/helm/checkmate/templates/server-service.yaml +++ b/charts/helm/checkmate/templates/server-service.yaml @@ -1,9 +1,9 @@ apiVersion: v1 kind: Service metadata: - name: server + name: checkmate-server spec: selector: - app: server + app: checkmate-server ports: - port: {{ .Values.server.port }} diff --git a/charts/helm/checkmate/values.yaml b/charts/helm/checkmate/values.yaml index 79c77a49e..1840cf909 100644 --- a/charts/helm/checkmate/values.yaml +++ b/charts/helm/checkmate/values.yaml @@ -1,40 +1,46 @@ client: - image: ghcr.io/bluewave-labs/checkmate-client:v2.3 + image: ghcr.io/bluewave-labs/checkmate-client:v3.2.0 port: 80 ingress: enabled: true host: change_me + className: nginx + annotations: {} server: - image: ghcr.io/bluewave-labs/checkmate-backend:v2.3 + image: ghcr.io/bluewave-labs/checkmate-backend:v3.2.0 port: 52345 - protocol: change_me + protocol: http ingress: enabled: true host: change_me + className: nginx + annotations: {} redis: + enabled: false image: redis:7.2 port: 6379 mongodb: - image: mongo:7.0 + image: ghcr.io/bluewave-labs/checkmate-mongo:v3.2.0 port: 27017 secrets: JWT_SECRET: change_me - REFRESH_TOKEN_SECRET: change_me - SYSTEM_EMAIL_ADDRESS: test@example.com - SYSTEM_EMAIL_PASSWORD: change_me - SYSTEM_EMAIL_HOST: smtp.example.com - SYSTEM_EMAIL_PORT: "587" - PAGESPEED_API_KEY: change_me - DB_CONNECTION_STRING: mongodb://mongodb:27017/uptime_db - REDIS_HOST: redis - REDIS_PORT: "6379" - DB_TYPE: MongoDB - TOKEN_TTL: 99d - REFRESH_TOKEN_TTL: 99d +# REFRESH_TOKEN_SECRET: change_me +# SYSTEM_EMAIL_ADDRESS: test@example.com +# SYSTEM_EMAIL_PASSWORD: change_me +# SYSTEM_EMAIL_HOST: smtp.example.com +# SYSTEM_EMAIL_PORT: "587" +# PAGESPEED_API_KEY: change_me + DB_CONNECTION_STRING: mongodb://checkmate-mongodb.namespace.svc:27017/uptime_db + CLIENT_HOST: change_me +# REDIS_HOST: redis +# REDIS_PORT: "6379" +# DB_TYPE: MongoDB +# TOKEN_TTL: 99d +# REFRESH_TOKEN_TTL: 99d persistence: mongodbSize: 5Gi From c6cf7a46391a9608156c385e0f626d922e6b2e90 Mon Sep 17 00:00:00 2001 From: ariretiarno Date: Tue, 30 Sep 2025 11:30:21 +0700 Subject: [PATCH 041/103] helm chart --- charts/helm/checkmate/templates/secrets.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/charts/helm/checkmate/templates/secrets.yaml b/charts/helm/checkmate/templates/secrets.yaml index 0a3f1ace5..af104e7d1 100644 --- a/charts/helm/checkmate/templates/secrets.yaml +++ b/charts/helm/checkmate/templates/secrets.yaml @@ -4,10 +4,6 @@ {{- fail "secrets.JWT_SECRET must be overridden and cannot be 'change_me'" }} {{- end }} -{{- if or (not $secrets.REFRESH_TOKEN_SECRET) (eq $secrets.REFRESH_TOKEN_SECRET "change_me") }} - {{- fail "secrets.REFRESH_TOKEN_SECRET must be overridden and cannot be 'change_me'" }} -{{- end }} - apiVersion: v1 kind: Secret metadata: From 4744c05f07669a0110ae634d26f756c38d88c633 Mon Sep 17 00:00:00 2001 From: ariretiarno Date: Tue, 30 Sep 2025 11:35:14 +0700 Subject: [PATCH 042/103] pvc --- charts/helm/checkmate/templates/mongodb-statefulsets.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/helm/checkmate/templates/mongodb-statefulsets.yaml b/charts/helm/checkmate/templates/mongodb-statefulsets.yaml index 4b490d7d7..1ffd1c228 100644 --- a/charts/helm/checkmate/templates/mongodb-statefulsets.yaml +++ b/charts/helm/checkmate/templates/mongodb-statefulsets.yaml @@ -19,7 +19,7 @@ spec: - containerPort: {{ .Values.mongodb.port }} command: ["mongod", "--quiet", "--bind_ip_all"] volumeMounts: - - name: mongodb-data + - name: checkmate-mongo-persistent-storage mountPath: /data/db volumeClaimTemplates: - metadata: From 7a352cdcb34a962798dae4f03c1a073809b5addf Mon Sep 17 00:00:00 2001 From: ariretiarno Date: Wed, 1 Oct 2025 09:16:45 +0700 Subject: [PATCH 043/103] client host --- charts/helm/checkmate/server-nginx-cm.yaml | 41 +++++++++++++++++++ .../templates/server-deployment.yaml | 3 ++ 2 files changed, 44 insertions(+) create mode 100644 charts/helm/checkmate/server-nginx-cm.yaml diff --git a/charts/helm/checkmate/server-nginx-cm.yaml b/charts/helm/checkmate/server-nginx-cm.yaml new file mode 100644 index 000000000..64eca82b7 --- /dev/null +++ b/charts/helm/checkmate/server-nginx-cm.yaml @@ -0,0 +1,41 @@ +apiVersion: apps/v1 +kind: ConfigMap +metadata: + name: checkmate-server-nginx-cm +data: + default.conf: | + server { + listen 80; + listen [::]:80; + + server_name checkmate-demo.bluewavelabs.ca; + server_tokens off; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; + } + + # location /api/ { + # proxy_pass http://server:5000/api/; + # proxy_http_version 1.1; + # proxy_set_header Host $host; + # proxy_set_header X-Real-IP $remote_addr; + # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # proxy_set_header X-Forwarded-Proto $scheme; + # } + + location /api-docs/ { + proxy_pass http://server:5000/api-docs/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } \ No newline at end of file diff --git a/charts/helm/checkmate/templates/server-deployment.yaml b/charts/helm/checkmate/templates/server-deployment.yaml index 19affe8d8..856e16922 100644 --- a/charts/helm/checkmate/templates/server-deployment.yaml +++ b/charts/helm/checkmate/templates/server-deployment.yaml @@ -20,3 +20,6 @@ spec: envFrom: - secretRef: name: checkmate-secrets + env: + - name: CLIENT_HOST + value: {{ .Values.client.ingress.host }} From d63731fcedcab31c58a94121e4d93dcfe21559ab Mon Sep 17 00:00:00 2001 From: ariretiarno Date: Wed, 1 Oct 2025 09:22:42 +0700 Subject: [PATCH 044/103] client host --- charts/helm/checkmate/templates/server-deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/helm/checkmate/templates/server-deployment.yaml b/charts/helm/checkmate/templates/server-deployment.yaml index 856e16922..8c9403916 100644 --- a/charts/helm/checkmate/templates/server-deployment.yaml +++ b/charts/helm/checkmate/templates/server-deployment.yaml @@ -22,4 +22,4 @@ spec: name: checkmate-secrets env: - name: CLIENT_HOST - value: {{ .Values.client.ingress.host }} + value: "http://{{ .Values.client.ingress.host }}" From 8d48b2cc8a8c24e1c9c5af4f512708fa70dfe463 Mon Sep 17 00:00:00 2001 From: ariretiarno Date: Wed, 1 Oct 2025 09:33:32 +0700 Subject: [PATCH 045/103] client host --- charts/helm/checkmate/templates/client-deployment.yaml | 8 ++++++++ charts/helm/checkmate/templates/client-ingress.yaml | 2 +- charts/helm/checkmate/templates/server-ingress.yaml | 2 +- .../helm/checkmate/{ => templates}/server-nginx-cm.yaml | 6 +++--- 4 files changed, 13 insertions(+), 5 deletions(-) rename charts/helm/checkmate/{ => templates}/server-nginx-cm.yaml (86%) diff --git a/charts/helm/checkmate/templates/client-deployment.yaml b/charts/helm/checkmate/templates/client-deployment.yaml index b6e13fc7d..a5d487dc8 100644 --- a/charts/helm/checkmate/templates/client-deployment.yaml +++ b/charts/helm/checkmate/templates/client-deployment.yaml @@ -22,3 +22,11 @@ spec: value: "{{ .Values.server.protocol }}://{{ .Values.server.ingress.host }}/api/v1" - name: UPTIME_APP_CLIENT_HOST value: "http://{{ .Values.client.ingress.host }}" + volumeMounts: + - name: config-volume + mountPath: /etc/nginx/conf.d/default.conf + subPath: default.conf + volumes: + - name: config-volume + configMap: + name: checkmate-server-nginx-cm diff --git a/charts/helm/checkmate/templates/client-ingress.yaml b/charts/helm/checkmate/templates/client-ingress.yaml index 014aa53b3..89e001e15 100644 --- a/charts/helm/checkmate/templates/client-ingress.yaml +++ b/charts/helm/checkmate/templates/client-ingress.yaml @@ -19,7 +19,7 @@ spec: pathType: Prefix backend: service: - name: client + name: checkmate-client port: number: {{ .Values.client.port }} {{- end }} diff --git a/charts/helm/checkmate/templates/server-ingress.yaml b/charts/helm/checkmate/templates/server-ingress.yaml index 3755c94b8..0d7ca867b 100644 --- a/charts/helm/checkmate/templates/server-ingress.yaml +++ b/charts/helm/checkmate/templates/server-ingress.yaml @@ -26,7 +26,7 @@ spec: pathType: Prefix backend: service: - name: server + name: checkmate-server port: number: {{ .Values.server.port }} {{- end }} diff --git a/charts/helm/checkmate/server-nginx-cm.yaml b/charts/helm/checkmate/templates/server-nginx-cm.yaml similarity index 86% rename from charts/helm/checkmate/server-nginx-cm.yaml rename to charts/helm/checkmate/templates/server-nginx-cm.yaml index 64eca82b7..84da55299 100644 --- a/charts/helm/checkmate/server-nginx-cm.yaml +++ b/charts/helm/checkmate/templates/server-nginx-cm.yaml @@ -1,4 +1,4 @@ -apiVersion: apps/v1 +apiVersion: v1 kind: ConfigMap metadata: name: checkmate-server-nginx-cm @@ -22,7 +22,7 @@ data: } # location /api/ { - # proxy_pass http://server:5000/api/; + # proxy_pass http://{{ .Values.server.ingress.host }}:5000/api/; # proxy_http_version 1.1; # proxy_set_header Host $host; # proxy_set_header X-Real-IP $remote_addr; @@ -31,7 +31,7 @@ data: # } location /api-docs/ { - proxy_pass http://server:5000/api-docs/; + proxy_pass http://{{ .Values.server.ingress.host }}:5000/api-docs/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; From 468a67f937353580e10c5ef370f403de6bf70285 Mon Sep 17 00:00:00 2001 From: karenvicent Date: Wed, 1 Oct 2025 11:34:51 -0400 Subject: [PATCH 046/103] git stash push -m "work in progress" --- client/src/Hooks/v1/userHooks.js | 26 +++- .../components/ChangePasswordModal/index.jsx | 140 ++++++++++++++++++ .../src/Pages/v1/Account/EditUser/index.jsx | 19 ++- client/src/Utils/NetworkService.js | 10 ++ client/src/locales/en.json | 8 +- server/src/controllers/v1/authController.js | 23 +++ server/src/routes/v1/authRoute.js | 1 + server/src/service/v1/business/userService.js | 5 + server/src/validation/joi.js | 5 + 9 files changed, 230 insertions(+), 7 deletions(-) create mode 100644 client/src/Pages/Account/components/ChangePasswordModal/index.jsx diff --git a/client/src/Hooks/v1/userHooks.js b/client/src/Hooks/v1/userHooks.js index 71b156594..f9c454450 100644 --- a/client/src/Hooks/v1/userHooks.js +++ b/client/src/Hooks/v1/userHooks.js @@ -7,7 +7,6 @@ export const useGetUser = (userId) => { const [user, setUser] = useState(undefined); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); - const fetchUser = useCallback(async () => { try { setIsLoading(true); @@ -54,8 +53,29 @@ export const useEditUser = (userId) => { setIsLoading(false); } }, - [userId] + [userId, t] ); - return [editUser, isLoading, error]; + const changePassword = useCallback( + async (passwordForm) => { + try { + setIsLoading(true); + + await networkService.changePasswordByAdmin({ userId, passwordForm }); + createToast({ + body: t("teamPanel.changeTeamPassword.success"), + }); + } catch (error) { + createToast({ + body: error.message, + }); + setError(error); + } finally { + setIsLoading(false); + } + }, + [userId, t] + ); + + return [editUser, isLoading, error, changePassword]; }; diff --git a/client/src/Pages/Account/components/ChangePasswordModal/index.jsx b/client/src/Pages/Account/components/ChangePasswordModal/index.jsx new file mode 100644 index 000000000..375595b29 --- /dev/null +++ b/client/src/Pages/Account/components/ChangePasswordModal/index.jsx @@ -0,0 +1,140 @@ +import { useState } from "react"; +import { Button, Stack } from "@mui/material"; +import { GenericDialog } from "../../../../Components/Dialog/genericDialog"; +import TextInput from "../../../../Components/Inputs/TextInput"; +import PasswordTooltip from "../../../Auth/components/PasswordTooltip"; +import { useTheme } from "@emotion/react"; +import { useTranslation } from "react-i18next"; +import { createToast } from "../../../../Utils/toastUtils"; +import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments"; +import usePasswordFeedback from "../../../Auth/hooks/usePasswordFeedback"; + +const ChangePasswordModal = ({ isSaving, isLoading, userId, changePassword, email }) => { + const INITIAL_FORM_STATE = { + password: "", + confirm: "", + }; + console.log("email", email); + const theme = useTheme(); + const { t } = useTranslation(); + const { feedback, handlePasswordFeedback } = usePasswordFeedback(); + const [form, setForm] = useState(INITIAL_FORM_STATE); + const [isChangePasswordOpen, setIsChangePasswordOpen] = useState(false); + const [errors, setErrors] = useState({}); + const [isLoadingSubmit, setIsLoadingSubmit] = useState(false); + const closeChangePasswordModal = () => { + setIsChangePasswordOpen(false); + setForm(INITIAL_FORM_STATE); + }; + + const onChange = (e) => { + let { name, value } = e.target; + const updatedForm = { ...form, [name]: value }; + //validateFields(name, value, updatedForm); + setForm(updatedForm); + + handlePasswordFeedback(updatedForm, name, value, form, errors, setErrors); + }; + const isFormValid = !errors.password && !errors.confirm; + const onsubmitChangePassword = async (event) => { + event.preventDefault(); + if (!isFormValid) return; + const newPasswordForm = { + password: form.password, + newPassword: form.confirm, + email: email, + }; + try { + setIsLoadingSubmit(true); + await changePassword(newPasswordForm); + closeChangePasswordModal(); + } catch (error) { + const errorMsg = error.response?.data?.msg || error.message || "unknownError"; + createToast({ + type: "error", + body: t(errorMsg), + }); + } finally { + setIsLoadingSubmit(false); + } + }; + return ( + <> + + + + } + sx={{ mb: theme.spacing(5) }} + /> + + } + sx={{ mb: theme.spacing(5) }} + /> + + + + + + + + ); +}; +export default ChangePasswordModal; diff --git a/client/src/Pages/v1/Account/EditUser/index.jsx b/client/src/Pages/v1/Account/EditUser/index.jsx index e2e976916..9fe307cf2 100644 --- a/client/src/Pages/v1/Account/EditUser/index.jsx +++ b/client/src/Pages/v1/Account/EditUser/index.jsx @@ -7,6 +7,7 @@ import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import Search from "@/Components/v1/Inputs/Search/index.jsx"; import Button from "@mui/material/Button"; import RoleTable from "../components/RoleTable/index.jsx"; +import ChangePasswordModal from "../components/ChangePasswordModal"; // Utils import { useParams } from "react-router-dom"; @@ -26,7 +27,7 @@ const EditUser = () => { ]; const [user, isLoading, error] = useGetUser(userId); - const [editUser, isSaving, saveError] = useEditUser(userId); + const [editUser, isSaving, saveError, changePassword] = useEditUser(userId); const [ form, setForm, @@ -104,7 +105,12 @@ const EditUser = () => { roles={form?.role} handleDeleteRole={handleDeleteRole} /> - + - + + ); diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js index dce0faaf5..7401994bb 100644 --- a/client/src/Utils/NetworkService.js +++ b/client/src/Utils/NetworkService.js @@ -1136,6 +1136,16 @@ class NetworkService { }, }); } + + async changePasswordByAdmin(config) { + const { userId, passwordForm } = config; + console.log("Password form data:", passwordForm); + return this.axiosInstance.put(`auth/users/${userId}/password`, passwordForm, { + headers: { + "Content-Type": "application/json", + }, + }); + } } export default NetworkService; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index d89ebc44e..642c8e0f6 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -425,7 +425,13 @@ } } }, - "role": "Role" + "role": "Role", + "changeTeamPassword": { + "changePasswordMenu": "Reset Password", + "title": "Reset team member password", + "description": "Create a new password for this team member. You will need to share the password with them securely.", + "success": "Password successfully reset. Make sure to provide the credentials to the member in a secure way." + } }, "monitorState": { "paused": "Paused", diff --git a/server/src/controllers/v1/authController.js b/server/src/controllers/v1/authController.js index 17ed517eb..b84aaacd6 100755 --- a/server/src/controllers/v1/authController.js +++ b/server/src/controllers/v1/authController.js @@ -456,6 +456,29 @@ class AuthController extends BaseController { SERVICE_NAME, "editUserById" ); + editUserPasswordById = this.asyncHandler( + async (req, res) => { + console.log("editUserPasswordById called"); + const roles = req?.user?.role; + if (!roles.includes("superadmin")) { + console.log("entro a error editUserPasswordById"); + throw createError("Unauthorized", 403); + } + + const userId = req.params.userId; + console.log("userId param:", userId); + const updates = { ...req.body }; + console.log("newPassword body:", req.body); + await editUserByIdParamValidation.validateAsync(req.params); + console.log("params validated"); + //await editUserPasswordByIdBodyValidation.validateAsync(req.body); + console.log("body validated"); + await this.userService.setPasswordByUserId( userId, updates ); + return res.success({ msg: "Password reset successfully" }); + }, + SERVICE_NAME, "editUserPasswordById" + ) + } export default AuthController; diff --git a/server/src/routes/v1/authRoute.js b/server/src/routes/v1/authRoute.js index d9b036b05..63b4c4201 100755 --- a/server/src/routes/v1/authRoute.js +++ b/server/src/routes/v1/authRoute.js @@ -25,6 +25,7 @@ class AuthRoutes { this.router.get("/users", verifyJWT, isAllowed(["admin", "superadmin"]), this.authController.getAllUsers); this.router.get("/users/:userId", verifyJWT, isAllowed(["superadmin"]), this.authController.getUserById); this.router.put("/users/:userId", verifyJWT, isAllowed(["superadmin"]), this.authController.editUserById); + this.router.put("/users/:userId/password", verifyJWT, isAllowed(["superadmin"]), this.authController.editUserPasswordById); this.router.put("/user", verifyJWT, upload.single("profileImage"), this.authController.editUser); this.router.delete("/user", verifyJWT, this.authController.deleteUser); diff --git a/server/src/service/v1/business/userService.js b/server/src/service/v1/business/userService.js index 0a965bced..6c036a5c8 100644 --- a/server/src/service/v1/business/userService.js +++ b/server/src/service/v1/business/userService.js @@ -211,5 +211,10 @@ class UserService { editUserById = async (userId, user) => { await this.db.userModule.editUserById(userId, user); }; + setPasswordByUserId = async (userId, updates) => { + const updatedUser = await this.db.userModule.updateUser({ userId: userId, user: updates, file: null }); + return updatedUser; + } + } export default UserService; diff --git a/server/src/validation/joi.js b/server/src/validation/joi.js index fa8dccde2..209135769 100755 --- a/server/src/validation/joi.js +++ b/server/src/validation/joi.js @@ -670,6 +670,10 @@ const editSuperadminUserByIdBodyValidation = joi.object({ .required(), }); +const editUserPasswordByIdBodyValidation = joi.object({ + newPassword: joi.string().min(8).required().pattern(passwordPattern), +}); + export { roleValidatior, loginValidation, @@ -739,4 +743,5 @@ export { editUserByIdParamValidation, editUserByIdBodyValidation, editSuperadminUserByIdBodyValidation, + editUserPasswordByIdBodyValidation, }; From 3ae2f857d4169f13cec49166dae0dd42ed9776ed Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 1 Oct 2025 13:55:32 -0700 Subject: [PATCH 047/103] add v2 sidebar --- .../v2/Layouts/Sidebar/BottomControls.tsx | 16 +++ .../Components/v2/Layouts/Sidebar/Menu.tsx | 58 ++++++++++ .../Components/v2/Layouts/Sidebar/NavItem.tsx | 103 ++++++++++++++++++ .../Components/v2/Layouts/Sidebar/index.tsx | 65 ++++++++++- client/src/Features/Auth/v2AuthSlice.ts | 19 ++++ client/src/Pages/v2/Auth/Login.tsx | 11 +- client/src/Routes/v2router.tsx | 6 +- client/src/Utils/Theme/v2/palette.ts | 2 + client/src/store.js | 4 +- client/src/types/mui.d.ts | 1 + 10 files changed, 279 insertions(+), 6 deletions(-) create mode 100644 client/src/Components/v2/Layouts/Sidebar/BottomControls.tsx create mode 100644 client/src/Components/v2/Layouts/Sidebar/Menu.tsx create mode 100644 client/src/Components/v2/Layouts/Sidebar/NavItem.tsx create mode 100644 client/src/Features/Auth/v2AuthSlice.ts diff --git a/client/src/Components/v2/Layouts/Sidebar/BottomControls.tsx b/client/src/Components/v2/Layouts/Sidebar/BottomControls.tsx new file mode 100644 index 000000000..a07ccde6c --- /dev/null +++ b/client/src/Components/v2/Layouts/Sidebar/BottomControls.tsx @@ -0,0 +1,16 @@ +import { useTheme } from "@mui/material/styles"; + +import Stack from "@mui/material/Stack"; + +export const BottomControls = ({}) => { + const theme = useTheme(); + return ( + + ); +}; diff --git a/client/src/Components/v2/Layouts/Sidebar/Menu.tsx b/client/src/Components/v2/Layouts/Sidebar/Menu.tsx new file mode 100644 index 000000000..5b3ed705e --- /dev/null +++ b/client/src/Components/v2/Layouts/Sidebar/Menu.tsx @@ -0,0 +1,58 @@ +import Notifications from "@/assets/icons/notifications.svg?react"; +import Monitors from "@/assets/icons/monitors.svg?react"; +import PageSpeed from "@/assets/icons/page-speed.svg?react"; +import Integrations from "@/assets/icons/integrations.svg?react"; +import Incidents from "@/assets/icons/incidents.svg?react"; +import StatusPages from "@/assets/icons/status-pages.svg?react"; +import Maintenance from "@/assets/icons/maintenance.svg?react"; +import Logs from "@/assets/icons/logs.svg?react"; +import Settings from "@/assets/icons/settings.svg?react"; +import Support from "@/assets/icons/support.svg?react"; +import Discussions from "@/assets/icons/discussions.svg?react"; +import Docs from "@/assets/icons/docs.svg?react"; +import ChangeLog from "@/assets/icons/changeLog.svg?react"; + +export const getMenu = (t: Function) => [ + { name: t("menu.uptime"), path: "v2/uptime", icon: }, + { name: t("menu.pagespeed"), path: "pagespeed", icon: }, + + { name: t("menu.infrastructure"), path: "infrastructure", icon: }, + { + name: t("menu.notifications"), + path: "notifications", + icon: , + }, + { name: t("menu.incidents"), path: "incidents", icon: }, + + { name: t("menu.statusPages"), path: "status", icon: }, + { name: t("menu.maintenance"), path: "maintenance", icon: }, + { name: t("menu.logs"), path: "logs", icon: }, + + { + name: t("menu.settings"), + icon: , + path: "settings", + }, +]; + +export const getBottomMenu = (t: Function) => [ + { name: t("menu.support"), path: "support", icon: , url: "invite" }, + { + name: t("menu.discussions"), + path: "discussions", + icon: , + url: "https://github.com/bluewave-labs/checkmate/discussions", + }, + { + name: t("menu.docs"), + path: "docs", + icon: , + url: "https://bluewavelabs.gitbook.io/checkmate", + }, + { + name: t("menu.changelog"), + path: "changelog", + icon: , + url: "https://github.com/bluewave-labs/checkmate/releases", + }, +]; diff --git a/client/src/Components/v2/Layouts/Sidebar/NavItem.tsx b/client/src/Components/v2/Layouts/Sidebar/NavItem.tsx new file mode 100644 index 000000000..8f7f5d68c --- /dev/null +++ b/client/src/Components/v2/Layouts/Sidebar/NavItem.tsx @@ -0,0 +1,103 @@ +import Tooltip from "@mui/material/Tooltip"; +import ListItemButton from "@mui/material/ListItemButton"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import { useTheme } from "@mui/material/styles"; + +export interface NavData { + name: string; + icon: JSX.Element; +} + +export const NavItem = ({ + item, + collapsed, + selected, + onClick, +}: { + item: NavData; + collapsed: boolean; + selected: boolean; + onClick: (event: React.MouseEvent) => void; +}) => { + const theme = useTheme(); + const iconStroke = selected + ? theme.palette.primary.contrastText + : theme.palette.primary.contrastTextTertiary; + + const buttonBgColor = selected ? theme.palette.secondary.main : "transparent"; + const buttonBgHoverColor = selected + ? theme.palette.secondary.main + : theme.palette.tertiary.main; + const fontWeight = selected ? 600 : 400; + return ( + + + + {item.icon} + + + + {item.name} + + + + + ); +}; diff --git a/client/src/Components/v2/Layouts/Sidebar/index.tsx b/client/src/Components/v2/Layouts/Sidebar/index.tsx index 43975adfc..b05716aee 100644 --- a/client/src/Components/v2/Layouts/Sidebar/index.tsx +++ b/client/src/Components/v2/Layouts/Sidebar/index.tsx @@ -3,10 +3,17 @@ import { useSelector, useDispatch } from "react-redux"; import { setCollapsed } from "@/Features/UI/uiSlice"; import useMediaQuery from "@mui/material/useMediaQuery"; import { useTheme } from "@mui/material/styles"; +import { useTranslation } from "react-i18next"; +import { useNavigate, useLocation } from "react-router-dom"; import { CollapseButton } from "@/Components/v2/Layouts/Sidebar/CollapseButton"; import Stack from "@mui/material/Stack"; +import List from "@mui/material/List"; +import Divider from "@mui/material/Divider"; import { Logo } from "@/Components/v2/Layouts/Sidebar/Logo"; +import { getMenu, getBottomMenu } from "@/Components/v2/Layouts/Sidebar/Menu"; +import { NavItem } from "@/Components/v2/Layouts/Sidebar/NavItem"; +import { BottomControls } from "@/Components/v2/Layouts/Sidebar/BottomControls"; export const COLLAPSED_WIDTH = 64; export const EXPANDED_WIDTH = 250; @@ -16,6 +23,11 @@ export const SideBar = () => { const isSmall = useMediaQuery(theme.breakpoints.down("md")); const dispatch = useDispatch(); const collapsed = useSelector((state: any) => state.ui.sidebar.collapsed); + const { t } = useTranslation(); + const navigate = useNavigate(); + const location = useLocation(); + const menu = getMenu(t); + const bottomMenu = getBottomMenu(t); useEffect(() => { dispatch(setCollapsed({ collapsed: isSmall })); @@ -23,6 +35,7 @@ export const SideBar = () => { return ( { borderRight={`1px solid ${theme.palette.primary.lowContrast}`} width={collapsed ? COLLAPSED_WIDTH : EXPANDED_WIDTH} sx={{ - transition: "width 0.3s ease", + transition: "width 650ms cubic-bezier(0.36, -0.01, 0, 0.77)", }} > + + {menu.map((item) => { + const selected = location.pathname.startsWith(`/${item.path}`); + return ( + navigate(`/${item.path}`)} + /> + ); + })} + + + {bottomMenu.map((item) => { + const selected = location.pathname.startsWith(`/${item.path}`); + + return ( + { + if (item.url) { + window.open(item.url, "_blank", "noreferrer"); + } else { + navigate(`/${item.path}`); + } + }} + /> + ); + })} + + + ); }; diff --git a/client/src/Features/Auth/v2AuthSlice.ts b/client/src/Features/Auth/v2AuthSlice.ts new file mode 100644 index 000000000..28a18b659 --- /dev/null +++ b/client/src/Features/Auth/v2AuthSlice.ts @@ -0,0 +1,19 @@ +import { createSlice } from "@reduxjs/toolkit"; + +const initialState = { + isAuthenticated: false, +}; + +const v2AuthSlice = createSlice({ + name: "v2Auth", + initialState, + reducers: { + setIsAuthenticated: (state, action) => { + const { authenticated } = action.payload; + state.isAuthenticated = authenticated; + }, + }, +}); + +export default v2AuthSlice.reducer; +export const { setIsAuthenticated } = v2AuthSlice.actions; diff --git a/client/src/Pages/v2/Auth/Login.tsx b/client/src/Pages/v2/Auth/Login.tsx index fd4a08e93..2a5629917 100644 --- a/client/src/Pages/v2/Auth/Login.tsx +++ b/client/src/Pages/v2/Auth/Login.tsx @@ -3,7 +3,9 @@ import { z } from "zod"; import { useTranslation } from "react-i18next"; import { useForm, Controller } from "react-hook-form"; import { useTheme } from "@mui/material/styles"; - +import { useDispatch } from "react-redux"; +import { setIsAuthenticated } from "@/Features/Auth/v2AuthSlice"; +import { useNavigate } from "react-router"; import { TextInput } from "@/Components/v2/Inputs/TextInput"; import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; @@ -19,8 +21,10 @@ type FormData = z.infer; const Login = () => { const { t } = useTranslation(); + const dispatch = useDispatch(); const theme = useTheme(); const { post, loading, error } = usePost("/auth/login"); + const navigate = useNavigate(); const { handleSubmit, @@ -37,9 +41,10 @@ const Login = () => { const onSubmit = async (data: FormData) => { const result = await post(data); if (result) { - console.log(result.message); + dispatch(setIsAuthenticated({ authenticated: true })); + navigate("/v2/uptime"); } else { - console.error("Login failed:", error); + dispatch(setIsAuthenticated({ authenticated: false })); } }; diff --git a/client/src/Routes/v2router.tsx b/client/src/Routes/v2router.tsx index 941550026..12d3eb595 100644 --- a/client/src/Routes/v2router.tsx +++ b/client/src/Routes/v2router.tsx @@ -25,7 +25,11 @@ const V2Routes = ({ mode = "light" }) => { element={} > Uptime} + /> + Test Page} /> diff --git a/client/src/Utils/Theme/v2/palette.ts b/client/src/Utils/Theme/v2/palette.ts index 2e41a159c..855f7b6b8 100644 --- a/client/src/Utils/Theme/v2/palette.ts +++ b/client/src/Utils/Theme/v2/palette.ts @@ -54,6 +54,7 @@ export const lightPalette = { main: colors.offWhite, contrastText: colors.blueGray800, contrastTextSecondary: colors.blueGray600, + contrastTextTertiary: colors.blueGray500, lowContrast: colors.gray250, }, secondary: { @@ -78,6 +79,7 @@ export const darkPalette = { main: colors.offBlack, contrastText: colors.blueGray50, contrastTextSecondary: colors.gray200, + contrastTextTertiary: colors.gray500, lowContrast: colors.blueGray600, }, secondary: { diff --git a/client/src/store.js b/client/src/store.js index 69aa3d50b..565d68519 100644 --- a/client/src/store.js +++ b/client/src/store.js @@ -1,6 +1,7 @@ import { configureStore, combineReducers } from "@reduxjs/toolkit"; import authReducer from "./Features/Auth/authSlice"; +import v2AuthReducer from "./Features/Auth/v2AuthSlice"; import uiReducer from "./Features/UI/uiSlice"; import storage from "redux-persist/lib/storage"; import { persistReducer, persistStore, createTransform } from "redux-persist"; @@ -19,12 +20,13 @@ const authTransform = createTransform( const persistConfig = { key: "root", storage, - whitelist: ["auth", "ui"], + whitelist: ["auth", "v2Auth", "ui"], transforms: [authTransform], }; const rootReducer = combineReducers({ auth: authReducer, + v2Auth: v2AuthReducer, ui: uiReducer, }); diff --git a/client/src/types/mui.d.ts b/client/src/types/mui.d.ts index 5f5c1c38b..84bcd799f 100644 --- a/client/src/types/mui.d.ts +++ b/client/src/types/mui.d.ts @@ -12,6 +12,7 @@ declare module "@mui/material/styles" { interface PaletteColor { contrastTextSecondary?: string; + contrastTextTertiary?: string; lowContrast?: string; } } From 735054b4e15a5fbd024522f56019c4723053631e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 1 Oct 2025 13:57:04 -0700 Subject: [PATCH 048/103] remove unused error --- client/src/Pages/v2/Auth/Login.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/Pages/v2/Auth/Login.tsx b/client/src/Pages/v2/Auth/Login.tsx index 2a5629917..a41750c27 100644 --- a/client/src/Pages/v2/Auth/Login.tsx +++ b/client/src/Pages/v2/Auth/Login.tsx @@ -23,7 +23,7 @@ const Login = () => { const { t } = useTranslation(); const dispatch = useDispatch(); const theme = useTheme(); - const { post, loading, error } = usePost("/auth/login"); + const { post, loading } = usePost("/auth/login"); const navigate = useNavigate(); const { From 3b22ce75dbe1d261d0cd3ab72538431613cfcca7 Mon Sep 17 00:00:00 2001 From: AlexHladin Date: Thu, 2 Oct 2025 00:47:11 +0300 Subject: [PATCH 049/103] chore: drop nodemon --- server/package-lock.json | 97 ---------------------------------------- server/package.json | 5 +-- 2 files changed, 2 insertions(+), 100 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index 8c9eac34c..3ea4269eb 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -56,7 +56,6 @@ "esm": "3.2.25", "globals": "^15.14.0", "mocha": "11.1.0", - "nodemon": "3.1.9", "prettier": "^3.3.3", "sinon": "19.0.2", "tsx": "4.20.5", @@ -6480,13 +6479,6 @@ "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", @@ -8322,58 +8314,6 @@ "node": ">=6.0.0" } }, - "node_modules/nodemon": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", - "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", - "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", @@ -9421,13 +9361,6 @@ "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", @@ -9948,19 +9881,6 @@ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", "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", @@ -10520,16 +10440,6 @@ "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", @@ -10676,13 +10586,6 @@ "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": "7.15.0", "resolved": "https://registry.npmjs.org/undici/-/undici-7.15.0.tgz", diff --git a/server/package.json b/server/package.json index 3bd79722a..1ce33e237 100755 --- a/server/package.json +++ b/server/package.json @@ -6,8 +6,8 @@ "type": "module", "scripts": { "test": "c8 mocha", - "dev": "nodemon --exec tsx src/index.js", - "start": "nodemon ./dist/index.js", + "dev": "tsx watch src/index.js", + "start": "node --watch ./dist/index.js", "build": "tsc", "lint": "eslint .", "lint-fix": "eslint --fix .", @@ -65,7 +65,6 @@ "esm": "3.2.25", "globals": "^15.14.0", "mocha": "11.1.0", - "nodemon": "3.1.9", "prettier": "^3.3.3", "sinon": "19.0.2", "tsx": "4.20.5", From 851d722746cde4445c50225f24e4b9e6d50b0ef9 Mon Sep 17 00:00:00 2001 From: Vivek Kavala Date: Thu, 2 Oct 2025 12:46:51 +0530 Subject: [PATCH 050/103] fix: remove manual response time calculation in favor of library-native timing The manual timing calculations were adding Node.js processing overhead and were less accurate than the native timing provided by the respective libraries (ping, got, etc.). Now using response.time from each library for more accurate network response time measurements. --- .../v1/infrastructure/networkService.js | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/server/src/service/v1/infrastructure/networkService.js b/server/src/service/v1/infrastructure/networkService.js index 47ef6878f..676a56da8 100644 --- a/server/src/service/v1/infrastructure/networkService.js +++ b/server/src/service/v1/infrastructure/networkService.js @@ -33,11 +33,11 @@ class NetworkService { const start = process.hrtime.bigint(); try { const response = await operation(); - const elapsedMs = Math.round(Number(process.hrtime.bigint() - start) / 1_000_000); - return { response, responseTime: elapsedMs }; + // const elapsedMs = Math.round(Number(process.hrtime.bigint() - start) / 1_000_000); + return { response }; } catch (error) { - const elapsedMs = Math.round(Number(process.hrtime.bigint() - start) / 1_000_000); - return { response: null, responseTime: elapsedMs, error }; + // const elapsedMs = Math.round(Number(process.hrtime.bigint() - start) / 1_000_000); + return { response: null, error }; } } @@ -70,7 +70,12 @@ class NetworkService { throw new Error("Monitor URL is required"); } - const { response, responseTime, error } = await this.timeRequest(() => this.ping.promise.probe(monitor.url)); + const rawUrl = monitor.url; + const sanitizedHost = rawUrl + .replace(/^https?:\/\//, "") + .replace(/\/.*$/, "") + .replace(/:.*/, ""); + const { response, error } = await this.timeRequest(() => this.ping.promise.probe(sanitizedHost)); if (!response) { if (error) { @@ -84,7 +89,7 @@ class NetworkService { type: "ping", status: response.alive, code: 200, - responseTime, + responseTime: response.time, message: "Success", payload: response, }; @@ -345,9 +350,9 @@ class NetworkService { } const container = docker.getContainer(targetContainer.Id); - const { response, responseTime, error } = await this.timeRequest(() => container.inspect()); + const { response, error } = await this.timeRequest(() => container.inspect()); - dockerResponse.responseTime = responseTime; + dockerResponse.responseTime = response.time; dockerResponse.status = response?.State?.Status === "running" ? true : false; dockerResponse.code = 200; dockerResponse.message = "Docker container status fetched successfully"; @@ -370,7 +375,7 @@ class NetworkService { async requestPort(monitor) { try { const { url, port } = monitor; - const { response, responseTime, error } = await this.timeRequest(async () => { + const { response, error } = await this.timeRequest(async () => { return new Promise((resolve, reject) => { const socket = this.net.createConnection( { @@ -403,7 +408,7 @@ class NetworkService { message: this.stringService.portSuccess, monitorId: monitor._id, type: monitor.type, - responseTime, + responseTime: response.time, }; if (error) { From 080a33fac71cc5305e5c99f2d8f52ccf713c4f04 Mon Sep 17 00:00:00 2001 From: jithendra Kavala Date: Thu, 2 Oct 2025 13:51:30 +0530 Subject: [PATCH 051/103] docs: improve local development setup instructions in CONTRIBUTING.md --- CONTRIBUTING.md | 184 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 172 insertions(+), 12 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 702436b38..848b7d2f7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ We truly appreciate all kinds of contributions — code, ideas, translations or Before you start, please take a moment to read the relevant section. It helps us review and accept contributions faster, and makes the whole process smoother for everyone. 💚 -PS: **We work closely with contributors on our [Discord channel](https://discord.com/invite/NAb6H3UTjK)**. You’ll find community members, core maintainers, and first-timers helping each other out. +PS: **We work closely with contributors on our [Discord channel](https://discord.com/invite/NAb6H3UTjK)**. You'll find community members, core maintainers, and first-timers helping each other out. --- @@ -32,12 +32,12 @@ PS: **We work closely with contributors on our [Discord channel](https://discord ### Get help or ask a question? -Ask anything in our [Discord server](https://discord.com/invite/NAb6H3UTjK) — we’re friendly and happy to help. [Our core contributors](https://github.com/bluewave-labs/checkmate?tab=readme-ov-file#-contributing) are active and ready to support you. You can also use [GitHub Discussions](https://github.com/bluewave-labs/Checkmate/discussions) section to ask your questions. +Ask anything in our [Discord server](https://discord.com/invite/NAb6H3UTjK) — we're friendly and happy to help. [Our core contributors](https://github.com/bluewave-labs/checkmate?tab=readme-ov-file#-contributing) are active and ready to support you. You can also use [GitHub Discussions](https://github.com/bluewave-labs/Checkmate/discussions) section to ask your questions. ### Report a bug? 1. Search [existing issues](https://github.com/bluewave-labs/checkmate/issues). -2. If it’s not listed, open a **new issue**. +2. If it's not listed, open a **new issue**. 3. Include as much detail as possible: what happened, what you expected, and steps to reproduce. Logs and screenshots help. ### Suggest a new feature? @@ -48,19 +48,181 @@ Ask anything in our [Discord server](https://discord.com/invite/NAb6H3UTjK) — ### Set up Checkmate locally? -Frontend & backend: +#### Prerequisites + +- Node.js (with npm) +- Docker (for MongoDB and Redis) +- Git + +#### Step 1: Clone the Repository + +```bash +git clone https://github.com/bluewave-labs/Checkmate.git +cd Checkmate +``` + +#### Step 2: Set Up Docker Containers (MongoDB & Redis) + +Navigate to the Docker dev directory: + +```bash +cd server/docker/dev +``` + +Build the Docker images: + +```bash +./build_images.sh +``` + +Run Redis container: + +```bash +docker run -d -p 6379:6379 -v $(pwd)/redis/data:/data --name uptime_redis uptime_redis +``` + +Run MongoDB container: + +```bash +docker run -d -p 27017:27017 -v $(pwd)/mongo/data:/data/db --name uptime_database_mongo uptime_database_mongo +``` + +Navigate back to the root directory: + +```bash +cd ../../.. +``` + +#### Step 3: Set Up the Backend (Server) + +Navigate to the server directory: + +```bash +cd server +``` + +Install dependencies: + ```bash npm install +``` + +Create a `.env` file in the `server` directory with the following minimum required configuration: + +```env +CLIENT_HOST="http://localhost:5173" +JWT_SECRET="my_secret_key_change_this" +DB_CONNECTION_STRING="mongodb://localhost:27017/uptime_db" +TOKEN_TTL="99d" +ORIGIN="localhost" +LOG_LEVEL="debug" +``` + +**Environment Variables Explained:** + +- `CLIENT_HOST`: Frontend URL (default: http://localhost:5173) +- `JWT_SECRET`: Secret key for JWT tokens (change to something secure) +- `DB_CONNECTION_STRING`: MongoDB connection URL +- `ORIGIN`: Origin for CORS purposes +- `TOKEN_TTL`: Token time to live (in vercel/ms format) +- `LOG_LEVEL`: Debug level (debug, info, warn, error) + +Start the backend server: + +```bash npm run dev ``` -By default, the frontend expects the backend on `http://localhost:3001`. Update configs if needed. +The server will run at `http://localhost:52345`. + +#### Step 4: Set Up the Frontend (Client) + +Open a new terminal window and navigate to the client directory from the root: + +```bash +cd client +``` + +Install dependencies: + +```bash +npm install +``` + +Create a `.env` file in the `client` directory: + +```env +VITE_APP_API_BASE_URL="http://localhost:52345/api/v1" +VITE_APP_LOG_LEVEL="debug" +``` + +**Environment Variables Explained:** + +- `VITE_APP_API_BASE_URL`: Backend API URL +- `VITE_APP_LOG_LEVEL`: Log level (none, error, warn, debug, info) + +Start the frontend: + +```bash +npm run dev +``` + +The client will run at `http://localhost:5173`. + +#### Step 5: Access the Application + +Open your browser and navigate to: + +- **Frontend**: http://localhost:5173 +- **Backend API**: http://localhost:52345 +- **API Documentation**: http://localhost:52345/api-docs + +#### Managing Docker Containers + +Stop containers: + +```bash +docker stop uptime_redis uptime_database_mongo +``` + +Start containers: + +```bash +docker start uptime_redis uptime_database_mongo +``` + +Remove containers (if needed): + +```bash +docker rm uptime_redis uptime_database_mongo +``` + +#### Troubleshooting + +**Port already in use:** + +- Check if another service is using ports 5173, 52345, 27017, or 6379 +- Stop the conflicting service or change the port in `.env` files + +**MongoDB/Redis connection issues:** + +- Verify containers are running: `docker ps` +- Check container logs: `docker logs uptime_database_mongo` or `docker logs uptime_redis` + +**Module not found errors:** + +- Ensure you ran `npm install` in both `client` and `server` directories + +**Need more help?** + +- Check the [full documentation](https://docs.checkmate.so) +- Ask on [Discord](https://discord.com/invite/NAb6H3UTjK) ### Start contributing code? 1. Pick or open an issue (check `good-first-issue`s first) 2. (optional but highly suggested) Read a detailed structure of [Checkmate](https://deepwiki.com/bluewave-labs/Checkmate) if you would like to deep dive into the architecture. -3. Ask to be assigned. If there is alrady someone assigned and it's been more than 7 days, you can raise the flag and ask to be assigned as well. +3. Ask to be assigned. If there is already someone assigned and it's been more than 7 days, you can raise the flag and ask to be assigned as well. 4. Create a branch from `develop`. 5. Write your code. 6. Run and test locally. @@ -75,6 +237,7 @@ Docs live in [checkmate-documentation](https://github.com/bluewave-labs/checkmat ### Help with translations? We use [PoEditor](https://poeditor.com) for translations. You can: + - [Sign up and join your language team](https://poeditor.com/join/project/lRUoGZFCsJ). - Translate UI strings. - Ask questions on Discord in the relevant #translations channel. @@ -84,6 +247,7 @@ Make sure all new UI strings in code use `t('key')`. ### Submit a pull request? Follow the [pull request checklist](#pull-request-checklist). Your PR should: + - Be focused on one issue. - Be tested locally. - Use our linting and translation rules. @@ -123,7 +287,6 @@ If one or more of these are missing, we may ask you to update your pull request ## Branching model - - Code contributions should go to the `develop` branch. - `master` is used for stable releases. - Use descriptive branch names, like `fix/login-error` or `feat/add-alerts`. @@ -137,13 +300,10 @@ If one or more of these are missing, we may ask you to update your pull request ## Thank you -Thanks for making Checkmate better. We mean it. Whether it’s your first pull request or your 50th, we’re excited to build with you. +Thanks for making Checkmate better. We mean it. Whether it's your first pull request or your 50th, we're excited to build with you. - -PS: feel free to introduce yourself on [Discord](https://discord.gg/YOUR-DISCORD-LINK) and say hi. +PS: feel free to introduce yourself on [Discord](https://discord.gg/NAb6H3UTjK) and say hi. -- Checkmate team Also make sure you read the [document about how to make a good pull request](/PULLREQUESTS.md). - - From 8bf3875d9a54af7b60793cc6eecd68c52f1a1f7f Mon Sep 17 00:00:00 2001 From: AlexHladin Date: Thu, 2 Oct 2025 14:17:23 +0300 Subject: [PATCH 052/103] chore: add engines section to package.json --- server/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/package.json b/server/package.json index 1ce33e237..74bd55804 100755 --- a/server/package.json +++ b/server/package.json @@ -17,6 +17,9 @@ "keywords": [], "author": "", "license": "ISC", + "engines": { + "node": ">=20" + }, "dependencies": { "@pulsecron/pulse": "1.6.8", "axios": "^1.7.2", From a1c04ba35eacf1e57e230390c7783b1e6fcc7666 Mon Sep 17 00:00:00 2001 From: a-y-a-n-das Date: Thu, 2 Oct 2025 20:43:00 +0530 Subject: [PATCH 053/103] fixed the format --- server/src/db/v1/modules/monitorModule.js | 1138 ++++++++++----------- 1 file changed, 569 insertions(+), 569 deletions(-) diff --git a/server/src/db/v1/modules/monitorModule.js b/server/src/db/v1/modules/monitorModule.js index 55cf37799..f795a7f2e 100755 --- a/server/src/db/v1/modules/monitorModule.js +++ b/server/src/db/v1/modules/monitorModule.js @@ -1,578 +1,578 @@ import { - buildUptimeDetailsPipeline, - buildHardwareDetailsPipeline, - buildMonitorSummaryByTeamIdPipeline, - buildMonitorsByTeamIdPipeline, - buildMonitorsAndSummaryByTeamIdPipeline, - buildMonitorsWithChecksByTeamIdPipeline, - buildFilteredMonitorsByTeamIdPipeline, + buildUptimeDetailsPipeline, + buildHardwareDetailsPipeline, + buildMonitorSummaryByTeamIdPipeline, + buildMonitorsByTeamIdPipeline, + buildMonitorsAndSummaryByTeamIdPipeline, + buildMonitorsWithChecksByTeamIdPipeline, + buildFilteredMonitorsByTeamIdPipeline, } from "./monitorModuleQueries.js"; const SERVICE_NAME = "monitorModule"; class MonitorModule { - constructor({ Monitor, MonitorStats, Check, 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; - this.fileURLToPath = fileURLToPath; - this.ObjectId = ObjectId; - this.NormalizeData = NormalizeData; - this.NormalizeDataUptimeDetails = NormalizeDataUptimeDetails; - - const __filename = fileURLToPath(import.meta.url); - const __dirname = path.dirname(__filename); - - this.demoMonitorsPath = path.resolve(__dirname, "../../../utils/demoMonitors.json"); - } - - // Helper - calculateUptimeDuration = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - const latestCheck = new Date(checks[0].createdAt); - let latestDownCheck = 0; - - for (let i = checks.length - 1; i >= 0; i--) { - if (checks[i].status === false) { - latestDownCheck = new Date(checks[i].createdAt); - break; - } - } - - // If no down check is found, uptime is from the last check to now - if (latestDownCheck === 0) { - return Date.now() - new Date(checks[checks.length - 1].createdAt); - } - - // Otherwise the uptime is from the last check to the last down check - return latestCheck - latestDownCheck; - }; - - // Helper - getLastChecked = (checks) => { - if (!checks || checks.length === 0) { - return 0; // Handle case when no checks are available - } - // Data is sorted newest->oldest, so last check is the most recent - return new Date() - new Date(checks[0].createdAt); - }; - getLatestResponseTime = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - - return checks[0]?.responseTime ?? 0; - }; - - // Helper - getAverageResponseTime = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - - const validChecks = checks.filter((check) => typeof check.responseTime === "number"); - if (validChecks.length === 0) { - return 0; - } - const aggResponseTime = validChecks.reduce((sum, check) => { - return sum + check.responseTime; - }, 0); - return aggResponseTime / validChecks.length; - }; - - // Helper - getUptimePercentage = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - const upCount = checks.reduce((count, check) => { - return check.status === true ? count + 1 : count; - }, 0); - return (upCount / checks.length) * 100; - }; - - // Helper - getIncidents = (checks) => { - if (!checks || checks.length === 0) { - return 0; // Handle case when no checks are available - } - return checks.reduce((acc, check) => { - return check.status === false ? (acc += 1) : acc; - }, 0); - }; - - // Helper - getDateRange = (dateRange) => { - 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(), - }; - }; - - //Helper - getMonitorChecks = async (monitorId, dateRange, sortOrder) => { - const indexSpec = { - monitorId: 1, - updatedAt: sortOrder, // This will be 1 or -1 - }; - - const [checksAll, checksForDateRange] = await Promise.all([ - this.Check.find({ monitorId }).sort({ createdAt: sortOrder }).hint(indexSpec).lean(), - this.Check.find({ - monitorId, - createdAt: { $gte: dateRange.start, $lte: dateRange.end }, - }) - .hint(indexSpec) - .lean(), - ]); - - return { checksAll, checksForDateRange }; - }; - - // Helper - processChecksForDisplay = (normalizeData, checks, numToDisplay, normalize) => { - let processedChecks = checks; - if (numToDisplay && checks.length > numToDisplay) { - const n = Math.ceil(checks.length / numToDisplay); - processedChecks = checks.filter((_, index) => index % n === 0); - } - return normalize ? normalizeData(processedChecks, 1, 100) : processedChecks; - }; - - // Helper - groupChecksByTime = (checks, dateRange) => { - return checks.reduce((acc, check) => { - // Validate the date - const checkDate = new Date(check.createdAt); - if (Number.isNaN(checkDate.getTime()) || checkDate.getTime() === 0) { - return acc; - } - - const time = dateRange === "day" ? checkDate.setMinutes(0, 0, 0) : checkDate.toISOString().split("T")[0]; - - if (!acc[time]) { - acc[time] = { time, checks: [] }; - } - acc[time].checks.push(check); - return acc; - }, {}); - }; - - // Helper - calculateGroupStats = (group) => { - const totalChecks = group.checks.length; - - const checksWithResponseTime = group.checks.filter((check) => typeof check.responseTime === "number" && !Number.isNaN(check.responseTime)); - - return { - time: group.time, - uptimePercentage: this.getUptimePercentage(group.checks), - totalChecks, - totalIncidents: group.checks.filter((check) => !check.status).length, - avgResponseTime: - checksWithResponseTime.length > 0 - ? checksWithResponseTime.reduce((sum, check) => sum + check.responseTime, 0) / checksWithResponseTime.length - : 0, - }; - }; - - 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); - if (monitor === null || monitor === undefined) { - const error = new Error(this.stringService.getDbFindMonitorById(monitorId)); - error.status = 404; - throw error; - } - - return monitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorById"; - throw error; - } - }; - - getMonitorsByIds = async (monitorIds) => { - try { - const objectIds = monitorIds.map((id) => new this.ObjectId(id)); - return await this.Monitor.find({ _id: { $in: objectIds } }, { _id: 1, teamId: 1 }).lean(); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorsByIds"; - throw error; - } - }; - getUptimeDetailsById = async ({ monitorId, dateRange }) => { - try { - const dates = this.getDateRange(dateRange); - 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", - }; - - const dateString = formatLookup[dateRange]; - - const results = await this.Check.aggregate(buildUptimeDetailsPipeline(monitorId, dates, dateString)); - - const monitorData = results[0]; - - monitorData.groupedUpChecks = this.NormalizeDataUptimeDetails(monitorData.groupedUpChecks, 10, 100); - - monitorData.groupedDownChecks = this.NormalizeDataUptimeDetails(monitorData.groupedDownChecks, 10, 100); - - const normalizedGroupChecks = this.NormalizeDataUptimeDetails(monitorData.groupedChecks, 10, 100); - - monitorData.groupedChecks = normalizedGroupChecks; - const monitorStats = await this.MonitorStats.findOne({ monitorId }); - return { monitorData, monitorStats }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getUptimeDetailsById"; - throw error; - } - }; - - 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); - const dates = this.getDateRange(dateRange); - - 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", - }; - const dateString = formatLookup[dateRange]; - - const hardwareStats = await this.Check.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString)); - - const stats = hardwareStats[0]; - - return { - ...monitor.toObject(), - stats, - }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getHardwareDetailsById"; - throw error; - } - }; - - 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; - } - monitor.checks = this.NormalizeData(monitor.checks, 10, 100); - return monitor; - }); - - return { summary, monitors, filteredMonitors: normalizedFilteredMonitors }; - }; - - getMonitorsAndSummaryByTeamId = async ({ type, explain, teamId }) => { - try { - const matchStage = { teamId: new this.ObjectId(teamId) }; - if (type !== undefined) { - matchStage.type = Array.isArray(type) ? { $in: type } : type; - } - - if (explain === true) { - return this.Monitor.aggregate(buildMonitorsAndSummaryByTeamIdPipeline({ matchStage })).explain("executionStats"); - } - - const queryResult = await this.Monitor.aggregate(buildMonitorsAndSummaryByTeamIdPipeline({ matchStage })); - const { monitors, summary } = queryResult?.[0] ?? {}; - return { monitors, summary }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorsAndSummaryByTeamId"; - throw error; - } - }; - - getMonitorsWithChecksByTeamId = async ({ limit, type, page, rowsPerPage, filter, field, order, teamId, explain }) => { - try { - 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; - } - - if (explain === true) { - return this.Monitor.aggregate( - buildMonitorsWithChecksByTeamIdPipeline({ - matchStage, - filter, - page, - rowsPerPage, - field, - order, - limit, - type, - }) - ).explain("executionStats"); - } - - const queryResult = await this.Monitor.aggregate( - buildMonitorsWithChecksByTeamIdPipeline({ - matchStage, - filter, - page, - rowsPerPage, - field, - order, - limit, - type, - }) - ); - const monitors = queryResult[0]?.monitors; - const count = queryResult[0]?.count; - const normalizedFilteredMonitors = monitors.map((monitor) => { - if (!monitor.checks) { - return monitor; - } - monitor.checks = this.NormalizeData(monitor.checks, 10, 100); - return monitor; - }); - return { count, monitors: normalizedFilteredMonitors }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorsWithChecksByTeamId"; - throw error; - } - }; - createMonitor = async ({ body, teamId, userId }) => { - try { - const monitor = new this.Monitor({ ...body, teamId, userId }); - const saved = await monitor.save(); - return saved; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createMonitor"; - throw error; - } - }; - - createBulkMonitors = async (req) => { - try { - const monitors = req.map((item) => new this.Monitor({ ...item, notifications: undefined })); - await this.Monitor.bulkSave(monitors); - return monitors; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createBulkMonitors"; - throw error; - } - }; - - deleteMonitor = async ({ teamId, monitorId }) => { - try { - const deletedMonitor = await this.Monitor.findOneAndDelete({ _id: monitorId, teamId }); - - if (!deletedMonitor) { - throw new Error(this.stringService.getDbFindMonitorById(monitorId)); - } - - return deletedMonitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMonitor"; - throw error; - } - }; - - deleteAllMonitors = async (teamId) => { - try { - const monitors = await this.Monitor.find({ teamId }); - const { deletedCount } = await this.Monitor.deleteMany({ teamId }); - - return { monitors, deletedCount }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteAllMonitors"; - throw error; - } - }; - - deleteMonitorsByUserId = async (userId) => { - try { - const result = await this.Monitor.deleteMany({ userId: userId }); - return result; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMonitorsByUserId"; - throw error; - } - }; - - editMonitor = async ({ monitorId, body }) => { - try { - const editedMonitor = await this.Monitor.findByIdAndUpdate(monitorId, body, { - new: true, - }); - return editedMonitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "editMonitor"; - throw error; - } - }; - - addDemoMonitors = async (userId, teamId) => { - try { - const demoMonitors = JSON.parse(this.fs.readFileSync(this.demoMonitorsPath, "utf8")); - - const demoMonitorsToInsert = demoMonitors.map((monitor) => { - return { - userId, - teamId, - name: monitor.name, - description: monitor.name, - type: "http", - url: monitor.url, - interval: 60000, - }; - }); - const insertedMonitors = await this.Monitor.insertMany(demoMonitorsToInsert); - return insertedMonitors; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "addDemoMonitors"; - throw error; - } - }; - - pauseMonitor = async ({ monitorId }) => { - try { - const monitor = await this.Monitor.findOneAndUpdate( - { _id: monitorId }, - [ - { - $set: { - isActive: { $not: "$isActive" }, - status: "$$REMOVE", - }, - }, - ], - { new: true } - ); - - return monitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "pauseMonitor"; - throw error; - } - }; + constructor({ Monitor, MonitorStats, Check, 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; + this.fileURLToPath = fileURLToPath; + this.ObjectId = ObjectId; + this.NormalizeData = NormalizeData; + this.NormalizeDataUptimeDetails = NormalizeDataUptimeDetails; + + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); + + this.demoMonitorsPath = path.resolve(__dirname, "../../../utils/demoMonitors.json"); + } + + // Helper + calculateUptimeDuration = (checks) => { + if (!checks || checks.length === 0) { + return 0; + } + const latestCheck = new Date(checks[0].createdAt); + let latestDownCheck = 0; + + for (let i = checks.length - 1; i >= 0; i--) { + if (checks[i].status === false) { + latestDownCheck = new Date(checks[i].createdAt); + break; + } + } + + // If no down check is found, uptime is from the last check to now + if (latestDownCheck === 0) { + return Date.now() - new Date(checks[checks.length - 1].createdAt); + } + + // Otherwise the uptime is from the last check to the last down check + return latestCheck - latestDownCheck; + }; + + // Helper + getLastChecked = (checks) => { + if (!checks || checks.length === 0) { + return 0; // Handle case when no checks are available + } + // Data is sorted newest->oldest, so last check is the most recent + return new Date() - new Date(checks[0].createdAt); + }; + getLatestResponseTime = (checks) => { + if (!checks || checks.length === 0) { + return 0; + } + + return checks[0]?.responseTime ?? 0; + }; + + // Helper + getAverageResponseTime = (checks) => { + if (!checks || checks.length === 0) { + return 0; + } + + const validChecks = checks.filter((check) => typeof check.responseTime === "number"); + if (validChecks.length === 0) { + return 0; + } + const aggResponseTime = validChecks.reduce((sum, check) => { + return sum + check.responseTime; + }, 0); + return aggResponseTime / validChecks.length; + }; + + // Helper + getUptimePercentage = (checks) => { + if (!checks || checks.length === 0) { + return 0; + } + const upCount = checks.reduce((count, check) => { + return check.status === true ? count + 1 : count; + }, 0); + return (upCount / checks.length) * 100; + }; + + // Helper + getIncidents = (checks) => { + if (!checks || checks.length === 0) { + return 0; // Handle case when no checks are available + } + return checks.reduce((acc, check) => { + return check.status === false ? (acc += 1) : acc; + }, 0); + }; + + // Helper + getDateRange = (dateRange) => { + 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(), + }; + }; + + //Helper + getMonitorChecks = async (monitorId, dateRange, sortOrder) => { + const indexSpec = { + monitorId: 1, + updatedAt: sortOrder, // This will be 1 or -1 + }; + + const [checksAll, checksForDateRange] = await Promise.all([ + this.Check.find({ monitorId }).sort({ createdAt: sortOrder }).hint(indexSpec).lean(), + this.Check.find({ + monitorId, + createdAt: { $gte: dateRange.start, $lte: dateRange.end }, + }) + .hint(indexSpec) + .lean(), + ]); + + return { checksAll, checksForDateRange }; + }; + + // Helper + processChecksForDisplay = (normalizeData, checks, numToDisplay, normalize) => { + let processedChecks = checks; + if (numToDisplay && checks.length > numToDisplay) { + const n = Math.ceil(checks.length / numToDisplay); + processedChecks = checks.filter((_, index) => index % n === 0); + } + return normalize ? normalizeData(processedChecks, 1, 100) : processedChecks; + }; + + // Helper + groupChecksByTime = (checks, dateRange) => { + return checks.reduce((acc, check) => { + // Validate the date + const checkDate = new Date(check.createdAt); + if (Number.isNaN(checkDate.getTime()) || checkDate.getTime() === 0) { + return acc; + } + + const time = dateRange === "day" ? checkDate.setMinutes(0, 0, 0) : checkDate.toISOString().split("T")[0]; + + if (!acc[time]) { + acc[time] = { time, checks: [] }; + } + acc[time].checks.push(check); + return acc; + }, {}); + }; + + // Helper + calculateGroupStats = (group) => { + const totalChecks = group.checks.length; + + const checksWithResponseTime = group.checks.filter((check) => typeof check.responseTime === "number" && !Number.isNaN(check.responseTime)); + + return { + time: group.time, + uptimePercentage: this.getUptimePercentage(group.checks), + totalChecks, + totalIncidents: group.checks.filter((check) => !check.status).length, + avgResponseTime: + checksWithResponseTime.length > 0 + ? checksWithResponseTime.reduce((sum, check) => sum + check.responseTime, 0) / checksWithResponseTime.length + : 0, + }; + }; + + 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); + if (monitor === null || monitor === undefined) { + const error = new Error(this.stringService.getDbFindMonitorById(monitorId)); + error.status = 404; + throw error; + } + + return monitor; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorById"; + throw error; + } + }; + + getMonitorsByIds = async (monitorIds) => { + try { + const objectIds = monitorIds.map((id) => new this.ObjectId(id)); + return await this.Monitor.find({ _id: { $in: objectIds } }, { _id: 1, teamId: 1 }).lean(); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorsByIds"; + throw error; + } + }; + getUptimeDetailsById = async ({ monitorId, dateRange }) => { + try { + const dates = this.getDateRange(dateRange); + 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", + }; + + const dateString = formatLookup[dateRange]; + + const results = await this.Check.aggregate(buildUptimeDetailsPipeline(monitorId, dates, dateString)); + + const monitorData = results[0]; + + monitorData.groupedUpChecks = this.NormalizeDataUptimeDetails(monitorData.groupedUpChecks, 10, 100); + + monitorData.groupedDownChecks = this.NormalizeDataUptimeDetails(monitorData.groupedDownChecks, 10, 100); + + const normalizedGroupChecks = this.NormalizeDataUptimeDetails(monitorData.groupedChecks, 10, 100); + + monitorData.groupedChecks = normalizedGroupChecks; + const monitorStats = await this.MonitorStats.findOne({ monitorId }); + return { monitorData, monitorStats }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getUptimeDetailsById"; + throw error; + } + }; + + 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); + const dates = this.getDateRange(dateRange); + + 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", + }; + const dateString = formatLookup[dateRange]; + + const hardwareStats = await this.Check.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString)); + + const stats = hardwareStats[0]; + + return { + ...monitor.toObject(), + stats, + }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getHardwareDetailsById"; + throw error; + } + }; + + 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; + } + monitor.checks = this.NormalizeData(monitor.checks, 10, 100); + return monitor; + }); + + return { summary, monitors, filteredMonitors: normalizedFilteredMonitors }; + }; + + getMonitorsAndSummaryByTeamId = async ({ type, explain, teamId }) => { + try { + const matchStage = { teamId: new this.ObjectId(teamId) }; + if (type !== undefined) { + matchStage.type = Array.isArray(type) ? { $in: type } : type; + } + + if (explain === true) { + return this.Monitor.aggregate(buildMonitorsAndSummaryByTeamIdPipeline({ matchStage })).explain("executionStats"); + } + + const queryResult = await this.Monitor.aggregate(buildMonitorsAndSummaryByTeamIdPipeline({ matchStage })); + const { monitors, summary } = queryResult?.[0] ?? {}; + return { monitors, summary }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorsAndSummaryByTeamId"; + throw error; + } + }; + + getMonitorsWithChecksByTeamId = async ({ limit, type, page, rowsPerPage, filter, field, order, teamId, explain }) => { + try { + 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; + } + + if (explain === true) { + return this.Monitor.aggregate( + buildMonitorsWithChecksByTeamIdPipeline({ + matchStage, + filter, + page, + rowsPerPage, + field, + order, + limit, + type, + }) + ).explain("executionStats"); + } + + const queryResult = await this.Monitor.aggregate( + buildMonitorsWithChecksByTeamIdPipeline({ + matchStage, + filter, + page, + rowsPerPage, + field, + order, + limit, + type, + }) + ); + const monitors = queryResult[0]?.monitors; + const count = queryResult[0]?.count; + const normalizedFilteredMonitors = monitors.map((monitor) => { + if (!monitor.checks) { + return monitor; + } + monitor.checks = this.NormalizeData(monitor.checks, 10, 100); + return monitor; + }); + return { count, monitors: normalizedFilteredMonitors }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorsWithChecksByTeamId"; + throw error; + } + }; + createMonitor = async ({ body, teamId, userId }) => { + try { + const monitor = new this.Monitor({ ...body, teamId, userId }); + const saved = await monitor.save(); + return saved; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createMonitor"; + throw error; + } + }; + + createBulkMonitors = async (req) => { + try { + const monitors = req.map((item) => new this.Monitor({ ...item, notifications: undefined })); + await this.Monitor.bulkSave(monitors); + return monitors; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createBulkMonitors"; + throw error; + } + }; + + deleteMonitor = async ({ teamId, monitorId }) => { + try { + const deletedMonitor = await this.Monitor.findOneAndDelete({ _id: monitorId, teamId }); + + if (!deletedMonitor) { + throw new Error(this.stringService.getDbFindMonitorById(monitorId)); + } + + return deletedMonitor; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteMonitor"; + throw error; + } + }; + + deleteAllMonitors = async (teamId) => { + try { + const monitors = await this.Monitor.find({ teamId }); + const { deletedCount } = await this.Monitor.deleteMany({ teamId }); + + return { monitors, deletedCount }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteAllMonitors"; + throw error; + } + }; + + deleteMonitorsByUserId = async (userId) => { + try { + const result = await this.Monitor.deleteMany({ userId: userId }); + return result; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteMonitorsByUserId"; + throw error; + } + }; + + editMonitor = async ({ monitorId, body }) => { + try { + const editedMonitor = await this.Monitor.findByIdAndUpdate(monitorId, body, { + new: true, + }); + return editedMonitor; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "editMonitor"; + throw error; + } + }; + + addDemoMonitors = async (userId, teamId) => { + try { + const demoMonitors = JSON.parse(this.fs.readFileSync(this.demoMonitorsPath, "utf8")); + + const demoMonitorsToInsert = demoMonitors.map((monitor) => { + return { + userId, + teamId, + name: monitor.name, + description: monitor.name, + type: "http", + url: monitor.url, + interval: 60000, + }; + }); + const insertedMonitors = await this.Monitor.insertMany(demoMonitorsToInsert); + return insertedMonitors; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "addDemoMonitors"; + throw error; + } + }; + + pauseMonitor = async ({ monitorId }) => { + try { + const monitor = await this.Monitor.findOneAndUpdate( + { _id: monitorId }, + [ + { + $set: { + isActive: { $not: "$isActive" }, + status: "$$REMOVE", + }, + }, + ], + { new: true } + ); + + return monitor; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "pauseMonitor"; + throw error; + } + }; } export default MonitorModule; From 4317d1a47e352d99d5d224416a54ba9f22a41049 Mon Sep 17 00:00:00 2001 From: karenvicent Date: Thu, 2 Oct 2025 15:19:42 -0400 Subject: [PATCH 054/103] chace password by superadmin --- .../components/ChangePasswordModal/index.jsx | 25 +++++++---- .../src/Pages/v1/Account/EditUser/index.jsx | 25 ++++++----- client/src/Utils/NetworkService.js | 1 - server/src/controllers/v1/authController.js | 45 +++++++++---------- server/src/service/v1/business/userService.js | 3 +- server/src/validation/joi.js | 2 + 6 files changed, 54 insertions(+), 47 deletions(-) diff --git a/client/src/Pages/Account/components/ChangePasswordModal/index.jsx b/client/src/Pages/Account/components/ChangePasswordModal/index.jsx index 375595b29..17984c3ae 100644 --- a/client/src/Pages/Account/components/ChangePasswordModal/index.jsx +++ b/client/src/Pages/Account/components/ChangePasswordModal/index.jsx @@ -1,20 +1,20 @@ import { useState } from "react"; import { Button, Stack } from "@mui/material"; -import { GenericDialog } from "../../../../Components/Dialog/genericDialog"; -import TextInput from "../../../../Components/Inputs/TextInput"; -import PasswordTooltip from "../../../Auth/components/PasswordTooltip"; +import { GenericDialog } from "@/Components/v1/Dialog/genericDialog"; +import TextInput from "@/Components/v1/Inputs/TextInput"; +import PasswordTooltip from "@/Pages/v1/Auth/components/PasswordTooltip"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import { createToast } from "../../../../Utils/toastUtils"; -import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments"; -import usePasswordFeedback from "../../../Auth/hooks/usePasswordFeedback"; +import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments"; +import usePasswordFeedback from "@/Pages/v1/Auth/hooks/usePasswordFeedback"; +import PropTypes from "prop-types"; -const ChangePasswordModal = ({ isSaving, isLoading, userId, changePassword, email }) => { +const ChangePasswordModal = ({ isSaving, isLoading, changePassword, email }) => { const INITIAL_FORM_STATE = { password: "", confirm: "", }; - console.log("email", email); const theme = useTheme(); const { t } = useTranslation(); const { feedback, handlePasswordFeedback } = usePasswordFeedback(); @@ -30,7 +30,6 @@ const ChangePasswordModal = ({ isSaving, isLoading, userId, changePassword, emai const onChange = (e) => { let { name, value } = e.target; const updatedForm = { ...form, [name]: value }; - //validateFields(name, value, updatedForm); setForm(updatedForm); handlePasswordFeedback(updatedForm, name, value, form, errors, setErrors); @@ -130,11 +129,19 @@ const ChangePasswordModal = ({ isSaving, isLoading, userId, changePassword, emai onClick={onsubmitChangePassword} disabled={isLoadingSubmit || !isFormValid} > - Save + {t("save")} ); }; + +ChangePasswordModal.propTypes = { + isSaving: PropTypes.bool.isRequired, + isLoading: PropTypes.bool.isRequired, + changePassword: PropTypes.func.isRequired, + email: PropTypes.string.isRequired, +}; + export default ChangePasswordModal; diff --git a/client/src/Pages/v1/Account/EditUser/index.jsx b/client/src/Pages/v1/Account/EditUser/index.jsx index 9fe307cf2..e11716683 100644 --- a/client/src/Pages/v1/Account/EditUser/index.jsx +++ b/client/src/Pages/v1/Account/EditUser/index.jsx @@ -7,8 +7,7 @@ import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import Search from "@/Components/v1/Inputs/Search/index.jsx"; import Button from "@mui/material/Button"; import RoleTable from "../components/RoleTable/index.jsx"; -import ChangePasswordModal from "../components/ChangePasswordModal"; - +import ChangePasswordModal from "@/Pages/Account/components/ChangePasswordModal/index.jsx"; // Utils import { useParams } from "react-router-dom"; import { useTheme } from "@emotion/react"; @@ -16,9 +15,12 @@ import { useTranslation } from "react-i18next"; import { useGetUser, useEditUser } from "../../../../Hooks/v1/userHooks.js"; import { EDITABLE_ROLES, ROLES } from "../../../../Utils/roleUtils.js"; import { useEditUserForm, useValidateEditUserForm } from "./hooks/editUser.js"; +import { useSelector } from "react-redux"; const EditUser = () => { + const { user } = useSelector((state) => state.auth); const { userId } = useParams(); + const isSameUser = user?._id === userId; const theme = useTheme(); const { t } = useTranslation(); const BREADCRUMBS = [ @@ -26,7 +28,7 @@ const EditUser = () => { { name: t("editUserPage.title"), path: "" }, ]; - const [user, isLoading, error] = useGetUser(userId); + const [userToEdit, isLoading, error] = useGetUser(userId); const [editUser, isSaving, saveError, changePassword] = useEditUser(userId); const [ form, @@ -35,7 +37,7 @@ const EditUser = () => { handleDeleteRole, searchInput, handleSearchInput, - ] = useEditUserForm(user); + ] = useEditUserForm(userToEdit); const [errors, validateForm, validateField] = useValidateEditUserForm(); const onChange = (e) => { @@ -119,13 +121,14 @@ const EditUser = () => { > {t("editUserPage.form.save")} - + {!isSameUser && ( + + )} diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js index 7401994bb..6372be646 100644 --- a/client/src/Utils/NetworkService.js +++ b/client/src/Utils/NetworkService.js @@ -1139,7 +1139,6 @@ class NetworkService { async changePasswordByAdmin(config) { const { userId, passwordForm } = config; - console.log("Password form data:", passwordForm); return this.axiosInstance.put(`auth/users/${userId}/password`, passwordForm, { headers: { "Content-Type": "application/json", diff --git a/server/src/controllers/v1/authController.js b/server/src/controllers/v1/authController.js index b84aaacd6..600c6730f 100755 --- a/server/src/controllers/v1/authController.js +++ b/server/src/controllers/v1/authController.js @@ -10,10 +10,12 @@ import { editUserByIdParamValidation, editUserByIdBodyValidation, editSuperadminUserByIdBodyValidation, + editUserPasswordByIdBodyValidation, } from "../../validation/joi.js"; +import ErrorService from "../../service/v1/infrastructure/errorService.js"; const SERVICE_NAME = "authController"; - +const errorService = new ErrorService(); /** * Authentication Controller * @@ -436,7 +438,7 @@ class AuthController extends BaseController { async (req, res) => { const roles = req?.user?.role; if (!roles.includes("superadmin")) { - throw createError("Unauthorized", 403); + throw errorService.createError("Unauthorized", 403); } const userId = req.params.userId; @@ -456,29 +458,24 @@ class AuthController extends BaseController { SERVICE_NAME, "editUserById" ); - editUserPasswordById = this.asyncHandler( - async (req, res) => { - console.log("editUserPasswordById called"); - const roles = req?.user?.role; - if (!roles.includes("superadmin")) { - console.log("entro a error editUserPasswordById"); - throw createError("Unauthorized", 403); - } - - const userId = req.params.userId; - console.log("userId param:", userId); - const updates = { ...req.body }; - console.log("newPassword body:", req.body); - await editUserByIdParamValidation.validateAsync(req.params); - console.log("params validated"); - //await editUserPasswordByIdBodyValidation.validateAsync(req.body); - console.log("body validated"); - await this.userService.setPasswordByUserId( userId, updates ); - return res.success({ msg: "Password reset successfully" }); - }, - SERVICE_NAME, "editUserPasswordById" - ) + editUserPasswordById = this.asyncHandler( + async (req, res) => { + const roles = req?.user?.role; + if (!roles.includes("superadmin")) { + throw errorService.createError("Unauthorized", 403); + } + const userId = req.params.userId; + const updates = { ...req.body }; + console.log("body", req.body); + await editUserByIdParamValidation.validateAsync(req.params); + await editUserPasswordByIdBodyValidation.validateAsync(req.body); + await this.userService.setPasswordByUserId(userId, updates); + return res.success({ msg: "Password reset successfully" }); + }, + SERVICE_NAME, + "editUserPasswordById" + ); } export default AuthController; diff --git a/server/src/service/v1/business/userService.js b/server/src/service/v1/business/userService.js index 6c036a5c8..c81d9135e 100644 --- a/server/src/service/v1/business/userService.js +++ b/server/src/service/v1/business/userService.js @@ -214,7 +214,6 @@ class UserService { setPasswordByUserId = async (userId, updates) => { const updatedUser = await this.db.userModule.updateUser({ userId: userId, user: updates, file: null }); return updatedUser; - } - + }; } export default UserService; diff --git a/server/src/validation/joi.js b/server/src/validation/joi.js index 209135769..4005e2ebe 100755 --- a/server/src/validation/joi.js +++ b/server/src/validation/joi.js @@ -671,7 +671,9 @@ const editSuperadminUserByIdBodyValidation = joi.object({ }); const editUserPasswordByIdBodyValidation = joi.object({ + email: joi.string().required(), newPassword: joi.string().min(8).required().pattern(passwordPattern), + password: joi.string().min(8).required().pattern(passwordPattern), }); export { From 5159d43cad589f1b717c9625c9bb0bddd47dbfd7 Mon Sep 17 00:00:00 2001 From: karenvicent Date: Thu, 2 Oct 2025 15:40:21 -0400 Subject: [PATCH 055/103] fix save button validation --- .../Pages/Account/components/ChangePasswordModal/index.jsx | 7 ++++++- server/src/controllers/v1/authController.js | 1 - 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/client/src/Pages/Account/components/ChangePasswordModal/index.jsx b/client/src/Pages/Account/components/ChangePasswordModal/index.jsx index 17984c3ae..b3f81eebb 100644 --- a/client/src/Pages/Account/components/ChangePasswordModal/index.jsx +++ b/client/src/Pages/Account/components/ChangePasswordModal/index.jsx @@ -34,7 +34,11 @@ const ChangePasswordModal = ({ isSaving, isLoading, changePassword, email }) => handlePasswordFeedback(updatedForm, name, value, form, errors, setErrors); }; - const isFormValid = !errors.password && !errors.confirm; + const isFormValid = + form.password.length > 1 && + form.confirm.length > 1 && + !errors.password && + !errors.confirm; const onsubmitChangePassword = async (event) => { event.preventDefault(); if (!isFormValid) return; @@ -57,6 +61,7 @@ const ChangePasswordModal = ({ isSaving, isLoading, changePassword, email }) => setIsLoadingSubmit(false); } }; + return ( <> + ( + + )} + /> + ( + + )} + /> + + + + - + ); }; diff --git a/client/src/Pages/v2/Auth/Register.tsx b/client/src/Pages/v2/Auth/Register.tsx index 2a02c401f..d4565c66c 100644 --- a/client/src/Pages/v2/Auth/Register.tsx +++ b/client/src/Pages/v2/Auth/Register.tsx @@ -1,14 +1,17 @@ +import { AuthBasePage } from "@/Components/v2/Auth"; +import { TextInput } from "@/Components/v2/Inputs"; +import { Button } from "@/Components/v2/Inputs"; +import Typography from "@mui/material/Typography"; +import Stack from "@mui/material/Stack"; + +import type { ApiResponse } from "@/Hooks/v2/UseApi"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { useTranslation } from "react-i18next"; import { useForm, Controller } from "react-hook-form"; import { useTheme } from "@mui/material/styles"; -import Typography from "@mui/material/Typography"; - -import { TextInput } from "@/Components/v2/Inputs/TextInput"; -import Button from "@mui/material/Button"; -import Stack from "@mui/material/Stack"; import { usePost } from "@/Hooks/v2/UseApi"; +import { useNavigate } from "react-router"; const schema = z .object({ @@ -30,14 +33,15 @@ type FormData = z.infer; const Register = () => { const { t } = useTranslation(); const theme = useTheme(); - const { post, loading, error } = usePost("/auth/register"); + const navigate = useNavigate(); + const { post, loading, error } = usePost(); const { handleSubmit, control, formState: { errors }, } = useForm({ - resolver: zodResolver(schema), // ⬅️ connect Zod + resolver: zodResolver(schema), defaultValues: { email: "", password: "", @@ -45,124 +49,128 @@ const Register = () => { }); const onSubmit = async (data: FormData) => { - const result = await post(data); + const result = await post("/auth/register", data); if (result) { - console.log(result); + navigate("/v2/uptime"); } else { console.error("Login failed:", error); } }; return ( - - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - - {error && {error}} + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + + {error && {error}} + - + ); }; diff --git a/client/src/Pages/v2/Uptime/Create.tsx b/client/src/Pages/v2/Uptime/Create.tsx index a135ec84c..38b1511f4 100644 --- a/client/src/Pages/v2/Uptime/Create.tsx +++ b/client/src/Pages/v2/Uptime/Create.tsx @@ -44,7 +44,7 @@ const UptimeCreatePage = () => { mode: "onChange", }); const { response } = useGet("/notification-channels"); - const { post, loading, error } = usePost("/monitors"); + const { post, loading, error } = usePost(); const selectedType = useWatch({ control, name: "type", @@ -58,7 +58,7 @@ const UptimeCreatePage = () => { let interval = humanInterval(data.interval); if (!interval) interval = 60000; const submitData = { ...data, interval }; - const result = await post(submitData); + const result = await post("/monitors", submitData); if (result) { console.log(result); } else { From fb90f193b1509dcbcee4bee01d04794fc2178b94 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 9 Oct 2025 12:44:27 -0700 Subject: [PATCH 086/103] add fallbacks --- .../Components/v2/DesignElements/BasePage.tsx | 58 ++++++- .../v2/DesignElements/BulletPointCheck.tsx | 45 +++++ .../Components/v2/DesignElements/Fallback.tsx | 158 ++++++++++++++++++ .../Components/v2/DesignElements/index.tsx | 4 +- client/src/Pages/v2/Uptime/UptimeMonitors.tsx | 22 +-- 5 files changed, 275 insertions(+), 12 deletions(-) create mode 100644 client/src/Components/v2/DesignElements/BulletPointCheck.tsx create mode 100644 client/src/Components/v2/DesignElements/Fallback.tsx diff --git a/client/src/Components/v2/DesignElements/BasePage.tsx b/client/src/Components/v2/DesignElements/BasePage.tsx index ad516f2c6..f458cbb0a 100644 --- a/client/src/Components/v2/DesignElements/BasePage.tsx +++ b/client/src/Components/v2/DesignElements/BasePage.tsx @@ -1,7 +1,9 @@ import Stack from "@mui/material/Stack"; +import { ErrorFallback, EmptyFallback } from "./Fallback"; + import type { StackProps } from "@mui/material/Stack"; import { useTheme } from "@mui/material/styles"; - +import { useTranslation } from "react-i18next"; interface BasePageProps extends StackProps { children: React.ReactNode; } @@ -22,3 +24,57 @@ export const BasePage: React.FC = ({ ); }; + +interface BasePageWithStatesProps extends StackProps { + loading: boolean; + error: any; + items: any[]; + page: string; + actionLink?: string; + children: React.ReactNode; +} + +const isEmpty = (items: any[]) => { + if (!items) return true; + if (Array.isArray(items) && items.length === 0) return true; + return false; +}; + +export const BasePageWithStates: React.FC = ({ + loading, + error, + items, + page, + actionLink, + children, + ...props +}: BasePageWithStatesProps) => { + const { t } = useTranslation(); + + if (loading) { + return null; + } + + if (error) { + return ( + + ); + } + + if (isEmpty(items)) { + return ( + + ); + } + + return {children}; +}; diff --git a/client/src/Components/v2/DesignElements/BulletPointCheck.tsx b/client/src/Components/v2/DesignElements/BulletPointCheck.tsx new file mode 100644 index 000000000..03147e011 --- /dev/null +++ b/client/src/Components/v2/DesignElements/BulletPointCheck.tsx @@ -0,0 +1,45 @@ +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import CheckOutlined from "@/assets/icons/check-outlined.svg?react"; +import { useTheme } from "@mui/material/styles"; + +export const BulletPointCheck = ({ + text, + variant = "info", +}: { + text: string; + noHighlightText?: string; + variant?: "success" | "error" | "info"; +}) => { + const theme = useTheme(); + const colors: Record = { + success: theme.palette.success.main, + error: theme.palette.error.main, + info: theme.palette.primary.contrastTextSecondary, + }; + + return ( + + + + {text} + + + ); +}; diff --git a/client/src/Components/v2/DesignElements/Fallback.tsx b/client/src/Components/v2/DesignElements/Fallback.tsx new file mode 100644 index 000000000..f7b48a113 --- /dev/null +++ b/client/src/Components/v2/DesignElements/Fallback.tsx @@ -0,0 +1,158 @@ +import Box from "@mui/material/Box"; +import Stack from "@mui/material/Stack"; +import OutputAnimation from "@/assets/Animations/output.gif"; +import DarkmodeOutput from "@/assets/Animations/darkmodeOutput.gif"; +import Typography from "@mui/material/Typography"; +import { BulletPointCheck } from "@/Components/v2/DesignElements"; +import { Button } from "@/Components/v2/Inputs"; + +import { useNavigate } from "react-router"; +import { useMediaQuery } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; +import { useSelector } from "react-redux"; + +import type { BoxProps } from "@mui/material"; + +interface BaseFallbackProps extends BoxProps { + children: React.ReactNode; +} + +export const BaseFallback: React.FC = ({ children, ...props }) => { + const theme = useTheme(); + const mode = useSelector((state: any) => state.ui.mode); + const isSmall = useMediaQuery(theme.breakpoints.down("md")); + + return ( + + + + + + {children} + + + + ); +}; + +export const ErrorFallback = ({ + title, + subtitle, +}: { + title: string; + subtitle: string; +}) => { + const theme = useTheme(); + return ( + + + {title} + + {subtitle} + + ); +}; + +export const EmptyFallback = ({ + page, + title, + bullets, + actionButtonText, + actionLink, +}: { + page: string; + title: string; + bullets: any; + actionButtonText: string; + actionLink: string; +}) => { + const theme = useTheme(); + const navigate = useNavigate(); + return ( + + + + {title} + + + {bullets?.map((bullet: string, index: number) => ( + + ))} + + + + + + + ); +}; diff --git a/client/src/Components/v2/DesignElements/index.tsx b/client/src/Components/v2/DesignElements/index.tsx index 6ba0a19ac..3574daffb 100644 --- a/client/src/Components/v2/DesignElements/index.tsx +++ b/client/src/Components/v2/DesignElements/index.tsx @@ -1,7 +1,9 @@ export { SplitBox as HorizontalSplitBox, ConfigBox } from "./SplitBox"; -export { BasePage } from "./BasePage"; +export { BasePage, BasePageWithStates } from "./BasePage"; export { BGBox, UpStatusBox, DownStatusBox, PausedStatusBox } from "./StatusBox"; export { DataTable as Table, Pagination } from "./Table"; export { GradientBox, StatBox } from "./StatBox"; export { BaseBox } from "./BaseBox"; export { StatusLabel } from "./StatusLabel"; +export { BaseFallback, ErrorFallback, EmptyFallback } from "./Fallback"; +export { BulletPointCheck } from "./BulletPointCheck"; diff --git a/client/src/Pages/v2/Uptime/UptimeMonitors.tsx b/client/src/Pages/v2/Uptime/UptimeMonitors.tsx index 57865d929..e5a151eb1 100644 --- a/client/src/Pages/v2/Uptime/UptimeMonitors.tsx +++ b/client/src/Pages/v2/Uptime/UptimeMonitors.tsx @@ -1,5 +1,5 @@ import { - BasePage, + BasePageWithStates, UpStatusBox, DownStatusBox, PausedStatusBox, @@ -18,10 +18,10 @@ const UptimeMonitors = () => { const theme = useTheme(); const isSmall = useMediaQuery(theme.breakpoints.down("md")); - const { response, loading, refetch } = useGet( + const { response, isValidating, error, refetch } = useGet( "/monitors?embedChecks=true", {}, - { refreshInterval: 30000 } + { refreshInterval: 30000, keepPreviousData: true } ); const monitors: IMonitor[] = response?.data ?? ([] as IMonitor[]); @@ -43,14 +43,16 @@ const UptimeMonitors = () => { } ); - if (monitors.length === 0 && !loading) { - return "No monitors found"; - } - return ( - + { monitors={monitors} refetch={refetch} /> - + ); }; From ab3e6cdff92bb5ad071f2371e4602a39d1dabeb7 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 9 Oct 2025 13:53:34 -0700 Subject: [PATCH 087/103] normalzie on FE --- .../v2/DesignElements/StatusLabel.tsx | 10 ++++- .../Components/v2/Layouts/Sidebar/Menu.tsx | 32 ++++++++-------- .../v2/Monitors/ChartResponseTime.tsx | 14 ++++--- .../v2/Monitors/HistogramResponseTime.tsx | 28 ++++---------- .../v2/Monitors/HistogramStatus.tsx | 22 ++++++----- client/src/Pages/v2/Uptime/MonitorTable.tsx | 7 +++- client/src/Types/Check.ts | 1 + client/src/Utils/DataUtils.ts | 37 +++++++++++++++++++ 8 files changed, 97 insertions(+), 54 deletions(-) create mode 100644 client/src/Utils/DataUtils.ts diff --git a/client/src/Components/v2/DesignElements/StatusLabel.tsx b/client/src/Components/v2/DesignElements/StatusLabel.tsx index 69b788e96..d601785f9 100644 --- a/client/src/Components/v2/DesignElements/StatusLabel.tsx +++ b/client/src/Components/v2/DesignElements/StatusLabel.tsx @@ -5,7 +5,13 @@ import type { MonitorStatus } from "@/Types/Monitor"; import { getStatusPalette } from "@/Utils/MonitorUtils"; import { useTheme } from "@mui/material/styles"; -export const StatusLabel = ({ status }: { status: MonitorStatus }) => { +export const StatusLabel = ({ + status, + isActive, +}: { + status: MonitorStatus; + isActive: boolean; +}) => { const theme = useTheme(); const palette = getStatusPalette(status); const transformedText = status.charAt(0).toUpperCase() + status.slice(1).toLowerCase(); @@ -29,7 +35,7 @@ export const StatusLabel = ({ status }: { status: MonitorStatus }) => { borderRadius="50%" marginRight="5px" /> - {transformedText} + {isActive === false ? "Paused" : transformedText} ); }; diff --git a/client/src/Components/v2/Layouts/Sidebar/Menu.tsx b/client/src/Components/v2/Layouts/Sidebar/Menu.tsx index 5b3ed705e..9ca555a92 100644 --- a/client/src/Components/v2/Layouts/Sidebar/Menu.tsx +++ b/client/src/Components/v2/Layouts/Sidebar/Menu.tsx @@ -14,25 +14,25 @@ import ChangeLog from "@/assets/icons/changeLog.svg?react"; export const getMenu = (t: Function) => [ { name: t("menu.uptime"), path: "v2/uptime", icon: }, - { name: t("menu.pagespeed"), path: "pagespeed", icon: }, + // { name: t("menu.pagespeed"), path: "pagespeed", icon: }, - { name: t("menu.infrastructure"), path: "infrastructure", icon: }, - { - name: t("menu.notifications"), - path: "notifications", - icon: , - }, - { name: t("menu.incidents"), path: "incidents", icon: }, + // { name: t("menu.infrastructure"), path: "infrastructure", icon: }, + // { + // name: t("menu.notifications"), + // path: "notifications", + // icon: , + // }, + // { name: t("menu.incidents"), path: "incidents", icon: }, - { name: t("menu.statusPages"), path: "status", icon: }, - { name: t("menu.maintenance"), path: "maintenance", icon: }, - { name: t("menu.logs"), path: "logs", icon: }, + // { name: t("menu.statusPages"), path: "status", icon: }, + // { name: t("menu.maintenance"), path: "maintenance", icon: }, + // { name: t("menu.logs"), path: "logs", icon: }, - { - name: t("menu.settings"), - icon: , - path: "settings", - }, + // { + // name: t("menu.settings"), + // icon: , + // path: "settings", + // }, ]; export const getBottomMenu = (t: Function) => [ diff --git a/client/src/Components/v2/Monitors/ChartResponseTime.tsx b/client/src/Components/v2/Monitors/ChartResponseTime.tsx index 97aef314d..efa00488a 100644 --- a/client/src/Components/v2/Monitors/ChartResponseTime.tsx +++ b/client/src/Components/v2/Monitors/ChartResponseTime.tsx @@ -1,6 +1,7 @@ import { BaseChart } from "./HistogramStatus"; import { BaseBox } from "../DesignElements"; import ResponseTimeIcon from "@/assets/icons/response-time-icon.svg?react"; +import { normalizeResponseTimes } from "@/Utils/DataUtils"; import { AreaChart, Area, @@ -18,7 +19,7 @@ import { tooltipDateFormatLookup, } from "@/Utils/TimeUtils"; import { useTheme } from "@mui/material/styles"; -import type { GroupedCheck } from "@/Types/Check"; +import type { Check, GroupedCheck } from "@/Types/Check"; import { useSelector } from "react-redux"; type XTickProps = { @@ -68,7 +69,7 @@ const ResponseTimeToolTip: React.FC = ({ if (!active) return null; const format = tooltipDateFormatLookup(range); - const responseTime = Math.floor(payload?.[0]?.value || 0); + const responseTime = Math.floor(payload?.[0]?.payload?.avgResponseTime || 0); return ( {formatDateWithTz(label, format, uiTimezone)} @@ -86,7 +87,10 @@ export const ChartResponseTime = ({ }) => { const theme = useTheme(); const uiTimezone = useSelector((state: any) => state.ui.timezone); - + const normalized = normalizeResponseTimes( + checks, + "avgResponseTime" + ); return ( } @@ -96,7 +100,7 @@ export const ChartResponseTime = ({ width="100%" height={300} > - + diff --git a/client/src/Components/v2/Monitors/HistogramResponseTime.tsx b/client/src/Components/v2/Monitors/HistogramResponseTime.tsx index d19ddbf53..1925ac93c 100644 --- a/client/src/Components/v2/Monitors/HistogramResponseTime.tsx +++ b/client/src/Components/v2/Monitors/HistogramResponseTime.tsx @@ -3,24 +3,20 @@ import Box from "@mui/material/Box"; import { useTheme } from "@mui/material/styles"; import type { Check } from "@/Types/Check"; import { HistogramResponseTimeTooltip } from "@/Components/v2/Monitors/HistogramResponseTimeTooltip"; +import { normalizeResponseTimes } from "@/Utils/DataUtils"; export const HistogramResponseTime = ({ checks }: { checks: Check[] }) => { + const normalChecks = normalizeResponseTimes(checks, "responseTime"); let data = Array(); - data = checks.map((c) => Math.max(c.responseTime, 1)); - - const logResponses = data.map((r) => Math.log10(r)); - const logMin = Math.min(...logResponses); - const logMax = Math.max(...logResponses); - - if (!checks) { + if (!normalChecks || normalChecks.length === 0) { return null; } - if (checks.length !== 25) { - const placeholders = Array(25 - checks.length).fill("placeholder"); - data = [...checks, ...placeholders]; + if (normalChecks.length !== 25) { + const placeholders = Array(25 - normalChecks.length).fill("placeholder"); + data = [...normalChecks, ...placeholders]; } else { - data = checks; + data = normalChecks; } const theme = useTheme(); @@ -38,14 +34,6 @@ export const HistogramResponseTime = ({ checks }: { checks: Check[] }) => { }} > {data.map((check, index) => { - const safeResponse = Math.max(check.responseTime, 1); - const logValue = Math.log10(safeResponse); - const minHeight = 10; - const barHeight = - logMax === logMin - ? 100 - : Math.max(minHeight, ((logValue - logMin) / (logMax - logMin)) * 100); - if (check === "placeholder") { return ( { position="absolute" bottom={0} width="100%" - height={`${barHeight}%`} + height={`${check.normalResponseTime}%`} bgcolor={ check.status ? theme.palette.success.lowContrast diff --git a/client/src/Components/v2/Monitors/HistogramStatus.tsx b/client/src/Components/v2/Monitors/HistogramStatus.tsx index aeacf31d2..2e10a8042 100644 --- a/client/src/Components/v2/Monitors/HistogramStatus.tsx +++ b/client/src/Components/v2/Monitors/HistogramStatus.tsx @@ -9,6 +9,7 @@ import IncidentsIcon from "@/assets/icons/incidents.svg?react"; import type { GroupedCheck } from "@/Types/Check"; import type { MonitorStatus } from "@/Types/Monitor"; +import { normalizeResponseTimes } from "@/Utils/DataUtils"; import { useState } from "react"; import { formatDateWithTz } from "@/Utils/TimeUtils"; import { useSelector } from "react-redux"; @@ -122,8 +123,9 @@ export const HistogramStatus = ({ const theme = useTheme(); const [idx, setIdx] = useState(null); const dateFormat = range === "1d" || range === "2h" ? "MMM D, h A" : "MMM D"; + const normalChecks = normalizeResponseTimes(checks, "avgResponseTime"); - if (checks.length === 0) { + if (normalChecks.length === 0) { return ( { + const totalChecks = normalChecks.reduce((count, check) => { return count + check.count; }, 0); @@ -161,12 +163,12 @@ export const HistogramStatus = ({ Total checks {idx ? ( - {checks[idx].count} + {normalChecks[idx].count} - {formatDateWithTz(checks[idx]._id, dateFormat, uiTimezone)} + {formatDateWithTz(normalChecks[idx]._id, dateFormat, uiTimezone)} ) : ( @@ -178,26 +180,26 @@ export const HistogramStatus = ({ width="100%" height={155} > - + } /> - {checks?.map((groupedCheck, idx) => { - const fillColor = getResponseTimeColor(groupedCheck.avgResponseTime); + {normalChecks?.map((groupedCheck, idx) => { + const fillColor = getResponseTimeColor(groupedCheck.normalResponseTime); return ( setIdx(idx)} diff --git a/client/src/Pages/v2/Uptime/MonitorTable.tsx b/client/src/Pages/v2/Uptime/MonitorTable.tsx index a7b1d203f..209784309 100644 --- a/client/src/Pages/v2/Uptime/MonitorTable.tsx +++ b/client/src/Pages/v2/Uptime/MonitorTable.tsx @@ -104,7 +104,12 @@ export const MonitorTable = ({ id: "status", content: t("status"), render: (row) => { - return ; + return ( + + ); }, }, { diff --git a/client/src/Types/Check.ts b/client/src/Types/Check.ts index 542507a4b..cccc56f3d 100644 --- a/client/src/Types/Check.ts +++ b/client/src/Types/Check.ts @@ -27,6 +27,7 @@ export interface Check { status: string; message: string; responseTime: number; + normalResponseTime?: number; httpStatusCode: number; ack: boolean; expiry: string; diff --git a/client/src/Utils/DataUtils.ts b/client/src/Utils/DataUtils.ts new file mode 100644 index 000000000..3878d7d6a --- /dev/null +++ b/client/src/Utils/DataUtils.ts @@ -0,0 +1,37 @@ +const MIN_OUT = 10; +const MAX_OUT = 100; + +export const normalizeResponseTimes = , K extends keyof T>( + checks: T[], + key: K +): (T & { normalResponseTime: number })[] => { + if (!Array.isArray(checks) || checks.length === 0) + return checks as (T & { + normalResponseTime: number; + })[]; + + if (checks.length === 1) { + return [ + { + ...checks[0], + normalResponseTime: 50, + }, + ]; + } + + const { min, max } = checks.reduce( + (acc, check) => { + if (check[key] > acc.max) acc.max = check[key]; + if (check[key] < acc.min) acc.min = check[key]; + return acc; + }, + { max: -Infinity, min: Infinity } + ); + + const range = max - min || 1; + + return checks.map((check) => ({ + ...check, + normalResponseTime: MIN_OUT + ((check[key] - min) * (MAX_OUT - MIN_OUT)) / range, + })); +}; From 78b5cc9c6f81367f8935971201737741066eb65e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 9 Oct 2025 13:55:39 -0700 Subject: [PATCH 088/103] ts --- .../Components/v2/DesignElements/StatusLabel.tsx | 2 +- .../src/Components/v2/Layouts/Sidebar/Menu.tsx | 16 ++++++++-------- .../Components/v2/Monitors/ChartResponseTime.tsx | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/client/src/Components/v2/DesignElements/StatusLabel.tsx b/client/src/Components/v2/DesignElements/StatusLabel.tsx index d601785f9..a314c09b0 100644 --- a/client/src/Components/v2/DesignElements/StatusLabel.tsx +++ b/client/src/Components/v2/DesignElements/StatusLabel.tsx @@ -10,7 +10,7 @@ export const StatusLabel = ({ isActive, }: { status: MonitorStatus; - isActive: boolean; + isActive?: boolean; }) => { const theme = useTheme(); const palette = getStatusPalette(status); diff --git a/client/src/Components/v2/Layouts/Sidebar/Menu.tsx b/client/src/Components/v2/Layouts/Sidebar/Menu.tsx index 9ca555a92..f0f99f33a 100644 --- a/client/src/Components/v2/Layouts/Sidebar/Menu.tsx +++ b/client/src/Components/v2/Layouts/Sidebar/Menu.tsx @@ -1,12 +1,12 @@ -import Notifications from "@/assets/icons/notifications.svg?react"; +// import Notifications from "@/assets/icons/notifications.svg?react"; import Monitors from "@/assets/icons/monitors.svg?react"; -import PageSpeed from "@/assets/icons/page-speed.svg?react"; -import Integrations from "@/assets/icons/integrations.svg?react"; -import Incidents from "@/assets/icons/incidents.svg?react"; -import StatusPages from "@/assets/icons/status-pages.svg?react"; -import Maintenance from "@/assets/icons/maintenance.svg?react"; -import Logs from "@/assets/icons/logs.svg?react"; -import Settings from "@/assets/icons/settings.svg?react"; +// import PageSpeed from "@/assets/icons/page-speed.svg?react"; +// import Integrations from "@/assets/icons/integrations.svg?react"; +// import Incidents from "@/assets/icons/incidents.svg?react"; +// import StatusPages from "@/assets/icons/status-pages.svg?react"; +// import Maintenance from "@/assets/icons/maintenance.svg?react"; +// import Logs from "@/assets/icons/logs.svg?react"; +// import Settings from "@/assets/icons/settings.svg?react"; import Support from "@/assets/icons/support.svg?react"; import Discussions from "@/assets/icons/discussions.svg?react"; import Docs from "@/assets/icons/docs.svg?react"; diff --git a/client/src/Components/v2/Monitors/ChartResponseTime.tsx b/client/src/Components/v2/Monitors/ChartResponseTime.tsx index efa00488a..28f40f254 100644 --- a/client/src/Components/v2/Monitors/ChartResponseTime.tsx +++ b/client/src/Components/v2/Monitors/ChartResponseTime.tsx @@ -19,7 +19,7 @@ import { tooltipDateFormatLookup, } from "@/Utils/TimeUtils"; import { useTheme } from "@mui/material/styles"; -import type { Check, GroupedCheck } from "@/Types/Check"; +import type { GroupedCheck } from "@/Types/Check"; import { useSelector } from "react-redux"; type XTickProps = { From 5018c56d0ddcdb58ba2b3c0b39e0a4f9bd6514b1 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 9 Oct 2025 14:00:04 -0700 Subject: [PATCH 089/103] stikcy sidebar --- client/src/Components/v2/Layouts/RootLayout.tsx | 1 - client/src/Components/v2/Layouts/Sidebar/index.tsx | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/client/src/Components/v2/Layouts/RootLayout.tsx b/client/src/Components/v2/Layouts/RootLayout.tsx index 87c186228..4237494b1 100644 --- a/client/src/Components/v2/Layouts/RootLayout.tsx +++ b/client/src/Components/v2/Layouts/RootLayout.tsx @@ -6,7 +6,6 @@ const RootLayout = () => { const theme = useTheme(); return ( diff --git a/client/src/Components/v2/Layouts/Sidebar/index.tsx b/client/src/Components/v2/Layouts/Sidebar/index.tsx index b05716aee..a17b9fa13 100644 --- a/client/src/Components/v2/Layouts/Sidebar/index.tsx +++ b/client/src/Components/v2/Layouts/Sidebar/index.tsx @@ -37,6 +37,9 @@ export const SideBar = () => { Date: Thu, 9 Oct 2025 18:16:25 -0400 Subject: [PATCH 090/103] select component V2 --- client/src/Components/v2/Inputs/Select.tsx | 73 ++++++++++++++++++++-- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/client/src/Components/v2/Inputs/Select.tsx b/client/src/Components/v2/Inputs/Select.tsx index fb388b334..cb3e12fcb 100644 --- a/client/src/Components/v2/Inputs/Select.tsx +++ b/client/src/Components/v2/Inputs/Select.tsx @@ -1,6 +1,71 @@ -import Select from "@mui/material/Select"; +import { Typography, Select } from "@mui/material"; +import MenuItem from "@mui/material/MenuItem"; +import { forwardRef } from "react"; import type { SelectProps } from "@mui/material/Select"; - -export const SelectInput: React.FC = ({ ...props }) => { - return { + if (!selected) { + return ( + + {placeholder ?? ""} + + ); + } + const selectedItem = items.find((item) => item._id === selected); + const displayName = selectedItem ? selectedItem.name : placeholder; + return ( + + {displayName} + + ); + }} + {...props} + > + {items.map((item) => ( + + {item.name} + + ))} + + ); + } +); +SelectInput.displayName = "SelectInput"; From 27d37ece7decd358cca955af8ab02c03bd2cb657 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Sun, 12 Oct 2025 21:11:20 -0400 Subject: [PATCH 091/103] feat: Add monitor grouping functionality to backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add optional 'group' field to Monitor schema with trimming and validation - Add group validation to Joi schemas for create and edit monitor endpoints - Add GET /api/v1/monitors/team/groups endpoint to fetch unique groups - Implement case-insensitive group filtering in database layer - Support for organizing monitors into collapsible groups 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- server/src/controllers/v1/monitorController.js | 18 ++++++++++++++++++ server/src/db/v1/models/Monitor.js | 9 +++++++++ server/src/db/v1/modules/monitorModule.js | 15 +++++++++++++++ server/src/routes/v1/monitorRoute.js | 1 + .../src/service/v1/business/monitorService.js | 5 +++++ server/src/validation/joi.js | 2 ++ 6 files changed, 50 insertions(+) diff --git a/server/src/controllers/v1/monitorController.js b/server/src/controllers/v1/monitorController.js index 462d5a278..5ac44fa94 100644 --- a/server/src/controllers/v1/monitorController.js +++ b/server/src/controllers/v1/monitorController.js @@ -452,6 +452,24 @@ class MonitorController extends BaseController { SERVICE_NAME, "getAllGames" ); + + getGroupsByTeamId = this.asyncHandler( + async (req, res) => { + const teamId = req?.user?.teamId; + if (!teamId) { + throw this.errorService.createBadRequestError("Team ID is required"); + } + + const groups = await this.monitorService.getGroupsByTeamId({ teamId }); + + return res.success({ + msg: "OK", + data: groups, + }); + }, + SERVICE_NAME, + "getGroupsByTeamId" + ); } export default MonitorController; diff --git a/server/src/db/v1/models/Monitor.js b/server/src/db/v1/models/Monitor.js index 0e12d3c26..7d499dcce 100755 --- a/server/src/db/v1/models/Monitor.js +++ b/server/src/db/v1/models/Monitor.js @@ -128,6 +128,15 @@ const MonitorSchema = mongoose.Schema( gameId: { type: String, }, + group: { + type: String, + trim: true, + maxLength: 50, + default: null, + set: function(value) { + return value && value.trim() ? value.trim() : null; + } + }, }, { timestamps: true, diff --git a/server/src/db/v1/modules/monitorModule.js b/server/src/db/v1/modules/monitorModule.js index f795a7f2e..83aa29dfe 100755 --- a/server/src/db/v1/modules/monitorModule.js +++ b/server/src/db/v1/modules/monitorModule.js @@ -573,6 +573,21 @@ class MonitorModule { throw error; } }; + + getGroupsByTeamId = async ({ teamId }) => { + try { + const groups = await this.Monitor.distinct("group", { + teamId: new this.ObjectId(teamId), + group: { $ne: null, $ne: "" } + }); + + return groups.filter(Boolean).sort(); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getGroupsByTeamId"; + throw error; + } + }; } export default MonitorModule; diff --git a/server/src/routes/v1/monitorRoute.js b/server/src/routes/v1/monitorRoute.js index 107964f6b..dc0dd694b 100755 --- a/server/src/routes/v1/monitorRoute.js +++ b/server/src/routes/v1/monitorRoute.js @@ -18,6 +18,7 @@ class MonitorRoutes { this.router.get("/team", this.monitorController.getMonitorsByTeamId); this.router.get("/team/with-checks", this.monitorController.getMonitorsWithChecksByTeamId); this.router.get("/team/summary", this.monitorController.getMonitorsAndSummaryByTeamId); + this.router.get("/team/groups", this.monitorController.getGroupsByTeamId); // Uptime routes this.router.get("/uptime/details/:monitorId", this.monitorController.getUptimeDetailsById); diff --git a/server/src/service/v1/business/monitorService.js b/server/src/service/v1/business/monitorService.js index 061234102..2b643ee64 100644 --- a/server/src/service/v1/business/monitorService.js +++ b/server/src/service/v1/business/monitorService.js @@ -267,6 +267,11 @@ class MonitorService { 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/validation/joi.js b/server/src/validation/joi.js index 1cd5cd5d6..87d2b2a8c 100755 --- a/server/src/validation/joi.js +++ b/server/src/validation/joi.js @@ -174,6 +174,7 @@ const createMonitorBodyValidation = joi.object({ expectedValue: joi.string().allow(""), matchMethod: joi.string(), gameId: joi.string().allow(""), + group: joi.string().max(50).trim().allow(null, "").optional(), }); const createMonitorsBodyValidation = joi.array().items( @@ -203,6 +204,7 @@ const editMonitorBodyValidation = joi.object({ usage_temperature: joi.number(), }), gameId: joi.string(), + group: joi.string().max(50).trim().allow(null, "").optional(), }); const pauseMonitorParamValidation = joi.object({ From 6bdf9891e898f76ee07e298c3e25aa29c5042a07 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Sun, 12 Oct 2025 21:51:06 -0400 Subject: [PATCH 092/103] Fix server formatting issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- server/src/db/v1/models/Monitor.js | 4 ++-- server/src/db/v1/modules/monitorModule.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/db/v1/models/Monitor.js b/server/src/db/v1/models/Monitor.js index 7d499dcce..e6e1a0024 100755 --- a/server/src/db/v1/models/Monitor.js +++ b/server/src/db/v1/models/Monitor.js @@ -133,9 +133,9 @@ const MonitorSchema = mongoose.Schema( trim: true, maxLength: 50, default: null, - set: function(value) { + set: function (value) { return value && value.trim() ? value.trim() : null; - } + }, }, }, { diff --git a/server/src/db/v1/modules/monitorModule.js b/server/src/db/v1/modules/monitorModule.js index 83aa29dfe..39168f0b2 100755 --- a/server/src/db/v1/modules/monitorModule.js +++ b/server/src/db/v1/modules/monitorModule.js @@ -578,7 +578,7 @@ class MonitorModule { try { const groups = await this.Monitor.distinct("group", { teamId: new this.ObjectId(teamId), - group: { $ne: null, $ne: "" } + group: { $ne: null, $ne: "" }, }); return groups.filter(Boolean).sort(); From 104cf194ac52c1e456284f390892a8d48bd93699 Mon Sep 17 00:00:00 2001 From: karenvicent Date: Tue, 14 Oct 2025 12:22:20 -0400 Subject: [PATCH 093/103] fix create more specific component --- client/src/Components/v2/Inputs/Select.tsx | 125 +++++++++++---------- 1 file changed, 66 insertions(+), 59 deletions(-) diff --git a/client/src/Components/v2/Inputs/Select.tsx b/client/src/Components/v2/Inputs/Select.tsx index 6aa2498bf..54aacf68a 100644 --- a/client/src/Components/v2/Inputs/Select.tsx +++ b/client/src/Components/v2/Inputs/Select.tsx @@ -1,81 +1,88 @@ import { Typography, Select } from "@mui/material"; import MenuItem from "@mui/material/MenuItem"; -import { forwardRef } from "react"; import type { SelectProps } from "@mui/material/Select"; import { useTheme } from "@mui/material/styles"; import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; -type ItemId = string | number; + +export const SelectInput: React.FC = ({ ...props }) => { + const theme = useTheme(); + return ( + { - if (!selected) { - return ( - - {placeholder ?? ""} - - ); - } - const selectedItem = items.find((item) => item._id === selected); - const displayName = selectedItem ? selectedItem.name : placeholder; +export const SelectFromItems: React.FC = ({ + items, + placeholder, + isHidden = false, + hasError = false, + ...props +}) => { + return ( + { + if (!selected) { return ( - {displayName} + {placeholder ?? ""} ); - }} - {...props} - > - {items.map((item) => ( - item._id === selected); + const displayName = selectedItem ? selectedItem.name : placeholder; + return ( + - {item.name} - - ))} - - ); - } -); + {displayName} + + ); + }} + {...props} + > + {items.map((item) => ( + + {item.name} + + ))} + + ); +}; + SelectInput.displayName = "SelectInput"; +SelectFromItems.displayName = "SelectFromItems"; From e18e2e26da9ad83b14104c55712d5394342366b7 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 14 Oct 2025 12:45:11 -0700 Subject: [PATCH 094/103] move v2 utils to v2 dir --- .../v2/DesignElements/StatusLabel.tsx | 2 +- .../v2/Monitors/ChartAvgResponse.tsx | 2 +- .../v2/Monitors/ChartResponseTime.tsx | 4 +- .../v2/Monitors/HistogramResponseTime.tsx | 2 +- .../Monitors/HistogramResponseTimeTooltip.tsx | 2 +- .../v2/Monitors/HistogramStatus.tsx | 6 +- .../Components/v2/Monitors/MonitorStatus.tsx | 2 +- client/src/Hooks/v2/UseApi.tsx | 2 +- client/src/Hooks/v2/useInitMonitorForm.tsx | 17 ++ client/src/Pages/v2/Uptime/CheckTable.tsx | 2 +- client/src/Pages/v2/Uptime/Configure.tsx | 43 +++ client/src/Pages/v2/Uptime/Details.tsx | 2 +- client/src/Pages/v2/Uptime/UptimeForm.tsx | 247 ++++++++++++++++++ client/src/Utils/{ => v2}/ApiClient.ts | 0 client/src/Utils/{ => v2}/DataUtils.ts | 0 client/src/Utils/{ => v2}/MonitorUtils.ts | 2 +- client/src/Utils/{ => v2}/TimeUtils.ts | 0 17 files changed, 321 insertions(+), 14 deletions(-) create mode 100644 client/src/Hooks/v2/useInitMonitorForm.tsx create mode 100644 client/src/Pages/v2/Uptime/Configure.tsx create mode 100644 client/src/Pages/v2/Uptime/UptimeForm.tsx rename client/src/Utils/{ => v2}/ApiClient.ts (100%) rename client/src/Utils/{ => v2}/DataUtils.ts (100%) rename client/src/Utils/{ => v2}/MonitorUtils.ts (94%) rename client/src/Utils/{ => v2}/TimeUtils.ts (100%) diff --git a/client/src/Components/v2/DesignElements/StatusLabel.tsx b/client/src/Components/v2/DesignElements/StatusLabel.tsx index a314c09b0..e4d288e63 100644 --- a/client/src/Components/v2/DesignElements/StatusLabel.tsx +++ b/client/src/Components/v2/DesignElements/StatusLabel.tsx @@ -2,7 +2,7 @@ import Box from "@mui/material/Box"; import { BaseBox } from "@/Components/v2/DesignElements"; import type { MonitorStatus } from "@/Types/Monitor"; -import { getStatusPalette } from "@/Utils/MonitorUtils"; +import { getStatusPalette } from "@/Utils/v2/MonitorUtils"; import { useTheme } from "@mui/material/styles"; export const StatusLabel = ({ diff --git a/client/src/Components/v2/Monitors/ChartAvgResponse.tsx b/client/src/Components/v2/Monitors/ChartAvgResponse.tsx index 10938f359..e9b85cb43 100644 --- a/client/src/Components/v2/Monitors/ChartAvgResponse.tsx +++ b/client/src/Components/v2/Monitors/ChartAvgResponse.tsx @@ -4,7 +4,7 @@ import Typography from "@mui/material/Typography"; import AverageResponseIcon from "@/assets/icons/average-response-icon.svg?react"; import { Cell, RadialBarChart, RadialBar, ResponsiveContainer } from "recharts"; -import { getResponseTimeColor } from "@/Utils/MonitorUtils"; +import { getResponseTimeColor } from "@/Utils/v2/MonitorUtils"; import { useTheme } from "@mui/material/styles"; export const ChartAvgResponse = ({ avg, max }: { avg: number; max: number }) => { diff --git a/client/src/Components/v2/Monitors/ChartResponseTime.tsx b/client/src/Components/v2/Monitors/ChartResponseTime.tsx index 28f40f254..3c1c93f01 100644 --- a/client/src/Components/v2/Monitors/ChartResponseTime.tsx +++ b/client/src/Components/v2/Monitors/ChartResponseTime.tsx @@ -1,7 +1,7 @@ import { BaseChart } from "./HistogramStatus"; import { BaseBox } from "../DesignElements"; import ResponseTimeIcon from "@/assets/icons/response-time-icon.svg?react"; -import { normalizeResponseTimes } from "@/Utils/DataUtils"; +import { normalizeResponseTimes } from "@/Utils/v2/DataUtils"; import { AreaChart, Area, @@ -17,7 +17,7 @@ import { formatDateWithTz, tickDateFormatLookup, tooltipDateFormatLookup, -} from "@/Utils/TimeUtils"; +} from "@/Utils/v2/TimeUtils"; import { useTheme } from "@mui/material/styles"; import type { GroupedCheck } from "@/Types/Check"; import { useSelector } from "react-redux"; diff --git a/client/src/Components/v2/Monitors/HistogramResponseTime.tsx b/client/src/Components/v2/Monitors/HistogramResponseTime.tsx index 1925ac93c..aa1996e2a 100644 --- a/client/src/Components/v2/Monitors/HistogramResponseTime.tsx +++ b/client/src/Components/v2/Monitors/HistogramResponseTime.tsx @@ -3,7 +3,7 @@ import Box from "@mui/material/Box"; import { useTheme } from "@mui/material/styles"; import type { Check } from "@/Types/Check"; import { HistogramResponseTimeTooltip } from "@/Components/v2/Monitors/HistogramResponseTimeTooltip"; -import { normalizeResponseTimes } from "@/Utils/DataUtils"; +import { normalizeResponseTimes } from "@/Utils/v2/DataUtils"; export const HistogramResponseTime = ({ checks }: { checks: Check[] }) => { const normalChecks = normalizeResponseTimes(checks, "responseTime"); diff --git a/client/src/Components/v2/Monitors/HistogramResponseTimeTooltip.tsx b/client/src/Components/v2/Monitors/HistogramResponseTimeTooltip.tsx index d704cf5d4..d8c0263f0 100644 --- a/client/src/Components/v2/Monitors/HistogramResponseTimeTooltip.tsx +++ b/client/src/Components/v2/Monitors/HistogramResponseTimeTooltip.tsx @@ -1,7 +1,7 @@ import Stack from "@mui/material/Stack"; import Tooltip from "@mui/material/Tooltip"; import Typography from "@mui/material/Typography"; -import { formatDateWithTz } from "@/Utils/TimeUtils"; +import { formatDateWithTz } from "@/Utils/v2/TimeUtils"; import { useSelector } from "react-redux"; import type { LatestCheck } from "@/Types/Check"; diff --git a/client/src/Components/v2/Monitors/HistogramStatus.tsx b/client/src/Components/v2/Monitors/HistogramStatus.tsx index 2e10a8042..bc6675bc3 100644 --- a/client/src/Components/v2/Monitors/HistogramStatus.tsx +++ b/client/src/Components/v2/Monitors/HistogramStatus.tsx @@ -9,12 +9,12 @@ import IncidentsIcon from "@/assets/icons/incidents.svg?react"; import type { GroupedCheck } from "@/Types/Check"; import type { MonitorStatus } from "@/Types/Monitor"; -import { normalizeResponseTimes } from "@/Utils/DataUtils"; +import { normalizeResponseTimes } from "@/Utils/v2/DataUtils"; import { useState } from "react"; -import { formatDateWithTz } from "@/Utils/TimeUtils"; +import { formatDateWithTz } from "@/Utils/v2/TimeUtils"; import { useSelector } from "react-redux"; import { useTheme } from "@mui/material/styles"; -import { getResponseTimeColor } from "@/Utils/MonitorUtils"; +import { getResponseTimeColor } from "@/Utils/v2/MonitorUtils"; const XLabel = ({ p1, diff --git a/client/src/Components/v2/Monitors/MonitorStatus.tsx b/client/src/Components/v2/Monitors/MonitorStatus.tsx index ff4d83352..f329e6bb8 100644 --- a/client/src/Components/v2/Monitors/MonitorStatus.tsx +++ b/client/src/Components/v2/Monitors/MonitorStatus.tsx @@ -3,7 +3,7 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; import { PulseDot } from "@/Components/v2/DesignElements/PulseDot"; import { Dot } from "@/Components/v2/DesignElements/Dot"; -import { getStatusColor, formatUrl } from "@/Utils/MonitorUtils"; +import { getStatusColor, formatUrl } from "@/Utils/v2/MonitorUtils"; import { useTheme } from "@mui/material/styles"; import prettyMilliseconds from "pretty-ms"; import { typographyLevels } from "@/Utils/Theme/v2/palette"; diff --git a/client/src/Hooks/v2/UseApi.tsx b/client/src/Hooks/v2/UseApi.tsx index d3001c87b..2ee37c0da 100644 --- a/client/src/Hooks/v2/UseApi.tsx +++ b/client/src/Hooks/v2/UseApi.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import useSWR from "swr"; import type { SWRConfiguration } from "swr"; import type { AxiosRequestConfig } from "axios"; -import { get, post, patch } from "@/Utils/ApiClient"; // your axios wrapper +import { get, post, patch } from "@/Utils/v2/ApiClient"; // your axios wrapper export type ApiResponse = { message: string; diff --git a/client/src/Hooks/v2/useInitMonitorForm.tsx b/client/src/Hooks/v2/useInitMonitorForm.tsx new file mode 100644 index 000000000..2e98b3753 --- /dev/null +++ b/client/src/Hooks/v2/useInitMonitorForm.tsx @@ -0,0 +1,17 @@ +import { monitorSchema } from "@/Validation/v2/zod"; +import { z } from "zod"; +export const useInitForm = ({ + initialData, +}: { + initialData: Partial> | undefined; +}) => { + const defaults: z.infer = { + type: initialData?.type || "http", + url: initialData?.url || "", + n: initialData?.n || 3, + notificationChannels: initialData?.notificationChannels || [], + name: initialData?.name || "", + interval: initialData?.interval || "1 minute", + }; + return { defaults }; +}; diff --git a/client/src/Pages/v2/Uptime/CheckTable.tsx b/client/src/Pages/v2/Uptime/CheckTable.tsx index 42da6da12..c2811c453 100644 --- a/client/src/Pages/v2/Uptime/CheckTable.tsx +++ b/client/src/Pages/v2/Uptime/CheckTable.tsx @@ -10,7 +10,7 @@ import type { MonitorStatus } from "@/Types/Monitor"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { useGet } from "@/Hooks/v2/UseApi"; -import { formatDateWithTz } from "@/Utils/TimeUtils"; +import { formatDateWithTz } from "@/Utils/v2/TimeUtils"; import { useSelector } from "react-redux"; const getHeaders = (t: Function, uiTimezone: string) => { const headers: Header[] = [ diff --git a/client/src/Pages/v2/Uptime/Configure.tsx b/client/src/Pages/v2/Uptime/Configure.tsx new file mode 100644 index 000000000..dc014669b --- /dev/null +++ b/client/src/Pages/v2/Uptime/Configure.tsx @@ -0,0 +1,43 @@ +import { monitorSchema } from "@/Validation/v2/zod"; +import { useGet, usePost } from "@/Hooks/v2/UseApi"; +import { UptimeForm } from "@/Pages/v2/Uptime/UptimeForm"; + +import { useParams } from "react-router"; +import type { ApiResponse } from "@/Hooks/v2/UseApi"; +import humanInterval from "human-interval"; +import { z } from "zod"; + +export const UptimeConfigurePage = () => { + type FormValues = z.infer; + type SubmitValues = Omit & { interval: number | undefined }; + + const { id } = useParams(); + const { response } = useGet("/notification-channels"); + const { response: monitorResponse } = useGet(`/monitors/${id}`); + const monitor = monitorResponse?.data || null; + const notificationOptions = response?.data ?? []; + + const { post, loading, error } = usePost(); + + const onSubmit = async (data: FormValues) => { + let interval = humanInterval(data.interval); + if (!interval) interval = 60000; + const submitData = { ...data, interval }; + const result = await post("/monitors", submitData); + if (result) { + console.log(result); + } else { + console.error(error); + } + }; + return ( + + ); +}; diff --git a/client/src/Pages/v2/Uptime/Details.tsx b/client/src/Pages/v2/Uptime/Details.tsx index c2069c7e4..5515e7b32 100644 --- a/client/src/Pages/v2/Uptime/Details.tsx +++ b/client/src/Pages/v2/Uptime/Details.tsx @@ -14,7 +14,7 @@ import { useTheme } from "@mui/material/styles"; import { useParams } from "react-router"; import { useGet, usePatch, type ApiResponse } from "@/Hooks/v2/UseApi"; import { useState } from "react"; -import { getStatusPalette } from "@/Utils/MonitorUtils"; +import { getStatusPalette } from "@/Utils/v2/MonitorUtils"; import prettyMilliseconds from "pretty-ms"; const UptimeDetailsPage = () => { diff --git a/client/src/Pages/v2/Uptime/UptimeForm.tsx b/client/src/Pages/v2/Uptime/UptimeForm.tsx new file mode 100644 index 000000000..e9636e5dd --- /dev/null +++ b/client/src/Pages/v2/Uptime/UptimeForm.tsx @@ -0,0 +1,247 @@ +import Stack from "@mui/material/Stack"; +import { TextInput } from "@/Components/v2/Inputs/TextInput"; +import { AutoCompleteInput } from "@/Components/v2/Inputs/AutoComplete"; +import { ConfigBox, BasePage } from "@/Components/v2/DesignElements"; +import RadioGroup from "@mui/material/RadioGroup"; +import FormControl from "@mui/material/FormControl"; +import { RadioWithDescription } from "@/Components/v2/Inputs/RadioInput"; +import { Button } from "@/Components/v2/Inputs"; +import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded"; +import { Typography } from "@mui/material"; + +import { useTranslation } from "react-i18next"; +import { monitorSchema } from "@/Validation/v2/zod"; +import { z } from "zod"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm, Controller, useWatch, type SubmitHandler } from "react-hook-form"; +import { useTheme } from "@mui/material/styles"; +import { useInitForm } from "@/Hooks/v2/useInitMonitorForm"; + +type FormValues = z.infer; + +export const UptimeForm = ({ + initialData, + onSubmit, + notificationOptions, + loading, +}: { + initialData?: Partial; + onSubmit: SubmitHandler; + notificationOptions: any[]; + loading: boolean; +}) => { + const { t } = useTranslation(); + const theme = useTheme(); + const { defaults } = useInitForm({ initialData: initialData }); + const { + handleSubmit, + control, + setValue, + formState: { errors }, + } = useForm({ + resolver: zodResolver(monitorSchema) as any, + defaultValues: defaults, + mode: "onChange", + }); + + const selectedType = useWatch({ + control, + name: "type", + }); + const notificationChannels = useWatch({ + control, + name: "notificationChannels", + }); + + return ( + + ( + + + + + + + + )} + /> + } + /> + + ( + + )} + /> + ( + + )} + /> + + } + /> + ( + { + const target = e.target as HTMLInputElement; + field.onChange(target.valueAsNumber); + }} + /> + )} + /> + } + /> + + ( + option.name} + value={notificationOptions.filter((o: any) => + (field.value || []).includes(o._id) + )} + onChange={(_, newValue) => { + field.onChange(newValue.map((o: any) => o._id)); + }} + /> + )} + /> + + {notificationChannels.map((notificationId) => { + const option = notificationOptions.find( + (o: any) => o._id === notificationId + ); + if (!option) return null; + return ( + + {option.name} + { + const updated = notificationChannels.filter( + (id) => id !== notificationId + ); + setValue("notificationChannels", updated); + }} + sx={{ cursor: "pointer" }} + /> + + ); + })} + + + } + /> + ( + + )} + /> + } + /> + + + + + ); +}; diff --git a/client/src/Utils/ApiClient.ts b/client/src/Utils/v2/ApiClient.ts similarity index 100% rename from client/src/Utils/ApiClient.ts rename to client/src/Utils/v2/ApiClient.ts diff --git a/client/src/Utils/DataUtils.ts b/client/src/Utils/v2/DataUtils.ts similarity index 100% rename from client/src/Utils/DataUtils.ts rename to client/src/Utils/v2/DataUtils.ts diff --git a/client/src/Utils/MonitorUtils.ts b/client/src/Utils/v2/MonitorUtils.ts similarity index 94% rename from client/src/Utils/MonitorUtils.ts rename to client/src/Utils/v2/MonitorUtils.ts index dcd202361..c47b7a82e 100644 --- a/client/src/Utils/MonitorUtils.ts +++ b/client/src/Utils/v2/MonitorUtils.ts @@ -1,5 +1,5 @@ import type { MonitorStatus } from "@/Types/Monitor"; -import type { PaletteKey } from "./Theme/v2/theme"; +import type { PaletteKey } from "@/Utils/Theme/v2/theme"; export const getStatusPalette = (status: MonitorStatus): PaletteKey => { const paletteMap: Record = { up: "success", diff --git a/client/src/Utils/TimeUtils.ts b/client/src/Utils/v2/TimeUtils.ts similarity index 100% rename from client/src/Utils/TimeUtils.ts rename to client/src/Utils/v2/TimeUtils.ts From 06ebb7694841573e1a74101718be7221e2d94ed6 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Sun, 19 Oct 2025 23:18:50 -0400 Subject: [PATCH 095/103] chore: remove 13 unused CSS variables from index.css MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed the following unused CSS variables that were leftovers from when the project switched to the MUI theme system: - --env-var-radius-1 - --env-var-width-1 - --env-var-width-3 - --env-var-height-1 - --env-var-spacing-3 - --env-var-spacing-4 - --env-var-spacing-5 - --env-var-img-width-1 - --env-var-img-width-2 - --env-var-img-width-3 - --env-var-default-1 - --env-var-default-2 - --env-var-shadow-1 These variables are not referenced anywhere in the codebase. The project now uses MUI theme configuration (theme.spacing(), theme.palette.*) instead of CSS variables for styling. Kept 19 CSS variables that are still actively used during the migration to pure MUI theme. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- client/src/index.css | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/client/src/index.css b/client/src/index.css index a5d305d46..d58a7e872 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -15,15 +15,11 @@ html { -moz-osx-font-smoothing: grayscale; /* Generalized Stylings */ - --env-var-radius-1: 4px; --env-var-radius-2: 8px; - --env-var-width-1: 100vw; --env-var-width-2: 360px; - --env-var-width-3: 250px; --env-var-width-4: 100px; - --env-var-height-1: 100vh; --env-var-height-2: 34px; --env-var-nav-bar-height: 70px; @@ -35,9 +31,6 @@ html { --env-var-spacing-1-plus: 16px; --env-var-spacing-1-minus: 10px; --env-var-spacing-2: 24px; - --env-var-spacing-3: 32px; - --env-var-spacing-4: 40px; - --env-var-spacing-5: 65px; --env-var-font-size-small: 11px; --env-var-font-size-small-plus: 12px; @@ -46,16 +39,6 @@ html { --env-var-font-size-large: 16px; --env-var-font-size-large-plus: 22px; --env-var-font-size-xlarge: 30px; - - --env-var-img-width-1: 20px; - --env-var-img-width-2: 16px; - --env-var-img-width-3: 12px; - - --env-var-default-1: 24px; - --env-var-default-2: 40px; - - --env-var-shadow-1: - 0px 4px 24px -4px rgba(16, 24, 40, 0.08), 0px 3px 3px -3px rgba(16, 24, 40, 0.03); } .MuiInputBase-root.Mui-disabled input { From 9ae83cc34dee175d49a1ce03dbe838a41f9eafa4 Mon Sep 17 00:00:00 2001 From: LinPr <314573849@qq.com> Date: Mon, 20 Oct 2025 14:22:20 +0800 Subject: [PATCH 096/103] fix/v1: remove unnecessary import --- server/src/routes/v1/maintenanceWindowRoute.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/routes/v1/maintenanceWindowRoute.js b/server/src/routes/v1/maintenanceWindowRoute.js index 3df040994..345f2c13c 100755 --- a/server/src/routes/v1/maintenanceWindowRoute.js +++ b/server/src/routes/v1/maintenanceWindowRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import MaintenanceWindow from "../../db/v1/models/MaintenanceWindow.js"; + class MaintenanceWindowRoutes { constructor(maintenanceWindowController) { this.router = Router(); From 0baa7d989187a40744b16dfb535e8d63e89c7190 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 20 Oct 2025 17:36:14 +0000 Subject: [PATCH 097/103] feat: translations updated from POEditor --- client/src/locales/ar.json | 17 +- client/src/locales/cs.json | 17 +- client/src/locales/de.json | 177 ++++---- client/src/locales/en.json | 8 +- client/src/locales/es.json | 83 ++-- client/src/locales/fi.json | 17 +- client/src/locales/fr.json | 17 +- client/src/locales/ja.json | 17 +- client/src/locales/pt-BR.json | 17 +- client/src/locales/ru.json | 738 ++++++++++++++++++---------------- client/src/locales/th.json | 17 +- client/src/locales/tr.json | 17 +- client/src/locales/zh-CN.json | 17 +- client/src/locales/zh-TW.json | 17 +- 14 files changed, 672 insertions(+), 504 deletions(-) diff --git a/client/src/locales/ar.json b/client/src/locales/ar.json index d8019d5de..6cec253d8 100644 --- a/client/src/locales/ar.json +++ b/client/src/locales/ar.json @@ -421,7 +421,13 @@ } } }, - "role": "" + "role": "", + "changeTeamPassword": { + "changePasswordMenu": "", + "title": "", + "description": "", + "success": "" + } }, "monitorState": { "paused": "", @@ -483,7 +489,8 @@ "ping": "", "docker": "", "port": "", - "game": "" + "game": "", + "https": "" }, "common": { "appName": "", @@ -1071,7 +1078,11 @@ "incidentConfigDescription": "", "incidentConfigStatusWindowLabel": "", "incidentConfigStatusWindowThresholdLabel": "", - "incidentConfigTitle": "" + "incidentConfigTitle": "", + "incidentConfigDescriptionV2": "", + "incidentConfigStatusCheckNumber": "", + "intervalTitle": "", + "intervalDescription": "" }, "dataRate": "", "dataReceived": "", diff --git a/client/src/locales/cs.json b/client/src/locales/cs.json index 71c7e24b9..837b49803 100644 --- a/client/src/locales/cs.json +++ b/client/src/locales/cs.json @@ -421,7 +421,13 @@ } } }, - "role": "" + "role": "", + "changeTeamPassword": { + "changePasswordMenu": "", + "title": "", + "description": "", + "success": "" + } }, "monitorState": { "paused": "", @@ -483,7 +489,8 @@ "ping": "", "docker": "", "port": "", - "game": "" + "game": "", + "https": "" }, "common": { "appName": "Checkmate", @@ -1071,7 +1078,11 @@ "incidentConfigDescription": "", "incidentConfigStatusWindowLabel": "", "incidentConfigStatusWindowThresholdLabel": "", - "incidentConfigTitle": "" + "incidentConfigTitle": "", + "incidentConfigDescriptionV2": "", + "incidentConfigStatusCheckNumber": "", + "intervalTitle": "", + "intervalDescription": "" }, "dataRate": "", "dataReceived": "", diff --git a/client/src/locales/de.json b/client/src/locales/de.json index ef8df5df4..da5a3ee61 100644 --- a/client/src/locales/de.json +++ b/client/src/locales/de.json @@ -382,50 +382,56 @@ "created": "Angelegt" }, "addTeamMember": { - "addMemberMenu": "", - "title": "", - "description": "", - "addButton": "" + "addMemberMenu": "Teammitglied hinzufügen", + "title": "Neues Teammitglied registrieren", + "description": "Einen neuen Benutzer anlegen und Zugangsdaten mit ihm teilen. Diese Methode gibt dem Mitglied direkt Zugriff auf alle Überwachungen.", + "addButton": "Mitglied hinzufügen" }, - "register": "", + "register": "Teammitglied registrieren", "registerToast": { - "success": "", - "dbUserExists": "", - "unknownError": "" + "success": "Benutzer erstellt, Zugangsdaten auf sichere Weise weitergeben.", + "dbUserExists": "Der Benutzer existiert bereits.", + "unknownError": "Unbekannter Fehler aufgetreten." }, "registerTeamMember": { - "title": "", + "title": "Teammitglied registrieren", "auth": { "common": { "inputs": { "firstName": { "errors": { - "empty": "", - "pattern": "" + "empty": "Bitte einen Namen eingeben", + "pattern": "Der Name darf nur Buchstaben, Leerzeichen, Apostrophe oder Bindestriche enthalten" } }, "lastName": { "errors": { - "empty": "", - "pattern": "" + "empty": "Bitte einen Vornamen eingeben", + "pattern": "Der Vorname darf nur Buchstaben, Leerzeichen, Apostrophe oder Bindestriche enthalten" } }, "email": { "errors": { - "empty": "", - "invalid": "" + "empty": "Zum Fortsetzen bitte eine E-Mail-Adresse eingeben", + "invalid": "Bitte die Gültigkeit der E-Mail-Adresse erneut prüfen" } }, "role": { "errors": { - "empty": "" + "empty": "Eine Rolle wird benötigt" } } } } } }, - "role": "" + "role": "Rolle", + "changeTeamPassword": { + "changePasswordMenu": "Passwort zurücksetzen", + "title": "Passwort des Teammitgliedes zurücksetzen", + "description": "Neues Passwort für dieses Teammitglied erstellen. Es muss auf einem Sicheren weg zur Verfügung gestellt werden.", + "success": "Passwort erfolgreich zurückgesetzt. Bitte sicherstellen, dass die Zugangsdaten auf sichere Weise weitergegeben werden." + } }, "monitorState": { "paused": "Pausiert", @@ -487,7 +493,8 @@ "ping": "Geben Sie die IP-Adresse oder den Hostnamen ein, der angepingt werden soll (z. B. 192.168.1.100 oder example.com), und fügen Sie einen eindeutigen Anzeigenamen hinzu, der auf dem Dashboard angezeigt wird.", "docker": "Geben Sie die Docker-ID Ihres Containers ein. Docker-IDs müssen die vollständige 64-Zeichen-ID sein. Sie können docker inspect ausführen, um die vollständige Container-ID abzurufen.", "port": "Geben Sie die URL oder IP des Servers, die Portnummer und einen eindeutigen Anzeigenamen ein, der auf dem Dashboard angezeigt wird.", - "game": "" + "game": "IP Adresse oder Hostnamen und Port für Ping eingeben (z.B. 192.168.1.100 oder beispiel.de) und Spieletyp auswählen.", + "https": "Zu überwachende Adresse oder IP (z.B. https://beispiel.de/ oder 192.168.1.100) eingeben und einen klaren Anzeigenamen, der im Dashboard erscheint." }, "common": { "appName": "Checkmate", @@ -607,7 +614,7 @@ "forgotPassword": "Passwort vergessen?", "register": "Noch keinen Account?", "forgotPasswordLink": "Passwort zurücksetzen", - "registerLink": "Hier resistrieren" + "registerLink": "Hier registrieren" }, "toasts": { "success": "Willkommen zurück. Erfolgreich angemeldet.", @@ -618,7 +625,7 @@ "incorrect": "Das Passwort stimmt nicht mit dem gespeicherten überein" } }, - "welcome": "" + "welcome": "Willkommen zurück bei Checkmate!" }, "registration": { "heading": { @@ -645,7 +652,7 @@ "toasts": { "success": "Willkommen! Benutzer erfolgreich angelegt" }, - "welcome": "" + "welcome": "Willkommen bei Checkmate!" }, "forgotPassword": { "heading": "Passwort vergessen?", @@ -746,8 +753,8 @@ "monitorStatus": { "checkingEvery": "Überprüfung alle {{interval}}", "withCaptureAgent": "mit Capture-Agent {{version}}", - "up": "hoch", - "down": "runter", + "up": "Online", + "down": "Offline", "paused": "pausiert" }, "advancedMatching": "Erweiterter Abgleich", @@ -792,7 +799,7 @@ "lastRunHeader": "Letzter Lauf um", "lastFinishedAtHeader": "Zuletzt abgeschlossen um", "lastRunTookHeader": "Letzter Lauf dauerte", - "intervalHeader": "" + "intervalHeader": "Invervall" }, "metricsTable": { "title": "Warteschlangenmetriken", @@ -900,8 +907,8 @@ "title": "Überwachen Sie IP/URL auf der Statusseite" }, "globalThresholds": { - "title": "", - "description": "" + "title": "Globale Schwellenwerte", + "description": "Globale CPU-, Speicher-, Festplatten- und Temperaturschwellenwerte festlegen. Wenn ein Wert angegeben ist, wird er automatisch für die Überwachung genutzt." } }, "statusPageCreate": { @@ -975,11 +982,11 @@ "oneHour": "1 Stunde", "oneDay": "1 Tag", "oneWeek": "1 Woche", - "fourMinutes": "", - "oneMinute": "", - "twoMinutes": "", - "fifteenSeconds": "", - "thirtySeconds": "" + "fourMinutes": "4 Minuten", + "oneMinute": "Eine Minute", + "twoMinutes": "Zwei Minuten", + "fifteenSeconds": "15 Sekunden", + "thirtySeconds": "30 Sekunden" }, "general": { "noOptionsFound": "Keine {{unit}} gefunden" @@ -1050,73 +1057,77 @@ "incidentsPageActionResolveMonitor": "Monitor-Vorfälle beheben", "incidentsPageActionResolveAll": "Alle Vorfälle lösen", "matchMethodOptions": { - "equal": "", - "equalPlaceholder": "", - "include": "", - "includePlaceholder": "", - "regex": "", - "regexPlaceholder": "", - "text": "" + "equal": "Gleich", + "equalPlaceholder": "Erfolg", + "include": "Enthält", + "includePlaceholder": "OK", + "regex": "Regex", + "regexPlaceholder": "^(success|ok)$", + "text": "Übereinstummungsmethode" }, "monitorType": { "docker": { - "label": "", - "namePlaceholder": "", - "placeholder": "" + "label": "Container Name/ID", + "namePlaceholder": "Mein Container", + "placeholder": "Meine-App" }, "http": { - "label": "", - "namePlaceholder": "", - "placeholder": "" + "label": "Zu überwachende URL", + "namePlaceholder": "Google", + "placeholder": "google.de" }, "ping": { - "label": "", - "namePlaceholder": "", - "placeholder": "" + "label": "Zu überwachende IP-Adresse", + "namePlaceholder": "Google", + "placeholder": "1.1.1.1" }, "port": { - "label": "", - "namePlaceholder": "", - "placeholder": "" + "label": "Zu überwachende URL", + "namePlaceholder": "localhost:5173", + "placeholder": "localhost" }, "game": { - "label": "", - "namePlaceholder": "", - "placeholder": "" + "label": "Zu überwachende URL", + "namePlaceholder": "localhost:5173", + "placeholder": "localhost" } }, "uptimeAdvancedMatching": { - "jsonPath": "" + "jsonPath": "JSON Pfad" }, - "bytesPerSecond": "", - "bytesReceived": "", - "bytesSent": "", - "chooseGame": "", + "bytesPerSecond": "Bytes pro Sekunde", + "bytesReceived": "Empfangene Bytes", + "bytesSent": "Gesendete Bytes", + "chooseGame": "Spiel auswählen", "createMonitorPage": { - "incidentConfigDescription": "", - "incidentConfigStatusWindowLabel": "", - "incidentConfigStatusWindowThresholdLabel": "", - "incidentConfigTitle": "" + "incidentConfigDescription": "Mithilfe eines gleitenden Fensters wird ermittelt, wann ein Monitor ausfällt. Der Status eines Monitors ändert sich nur, wenn der Prozentsatz der Prüfungen im gleitenden Fenster den angegebenen Wert erreicht.", + "incidentConfigStatusWindowLabel": "Wie viele Prüfungen sollen im gleitenden Fenster ausgeführt werden?", + "incidentConfigStatusWindowThresholdLabel": "Wie viel Prozent der Prüfungen im gleitenden Fenster sollen fehlschlagen/erfolgreich sein, bevor sich der Status ändert?", + "incidentConfigTitle": "Vorfälle", + "incidentConfigDescriptionV2": "Anzahl der aufeinanderfolgenden Prüfungen, die zum Ändern des Monitorstatus erforderlich sind. Maximal 25", + "incidentConfigStatusCheckNumber": "Anzahl der Prüfungen bevor sich er Status ändert", + "intervalTitle": "Prüfintervall", + "intervalDescription": "Wie oft soll die Überwachung geprüft werden." }, - "dataRate": "", - "dataReceived": "", - "dataSent": "", - "details": "", - "drops": "", - "errors": "", - "errorsIn": "", - "errorsOut": "", - "gameServerMonitoring": "", - "gameServerMonitoringDescription": "", - "network": "", - "networkDrops": "", - "networkErrors": "", - "networkInterface": "", - "noNetworkStatsAvailable": "", - "packetsPerSecond": "", - "packetsReceived": "", - "packetsReceivedRate": "", - "packetsSent": "", - "rate": "", - "selectInterface": "" + "dataRate": "Datenrate", + "dataReceived": "Empfangene Daten", + "dataSent": "Gesendete Daten", + "details": "Details", + "drops": "verloren", + "errors": "Fehler", + "errorsIn": "Fehler eingehend", + "errorsOut": "Fehler ausgehend", + "gameServerMonitoring": "Spiele Server Überwachung", + "gameServerMonitoringDescription": "Prüfen ob der Spiele-Server läuft oder nicht", + "network": "Netzwerk", + "networkDrops": "Netzwerkabbrüche", + "networkErrors": "Netzwerkfehler", + "networkInterface": "Netzwerkschnittstelle", + "noNetworkStatsAvailable": "Keine Netzwerkstatistiken verfügbar.", + "packetsPerSecond": "Pakete pro Sekunde", + "packetsReceived": "Empfangene Pakete", + "packetsReceivedRate": "Rate empfangener Pakete", + "packetsSent": "Gesendete Pakete", + "rate": "Rate", + "selectInterface": "Schnittstelle auswählen" } diff --git a/client/src/locales/en.json b/client/src/locales/en.json index da19c38da..13d4a7731 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -490,11 +490,11 @@ }, "uptimeGeneralInstructions": { "http": "Enter the URL or IP to monitor (e.g., https://example.com/ or 192.168.1.100) and add a clear display name that appears on the dashboard.", - "https": "Enter the URL or IP to monitor (e.g., https://example.com/ or 192.168.1.100) and add a clear display name that appears on the dashboard.", "ping": "Enter the IP address or hostname to ping (e.g., 192.168.1.100 or example.com) and add a clear display name that appears on the dashboard.", "docker": "Enter the Docker container name or ID. You can use either the container name (e.g., my-app) or the container ID (full 64-char ID or short ID).", "port": "Enter the URL or IP of the server, the port number and a clear display name that appears on the dashboard.", - "game": "Enter the IP address or hostname and the port number to ping (e.g., 192.168.1.100 or example.com) and choose game type." + "game": "Enter the IP address or hostname and the port number to ping (e.g., 192.168.1.100 or example.com) and choose game type.", + "https": "Enter the URL or IP to monitor (e.g., https://example.com/ or 192.168.1.100) and add a clear display name that appears on the dashboard." }, "common": { "appName": "Checkmate", @@ -1101,11 +1101,11 @@ "chooseGame": "Choose game", "createMonitorPage": { "incidentConfigDescription": "A sliding window is used to determine when a monitor goes down. The status of a monitor will only change when the percentage of checks in the sliding window meet the specified value.", - "incidentConfigDescriptionV2": "Number of consecutive checks needed to change monitor status. Maximum 25", "incidentConfigStatusWindowLabel": "How many checks should be in the sliding window?", - "incidentConfigStatusCheckNumber": "How many checks before status change?", "incidentConfigStatusWindowThresholdLabel": "What percentage of checks in the sliding window fail/succeed before monitor status changes?", "incidentConfigTitle": "Incidents", + "incidentConfigDescriptionV2": "Number of consecutive checks needed to change monitor status. Maximum 25", + "incidentConfigStatusCheckNumber": "How many checks before status change?", "intervalTitle": "Check interval", "intervalDescription": "How often the monitor should be checked." }, diff --git a/client/src/locales/es.json b/client/src/locales/es.json index 440d40027..ed6a98c5d 100644 --- a/client/src/locales/es.json +++ b/client/src/locales/es.json @@ -129,32 +129,32 @@ "distributedUptimeStatusPublishedLabel": "", "distributedUptimeStatusCompanyNameLabel": "", "distributedUptimeStatusPageAddressLabel": "", - "distributedUptimeStatus30Days": "", - "distributedUptimeStatus60Days": "", - "distributedUptimeStatus90Days": "", + "distributedUptimeStatus30Days": "30 dias", + "distributedUptimeStatus60Days": "60 días", + "distributedUptimeStatus90Days": "90 dias", "distributedUptimeStatusPageNotSetUp": "", "distributedUptimeStatusContactAdmin": "", "distributedUptimeStatusPageNotPublic": "", "distributedUptimeStatusPageDeleteDialog": "", "distributedUptimeStatusPageDeleteConfirm": "", "distributedUptimeStatusPageDeleteDescription": "", - "distributedUptimeStatusDevices": "", + "distributedUptimeStatusDevices": "Dispositivos", "distributedUptimeStatusUpt": "", "distributedUptimeStatusUptBurned": "", "distributedUptimeStatusUptLogo": "", "incidentsTableNoIncidents": "", - "incidentsTablePaginationLabel": "", + "incidentsTablePaginationLabel": "Incidentes", "incidentsTableMonitorName": "", - "incidentsTableStatus": "", + "incidentsTableStatus": "Estado", "incidentsTableDateTime": "", "incidentsTableStatusCode": "", - "incidentsTableMessage": "", + "incidentsTableMessage": "Mensaje", "incidentsOptionsHeader": "", "incidentsOptionsHeaderFilterBy": "", - "incidentsOptionsHeaderFilterAll": "", + "incidentsOptionsHeaderFilterAll": "Todo", "incidentsOptionsHeaderFilterDown": "", "incidentsOptionsHeaderFilterCannotResolve": "", - "incidentsOptionsHeaderShow": "", + "incidentsOptionsHeaderShow": "Mostrar:", "incidentsOptionsHeaderLastHour": "", "incidentsOptionsHeaderLastDay": "", "incidentsOptionsHeaderLastWeek": "", @@ -165,22 +165,22 @@ "infrastructureCustomizeAlerts": "", "infrastructureAlertNotificationDescription": "", "infrastructureCreateMonitor": "", - "infrastructureProtocol": "", + "infrastructureProtocol": "Protocolo", "infrastructureServerUrlLabel": "", "infrastructureDisplayNameLabel": "", "infrastructureAuthorizationSecretLabel": "", - "gb": "", - "mb": "", - "mem": "", + "gb": "GB", + "mb": "MB", + "mem": "Mem", "memoryUsage": "", - "cpu": "", + "cpu": "CPU", "cpuUsage": "", "cpuTemperature": "", "diskUsage": "", "used": "", - "total": "", - "cores": "", - "frequency": "", + "total": "Total", + "cores": "Nucleos", + "frequency": "Frecuencia", "status": "", "cpuPhysical": "", "cpuLogical": "", @@ -189,25 +189,25 @@ "memory": "", "disk": "", "uptime": "", - "os": "", + "os": "SO", "host": "", "actions": "", "integrations": "", "integrationsPrism": "", - "integrationsSlack": "", + "integrationsSlack": "Slack", "integrationsSlackInfo": "", - "integrationsDiscord": "", + "integrationsDiscord": "Discord", "integrationsDiscordInfo": "", - "integrationsZapier": "", + "integrationsZapier": "Zapier", "integrationsZapierInfo": "", - "commonSave": "", + "commonSave": "Guardar", "createYour": "", "createMonitor": "", - "pause": "", - "resume": "", - "editing": "", - "url": "", - "access": "", + "pause": "Pausar", + "resume": "Resumir", + "editing": "Editando...", + "url": "URL", + "access": "Acceso", "timezone": "", "features": "", "administrator": "", @@ -231,13 +231,13 @@ "friendlyNamePlaceholder": "", "maintenanceRepeat": "", "maintenance": "", - "duration": "", + "duration": "Duración", "addMonitors": "", - "window": "", - "cancel": "", - "message": "", - "low": "", - "high": "", + "window": "ventana", + "cancel": "Cancelar", + "message": "Mensaje", + "low": "bajo", + "high": "alto", "statusCode": "", "date&Time": "", "type": "", @@ -421,7 +421,13 @@ } } }, - "role": "" + "role": "", + "changeTeamPassword": { + "changePasswordMenu": "", + "title": "", + "description": "", + "success": "" + } }, "monitorState": { "paused": "", @@ -483,7 +489,8 @@ "ping": "", "docker": "", "port": "", - "game": "" + "game": "", + "https": "" }, "common": { "appName": "", @@ -1071,7 +1078,11 @@ "incidentConfigDescription": "", "incidentConfigStatusWindowLabel": "", "incidentConfigStatusWindowThresholdLabel": "", - "incidentConfigTitle": "" + "incidentConfigTitle": "", + "incidentConfigDescriptionV2": "", + "incidentConfigStatusCheckNumber": "", + "intervalTitle": "", + "intervalDescription": "" }, "dataRate": "", "dataReceived": "", diff --git a/client/src/locales/fi.json b/client/src/locales/fi.json index d98ece35b..4949817c7 100644 --- a/client/src/locales/fi.json +++ b/client/src/locales/fi.json @@ -421,7 +421,13 @@ } } }, - "role": "" + "role": "", + "changeTeamPassword": { + "changePasswordMenu": "", + "title": "", + "description": "", + "success": "" + } }, "monitorState": { "paused": "Tauota", @@ -483,7 +489,8 @@ "ping": "", "docker": "", "port": "", - "game": "" + "game": "", + "https": "" }, "common": { "appName": "", @@ -1071,7 +1078,11 @@ "incidentConfigDescription": "", "incidentConfigStatusWindowLabel": "", "incidentConfigStatusWindowThresholdLabel": "", - "incidentConfigTitle": "" + "incidentConfigTitle": "", + "incidentConfigDescriptionV2": "", + "incidentConfigStatusCheckNumber": "", + "intervalTitle": "", + "intervalDescription": "" }, "dataRate": "", "dataReceived": "", diff --git a/client/src/locales/fr.json b/client/src/locales/fr.json index bfce12843..1ebc8bad4 100644 --- a/client/src/locales/fr.json +++ b/client/src/locales/fr.json @@ -425,7 +425,13 @@ } } }, - "role": "" + "role": "", + "changeTeamPassword": { + "changePasswordMenu": "", + "title": "", + "description": "", + "success": "" + } }, "monitorState": { "paused": "Pause", @@ -487,7 +493,8 @@ "ping": "Entrez l'adresse IP ou le nom d'hôte à tester (par exemple, 192.168.1.100 ou exemple.fr) et ajoutez un nom familier qui apparaîtra sur le tableau de bord.", "docker": "Entrer l'ID Docker du container. Les identifiants Docker doivent être les 64 caractères de l'ID Docker. Vous pouvez utiliser la commande docker inspect pour avoir l'ID complet.", "port": "Entrez l'URL ou l'adresse IP du serveur, le numéro de port et un nom d'affichage familier qui apparaîtra sur le tableau de bord.", - "game": "" + "game": "", + "https": "" }, "common": { "appName": "Checkmate", @@ -1096,7 +1103,11 @@ "incidentConfigDescription": "", "incidentConfigStatusWindowLabel": "", "incidentConfigStatusWindowThresholdLabel": "", - "incidentConfigTitle": "" + "incidentConfigTitle": "", + "incidentConfigDescriptionV2": "", + "incidentConfigStatusCheckNumber": "", + "intervalTitle": "", + "intervalDescription": "" }, "dataRate": "", "dataReceived": "", diff --git a/client/src/locales/ja.json b/client/src/locales/ja.json index 6107c19d0..f458b8ed0 100644 --- a/client/src/locales/ja.json +++ b/client/src/locales/ja.json @@ -425,7 +425,13 @@ } } }, - "role": "" + "role": "", + "changeTeamPassword": { + "changePasswordMenu": "", + "title": "", + "description": "", + "success": "" + } }, "monitorState": { "paused": "一時停止", @@ -487,7 +493,8 @@ "ping": "pingするIPアドレスまたはホスト名を入力(例: 192.168.1.100 または example.com)し、ダッシュボードに表示される明確な表示名を追加。", "docker": "コンテナのDocker IDを入力。Docker IDは64文字のフルDocker IDである必要があります。docker inspect を実行してフルコンテナIDを取得できます。", "port": "サーバーのURLまたはIP、ポート番号、ダッシュボードに表示される明確な表示名を入力。", - "game": "" + "game": "", + "https": "" }, "common": { "appName": "Checkmate", @@ -1075,7 +1082,11 @@ "incidentConfigDescription": "", "incidentConfigStatusWindowLabel": "", "incidentConfigStatusWindowThresholdLabel": "", - "incidentConfigTitle": "" + "incidentConfigTitle": "", + "incidentConfigDescriptionV2": "", + "incidentConfigStatusCheckNumber": "", + "intervalTitle": "", + "intervalDescription": "" }, "dataRate": "", "dataReceived": "", diff --git a/client/src/locales/pt-BR.json b/client/src/locales/pt-BR.json index 9e1fa6bc0..1a6d9a62f 100644 --- a/client/src/locales/pt-BR.json +++ b/client/src/locales/pt-BR.json @@ -425,7 +425,13 @@ } } }, - "role": "" + "role": "", + "changeTeamPassword": { + "changePasswordMenu": "", + "title": "", + "description": "", + "success": "" + } }, "monitorState": { "paused": "Pausado", @@ -487,7 +493,8 @@ "ping": "Insira o endereço IP ou nome de domínio para ping (ex. 192.168.1.100 ou exemplo.com.br) e adicione uma descrição que aparecerá na dashboard.", "docker": "Insira o Docker Id do seu container. Docker Ids devem ser todos os 64 caracteres. Você pode executar docker inspect para descobrir o Id completo.", "port": "Insira a URL ou o IP do servidor, aporta e uma descrição que aparecerá na dashboard.", - "game": "" + "game": "", + "https": "" }, "common": { "appName": "Checkmate", @@ -1096,7 +1103,11 @@ "incidentConfigDescription": "", "incidentConfigStatusWindowLabel": "", "incidentConfigStatusWindowThresholdLabel": "", - "incidentConfigTitle": "" + "incidentConfigTitle": "", + "incidentConfigDescriptionV2": "", + "incidentConfigStatusCheckNumber": "", + "intervalTitle": "", + "intervalDescription": "" }, "dataRate": "", "dataReceived": "", diff --git a/client/src/locales/ru.json b/client/src/locales/ru.json index aa54de393..cdb4d235d 100644 --- a/client/src/locales/ru.json +++ b/client/src/locales/ru.json @@ -10,7 +10,7 @@ "settingsFailedToClearStats": "Не удалось очистить статистику", "settingsMonitorsDeleted": "Успешно удалены все мониторы", "settingsFailedToDeleteMonitors": "Не удалось удалить все мониторы", - "starPromptTitle": "", + "starPromptTitle": "Запустить Checkmate.", "starPromptDescription": "Ознакомьтесь с последними релизами и помогите развить сообщество на GitHub", "https": "HTTPS", "http": "HTTP", @@ -84,31 +84,35 @@ "unsupportedType": "Неподдерживаемый тип уведомления", "networkError": "Произошла сетевая ошибка", "fallback": { - "title": "", - "checks": [""], - "actionButton": "" + "title": "Канал уведомлений используется для:", + "checks": [ + "Дайте знать команде, если сервер ложится или что-то виснет", + "Сообщите инженерам, когда появляется какая-то проблема", + "Уведомляйте админов, если в системе что-то поменялось" + ], + "actionButton": "Давайте создадим ваш первый канал уведомлений!" }, - "createButton": "", - "createTitle": "", + "createButton": "Создать канал уведомлений", + "createTitle": "Канал уведомлений", "create": { - "success": "", - "failed": "" + "success": "Уведомление успешно создано", + "failed": "Не удалось создать уведомление." }, "fetch": { - "success": "", - "failed": "" + "success": "Уведомления успешно получены", + "failed": "Не удалось получить уведомления." }, "delete": { - "success": "", - "failed": "" + "success": "Уведомление успешно удалено", + "failed": "Не удалось удалить уведомление." }, "edit": { - "success": "", - "failed": "" + "success": "Уведомление успешно обновлено", + "failed": "Не удалось обновить уведомление." }, "test": { - "success": "", - "failed": "" + "success": "Тестовое уведомление успешно отправлено", + "failed": "Не удалось отправить тестовое уведомление." } }, "testLocale": "testLocale", @@ -332,8 +336,8 @@ "validationFailed": "Не удалось выполнить проверку", "noFileSelected": "Файл не выбран", "fallbackPage": "Импортируйте файл для массовой загрузки списка серверов", - "invalidFileType": "", - "uploadFailed": "" + "invalidFileType": "Неверный тип файла", + "uploadFailed": "Загрузка не удалась" }, "DeleteAccountTitle": "Удалить аккаунт", "DeleteAccountButton": "Удалить аккаунт", @@ -378,50 +382,56 @@ "created": "Создано" }, "addTeamMember": { - "addMemberMenu": "", - "title": "", - "description": "", - "addButton": "" + "addMemberMenu": "Добавить члена команды", + "title": "Зарегистрируйте нового члена команды", + "description": "Создайте нового пользователя и передайте ему учётные данные. Этот метод даёт участнику мгновенный доступ ко всем мониторам.", + "addButton": "Добавить участника" }, - "register": "", + "register": "Зарегистрируйте члена команды", "registerToast": { - "success": "", - "dbUserExists": "", - "unknownError": "" + "success": "Созданный пользователем, безопасный обмен учетными данными с участником.", + "dbUserExists": "Пользователь уже существует.", + "unknownError": "Произошла неизвестная ошибка." }, "registerTeamMember": { - "title": "", + "title": "Зарегистрировать члена команды", "auth": { "common": { "inputs": { "firstName": { "errors": { - "empty": "", - "pattern": "" + "empty": "Пожалуйста, введите имя", + "pattern": "Имя должно содержать только буквы, пробелы, апострофы или дефисы." } }, "lastName": { "errors": { - "empty": "", - "pattern": "" + "empty": "Пожалуйста, введите фамилию", + "pattern": "Фамилия должна содержать только буквы, пробелы, апострофы или дефисы." } }, "email": { "errors": { - "empty": "", - "invalid": "" + "empty": "Чтобы продолжить, введите адрес электронной почты.", + "invalid": "Пожалуйста, перепроверьте правильность введенного адреса электронной почты." } }, "role": { "errors": { - "empty": "" + "empty": "Требуется роль" } } } } } }, - "role": "" + "role": "Роль", + "changeTeamPassword": { + "changePasswordMenu": "Сбросить пароль", + "title": "Сбросить пароль члена команды", + "description": "Создайте новый пароль для этого члена команды. Вам необходимо будет сообщить ему пароль по секретному адресу.", + "success": "Пароль успешно сброшен. Убедитесь, что вы предоставили учётные данные участнику безопасным способом." + } }, "monitorState": { "paused": "Остановлено", @@ -445,8 +455,8 @@ "password": "Пароль", "team": "Команда", "logOut": "Выйти", - "notifications": "", - "logs": "" + "notifications": "Уведомления", + "logs": "Логи" }, "settingsEmailUser": "Email пользователь", "state": "Состояние", @@ -474,8 +484,8 @@ "settingsTestEmailUnknownError": "Неизвестная ошибка", "statusMsg": { "paused": "Мониторинг приостановлен", - "up": "", - "down": "", + "up": "Ваш сайт запущен.", + "down": "Сайт оффлайн.", "pending": "Ожидание..." }, "uptimeGeneralInstructions": { @@ -483,13 +493,14 @@ "ping": "Ведите IP-адрес или имя хоста для пинга (например, 192.168.1.100 или example.com) и добавьте понятное имя, которое будет отображаться на панели инструментов.", "docker": "Введите Docker ID вашего контейнера. Docker ID должен быть полным 64-символьным Docker ID. Вы можете запустить docker inspect , чтобы получить полный ID контейнера.", "port": "Введите URL-адрес или IP-адрес сервера, номер порта и понятное имя, которое будет отображаться на панели инструментов.", - "game": "" + "game": "Введите IP-адрес или имя хоста и номер порта для пинга (например, 192.168.1.100 или example.com) и выберите тип игры.", + "https": "Введите URL-адрес или IP-адрес для мониторинга (например, https://example.com/ или 192.168.1.100) и добавьте понятное отображаемое имя, которое будет отображаться на панели управления." }, "common": { - "appName": "", - "monitoringAgentName": "", + "appName": "Checkmate", + "monitoringAgentName": "Захватить", "buttons": { - "toggleTheme": "" + "toggleTheme": "Переключить тему" }, "toasts": { "networkError": "Ошибка сети", @@ -506,7 +517,7 @@ "inputs": { "email": { "label": "Электронная почта", - "placeholder": "", + "placeholder": "jordan.ellis@domain.com", "errors": { "empty": "Для продолжения введите свой адрес электронной почты", "invalid": "Пожалуйста, проверьте правильность введённого адреса электронной почты" @@ -560,7 +571,7 @@ }, "firstName": { "label": "Имя", - "placeholder": "", + "placeholder": "Jordan", "errors": { "empty": "Пожалуйста, введите свое имя", "length": "Имя должно быть короче 50 символов", @@ -569,7 +580,7 @@ }, "lastName": { "label": "Фамилия", - "placeholder": "", + "placeholder": "Ellis", "errors": { "empty": "Пожалуйста, введите свою фамилию", "length": "Фамилия должна быть короче 50 символов", @@ -583,12 +594,12 @@ "fields": { "password": { "errors": { - "incorrect": "" + "incorrect": "Введённый вами пароль неверный" } }, "role": { "errors": { - "min": "" + "min": "Требуется хотя бы одна роль" } } } @@ -602,8 +613,8 @@ "links": { "forgotPassword": "Забыли пароль?", "register": "У вас нет учетной записи?", - "forgotPasswordLink": "", - "registerLink": "" + "forgotPasswordLink": "Сбросить пароль", + "registerLink": "Зарегистрируйтесь здесь" }, "toasts": { "success": "С возвращением! Вы успешно вошли в систему.", @@ -611,10 +622,10 @@ }, "errors": { "password": { - "incorrect": "" + "incorrect": "Предоставленный вами пароль не соответствует нашим записям." } }, - "welcome": "" + "welcome": "Добро пожаловать обратно в Checkmate!" }, "registration": { "heading": { @@ -641,7 +652,7 @@ "toasts": { "success": "Добро пожаловать! Ваша учетная запись была создана успешно." }, - "welcome": "" + "welcome": "Добро пожаловать в Checkmate!" }, "forgotPassword": { "heading": "Забыли пароль?", @@ -652,446 +663,471 @@ "stepFour": "Ваш пароль был успешно сброшен. Нажмите ниже, чтобы волшебным образом войти в систему." }, "buttons": { - "openEmail": "", + "openEmail": "Открыть Gmail", "resetPassword": "Сбросить пароль" }, "imageAlts": { - "passwordKey": "", - "email": "", - "lock": "", - "passwordConfirm": "" + "passwordKey": "Password key icon", + "email": "Email icon", + "lock": "Lock icon", + "passwordConfirm": "Password confirm icon" }, "links": { - "login": "", - "resend": "" + "login": "Вернуться к Входу", + "resend": "Не получили письмо? Нажмите, чтобы отправить повторно" }, "toasts": { - "sent": "", - "emailNotFound": "", - "redirect": "", - "success": "", - "error": "" + "sent": "Инструкция отправлена на .", + "emailNotFound": "Email не найден.", + "redirect": "Перенаправление через ...", + "success": "Ваш пароль успешно сброшен.", + "error": "Не удалось сбросить пароль. Повторите попытку позже или обратитесь в службу поддержки." } } }, "errorPages": { "serverUnreachable": { "toasts": { - "reconnected": "", - "stillUnreachable": "" + "reconnected": "Успешное повторное подключение к серверу.", + "stillUnreachable": "Сервер по-прежнему недоступен. Повторите попытку позже." }, - "alertBox": "", - "description": "", + "alertBox": "Ошибка подключения к серверу", + "description": "Не удаётся подключиться к серверу. Проверьте подключение к Интернету или конфигурацию развёртывания, если проблема не устранена.", "retryButton": { - "default": "", - "processing": "" + "default": "Повторите попытку подключения", + "processing": "Подключение..." } } }, "createNotifications": { - "title": "", + "title": "Создать канал уведомлений", "nameSettings": { - "title": "", - "description": "", - "nameLabel": "", - "namePlaceholder": "" + "title": "Имя", + "description": "Описательное имя для вашей интеграции.", + "nameLabel": "Имя", + "namePlaceholder": "например, уведомления Slack" }, "typeSettings": { - "title": "", - "description": "", - "typeLabel": "" + "title": "Тип", + "description": "Выберите тип канала уведомлений, который вы хотите создать.", + "typeLabel": "Тип" }, "emailSettings": { - "title": "", - "description": "", - "emailLabel": "", - "emailPlaceholder": "" + "title": "Электронная почта", + "description": "Адреса электронной почты получателей.", + "emailLabel": "Адрес электронной почты", + "emailPlaceholder": "например, john@example.com" }, "slackSettings": { - "title": "", - "description": "", - "webhookLabel": "", - "webhookPlaceholder": "" + "title": "Slack", + "description": "Настройте свой вебхук Slack здесь", + "webhookLabel": "Slack вебхук URL", + "webhookPlaceholder": "https://hooks.slack.com/services/..." }, "pagerdutySettings": { - "title": "", - "description": "", - "integrationKeyLabel": "", - "integrationKeyPlaceholder": "" + "title": "PagerDuty", + "description": "Настройте интеграцию PagerDuty здесь", + "integrationKeyLabel": "Интеграционный ключ", + "integrationKeyPlaceholder": "1234567890" }, "discordSettings": { - "title": "", - "description": "", - "webhookLabel": "", - "webhookPlaceholder": "" + "title": "Discord", + "description": "Настройте вебхук Discord здесь", + "webhookLabel": "Discord вебхук URL", + "webhookPlaceholder": "https://your-server.com/webhook" }, "webhookSettings": { - "title": "", - "description": "", - "webhookLabel": "", - "webhookPlaceholder": "" + "title": "Вебхук", + "description": "Настройте свой вебхук здесь", + "webhookLabel": "URL-адрес веб-перехватчика", + "webhookPlaceholder": "https://your-server.com/webhook" }, - "testNotification": "", - "dialogDeleteTitle": "", - "dialogDeleteConfirm": "" + "testNotification": "Тестовое уведомление", + "dialogDeleteTitle": "Вы уверены, что хотите удалить это уведомление?", + "dialogDeleteConfirm": "Удалить" }, "notificationConfig": { - "title": "", - "description": "" + "title": "Уведомления", + "description": "Выберите каналы уведомлений, которые вы хотите использовать" }, "monitorStatus": { - "checkingEvery": "", - "withCaptureAgent": "", - "up": "", - "down": "", - "paused": "" + "checkingEvery": "Проверка каждый {{interval}}", + "withCaptureAgent": "с агентом захвата {{version}}", + "up": "вверх", + "down": "вниз", + "paused": "приостановлено" }, - "advancedMatching": "", - "sendTestNotifications": "", - "selectAll": "", - "showAdminLoginLink": "", + "advancedMatching": "Расширенное сопоставление", + "sendTestNotifications": "Отправить тестовое уведомление", + "selectAll": "Выбрать все", + "showAdminLoginLink": "Показывать ссылку «Администратор? Войти здесь» на странице статуса", "logsPage": { - "title": "", - "description": "", + "title": "Логи", + "description": "Системные логи - последние 1000 строк", "tabs": { - "queue": "", - "logs": "", - "diagnostics": "" + "queue": "Очередь заданий", + "logs": "Серверные логи", + "diagnostics": "Диагностика" }, "toast": { - "fetchLogsSuccess": "" + "fetchLogsSuccess": "Логи успешно получены" }, "logLevelSelect": { - "title": "", + "title": "Уровень логов", "values": { - "all": "", - "info": "", - "warn": "", - "error": "", - "debug": "" + "all": "Все", + "info": "Информация", + "warn": "Предупреждение", + "error": "Ошибка", + "debug": "Дебаг" } } }, "queuePage": { - "title": "", - "refreshButton": "", - "flushButton": "", + "title": "Очередь", + "refreshButton": "Обновить", + "flushButton": "Очистить очередь", "jobTable": { - "title": "", - "idHeader": "", - "urlHeader": "", - "typeHeader": "", - "activeHeader": "", - "lockedAtHeader": "", - "runCountHeader": "", - "failCountHeader": "", - "lastRunHeader": "", - "lastFinishedAtHeader": "", - "lastRunTookHeader": "", - "intervalHeader": "" + "title": "Задачи в настоящее время в очереди", + "idHeader": "ID монитора", + "urlHeader": "URL", + "typeHeader": "Тип", + "activeHeader": "Активный", + "lockedAtHeader": "Заблокировано на", + "runCountHeader": "Количество запусков", + "failCountHeader": "Количество неудач", + "lastRunHeader": "Последний запуск в", + "lastFinishedAtHeader": "Последний раз закончил в", + "lastRunTookHeader": "Последний забег занял", + "intervalHeader": "Интервал" }, "metricsTable": { - "title": "", - "metricHeader": "", - "valueHeader": "" + "title": "Метрики очереди", + "metricHeader": "Метрическая", + "valueHeader": "Ценить" }, "failedJobTable": { - "title": "", - "monitorIdHeader": "", - "monitorUrlHeader": "", - "failCountHeader": "", - "failedAtHeader": "", - "failReasonHeader": "" + "title": "Неудачные задачи", + "monitorIdHeader": "ID монитора", + "monitorUrlHeader": "URL монитора", + "failCountHeader": "Количество падений", + "failedAtHeader": "Последнее падение в", + "failReasonHeader": "Причина падения" } }, "export": { - "title": "", - "success": "", - "failed": "" + "title": "Экспорт мониторов", + "success": "Мониторы успешно экспортированы!", + "failed": "Не удалось экспортировать мониторы" }, "monitorActions": { - "title": "", - "import": "", - "export": "", - "deleteSuccess": "", - "deleteFailed": "", - "details": "" + "title": "Экспорт/Импорт", + "import": "Импортные мониторы", + "export": "Экспорт мониторов", + "deleteSuccess": "Монитор успешно удален", + "deleteFailed": "Не удалось удалить монитор", + "details": "Подробности" }, "settingsPage": { "aboutSettings": { - "labelDevelopedBy": "", - "labelVersion": "", - "title": "" + "labelDevelopedBy": "Разработано Bluewave Labs", + "labelVersion": "Версия", + "title": "Об" }, "demoMonitorsSettings": { - "buttonAddMonitors": "", - "description": "", - "title": "" + "buttonAddMonitors": "Добавить демо мониторы", + "description": "Добавьте образцы мониторов для демонстрации.", + "title": "Демо мониторы" }, "emailSettings": { - "buttonSendTestEmail": "", - "description": "", - "descriptionTransport": "", - "labelAddress": "", - "labelConnectionHost": "", - "labelHost": "", - "labelIgnoreTLS": "", - "labelPassword": "", - "labelPasswordSet": "", - "labelPool": "", - "labelPort": "", - "labelRejectUnauthorized": "", - "labelRequireTLS": "", - "labelSecure": "", - "labelTLSServername": "", - "labelUser": "", - "linkTransport": "", - "placeholderUser": "", - "title": "", - "toastEmailRequiredFieldsError": "" + "buttonSendTestEmail": "Отправить тестовое письмо", + "description": "Настройте параметры электронной почты в вашей системе. Она используется для отправки уведомлений и оповещений.", + "descriptionTransport": "Это создает SMTP-транспорт для NodeMailer.", + "labelAddress": "Адрес электронной почты — используется для аутентификации.", + "labelConnectionHost": "Хост подключения электронной почты — имя хоста для использования в приветствии HELO/EHLO.", + "labelHost": "Хост электронной почты — имя хоста или IP-адрес для подключения.", + "labelIgnoreTLS": "Отключить STARTTLS: не использовать TLS, даже если сервер его поддерживает.", + "labelPassword": "Пароль электронной почты - Пароль для аутентификации", + "labelPasswordSet": "Пароль установлен. Нажмите «Сброс», чтобы изменить его.", + "labelPool": "Включить пул соединений: повторно использовать существующие соединения для повышения производительности.", + "labelPort": "Порт электронной почты — порт для подключения", + "labelRejectUnauthorized": "Отклонять недействительные сертификаты: отклонять соединения с самоподписанными или ненадежными сертификатами.", + "labelRequireTLS": "Принудительный STARTTLS: требуется обновление TLS, выдается ошибка, если не поддерживается", + "labelSecure": "Использовать SSL (рекомендуется): зашифровать соединение с помощью SSL/TLS.", + "labelTLSServername": "Имя сервера TLS — необязательное имя хоста для проверки TLS, если хост — это IP-адрес.", + "labelUser": "Пользователь электронной почты — имя пользователя для аутентификации, переопределяет адрес электронной почты, если указан", + "linkTransport": "Технические характеристики смотрите здесь.", + "placeholderUser": "Оставьте пустым, если не требуется", + "title": "Электронная почта", + "toastEmailRequiredFieldsError": "Требуется адрес электронной почты, хост, порт и пароль" }, "pageSpeedSettings": { - "description": "", - "labelApiKeySet": "", - "labelApiKey": "", - "title": "" + "description": "Введите ключ API Google PageSpeed, чтобы включить мониторинг Google PageSpeed. Нажмите «Сбросить», чтобы обновить ключ.", + "labelApiKeySet": "API-ключ установлен. Нажмите «Сброс», чтобы изменить его.", + "labelApiKey": "API-ключ PageSpeed", + "title": "API-ключ Google PageSpeed" }, - "saveButtonLabel": "", + "saveButtonLabel": "Сохранить", "statsSettings": { - "clearAllStatsButton": "", - "clearAllStatsDescription": "", - "clearAllStatsDialogConfirm": "", - "clearAllStatsDialogDescription": "", - "clearAllStatsDialogTitle": "", - "description": "", - "labelTTL": "", - "labelTTLOptional": "", - "title": "" + "clearAllStatsButton": "Очистить всю статистику", + "clearAllStatsDescription": "Очистить всю статистику. Это необратимо.", + "clearAllStatsDialogConfirm": "Да, очистить всю статистику", + "clearAllStatsDialogDescription": "После удаления история и статистика мониторинга не могут быть восстановлены.", + "clearAllStatsDialogTitle": "Вы хотите очистить всю статистику?", + "description": "Укажите, как долго вы хотите хранить данные. Вы также можете удалить все существующие данные.", + "labelTTL": "Дни, за которые вы хотите посмотреть историю.", + "labelTTLOptional": "0 для бесконечности", + "title": "История монитора" }, "systemResetSettings": { - "buttonRemoveAllMonitors": "", - "description": "", - "dialogConfirm": "", - "dialogDescription": "", - "dialogTitle": "", - "title": "" + "buttonRemoveAllMonitors": "Удалить все мониторы", + "description": "Удалите все мониторы из вашей системы.", + "dialogConfirm": "Да, удалить все мониторы", + "dialogDescription": "После удаления, мониторы нельзя будет воостановить.", + "dialogTitle": "Вы хотите удалить все мониторы?", + "title": "Сброс системы" }, "timezoneSettings": { - "description": "", - "label": "", - "title": "" + "description": "Выберите часовой пояс, используемый для отображения даты и времени в приложении.", + "label": "Отображение часового пояса", + "title": "Отображение часового пояса" }, - "title": "", + "title": "Настройки", "uiSettings": { - "description": "", - "labelLanguage": "", - "labelTheme": "", - "title": "" + "description": "Переключайтесь между светлым и темным режимами или меняйте язык пользовательского интерфейса.", + "labelLanguage": "Язык", + "labelTheme": "Режим темы", + "title": "Появление" }, "urlSettings": { - "description": "", - "label": "", - "selectDisabled": "", - "selectEnabled": "", - "title": "" + "description": "Отображение IP-адреса или URL-адреса монитора на общедоступной странице состояния. Если эта опция отключена, для защиты конфиденциальной информации будет отображаться только имя монитора.", + "label": "Отображать IP/URL на странице статуса", + "selectDisabled": "Выключено", + "selectEnabled": "Включено", + "title": "Мониторинг IP/URL на странице статуса" }, "globalThresholds": { - "title": "", - "description": "" + "title": "Глобальные пороги", + "description": "Настройте глобальные пороговые значения для процессора, памяти, диска и температуры. Если значение указано, оно будет автоматически включено для мониторинга." } }, "statusPageCreate": { - "buttonSave": "" + "buttonSave": "Сохранить" }, - "incidentsOptionsHeaderFilterResolved": "", - "settingsSave": "", - "statusPageCreateAppearanceTitle": "", - "confirmPassword": "", + "incidentsOptionsHeaderFilterResolved": "Решено", + "settingsSave": "Сохранить", + "statusPageCreateAppearanceTitle": "Внешний вид", + "confirmPassword": "Подтвердите пароль", "monitorHooks": { - "failureAddDemoMonitors": "", - "successAddDemoMonitors": "" + "failureAddDemoMonitors": "Не удалось добавить демонстрационные мониторы.", + "successAddDemoMonitors": "Демонстрационные мониторы успешно добавлены" }, - "settingsAppearance": "", - "settingsDisplayTimezone": "", - "settingsGeneralSettings": "", - "incidentsOptionsHeaderTotalIncidents": "", + "settingsAppearance": "Внешний вид", + "settingsDisplayTimezone": "Отображение часового пояса", + "settingsGeneralSettings": "Общие настройки", + "incidentsOptionsHeaderTotalIncidents": "Всего инцидентов", "statusPage": { - "deleteSuccess": "", - "deleteFailed": "", - "createSuccess": "", - "updateSuccess": "", - "generalSettings": "", - "contents": "", + "deleteSuccess": "Страница статуса успешно удалена", + "deleteFailed": "Не удалось удалить страницу статуса", + "createSuccess": "Страница статуса успешно создана", + "updateSuccess": "Страница статуса успешно обновлена", + "generalSettings": "Общие настройки", + "contents": "Содержание", "fallback": { - "checks": [""], - "title": "", - "actionButton": "" + "checks": [ + "Мониторинг и отображение состояния ваших сервисов в режиме реального времени", + "Отслеживайте состояние нескольких сервисов и делитесь их статусом", + "Дайте пользователям информацию о сбоях и производительности" + ], + "title": "Страница статуса используется для:", + "actionButton": "Давайте создадим вашу первую страницу статуса!" } }, - "testNotificationsDisabled": "", - "incidentsTableResolvedAt": "", - "incidentsTableActionResolve": "", + "testNotificationsDisabled": "Для этого монитора не настроены уведомления. Вам нужно добавить их, нажав кнопку «Настроить».", + "incidentsTableResolvedAt": "Решено в", + "incidentsTableActionResolve": "Решено", "checkHooks": { - "failureResolveOne": "", - "failureResolveAll": "", - "failureResolveMonitor": "" + "failureResolveOne": "Не удалось разрешить инцидент.", + "failureResolveAll": "Не удалось разрешить все инциденты.", + "failureResolveMonitor": "Не удалось устранить инциденты с монитором." }, - "checkFormError": "", + "checkFormError": "Пожалуйста, проверьте форму на наличие ошибок.", "diagnosticsPage": { - "diagnosticDescription": "", - "statsDescription": "", + "diagnosticDescription": "Системная диагностика", + "statsDescription": "Системная статистика", "gauges": { - "heapAllocationTitle": "", - "heapAllocationSubtitle": "", - "heapUsageTitle": "", - "heapUsageSubtitle": "", - "heapUtilizationTitle": "", - "heapUtilizationSubtitle": "", - "instantCpuUsageTitle": "", - "instantCpuUsageSubtitle": "" + "heapAllocationTitle": "Использовано кучи", + "heapAllocationSubtitle": "% доступной памяти", + "heapUsageTitle": "Выделено памяти в куче", + "heapUsageSubtitle": "% доступно памяти", + "heapUtilizationTitle": "Использовано кучи", + "heapUtilizationSubtitle": "% от доступного", + "instantCpuUsageTitle": "Загруженность CPU", + "instantCpuUsageSubtitle": "% загрузка CPU за 1s" }, "stats": { - "eventLoopDelayTitle": "", - "uptimeTitle": "", - "usedHeapSizeTitle": "", - "totalHeapSizeTitle": "", - "osMemoryLimitTitle": "" + "eventLoopDelayTitle": "Задержка цикла событий", + "uptimeTitle": "Аптайм", + "usedHeapSizeTitle": "Размер используемой кучи", + "totalHeapSizeTitle": "Общий размер кучи", + "osMemoryLimitTitle": "Ограничение памяти ОS" } }, - "pageSpeedLighthouseAPI": "", + "pageSpeedLighthouseAPI": "Используйте API Lighthouse PageSpeed для мониторинга вашего сайта", "time": { - "threeMinutes": "", - "fiveMinutes": "", - "tenMinutes": "", - "twentyMinutes": "", - "oneHour": "", - "oneDay": "", - "oneWeek": "", - "fourMinutes": "", - "oneMinute": "", - "twoMinutes": "", - "fifteenSeconds": "", - "thirtySeconds": "" + "threeMinutes": "3 минуты", + "fiveMinutes": "5 минут", + "tenMinutes": "10 минут", + "twentyMinutes": "20 минут", + "oneHour": "1 час", + "oneDay": "1 день", + "oneWeek": "1 неделя", + "fourMinutes": "4 минуты", + "oneMinute": "1 минута", + "twoMinutes": "2 минуты", + "fifteenSeconds": "15 секунд", + "thirtySeconds": "30 секунд" }, "general": { - "noOptionsFound": "" + "noOptionsFound": "{{unit}} не найдено" }, "infrastructureMonitor": { "fallback": { - "checks": [""], - "title": "", - "actionButton": "" + "checks": [ + "Отслеживайте производительность серверов", + "Выявляйте узкие места и оптимизируйте использование", + "Обеспечивайте надежность с помощью мониторинга в режиме реального времени" + ], + "title": "Монитор инфраструктуры используется для:", + "actionButton": "Давайте создадим ваш первый монитор инфраструктуры!" } }, "maintenanceWindow": { "fallback": { - "checks": [""], - "title": "", - "actionButton": "" + "checks": [ + "Отметьте периоды технического обслуживания.", + "Устраните любые недопонимания.", + "Прекратите отправку оповещений в периоды технического обслуживания." + ], + "title": "Период технического обслуживания используется для:", + "actionButton": "Давайте создадим ваше первое окно технического обслуживания!" } }, "pageSpeed": { "fallback": { - "checks": [""], - "title": "", - "actionButton": "" + "checks": [ + "Отчет об удобстве использования страницы", + "Помощь в анализе скорости загрузки веб-страницы", + "Предложение о том, как можно улучшить страницу" + ], + "title": "Монитор PageSpeed используется для:", + "actionButton": "Давайте создадим ваш первый монитор PageSpeed!" } }, "uptimeMonitor": { "fallback": { - "checks": [""], - "title": "", - "actionButton": "" + "checks": [ + "Проверьте, доступны ли веб-сайты или серверы и отвечают ли они", + "Оповещайте команды о сбоях или проблемах с производительностью", + "Мониторинг конечных точек HTTP, пингов, контейнеров и портов", + "Отслеживайте динамику времени безотказной работы и надежности" + ], + "title": "Монитор работоспособности используется для:", + "actionButton": "Давайте создадим ваш первый монитор времени безотказной работы!" } }, "editUserPage": { "form": { - "email": "", - "firstName": "", - "lastName": "", - "role": "", - "save": "" + "email": "Электронная почта", + "firstName": "Имя", + "lastName": "Фамилия", + "role": "Роли", + "save": "Сохранить" }, "table": { - "actionHeader": "", - "roleHeader": "" + "actionHeader": "Действие", + "roleHeader": "Роль" }, - "title": "", + "title": "Редактировать пользователя", "toast": { - "successUserUpdate": "", - "validationErrors": "" + "successUserUpdate": "Пользователь успешно обновлен", + "validationErrors": "Ошибки проверки" } }, - "incidentsPageActionResolveMonitor": "", - "incidentsPageActionResolveAll": "", + "incidentsPageActionResolveMonitor": "Разрешение инцидентов мониторинга", + "incidentsPageActionResolveAll": "Решить все инциденты", "matchMethodOptions": { - "equal": "", - "equalPlaceholder": "", - "include": "", - "includePlaceholder": "", - "regex": "", - "regexPlaceholder": "", - "text": "" + "equal": "Равный", + "equalPlaceholder": "успех", + "include": "Включить", + "includePlaceholder": "хорошо", + "regex": "Regex", + "regexPlaceholder": "^(success|ok)$", + "text": "Метод сопоставления" }, "monitorType": { "docker": { - "label": "", - "namePlaceholder": "", - "placeholder": "" + "label": "Имя/ID контейнера", + "namePlaceholder": "Мой контейнер", + "placeholder": "my-app или abcd1234" }, "http": { - "label": "", - "namePlaceholder": "", - "placeholder": "" + "label": "URL для мониторинга", + "namePlaceholder": "Google", + "placeholder": "google.com" }, "ping": { - "label": "", - "namePlaceholder": "", - "placeholder": "" + "label": "IP-адрес для мониторинга", + "namePlaceholder": "Google", + "placeholder": "1.1.1.1" }, "port": { - "label": "", - "namePlaceholder": "", - "placeholder": "" + "label": "URL для мониторинга", + "namePlaceholder": "Локальный хост:5173", + "placeholder": "локальный хост" }, "game": { - "label": "", - "namePlaceholder": "", - "placeholder": "" + "label": "URL для мониторинга", + "namePlaceholder": "localhost:5173", + "placeholder": "localhost" } }, "uptimeAdvancedMatching": { - "jsonPath": "" + "jsonPath": "путь к JSON" }, - "bytesPerSecond": "", - "bytesReceived": "", - "bytesSent": "", - "chooseGame": "", + "bytesPerSecond": "Байты в секунду", + "bytesReceived": "Получено байтов", + "bytesSent": "Отправлено байтов", + "chooseGame": "Выбрать игру", "createMonitorPage": { - "incidentConfigDescription": "", - "incidentConfigStatusWindowLabel": "", - "incidentConfigStatusWindowThresholdLabel": "", - "incidentConfigTitle": "" + "incidentConfigDescription": "Мониторинг использует скользящее окно, чтобы понять, когда ресурс недоступен. Статус изменится, если нужный процент проверок покажет сбой.", + "incidentConfigStatusWindowLabel": "Сколько проверок должно быть в скользящем окне?", + "incidentConfigStatusWindowThresholdLabel": "Какой процент проверок в скользящем окне завершается неудачей/успешностью до изменения статуса монитора?", + "incidentConfigTitle": "Инциденты", + "incidentConfigDescriptionV2": "Количество последовательных проверок, необходимых для изменения статуса монитора. Максимум 25.", + "incidentConfigStatusCheckNumber": "Сколько проверок перед сменой статуса?", + "intervalTitle": "Интервал проверки", + "intervalDescription": "Как часто следует проверять монитор." }, - "dataRate": "", - "dataReceived": "", - "dataSent": "", - "details": "", - "drops": "", - "errors": "", - "errorsIn": "", - "errorsOut": "", - "gameServerMonitoring": "", - "gameServerMonitoringDescription": "", - "network": "", - "networkDrops": "", - "networkErrors": "", - "networkInterface": "", - "noNetworkStatsAvailable": "", - "packetsPerSecond": "", - "packetsReceived": "", - "packetsReceivedRate": "", - "packetsSent": "", - "rate": "", - "selectInterface": "" + "dataRate": "Скорость передачи данных", + "dataReceived": "Данные получены", + "dataSent": "Данные отправлены", + "details": "Подробности", + "drops": "Падений", + "errors": "Ошибки", + "errorsIn": "Ошибки в", + "errorsOut": "Ошибки вне", + "gameServerMonitoring": "Мониторинг игрового сервера", + "gameServerMonitoringDescription": "Проверьте, запущен ли ваш игровой сервер.", + "network": "Сеть", + "networkDrops": "Сетевые падения", + "networkErrors": "Ошибки сети", + "networkInterface": "Сетевой интерфейс", + "noNetworkStatsAvailable": "Статистика сети отсутствует.", + "packetsPerSecond": "Пакетов в секунду", + "packetsReceived": "Получено пакетов", + "packetsReceivedRate": "Скорость получения пакетов", + "packetsSent": "Отправлено пакетов", + "rate": "коэффициент", + "selectInterface": "Выберите интерфейс" } diff --git a/client/src/locales/th.json b/client/src/locales/th.json index c3f571b6b..c32849afe 100644 --- a/client/src/locales/th.json +++ b/client/src/locales/th.json @@ -425,7 +425,13 @@ } } }, - "role": "บทบาท" + "role": "บทบาท", + "changeTeamPassword": { + "changePasswordMenu": "", + "title": "", + "description": "", + "success": "" + } }, "monitorState": { "paused": "หยุดชั่วคราว", @@ -487,7 +493,8 @@ "ping": "กรอกที่อยู่ IP หรือชื่อโฮสต์ที่ต้องการ ping (เช่น 192.168.1.100 หรือ example.com) และตั้งชื่อที่ชัดเจนให้แสดงบนแดชบอร์ด", "docker": "กรอกชื่อหรือ ID ของคอนเทนเนอร์ Docker คุณสามารถใช้ชื่อคอนเทนเนอร์ (เช่น my-app) หรือ ID ของคอนเทนเนอร์ (ID เต็ม 64 ตัวอักษร หรือ ID สั้น)", "port": "กรอก URL หรือ IP ของเซิร์ฟเวอร์ หมายเลขพอร์ต และตั้งชื่อที่ชัดเจนให้แสดงบนแดชบอร์ด", - "game": "กรอกที่อยู่ IP หรือชื่อโฮสต์และหมายเลขพอร์ตเพื่อทำการ ping (เช่น 192.168.1.100 หรือ example.com) และเลือกประเภทเกม" + "game": "กรอกที่อยู่ IP หรือชื่อโฮสต์และหมายเลขพอร์ตเพื่อทำการ ping (เช่น 192.168.1.100 หรือ example.com) และเลือกประเภทเกม", + "https": "" }, "common": { "appName": "Checkmate", @@ -1096,7 +1103,11 @@ "incidentConfigDescription": "การใช้หน้าต่างเลื่อนจะช่วยกำหนดเวลาที่ตัวตรวจสอบล้มเหลว สถานะของตัวตรวจสอบจะเปลี่ยนก็ต่อเมื่อเปอร์เซ็นต์ของการตรวจสอบในหน้าต่างเลื่อนตรงตามค่าที่กำหนด", "incidentConfigStatusWindowLabel": "ต้องมีการตรวจสอบกี่ครั้งในหน้าต่างเลื่อน?", "incidentConfigStatusWindowThresholdLabel": "ต้องการให้กี่เปอร์เซ็นต์ของการตรวจสอบในหน้าต่างเลื่อนล้มเหลว/สำเร็จ ก่อนที่จะเปลี่ยนสถานะของตัวตรวจสอบ?", - "incidentConfigTitle": "เหตุการณ์" + "incidentConfigTitle": "เหตุการณ์", + "incidentConfigDescriptionV2": "", + "incidentConfigStatusCheckNumber": "", + "intervalTitle": "", + "intervalDescription": "" }, "dataRate": "อัตราการส่งข้อมูล", "dataReceived": "ข้อมูลที่รับ", diff --git a/client/src/locales/tr.json b/client/src/locales/tr.json index 0ab45ebe9..59f294533 100644 --- a/client/src/locales/tr.json +++ b/client/src/locales/tr.json @@ -421,7 +421,13 @@ } } }, - "role": "" + "role": "", + "changeTeamPassword": { + "changePasswordMenu": "", + "title": "", + "description": "", + "success": "" + } }, "monitorState": { "paused": "Durduruldu", @@ -483,7 +489,8 @@ "ping": "", "docker": "", "port": "", - "game": "" + "game": "", + "https": "" }, "common": { "appName": "Checkmate", @@ -1071,7 +1078,11 @@ "incidentConfigDescription": "", "incidentConfigStatusWindowLabel": "", "incidentConfigStatusWindowThresholdLabel": "", - "incidentConfigTitle": "" + "incidentConfigTitle": "", + "incidentConfigDescriptionV2": "", + "incidentConfigStatusCheckNumber": "", + "intervalTitle": "", + "intervalDescription": "" }, "dataRate": "", "dataReceived": "", diff --git a/client/src/locales/zh-CN.json b/client/src/locales/zh-CN.json index c2d1aa6fc..305cefe87 100644 --- a/client/src/locales/zh-CN.json +++ b/client/src/locales/zh-CN.json @@ -425,7 +425,13 @@ } } }, - "role": "角色" + "role": "角色", + "changeTeamPassword": { + "changePasswordMenu": "", + "title": "", + "description": "", + "success": "" + } }, "monitorState": { "paused": "暂停", @@ -487,7 +493,8 @@ "ping": "输入要 ping 的 IP 地址或主机名(例如.. 192.168.1.100 或 example.com),并添加显示在仪表板上的清晰显示名称。", "docker": "码输入 Docker 容器名称或 ID。您可以使用容器名称(例如.. my-app)或容器 ID(完整的 64 字符 ID 或短 ID)。", "port": "输入服务器的 URL 或 IP、端口号和仪表板上显示的清晰显示名称。", - "game": "输入要 ping 的 IP 地址或主机名和端口号(例如.. 192.168.1.100 或 example.com)并选择游戏类型。" + "game": "输入要 ping 的 IP 地址或主机名和端口号(例如.. 192.168.1.100 或 example.com)并选择游戏类型。", + "https": "" }, "common": { "appName": "Checkmate", @@ -1088,7 +1095,11 @@ "incidentConfigDescription": "滑动窗口用于确定监视器何时发生故障。仅当滑动窗口中的检查百分比满足指定值时,监视器的状态才会改变。", "incidentConfigStatusWindowLabel": "在滑动窗口中应包含多少次检测?", "incidentConfigStatusWindowThresholdLabel": "在监视器状态改变之前,滑动窗口中的检查失败/成功的百分比是多少?", - "incidentConfigTitle": "事件" + "incidentConfigTitle": "事件", + "incidentConfigDescriptionV2": "", + "incidentConfigStatusCheckNumber": "", + "intervalTitle": "", + "intervalDescription": "" }, "dataRate": "数据速率", "dataReceived": "已接收数据", diff --git a/client/src/locales/zh-TW.json b/client/src/locales/zh-TW.json index e57ebd487..e5ee58e58 100644 --- a/client/src/locales/zh-TW.json +++ b/client/src/locales/zh-TW.json @@ -425,7 +425,13 @@ } } }, - "role": "" + "role": "", + "changeTeamPassword": { + "changePasswordMenu": "", + "title": "", + "description": "", + "success": "" + } }, "monitorState": { "paused": "", @@ -487,7 +493,8 @@ "ping": "", "docker": "", "port": "", - "game": "" + "game": "", + "https": "" }, "common": { "appName": "Checkmate", @@ -1075,7 +1082,11 @@ "incidentConfigDescription": "", "incidentConfigStatusWindowLabel": "", "incidentConfigStatusWindowThresholdLabel": "", - "incidentConfigTitle": "" + "incidentConfigTitle": "", + "incidentConfigDescriptionV2": "", + "incidentConfigStatusCheckNumber": "", + "intervalTitle": "", + "intervalDescription": "" }, "dataRate": "", "dataReceived": "", From ce025df7b7fa24f8c95de7f518bc5466357256b9 Mon Sep 17 00:00:00 2001 From: Tudor Gradinaru Date: Thu, 23 Oct 2025 15:28:20 +0300 Subject: [PATCH 098/103] feat: Add monitor name to notification titles --- server/src/locales/en.json | 4 ++-- server/src/service/v1/infrastructure/notificationUtils.js | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/server/src/locales/en.json b/server/src/locales/en.json index 1345f84a8..373911a55 100755 --- a/server/src/locales/en.json +++ b/server/src/locales/en.json @@ -175,7 +175,7 @@ "checkmate": "Checkmate", "url": "URL", "unknown": "Unknown", - "uptimeAlert": "Uptime Alert: One of your monitors is back online", - "downtimeAlert": "Downtime Alert: One of your monitors went offline" + "uptimeAlert": "Uptime Alert: Your monitor {monitorName} is back online", + "downtimeAlert": "Downtime Alert: Your monitor {monitorName} went offline" } } diff --git a/server/src/service/v1/infrastructure/notificationUtils.js b/server/src/service/v1/infrastructure/notificationUtils.js index d6b92cc0e..879ad927c 100644 --- a/server/src/service/v1/infrastructure/notificationUtils.js +++ b/server/src/service/v1/infrastructure/notificationUtils.js @@ -74,7 +74,9 @@ class NotificationUtils { let discordMessageText = { embeds: [ { - title: status ? dn.uptimeAlert : dn.downtimeAlert, + title: status + ? dn.uptimeAlert.replace("{monitorName}", monitor?.name ?? dn.unknown) + : dn.downtimeAlert.replace("{monitorName}", monitor?.name ?? dn.unknown), color: status ? 5763719 : 15548997, fields: [ From 1fad85d48d3d27c1e238ab14ca11a930e0c1cd68 Mon Sep 17 00:00:00 2001 From: Tudor Gradinaru Date: Sat, 25 Oct 2025 18:31:01 +0300 Subject: [PATCH 099/103] feat: Add Matrix notification support Fixes #3034 --- .../Pages/v1/Notifications/create/index.jsx | 58 +++++++++++++++---- client/src/Pages/v1/Notifications/utils.js | 5 ++ client/src/Validation/validation.js | 34 ++++++++++- client/src/locales/en.json | 10 ++++ server/src/db/v1/models/Notification.js | 12 +++- .../v1/infrastructure/networkService.js | 36 ++++++++++++ .../notificationProviders/matrix.js | 34 +++++++++++ .../v1/infrastructure/notificationService.js | 9 +++ server/src/validation/joi.js | 36 +++++++++++- 9 files changed, 219 insertions(+), 15 deletions(-) create mode 100644 server/src/service/v1/infrastructure/notificationProviders/matrix.js diff --git a/client/src/Pages/v1/Notifications/create/index.jsx b/client/src/Pages/v1/Notifications/create/index.jsx index eb2b04aa6..b1c50493c 100644 --- a/client/src/Pages/v1/Notifications/create/index.jsx +++ b/client/src/Pages/v1/Notifications/create/index.jsx @@ -223,18 +223,54 @@ const CreateNotifications = () => { {t(DESCRIPTION_MAP[type])} - + {type === "matrix" ? ( + <> + + + + + ) : ( + + )} - - + {" "} Date: Mon, 27 Oct 2025 09:42:27 -0700 Subject: [PATCH 100/103] bump-scheduler --- server/package-lock.json | 2316 +++++++++++++++++++++----------------- server/package.json | 2 +- 2 files changed, 1268 insertions(+), 1050 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index 3ea4269eb..8de5cfc0c 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -39,7 +39,7 @@ "ping": "0.4.4", "sharp": "0.33.5", "ssl-checker": "2.0.10", - "super-simple-scheduler": "1.4.1", + "super-simple-scheduler": "1.4.4", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, @@ -60,6 +60,9 @@ "sinon": "19.0.2", "tsx": "4.20.5", "typescript": "5.9.2" + }, + "engines": { + "node": ">=20" } }, "node_modules/@asamuzakjp/css-color": { @@ -75,6 +78,34 @@ "lru-cache": "^10.4.3" } }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.3.tgz", + "integrity": "sha512-kiGFeY+Hxf5KbPpjRLf+ffWbkos1aGo8MBfd91oxS3O57RgU3XhZrt/6UzoVF9VMpWbC3v87SRc9jxGrc9qHtQ==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.2" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "license": "MIT" + }, "node_modules/@aws-crypto/sha256-browser": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", @@ -211,51 +242,51 @@ } }, "node_modules/@aws-sdk/client-sesv2": { - "version": "3.895.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.895.0.tgz", - "integrity": "sha512-tYfgwoG4n8LEc0NHVriNNS+YqeDMJHHcUzJxJJ7Nols5Y3EJzv76QRvu2rdLa9XoxvTSFGXJSuqrd8s0ElbCQQ==", + "version": "3.917.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.917.0.tgz", + "integrity": "sha512-Jwz+3rO9fc2H8JyF4ainL5x06tiQ0n4ewKIpdUeGIauKdndMItYP1aeM0Y8wJQPTF1XEpWFC36cTSDgk/eypxA==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.894.0", - "@aws-sdk/credential-provider-node": "3.895.0", - "@aws-sdk/middleware-host-header": "3.893.0", - "@aws-sdk/middleware-logger": "3.893.0", - "@aws-sdk/middleware-recursion-detection": "3.893.0", - "@aws-sdk/middleware-user-agent": "3.895.0", - "@aws-sdk/region-config-resolver": "3.893.0", - "@aws-sdk/signature-v4-multi-region": "3.894.0", - "@aws-sdk/types": "3.893.0", - "@aws-sdk/util-endpoints": "3.895.0", - "@aws-sdk/util-user-agent-browser": "3.893.0", - "@aws-sdk/util-user-agent-node": "3.895.0", - "@smithy/config-resolver": "^4.2.2", - "@smithy/core": "^3.11.1", - "@smithy/fetch-http-handler": "^5.2.1", - "@smithy/hash-node": "^4.1.1", - "@smithy/invalid-dependency": "^4.1.1", - "@smithy/middleware-content-length": "^4.1.1", - "@smithy/middleware-endpoint": "^4.2.3", - "@smithy/middleware-retry": "^4.2.4", - "@smithy/middleware-serde": "^4.1.1", - "@smithy/middleware-stack": "^4.1.1", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/node-http-handler": "^4.2.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/smithy-client": "^4.6.3", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-body-length-browser": "^4.1.0", - "@smithy/util-body-length-node": "^4.1.0", - "@smithy/util-defaults-mode-browser": "^4.1.3", - "@smithy/util-defaults-mode-node": "^4.1.3", - "@smithy/util-endpoints": "^3.1.2", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-retry": "^4.1.2", - "@smithy/util-utf8": "^4.1.0", + "@aws-sdk/core": "3.916.0", + "@aws-sdk/credential-provider-node": "3.917.0", + "@aws-sdk/middleware-host-header": "3.914.0", + "@aws-sdk/middleware-logger": "3.914.0", + "@aws-sdk/middleware-recursion-detection": "3.914.0", + "@aws-sdk/middleware-user-agent": "3.916.0", + "@aws-sdk/region-config-resolver": "3.914.0", + "@aws-sdk/signature-v4-multi-region": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@aws-sdk/util-endpoints": "3.916.0", + "@aws-sdk/util-user-agent-browser": "3.914.0", + "@aws-sdk/util-user-agent-node": "3.916.0", + "@smithy/config-resolver": "^4.4.0", + "@smithy/core": "^3.17.1", + "@smithy/fetch-http-handler": "^5.3.4", + "@smithy/hash-node": "^4.2.3", + "@smithy/invalid-dependency": "^4.2.3", + "@smithy/middleware-content-length": "^4.2.3", + "@smithy/middleware-endpoint": "^4.3.5", + "@smithy/middleware-retry": "^4.4.5", + "@smithy/middleware-serde": "^4.2.3", + "@smithy/middleware-stack": "^4.2.3", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/node-http-handler": "^4.4.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/smithy-client": "^4.9.1", + "@smithy/types": "^4.8.0", + "@smithy/url-parser": "^4.2.3", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.4", + "@smithy/util-defaults-mode-node": "^4.2.6", + "@smithy/util-endpoints": "^3.2.3", + "@smithy/util-middleware": "^4.2.3", + "@smithy/util-retry": "^4.2.3", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -263,49 +294,49 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.895.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.895.0.tgz", - "integrity": "sha512-AQHk6iJrwce/NwZa5/Njy0ZGoHdxWCajkgufhXk53L0kRiC3vUPPWEV1m1F3etQWhaUsatcO2xtRuKvLpe4zgA==", + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.916.0.tgz", + "integrity": "sha512-Eu4PtEUL1MyRvboQnoq5YKg0Z9vAni3ccebykJy615xokVZUdA3di2YxHM/hykDQX7lcUC62q9fVIvh0+UNk/w==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.894.0", - "@aws-sdk/middleware-host-header": "3.893.0", - "@aws-sdk/middleware-logger": "3.893.0", - "@aws-sdk/middleware-recursion-detection": "3.893.0", - "@aws-sdk/middleware-user-agent": "3.895.0", - "@aws-sdk/region-config-resolver": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@aws-sdk/util-endpoints": "3.895.0", - "@aws-sdk/util-user-agent-browser": "3.893.0", - "@aws-sdk/util-user-agent-node": "3.895.0", - "@smithy/config-resolver": "^4.2.2", - "@smithy/core": "^3.11.1", - "@smithy/fetch-http-handler": "^5.2.1", - "@smithy/hash-node": "^4.1.1", - "@smithy/invalid-dependency": "^4.1.1", - "@smithy/middleware-content-length": "^4.1.1", - "@smithy/middleware-endpoint": "^4.2.3", - "@smithy/middleware-retry": "^4.2.4", - "@smithy/middleware-serde": "^4.1.1", - "@smithy/middleware-stack": "^4.1.1", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/node-http-handler": "^4.2.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/smithy-client": "^4.6.3", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-body-length-browser": "^4.1.0", - "@smithy/util-body-length-node": "^4.1.0", - "@smithy/util-defaults-mode-browser": "^4.1.3", - "@smithy/util-defaults-mode-node": "^4.1.3", - "@smithy/util-endpoints": "^3.1.2", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-retry": "^4.1.2", - "@smithy/util-utf8": "^4.1.0", + "@aws-sdk/core": "3.916.0", + "@aws-sdk/middleware-host-header": "3.914.0", + "@aws-sdk/middleware-logger": "3.914.0", + "@aws-sdk/middleware-recursion-detection": "3.914.0", + "@aws-sdk/middleware-user-agent": "3.916.0", + "@aws-sdk/region-config-resolver": "3.914.0", + "@aws-sdk/types": "3.914.0", + "@aws-sdk/util-endpoints": "3.916.0", + "@aws-sdk/util-user-agent-browser": "3.914.0", + "@aws-sdk/util-user-agent-node": "3.916.0", + "@smithy/config-resolver": "^4.4.0", + "@smithy/core": "^3.17.1", + "@smithy/fetch-http-handler": "^5.3.4", + "@smithy/hash-node": "^4.2.3", + "@smithy/invalid-dependency": "^4.2.3", + "@smithy/middleware-content-length": "^4.2.3", + "@smithy/middleware-endpoint": "^4.3.5", + "@smithy/middleware-retry": "^4.4.5", + "@smithy/middleware-serde": "^4.2.3", + "@smithy/middleware-stack": "^4.2.3", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/node-http-handler": "^4.4.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/smithy-client": "^4.9.1", + "@smithy/types": "^4.8.0", + "@smithy/url-parser": "^4.2.3", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.4", + "@smithy/util-defaults-mode-node": "^4.2.6", + "@smithy/util-endpoints": "^3.2.3", + "@smithy/util-middleware": "^4.2.3", + "@smithy/util-retry": "^4.2.3", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -313,25 +344,24 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.894.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.894.0.tgz", - "integrity": "sha512-7zbO31NV2FaocmMtWOg/fuTk3PC2Ji2AC0Fi2KqrppEDIcwLlTTuT9w/rdu/93Pz+wyUhCxWnDc0tPbwtCLs+A==", + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.916.0.tgz", + "integrity": "sha512-1JHE5s6MD5PKGovmx/F1e01hUbds/1y3X8rD+Gvi/gWVfdg5noO7ZCerpRsWgfzgvCMZC9VicopBqNHCKLykZA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@aws-sdk/xml-builder": "3.894.0", - "@smithy/core": "^3.11.1", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/property-provider": "^4.1.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/signature-v4": "^5.2.1", - "@smithy/smithy-client": "^4.6.3", - "@smithy/types": "^4.5.0", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-body-length-browser": "^4.1.0", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-utf8": "^4.1.0", + "@aws-sdk/types": "3.914.0", + "@aws-sdk/xml-builder": "3.914.0", + "@smithy/core": "^3.17.1", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/property-provider": "^4.2.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/signature-v4": "^5.3.3", + "@smithy/smithy-client": "^4.9.1", + "@smithy/types": "^4.8.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.3", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -339,16 +369,16 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.894.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.894.0.tgz", - "integrity": "sha512-2aiQJIRWOuROPPISKgzQnH/HqSfucdk5z5VMemVH3Mm2EYOrzBwmmiiFpmSMN3ST+sE8c7gusqycUchP+KfALQ==", + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.916.0.tgz", + "integrity": "sha512-3gDeqOXcBRXGHScc6xb7358Lyf64NRG2P08g6Bu5mv1Vbg9PKDyCAZvhKLkG7hkdfAM8Yc6UJNhbFxr1ud/tCQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.894.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/types": "^4.5.0", + "@aws-sdk/core": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@smithy/property-provider": "^4.2.3", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -356,21 +386,21 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.894.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.894.0.tgz", - "integrity": "sha512-Z5QQpqFRflszrT+lUq6+ORuu4jRDcpgCUSoTtlhczidMqfdOSckKmK3chZEfmUUJPSwoFQZ7EiVTsX3c886fBg==", + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.916.0.tgz", + "integrity": "sha512-NmooA5Z4/kPFJdsyoJgDxuqXC1C6oPMmreJjbOPqcwo6E/h2jxaG8utlQFgXe5F9FeJsMx668dtxVxSYnAAqHQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.894.0", - "@aws-sdk/types": "3.893.0", - "@smithy/fetch-http-handler": "^5.2.1", - "@smithy/node-http-handler": "^4.2.1", - "@smithy/property-provider": "^4.1.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/smithy-client": "^4.6.3", - "@smithy/types": "^4.5.0", - "@smithy/util-stream": "^4.3.2", + "@aws-sdk/core": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@smithy/fetch-http-handler": "^5.3.4", + "@smithy/node-http-handler": "^4.4.3", + "@smithy/property-provider": "^4.2.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/smithy-client": "^4.9.1", + "@smithy/types": "^4.8.0", + "@smithy/util-stream": "^4.5.4", "tslib": "^2.6.2" }, "engines": { @@ -378,24 +408,24 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.895.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.895.0.tgz", - "integrity": "sha512-uIh7N4IN/yIk+qYMAkVpVkjhB90SGKSfaXEVcnmxzBDG6e5304HKT0esqoCVZvtFfLKasjm2TOpalM5l3fi/dA==", + "version": "3.917.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.917.0.tgz", + "integrity": "sha512-rvQ0QamLySRq+Okc0ZqFHZ3Fbvj3tYuWNIlzyEKklNmw5X5PM1idYKlOJflY2dvUGkIqY3lUC9SC2WL+1s7KIw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.894.0", - "@aws-sdk/credential-provider-env": "3.894.0", - "@aws-sdk/credential-provider-http": "3.894.0", - "@aws-sdk/credential-provider-process": "3.894.0", - "@aws-sdk/credential-provider-sso": "3.895.0", - "@aws-sdk/credential-provider-web-identity": "3.895.0", - "@aws-sdk/nested-clients": "3.895.0", - "@aws-sdk/types": "3.893.0", - "@smithy/credential-provider-imds": "^4.1.2", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/core": "3.916.0", + "@aws-sdk/credential-provider-env": "3.916.0", + "@aws-sdk/credential-provider-http": "3.916.0", + "@aws-sdk/credential-provider-process": "3.916.0", + "@aws-sdk/credential-provider-sso": "3.916.0", + "@aws-sdk/credential-provider-web-identity": "3.917.0", + "@aws-sdk/nested-clients": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@smithy/credential-provider-imds": "^4.2.3", + "@smithy/property-provider": "^4.2.3", + "@smithy/shared-ini-file-loader": "^4.3.3", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -403,23 +433,23 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.895.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.895.0.tgz", - "integrity": "sha512-7xsBCmkBUz+2sNqNsDJ1uyQsBvwhNFzwFt8wX39WrFJTpTQh3uNQ5g8QH21BbkKqIFKCLdvgHgwt3Ub5RGVuPA==", + "version": "3.917.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.917.0.tgz", + "integrity": "sha512-n7HUJ+TgU9wV/Z46yR1rqD9hUjfG50AKi+b5UXTlaDlVD8bckg40i77ROCllp53h32xQj/7H0yBIYyphwzLtmg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.894.0", - "@aws-sdk/credential-provider-http": "3.894.0", - "@aws-sdk/credential-provider-ini": "3.895.0", - "@aws-sdk/credential-provider-process": "3.894.0", - "@aws-sdk/credential-provider-sso": "3.895.0", - "@aws-sdk/credential-provider-web-identity": "3.895.0", - "@aws-sdk/types": "3.893.0", - "@smithy/credential-provider-imds": "^4.1.2", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/credential-provider-env": "3.916.0", + "@aws-sdk/credential-provider-http": "3.916.0", + "@aws-sdk/credential-provider-ini": "3.917.0", + "@aws-sdk/credential-provider-process": "3.916.0", + "@aws-sdk/credential-provider-sso": "3.916.0", + "@aws-sdk/credential-provider-web-identity": "3.917.0", + "@aws-sdk/types": "3.914.0", + "@smithy/credential-provider-imds": "^4.2.3", + "@smithy/property-provider": "^4.2.3", + "@smithy/shared-ini-file-loader": "^4.3.3", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -427,17 +457,17 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.894.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.894.0.tgz", - "integrity": "sha512-VU74GNsj+SsO+pl4d+JimlQ7+AcderZaC6bFndQssQdFZ5NRad8yFNz5Xbec8CPJr+z/VAwHib6431F5nYF46g==", + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.916.0.tgz", + "integrity": "sha512-SXDyDvpJ1+WbotZDLJW1lqP6gYGaXfZJrgFSXIuZjHb75fKeNRgPkQX/wZDdUvCwdrscvxmtyJorp2sVYkMcvA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.894.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/core": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@smithy/property-provider": "^4.2.3", + "@smithy/shared-ini-file-loader": "^4.3.3", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -445,19 +475,19 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.895.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.895.0.tgz", - "integrity": "sha512-bZCcHUZGz+XlCaK0KEOHGHkMtlwIvnpxJvlZtSCVaBdX/IgouxaB42fxChflxSMRWF45ygdezfky4i17f6vC4w==", + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.916.0.tgz", + "integrity": "sha512-gu9D+c+U/Dp1AKBcVxYHNNoZF9uD4wjAKYCjgSN37j4tDsazwMEylbbZLuRNuxfbXtizbo4/TiaxBXDbWM7AkQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.895.0", - "@aws-sdk/core": "3.894.0", - "@aws-sdk/token-providers": "3.895.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/client-sso": "3.916.0", + "@aws-sdk/core": "3.916.0", + "@aws-sdk/token-providers": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@smithy/property-provider": "^4.2.3", + "@smithy/shared-ini-file-loader": "^4.3.3", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -465,18 +495,18 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.895.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.895.0.tgz", - "integrity": "sha512-tKbXbOp2xrL02fxKvB7ko1E4Uvyy5TF9qi5pT2MVWNnfSsBlUM80aJ6tyUPKWXdUTdAlPrU3XcwgQl/DnnRa9A==", + "version": "3.917.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.917.0.tgz", + "integrity": "sha512-pZncQhFbwW04pB0jcD5OFv3x2gAddDYCVxyJVixgyhSw7bKCYxqu6ramfq1NxyVpmm+qsw+ijwi/3cCmhUHF/A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.894.0", - "@aws-sdk/nested-clients": "3.895.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/core": "3.916.0", + "@aws-sdk/nested-clients": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@smithy/property-provider": "^4.2.3", + "@smithy/shared-ini-file-loader": "^4.3.3", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -484,15 +514,15 @@ } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.893.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.893.0.tgz", - "integrity": "sha512-qL5xYRt80ahDfj9nDYLhpCNkDinEXvjLe/Qen/Y/u12+djrR2MB4DRa6mzBCkLkdXDtf0WAoW2EZsNCfGrmOEQ==", + "version": "3.914.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.914.0.tgz", + "integrity": "sha512-7r9ToySQ15+iIgXMF/h616PcQStByylVkCshmQqcdeynD/lCn2l667ynckxW4+ql0Q+Bo/URljuhJRxVJzydNA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", + "@aws-sdk/types": "3.914.0", + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -500,14 +530,14 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.893.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.893.0.tgz", - "integrity": "sha512-ZqzMecjju5zkBquSIfVfCORI/3Mge21nUY4nWaGQy+NUXehqCGG4W7AiVpiHGOcY2cGJa7xeEkYcr2E2U9U0AA==", + "version": "3.914.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.914.0.tgz", + "integrity": "sha512-/gaW2VENS5vKvJbcE1umV4Ag3NuiVzpsANxtrqISxT3ovyro29o1RezW/Avz/6oJqjnmgz8soe9J1t65jJdiNg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/types": "3.914.0", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -515,16 +545,16 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.893.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.893.0.tgz", - "integrity": "sha512-H7Zotd9zUHQAr/wr3bcWHULYhEeoQrF54artgsoUGIf/9emv6LzY89QUccKIxYd6oHKNTrTyXm9F0ZZrzXNxlg==", + "version": "3.914.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.914.0.tgz", + "integrity": "sha512-yiAjQKs5S2JKYc+GrkvGMwkUvhepXDigEXpSJqUseR/IrqHhvGNuOxDxq+8LbDhM4ajEW81wkiBbU+Jl9G82yQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", + "@aws-sdk/types": "3.914.0", "@aws/lambda-invoke-store": "^0.0.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -532,25 +562,25 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.894.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.894.0.tgz", - "integrity": "sha512-0C3lTVdTuv5CkJ4LulpA7FmGFSKrGUKxnFZ6+qGjYjNzbdiHXfq0TyEBiDmVqDkoV2k4AT2H/m0Xw//rTkcNEQ==", + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.916.0.tgz", + "integrity": "sha512-pjmzzjkEkpJObzmTthqJPq/P13KoNFuEi/x5PISlzJtHofCNcyXeVAQ90yvY2dQ6UXHf511Rh1/ytiKy2A8M0g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.894.0", - "@aws-sdk/types": "3.893.0", + "@aws-sdk/core": "3.916.0", + "@aws-sdk/types": "3.914.0", "@aws-sdk/util-arn-parser": "3.893.0", - "@smithy/core": "^3.11.1", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/protocol-http": "^5.2.1", - "@smithy/signature-v4": "^5.2.1", - "@smithy/smithy-client": "^4.6.3", - "@smithy/types": "^4.5.0", - "@smithy/util-config-provider": "^4.1.0", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-stream": "^4.3.2", - "@smithy/util-utf8": "^4.1.0", + "@smithy/core": "^3.17.1", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/signature-v4": "^5.3.3", + "@smithy/smithy-client": "^4.9.1", + "@smithy/types": "^4.8.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.3", + "@smithy/util-stream": "^4.5.4", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -558,18 +588,18 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.895.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.895.0.tgz", - "integrity": "sha512-JUqQW2RPp4I95wZ/Im9fTiaX3DF55oJgeoiNlLdHkQZPSNNS/pT1WMWMReSvJdcfSNU3xSUaLtI+h4mQjQUDbQ==", + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.916.0.tgz", + "integrity": "sha512-mzF5AdrpQXc2SOmAoaQeHpDFsK2GE6EGcEACeNuoESluPI2uYMpuuNMYrUufdnIAIyqgKlis0NVxiahA5jG42w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.894.0", - "@aws-sdk/types": "3.893.0", - "@aws-sdk/util-endpoints": "3.895.0", - "@smithy/core": "^3.11.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", + "@aws-sdk/core": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@aws-sdk/util-endpoints": "3.916.0", + "@smithy/core": "^3.17.1", + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -577,49 +607,49 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.895.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.895.0.tgz", - "integrity": "sha512-8w1ihfYgvds6kfal/qJXQQrHRsKYh2nujSyzWMo2TMKMze9WPZA93G4mRbRtKtbSuQ66mVWePH8Cksq35ABu2Q==", + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.916.0.tgz", + "integrity": "sha512-tgg8e8AnVAer0rcgeWucFJ/uNN67TbTiDHfD+zIOPKep0Z61mrHEoeT/X8WxGIOkEn4W6nMpmS4ii8P42rNtnA==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.894.0", - "@aws-sdk/middleware-host-header": "3.893.0", - "@aws-sdk/middleware-logger": "3.893.0", - "@aws-sdk/middleware-recursion-detection": "3.893.0", - "@aws-sdk/middleware-user-agent": "3.895.0", - "@aws-sdk/region-config-resolver": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@aws-sdk/util-endpoints": "3.895.0", - "@aws-sdk/util-user-agent-browser": "3.893.0", - "@aws-sdk/util-user-agent-node": "3.895.0", - "@smithy/config-resolver": "^4.2.2", - "@smithy/core": "^3.11.1", - "@smithy/fetch-http-handler": "^5.2.1", - "@smithy/hash-node": "^4.1.1", - "@smithy/invalid-dependency": "^4.1.1", - "@smithy/middleware-content-length": "^4.1.1", - "@smithy/middleware-endpoint": "^4.2.3", - "@smithy/middleware-retry": "^4.2.4", - "@smithy/middleware-serde": "^4.1.1", - "@smithy/middleware-stack": "^4.1.1", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/node-http-handler": "^4.2.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/smithy-client": "^4.6.3", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-body-length-browser": "^4.1.0", - "@smithy/util-body-length-node": "^4.1.0", - "@smithy/util-defaults-mode-browser": "^4.1.3", - "@smithy/util-defaults-mode-node": "^4.1.3", - "@smithy/util-endpoints": "^3.1.2", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-retry": "^4.1.2", - "@smithy/util-utf8": "^4.1.0", + "@aws-sdk/core": "3.916.0", + "@aws-sdk/middleware-host-header": "3.914.0", + "@aws-sdk/middleware-logger": "3.914.0", + "@aws-sdk/middleware-recursion-detection": "3.914.0", + "@aws-sdk/middleware-user-agent": "3.916.0", + "@aws-sdk/region-config-resolver": "3.914.0", + "@aws-sdk/types": "3.914.0", + "@aws-sdk/util-endpoints": "3.916.0", + "@aws-sdk/util-user-agent-browser": "3.914.0", + "@aws-sdk/util-user-agent-node": "3.916.0", + "@smithy/config-resolver": "^4.4.0", + "@smithy/core": "^3.17.1", + "@smithy/fetch-http-handler": "^5.3.4", + "@smithy/hash-node": "^4.2.3", + "@smithy/invalid-dependency": "^4.2.3", + "@smithy/middleware-content-length": "^4.2.3", + "@smithy/middleware-endpoint": "^4.3.5", + "@smithy/middleware-retry": "^4.4.5", + "@smithy/middleware-serde": "^4.2.3", + "@smithy/middleware-stack": "^4.2.3", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/node-http-handler": "^4.4.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/smithy-client": "^4.9.1", + "@smithy/types": "^4.8.0", + "@smithy/url-parser": "^4.2.3", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.4", + "@smithy/util-defaults-mode-node": "^4.2.6", + "@smithy/util-endpoints": "^3.2.3", + "@smithy/util-middleware": "^4.2.3", + "@smithy/util-retry": "^4.2.3", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -627,17 +657,15 @@ } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.893.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.893.0.tgz", - "integrity": "sha512-/cJvh3Zsa+Of0Zbg7vl9wp/kZtdb40yk/2+XcroAMVPO9hPvmS9r/UOm6tO7FeX4TtkRFwWaQJiTZTgSdsPY+Q==", + "version": "3.914.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.914.0.tgz", + "integrity": "sha512-KlmHhRbn1qdwXUdsdrJ7S/MAkkC1jLpQ11n+XvxUUUCGAJd1gjC7AjxPZUM7ieQ2zcb8bfEzIU7al+Q3ZT0u7Q==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/types": "^4.5.0", - "@smithy/util-config-provider": "^4.1.0", - "@smithy/util-middleware": "^4.1.1", + "@aws-sdk/types": "3.914.0", + "@smithy/config-resolver": "^4.4.0", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -645,17 +673,17 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.894.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.894.0.tgz", - "integrity": "sha512-Te5b3fSbatkZrh3eYNmpOadZFKsCLNSwiolQKQeEeKHxdnqORwYXa+0ypcTHle6ukic+tFRRd9n3NuMVo9uiVg==", + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.916.0.tgz", + "integrity": "sha512-fuzUMo6xU7e0NBzBA6TQ4FUf1gqNbg4woBSvYfxRRsIfKmSMn9/elXXn4sAE5UKvlwVQmYnb6p7dpVRPyFvnQA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.894.0", - "@aws-sdk/types": "3.893.0", - "@smithy/protocol-http": "^5.2.1", - "@smithy/signature-v4": "^5.2.1", - "@smithy/types": "^4.5.0", + "@aws-sdk/middleware-sdk-s3": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@smithy/protocol-http": "^5.3.3", + "@smithy/signature-v4": "^5.3.3", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -663,18 +691,18 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.895.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.895.0.tgz", - "integrity": "sha512-vJqrEHFFGRZ3ok5T+jII00sa2DQ3HdVkTBIfM0DcrcPssqDV18VKdA767qiBdIEN/cygjdBg8Ri/cuq6ER9BeQ==", + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.916.0.tgz", + "integrity": "sha512-13GGOEgq5etbXulFCmYqhWtpcEQ6WI6U53dvXbheW0guut8fDFJZmEv7tKMTJgiybxh7JHd0rWcL9JQND8DwoQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.894.0", - "@aws-sdk/nested-clients": "3.895.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/core": "3.916.0", + "@aws-sdk/nested-clients": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@smithy/property-provider": "^4.2.3", + "@smithy/shared-ini-file-loader": "^4.3.3", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -682,13 +710,13 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.893.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", - "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", + "version": "3.914.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.914.0.tgz", + "integrity": "sha512-kQWPsRDmom4yvAfyG6L1lMmlwnTzm1XwMHOU+G5IFlsP4YEaMtXidDzW/wiivY0QFrhfCz/4TVmu0a2aPU57ug==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.5.0", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -709,16 +737,16 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.895.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.895.0.tgz", - "integrity": "sha512-MhxBvWbwxmKknuggO2NeMwOVkHOYL98pZ+1ZRI5YwckoCL3AvISMnPJgfN60ww6AIXHGpkp+HhpFdKOe8RHSEg==", + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.916.0.tgz", + "integrity": "sha512-bAgUQwvixdsiGNcuZSDAOWbyHlnPtg8G8TyHD6DTfTmKTHUW6tAn+af/ZYJPXEzXhhpwgJqi58vWnsiDhmr7NQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", - "@smithy/util-endpoints": "^3.1.2", + "@aws-sdk/types": "3.914.0", + "@smithy/types": "^4.8.0", + "@smithy/url-parser": "^4.2.3", + "@smithy/util-endpoints": "^3.2.3", "tslib": "^2.6.2" }, "engines": { @@ -739,29 +767,29 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.893.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.893.0.tgz", - "integrity": "sha512-PE9NtbDBW6Kgl1bG6A5fF3EPo168tnkj8TgMcT0sg4xYBWsBpq0bpJZRh+Jm5Bkwiw9IgTCLjEU7mR6xWaMB9w==", + "version": "3.914.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.914.0.tgz", + "integrity": "sha512-rMQUrM1ECH4kmIwlGl9UB0BtbHy6ZuKdWFrIknu8yGTRI/saAucqNTh5EI1vWBxZ0ElhK5+g7zOnUuhSmVQYUA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/types": "3.914.0", + "@smithy/types": "^4.8.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.895.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.895.0.tgz", - "integrity": "sha512-lLRC7BAFOPtJk4cZC0Q0MZBMCGF109QpGnug3L3n/2TJW02Sinz9lzA0ykBpYXe9j60LjIYSENCg+F4DZE5vxg==", + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.916.0.tgz", + "integrity": "sha512-CwfWV2ch6UdjuSV75ZU99N03seEUb31FIUrXBnwa6oONqj/xqXwrxtlUMLx6WH3OJEE4zI3zt5PjlTdGcVwf4g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.895.0", - "@aws-sdk/types": "3.893.0", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/types": "^4.5.0", + "@aws-sdk/middleware-user-agent": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -777,13 +805,13 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.894.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.894.0.tgz", - "integrity": "sha512-E6EAMc9dT1a2DOdo4zyOf3fp5+NJ2wI+mcm7RaW1baFIWDwcb99PpvWoV7YEiK7oaBDshuOEGWKUSYXdW+JYgA==", + "version": "3.914.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.914.0.tgz", + "integrity": "sha512-k75evsBD5TcIjedycYS7QXQ98AmOtbnxRJOPtCo0IwYRmy7UvqgS/gBL5SmrIqeV6FDSYRQMgdBxSMp6MLmdew==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.5.0", + "@smithy/types": "^4.8.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" }, @@ -791,25 +819,6 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", - "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "strnum": "^2.1.0" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, "node_modules/@aws/lambda-invoke-store": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.0.1.tgz", @@ -835,18 +844,18 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/runtime": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", - "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -968,6 +977,28 @@ "@csstools/css-tokenizer": "^3.0.4" } }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.14.tgz", + "integrity": "sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/@csstools/css-tokenizer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", @@ -988,20 +1019,20 @@ } }, "node_modules/@dabh/diagnostics": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", - "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", + "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", "license": "MIT", "dependencies": { - "colorspace": "1.1.x", + "@so-ric/colorspace": "^1.1.6", "enabled": "2.0.x", "kuler": "^2.0.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", - "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.6.0.tgz", + "integrity": "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA==", "license": "MIT", "optional": true, "dependencies": { @@ -1009,9 +1040,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", - "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", + "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", "cpu": [ "ppc64" ], @@ -1026,9 +1057,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", - "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz", + "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", "cpu": [ "arm" ], @@ -1043,9 +1074,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", - "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", + "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", "cpu": [ "arm64" ], @@ -1060,9 +1091,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", - "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz", + "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", "cpu": [ "x64" ], @@ -1077,9 +1108,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", - "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", + "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", "cpu": [ "arm64" ], @@ -1094,9 +1125,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", - "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", + "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", "cpu": [ "x64" ], @@ -1111,9 +1142,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", - "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", + "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", "cpu": [ "arm64" ], @@ -1128,9 +1159,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", - "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", + "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", "cpu": [ "x64" ], @@ -1145,9 +1176,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", - "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", + "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", "cpu": [ "arm" ], @@ -1162,9 +1193,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", - "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", + "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", "cpu": [ "arm64" ], @@ -1179,9 +1210,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", - "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", + "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", "cpu": [ "ia32" ], @@ -1196,9 +1227,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", - "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", + "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", "cpu": [ "loong64" ], @@ -1213,9 +1244,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", - "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", + "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", "cpu": [ "mips64el" ], @@ -1230,9 +1261,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", - "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", + "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", "cpu": [ "ppc64" ], @@ -1247,9 +1278,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", - "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", + "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", "cpu": [ "riscv64" ], @@ -1264,9 +1295,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", - "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", + "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", "cpu": [ "s390x" ], @@ -1281,9 +1312,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", - "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", + "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", "cpu": [ "x64" ], @@ -1298,9 +1329,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", - "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", + "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", "cpu": [ "arm64" ], @@ -1315,9 +1346,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", - "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", + "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", "cpu": [ "x64" ], @@ -1332,9 +1363,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", - "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", + "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", "cpu": [ "arm64" ], @@ -1349,9 +1380,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", - "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", + "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", "cpu": [ "x64" ], @@ -1366,9 +1397,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", - "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", + "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", "cpu": [ "arm64" ], @@ -1383,9 +1414,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", - "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", + "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", "cpu": [ "x64" ], @@ -1400,9 +1431,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", - "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", + "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", "cpu": [ "arm64" ], @@ -1417,9 +1448,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", - "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", + "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", "cpu": [ "ia32" ], @@ -1434,9 +1465,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", - "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", + "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", "cpu": [ "x64" ], @@ -1451,9 +1482,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.8.0.tgz", - "integrity": "sha512-MJQFqrZgcW0UNYLGOuQpey/oTN59vyWwplvCGZztn1cKz9agZPPYpJB7h2OMmuu7VLqkvEjN8feFZJmxNF9D+Q==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, "license": "MIT", "dependencies": { @@ -1483,9 +1514,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { @@ -1493,13 +1524,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.6", + "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -1508,19 +1539,22 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", - "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.1.tgz", + "integrity": "sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.16.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", + "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1568,9 +1602,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.34.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz", - "integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==", + "version": "9.38.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz", + "integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==", "dev": true, "license": "MIT", "engines": { @@ -1581,9 +1615,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1591,13 +1625,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz", + "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.2", + "@eslint/core": "^0.16.0", "levn": "^0.4.1" }, "engines": { @@ -1605,18 +1639,36 @@ } }, "node_modules/@grpc/grpc-js": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz", - "integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.0.tgz", + "integrity": "sha512-N8Jx6PaYzcTRNzirReJCtADVoq4z7+1KQ4E70jTg/koQiMoUSN1kbNjPOqpPbhMFhfU1/l7ixspPl8dNY+FoUg==", "license": "Apache-2.0", "dependencies": { - "@grpc/proto-loader": "^0.7.13", + "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" }, "engines": { "node": ">=12.10.0" } }, + "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", + "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.3", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@grpc/proto-loader": { "version": "0.7.15", "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", @@ -2064,9 +2116,9 @@ } }, "node_modules/@ioredis/commands": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.3.1.tgz", - "integrity": "sha512-bYtU8avhGIcje3IhvF9aSjsa5URMZBHnwKtOvXsT4sfYy9gppW11gLPT/9oNqlJZD47yPKveQFTAFWpHjKvUoQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.4.0.tgz", + "integrity": "sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==", "license": "MIT" }, "node_modules/@isaacs/cliui": { @@ -2114,9 +2166,9 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.30", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", - "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -2135,9 +2187,9 @@ } }, "node_modules/@mongodb-js/saslprep": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz", - "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz", + "integrity": "sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==", "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" @@ -2344,9 +2396,9 @@ "license": "BSD-3-Clause" }, "node_modules/@sindresorhus/is": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.0.2.tgz", - "integrity": "sha512-d9xRovfKNz1SKieM0qJdO+PQonjnnIfSNWfHYnBSJ9hkjm0ZPw6HlxscDXYstp3z+7V2GOFHc+J0CYrYTjqCJw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.1.0.tgz", + "integrity": "sha512-7F/yz2IphV39hiS2zB4QYVkivrptHHh0K8qJJd9HhuWSdvf8AN7NpebW3CcDZDBQsUPMoDKWsY2WWgW7bqOcfA==", "license": "MIT", "engines": { "node": ">=18" @@ -2404,13 +2456,13 @@ "license": "(Unlicense OR Apache-2.0)" }, "node_modules/@smithy/abort-controller": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.1.1.tgz", - "integrity": "sha512-vkzula+IwRvPR6oKQhMYioM3A/oX/lFCZiwuxkQbRhqJS2S4YRY2k7k/SyR2jMf3607HLtbEwlRxi0ndXHMjRg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.3.tgz", + "integrity": "sha512-xWL9Mf8b7tIFuAlpjKtRPnHrR8XVrwTj5NPYO/QwZPtc0SDLsPxb56V5tzi5yspSMytISHybifez+4jlrx0vkQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.5.0", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -2418,16 +2470,17 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.2.2.tgz", - "integrity": "sha512-IT6MatgBWagLybZl1xQcURXRICvqz1z3APSCAI9IqdvfCkrA7RaQIEfgC6G/KvfxnDfQUDqFV+ZlixcuFznGBQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.0.tgz", + "integrity": "sha512-Kkmz3Mup2PGp/HNJxhCWkLNdlajJORLSjwkcfrj0E7nu6STAEdcMR1ir5P9/xOmncx8xXfru0fbUYLlZog/cFg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.2.2", - "@smithy/types": "^4.5.0", - "@smithy/util-config-provider": "^4.1.0", - "@smithy/util-middleware": "^4.1.1", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/types": "^4.8.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.3", + "@smithy/util-middleware": "^4.2.3", "tslib": "^2.6.2" }, "engines": { @@ -2435,21 +2488,21 @@ } }, "node_modules/@smithy/core": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.12.0.tgz", - "integrity": "sha512-zJeAgogZfbwlPGL93y4Z/XNeIN37YCreRUd6YMIRvaq+6RnBK8PPYYIQ85Is/GglPh3kNImD5riDCXbVSDpCiQ==", + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.17.1.tgz", + "integrity": "sha512-V4Qc2CIb5McABYfaGiIYLTmo/vwNIK7WXI5aGveBd9UcdhbOMwcvIMxIw/DJj1S9QgOMa/7FBkarMdIC0EOTEQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.1.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-body-length-browser": "^4.1.0", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-stream": "^4.3.2", - "@smithy/util-utf8": "^4.1.0", - "@smithy/uuid": "^1.0.0", + "@smithy/middleware-serde": "^4.2.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.3", + "@smithy/util-stream": "^4.5.4", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" }, "engines": { @@ -2457,16 +2510,16 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.1.2.tgz", - "integrity": "sha512-JlYNq8TShnqCLg0h+afqe2wLAwZpuoSgOyzhYvTgbiKBWRov+uUve+vrZEQO6lkdLOWPh7gK5dtb9dS+KGendg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.3.tgz", + "integrity": "sha512-hA1MQ/WAHly4SYltJKitEsIDVsNmXcQfYBRv2e+q04fnqtAX5qXaybxy/fhUeAMCnQIdAjaGDb04fMHQefWRhw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.2.2", - "@smithy/property-provider": "^4.1.1", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/property-provider": "^4.2.3", + "@smithy/types": "^4.8.0", + "@smithy/url-parser": "^4.2.3", "tslib": "^2.6.2" }, "engines": { @@ -2474,16 +2527,16 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.2.1.tgz", - "integrity": "sha512-5/3wxKNtV3wO/hk1is+CZUhL8a1yy/U+9u9LKQ9kZTkMsHaQjJhc3stFfiujtMnkITjzWfndGA2f7g9Uh9vKng==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.4.tgz", + "integrity": "sha512-bwigPylvivpRLCm+YK9I5wRIYjFESSVwl8JQ1vVx/XhCw0PtCi558NwTnT2DaVCl5pYlImGuQTSwMsZ+pIavRw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.2.1", - "@smithy/querystring-builder": "^4.1.1", - "@smithy/types": "^4.5.0", - "@smithy/util-base64": "^4.1.0", + "@smithy/protocol-http": "^5.3.3", + "@smithy/querystring-builder": "^4.2.3", + "@smithy/types": "^4.8.0", + "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" }, "engines": { @@ -2491,15 +2544,15 @@ } }, "node_modules/@smithy/hash-node": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.1.1.tgz", - "integrity": "sha512-H9DIU9WBLhYrvPs9v4sYvnZ1PiAI0oc8CgNQUJ1rpN3pP7QADbTOUjchI2FB764Ub0DstH5xbTqcMJu1pnVqxA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.3.tgz", + "integrity": "sha512-6+NOdZDbfuU6s1ISp3UOk5Rg953RJ2aBLNLLBEcamLjHAg1Po9Ha7QIB5ZWhdRUVuOUrT8BVFR+O2KIPmw027g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.5.0", - "@smithy/util-buffer-from": "^4.1.0", - "@smithy/util-utf8": "^4.1.0", + "@smithy/types": "^4.8.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -2507,13 +2560,13 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.1.1.tgz", - "integrity": "sha512-1AqLyFlfrrDkyES8uhINRlJXmHA2FkG+3DY8X+rmLSqmFwk3DJnvhyGzyByPyewh2jbmV+TYQBEfngQax8IFGg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.3.tgz", + "integrity": "sha512-Cc9W5DwDuebXEDMpOpl4iERo8I0KFjTnomK2RMdhhR87GwrSmUmwMxS4P5JdRf+LsjOdIqumcerwRgYMr/tZ9Q==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.5.0", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -2521,9 +2574,9 @@ } }, "node_modules/@smithy/is-array-buffer": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.1.0.tgz", - "integrity": "sha512-ePTYUOV54wMogio+he4pBybe8fwg4sDvEVDBU8ZlHOZXbXK3/C0XfJgUCu6qAZcawv05ZhZzODGUerFBPsPUDQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2534,14 +2587,14 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.1.1.tgz", - "integrity": "sha512-9wlfBBgTsRvC2JxLJxv4xDGNBrZuio3AgSl0lSFX7fneW2cGskXTYpFxCdRYD2+5yzmsiTuaAJD1Wp7gWt9y9w==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.3.tgz", + "integrity": "sha512-/atXLsT88GwKtfp5Jr0Ks1CSa4+lB+IgRnkNrrYP0h1wL4swHNb0YONEvTceNKNdZGJsye+W2HH8W7olbcPUeA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -2549,19 +2602,19 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.2.4.tgz", - "integrity": "sha512-FZ4hzupOmthm8Q8ujYrd0I+/MHwVMuSTdkDtIQE0xVuvJt9pLT6Q+b0p4/t+slDyrpcf+Wj7SN+ZqT5OryaaZg==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.5.tgz", + "integrity": "sha512-SIzKVTvEudFWJbxAaq7f2GvP3jh2FHDpIFI6/VAf4FOWGFZy0vnYMPSRj8PGYI8Hjt29mvmwSRgKuO3bK4ixDw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.12.0", - "@smithy/middleware-serde": "^4.1.1", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", - "@smithy/util-middleware": "^4.1.1", + "@smithy/core": "^3.17.1", + "@smithy/middleware-serde": "^4.2.3", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/shared-ini-file-loader": "^4.3.3", + "@smithy/types": "^4.8.0", + "@smithy/url-parser": "^4.2.3", + "@smithy/util-middleware": "^4.2.3", "tslib": "^2.6.2" }, "engines": { @@ -2569,20 +2622,20 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.3.0.tgz", - "integrity": "sha512-qhEX9745fAxZvtLM4bQJAVC98elWjiMO2OiHl1s6p7hUzS4QfZO1gXUYNwEK8m0J6NoCD5W52ggWxbIDHI0XSg==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.5.tgz", + "integrity": "sha512-DCaXbQqcZ4tONMvvdz+zccDE21sLcbwWoNqzPLFlZaxt1lDtOE2tlVpRSwcTOJrjJSUThdgEYn7HrX5oLGlK9A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.2.2", - "@smithy/protocol-http": "^5.2.1", - "@smithy/service-error-classification": "^4.1.2", - "@smithy/smithy-client": "^4.6.4", - "@smithy/types": "^4.5.0", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-retry": "^4.1.2", - "@smithy/uuid": "^1.0.0", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/service-error-classification": "^4.2.3", + "@smithy/smithy-client": "^4.9.1", + "@smithy/types": "^4.8.0", + "@smithy/util-middleware": "^4.2.3", + "@smithy/util-retry": "^4.2.3", + "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" }, "engines": { @@ -2590,14 +2643,14 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.1.1.tgz", - "integrity": "sha512-lh48uQdbCoj619kRouev5XbWhCwRKLmphAif16c4J6JgJ4uXjub1PI6RL38d3BLliUvSso6klyB/LTNpWSNIyg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.3.tgz", + "integrity": "sha512-8g4NuUINpYccxiCXM5s1/V+uLtts8NcX4+sPEbvYQDZk4XoJfDpq5y2FQxfmUL89syoldpzNzA0R9nhzdtdKnQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -2605,13 +2658,13 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.1.1.tgz", - "integrity": "sha512-ygRnniqNcDhHzs6QAPIdia26M7e7z9gpkIMUe/pK0RsrQ7i5MblwxY8078/QCnGq6AmlUUWgljK2HlelsKIb/A==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.3.tgz", + "integrity": "sha512-iGuOJkH71faPNgOj/gWuEGS6xvQashpLwWB1HjHq1lNNiVfbiJLpZVbhddPuDbx9l4Cgl0vPLq5ltRfSaHfspA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.5.0", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -2619,15 +2672,15 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.2.2.tgz", - "integrity": "sha512-SYGTKyPvyCfEzIN5rD8q/bYaOPZprYUPD2f5g9M7OjaYupWOoQFYJ5ho+0wvxIRf471i2SR4GoiZ2r94Jq9h6A==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.3.tgz", + "integrity": "sha512-NzI1eBpBSViOav8NVy1fqOlSfkLgkUjUTlohUSgAEhHaFWA3XJiLditvavIP7OpvTjDp5u2LhtlBhkBlEisMwA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@smithy/property-provider": "^4.2.3", + "@smithy/shared-ini-file-loader": "^4.3.3", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -2635,16 +2688,16 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.2.1.tgz", - "integrity": "sha512-REyybygHlxo3TJICPF89N2pMQSf+p+tBJqpVe1+77Cfi9HBPReNjTgtZ1Vg73exq24vkqJskKDpfF74reXjxfw==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.3.tgz", + "integrity": "sha512-MAwltrDB0lZB/H6/2M5PIsISSwdI5yIh6DaBB9r0Flo9nx3y0dzl/qTMJPd7tJvPdsx6Ks/cwVzheGNYzXyNbQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.1.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/querystring-builder": "^4.1.1", - "@smithy/types": "^4.5.0", + "@smithy/abort-controller": "^4.2.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/querystring-builder": "^4.2.3", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -2652,13 +2705,13 @@ } }, "node_modules/@smithy/property-provider": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.1.1.tgz", - "integrity": "sha512-gm3ZS7DHxUbzC2wr8MUCsAabyiXY0gaj3ROWnhSx/9sPMc6eYLMM4rX81w1zsMaObj2Lq3PZtNCC1J6lpEY7zg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.3.tgz", + "integrity": "sha512-+1EZ+Y+njiefCohjlhyOcy1UNYjT+1PwGFHCxA/gYctjg3DQWAU19WigOXAco/Ql8hZokNehpzLd0/+3uCreqQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.5.0", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -2666,13 +2719,13 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.2.1.tgz", - "integrity": "sha512-T8SlkLYCwfT/6m33SIU/JOVGNwoelkrvGjFKDSDtVvAXj/9gOT78JVJEas5a+ETjOu4SVvpCstKgd0PxSu/aHw==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.3.tgz", + "integrity": "sha512-Mn7f/1aN2/jecywDcRDvWWWJF4uwg/A0XjFMJtj72DsgHTByfjRltSqcT9NyE9RTdBSN6X1RSXrhn/YWQl8xlw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.5.0", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -2680,14 +2733,14 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.1.1.tgz", - "integrity": "sha512-J9b55bfimP4z/Jg1gNo+AT84hr90p716/nvxDkPGCD4W70MPms0h8KF50RDRgBGZeL83/u59DWNqJv6tEP/DHA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.3.tgz", + "integrity": "sha512-LOVCGCmwMahYUM/P0YnU/AlDQFjcu+gWbFJooC417QRB/lDJlWSn8qmPSDp+s4YVAHOgtgbNG4sR+SxF/VOcJQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.5.0", - "@smithy/util-uri-escape": "^4.1.0", + "@smithy/types": "^4.8.0", + "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -2695,13 +2748,13 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.1.1.tgz", - "integrity": "sha512-63TEp92YFz0oQ7Pj9IuI3IgnprP92LrZtRAkE3c6wLWJxfy/yOPRt39IOKerVr0JS770olzl0kGafXlAXZ1vng==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.3.tgz", + "integrity": "sha512-cYlSNHcTAX/wc1rpblli3aUlLMGgKZ/Oqn8hhjFASXMCXjIqeuQBei0cnq2JR8t4RtU9FpG6uyl6PxyArTiwKA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.5.0", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -2709,26 +2762,26 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.1.2.tgz", - "integrity": "sha512-Kqd8wyfmBWHZNppZSMfrQFpc3M9Y/kjyN8n8P4DqJJtuwgK1H914R471HTw7+RL+T7+kI1f1gOnL7Vb5z9+NgQ==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.3.tgz", + "integrity": "sha512-NkxsAxFWwsPsQiwFG2MzJ/T7uIR6AQNh1SzcxSUnmmIqIQMlLRQDKhc17M7IYjiuBXhrQRjQTo3CxX+DobS93g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.5.0" + "@smithy/types": "^4.8.0" }, "engines": { "node": ">=18.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.2.0.tgz", - "integrity": "sha512-OQTfmIEp2LLuWdxa8nEEPhZmiOREO6bcB6pjs0AySf4yiZhl6kMOfqmcwcY8BaBPX+0Tb+tG7/Ia/6mwpoZ7Pw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.3.3.tgz", + "integrity": "sha512-9f9Ixej0hFhroOK2TxZfUUDR13WVa8tQzhSzPDgXe5jGL3KmaM9s8XN7RQwqtEypI82q9KHnKS71CJ+q/1xLtQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.5.0", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -2736,19 +2789,19 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.2.1.tgz", - "integrity": "sha512-M9rZhWQLjlQVCCR37cSjHfhriGRN+FQ8UfgrYNufv66TJgk+acaggShl3KS5U/ssxivvZLlnj7QH2CUOKlxPyA==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.3.tgz", + "integrity": "sha512-CmSlUy+eEYbIEYN5N3vvQTRfqt0lJlQkaQUIf+oizu7BbDut0pozfDjBGecfcfWf7c62Yis4JIEgqQ/TCfodaA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.1.0", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", - "@smithy/util-hex-encoding": "^4.1.0", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-uri-escape": "^4.1.0", - "@smithy/util-utf8": "^4.1.0", + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.3", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -2756,18 +2809,18 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.6.4.tgz", - "integrity": "sha512-qL7O3VDyfzCSN9r+sdbQXGhaHtrfSJL30En6Jboj0I3bobf2g1/T0eP2L4qxqrEW26gWhJ4THI4ElVVLjYyBHg==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.1.tgz", + "integrity": "sha512-Ngb95ryR5A9xqvQFT5mAmYkCwbXvoLavLFwmi7zVg/IowFPCfiqRfkOKnbc/ZRL8ZKJ4f+Tp6kSu6wjDQb8L/g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.12.0", - "@smithy/middleware-endpoint": "^4.2.4", - "@smithy/middleware-stack": "^4.1.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", - "@smithy/util-stream": "^4.3.2", + "@smithy/core": "^3.17.1", + "@smithy/middleware-endpoint": "^4.3.5", + "@smithy/middleware-stack": "^4.2.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", + "@smithy/util-stream": "^4.5.4", "tslib": "^2.6.2" }, "engines": { @@ -2775,9 +2828,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.5.0.tgz", - "integrity": "sha512-RkUpIOsVlAwUIZXO1dsz8Zm+N72LClFfsNqf173catVlvRZiwPy0x2u0JLEA4byreOPKDZPGjmPDylMoP8ZJRg==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.8.0.tgz", + "integrity": "sha512-QpELEHLO8SsQVtqP+MkEgCYTFW0pleGozfs3cZ183ZBj9z3VC1CX1/wtFMK64p+5bhtZo41SeLK1rBRtd25nHQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2788,14 +2841,14 @@ } }, "node_modules/@smithy/url-parser": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.1.1.tgz", - "integrity": "sha512-bx32FUpkhcaKlEoOMbScvc93isaSiRM75pQ5IgIBaMkT7qMlIibpPRONyx/0CvrXHzJLpOn/u6YiDX2hcvs7Dg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.3.tgz", + "integrity": "sha512-I066AigYvY3d9VlU3zG9XzZg1yT10aNqvCaBTw9EPgu5GrsEl1aUkcMvhkIXascYH1A8W0LQo3B1Kr1cJNcQEw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.1.1", - "@smithy/types": "^4.5.0", + "@smithy/querystring-parser": "^4.2.3", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -2803,14 +2856,14 @@ } }, "node_modules/@smithy/util-base64": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.1.0.tgz", - "integrity": "sha512-RUGd4wNb8GeW7xk+AY5ghGnIwM96V0l2uzvs/uVHf+tIuVX2WSvynk5CxNoBCsM2rQRSZElAo9rt3G5mJ/gktQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.1.0", - "@smithy/util-utf8": "^4.1.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -2818,9 +2871,9 @@ } }, "node_modules/@smithy/util-body-length-browser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.1.0.tgz", - "integrity": "sha512-V2E2Iez+bo6bUMOTENPr6eEmepdY8Hbs+Uc1vkDKgKNA/brTJqOW/ai3JO1BGj9GbCeLqw90pbbH7HFQyFotGQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2831,9 +2884,9 @@ } }, "node_modules/@smithy/util-body-length-node": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.1.0.tgz", - "integrity": "sha512-BOI5dYjheZdgR9XiEM3HJcEMCXSoqbzu7CzIgYrx0UtmvtC3tC2iDGpJLsSRFffUpy8ymsg2ARMP5fR8mtuUQQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2844,13 +2897,13 @@ } }, "node_modules/@smithy/util-buffer-from": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.1.0.tgz", - "integrity": "sha512-N6yXcjfe/E+xKEccWEKzK6M+crMrlwaCepKja0pNnlSkm6SjAeLKKA++er5Ba0I17gvKfN/ThV+ZOx/CntKTVw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.1.0", + "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -2858,9 +2911,9 @@ } }, "node_modules/@smithy/util-config-provider": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.1.0.tgz", - "integrity": "sha512-swXz2vMjrP1ZusZWVTB/ai5gK+J8U0BWvP10v9fpcFvg+Xi/87LHvHfst2IgCs1i0v4qFZfGwCmeD/KNCdJZbQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2871,16 +2924,15 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.1.4.tgz", - "integrity": "sha512-mLDJ1s4eA3vwOGaQOEPlg5LB4LdZUUMpB5UMOMofeGhWqiS7WR7dTpLiNi9zVn+YziKUd3Af5NLfxDs7NJqmIw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.4.tgz", + "integrity": "sha512-qI5PJSW52rnutos8Bln8nwQZRpyoSRN6k2ajyoUHNMUzmWqHnOJCnDELJuV6m5PML0VkHI+XcXzdB+6awiqYUw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.1.1", - "@smithy/smithy-client": "^4.6.4", - "@smithy/types": "^4.5.0", - "bowser": "^2.11.0", + "@smithy/property-provider": "^4.2.3", + "@smithy/smithy-client": "^4.9.1", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -2888,18 +2940,18 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.1.4.tgz", - "integrity": "sha512-pjX2iMTcOASaSanAd7bu6i3fcMMezr3NTr8Rh64etB0uHRZi+Aw86DoCxPESjY4UTIuA06hhqtTtw95o//imYA==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.6.tgz", + "integrity": "sha512-c6M/ceBTm31YdcFpgfgQAJaw3KbaLuRKnAz91iMWFLSrgxRpYm03c3bu5cpYojNMfkV9arCUelelKA7XQT36SQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.2.2", - "@smithy/credential-provider-imds": "^4.1.2", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/property-provider": "^4.1.1", - "@smithy/smithy-client": "^4.6.4", - "@smithy/types": "^4.5.0", + "@smithy/config-resolver": "^4.4.0", + "@smithy/credential-provider-imds": "^4.2.3", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/property-provider": "^4.2.3", + "@smithy/smithy-client": "^4.9.1", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -2907,14 +2959,14 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.1.2.tgz", - "integrity": "sha512-+AJsaaEGb5ySvf1SKMRrPZdYHRYSzMkCoK16jWnIMpREAnflVspMIDeCVSZJuj+5muZfgGpNpijE3mUNtjv01Q==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.3.tgz", + "integrity": "sha512-aCfxUOVv0CzBIkU10TubdgKSx5uRvzH064kaiPEWfNIvKOtNpu642P4FP1hgOFkjQIkDObrfIDnKMKkeyrejvQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.2.2", - "@smithy/types": "^4.5.0", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -2922,9 +2974,9 @@ } }, "node_modules/@smithy/util-hex-encoding": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.1.0.tgz", - "integrity": "sha512-1LcueNN5GYC4tr8mo14yVYbh/Ur8jHhWOxniZXii+1+ePiIbsLZ5fEI0QQGtbRRP5mOhmooos+rLmVASGGoq5w==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2935,13 +2987,13 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.1.1.tgz", - "integrity": "sha512-CGmZ72mL29VMfESz7S6dekqzCh8ZISj3B+w0g1hZFXaOjGTVaSqfAEFAq8EGp8fUL+Q2l8aqNmt8U1tglTikeg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.3.tgz", + "integrity": "sha512-v5ObKlSe8PWUHCqEiX2fy1gNv6goiw6E5I/PN2aXg3Fb/hse0xeaAnSpXDiWl7x6LamVKq7senB+m5LOYHUAHw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.5.0", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -2949,14 +3001,14 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.1.2.tgz", - "integrity": "sha512-NCgr1d0/EdeP6U5PSZ9Uv5SMR5XRRYoVr1kRVtKZxWL3tixEL3UatrPIMFZSKwHlCcp2zPLDvMubVDULRqeunA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.3.tgz", + "integrity": "sha512-lLPWnakjC0q9z+OtiXk+9RPQiYPNAovt2IXD3CP4LkOnd9NpUsxOjMx1SnoUVB7Orb7fZp67cQMtTBKMFDvOGg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.1.2", - "@smithy/types": "^4.5.0", + "@smithy/service-error-classification": "^4.2.3", + "@smithy/types": "^4.8.0", "tslib": "^2.6.2" }, "engines": { @@ -2964,19 +3016,19 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.3.2.tgz", - "integrity": "sha512-Ka+FA2UCC/Q1dEqUanCdpqwxOFdf5Dg2VXtPtB1qxLcSGh5C1HdzklIt18xL504Wiy9nNUKwDMRTVCbKGoK69g==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.4.tgz", + "integrity": "sha512-+qDxSkiErejw1BAIXUFBSfM5xh3arbz1MmxlbMCKanDDZtVEQ7PSKW9FQS0Vud1eI/kYn0oCTVKyNzRlq+9MUw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.2.1", - "@smithy/node-http-handler": "^4.2.1", - "@smithy/types": "^4.5.0", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-buffer-from": "^4.1.0", - "@smithy/util-hex-encoding": "^4.1.0", - "@smithy/util-utf8": "^4.1.0", + "@smithy/fetch-http-handler": "^5.3.4", + "@smithy/node-http-handler": "^4.4.3", + "@smithy/types": "^4.8.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -2984,9 +3036,9 @@ } }, "node_modules/@smithy/util-uri-escape": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.1.0.tgz", - "integrity": "sha512-b0EFQkq35K5NHUYxU72JuoheM6+pytEVUGlTwiFxWFpmddA+Bpz3LgsPRIpBk8lnPE47yT7AF2Egc3jVnKLuPg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2997,13 +3049,13 @@ } }, "node_modules/@smithy/util-utf8": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.1.0.tgz", - "integrity": "sha512-mEu1/UIXAdNYuBcyEPbjScKi/+MQVXNIuY/7Cm5XLIWe319kDrT5SizBE95jqtmEXoDbGoZxKLCMttdZdqTZKQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.1.0", + "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -3011,9 +3063,9 @@ } }, "node_modules/@smithy/uuid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.0.0.tgz", - "integrity": "sha512-OlA/yZHh0ekYFnbUkmYBDQPE6fGfdrvgz39ktp8Xf+FA6BfxLejPTMDOG0Nfk5/rDySAz1dRbFf24zaAFYVXlQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", + "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3023,6 +3075,62 @@ "node": ">=18.0.0" } }, + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "license": "MIT", + "dependencies": { + "color": "^5.0.2", + "text-hex": "1.0.x" + } + }, + "node_modules/@so-ric/colorspace/node_modules/color": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.2.tgz", + "integrity": "sha512-e2hz5BzbUPcYlIRHo8ieAhYgoajrJr+hWoceg6E345TPsATMUKqDgzt8fSXZJJbxfpiPzkWyphz8yn8At7q3fA==", + "license": "MIT", + "dependencies": { + "color-convert": "^3.0.1", + "color-string": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@so-ric/colorspace/node_modules/color-convert": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.2.tgz", + "integrity": "sha512-UNqkvCDXstVck3kdowtOTWROIJQwafjOfXSmddoDrXo4cewMKmusCeF22Q24zvjR8nwWib/3S/dfyzPItPEiJg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=14.6" + } + }, + "node_modules/@so-ric/colorspace/node_modules/color-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", + "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/@so-ric/colorspace/node_modules/color-string": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.2.tgz", + "integrity": "sha512-RxmjYxbWemV9gKu4zPgiZagUxbH3RQpEIO77XoSSX0ivgABDZ+h8Zuash/EMFLTI4N9QgFPOJ6JQpPZKFxa+dA==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@szmarczak/http-timer": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", @@ -3035,17 +3143,6 @@ "node": ">=14.16" } }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "license": "ISC", - "optional": true, - "peer": true, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", @@ -3087,9 +3184,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", - "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", + "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", "dev": true, "license": "MIT", "dependencies": { @@ -3152,12 +3249,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", - "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", + "version": "24.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz", + "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", "license": "MIT", "dependencies": { - "undici-types": "~7.10.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/nodemailer": { @@ -3199,26 +3296,36 @@ "license": "MIT" }, "node_modules/@types/send": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/mime": "^1", "@types/node": "*" } }, "node_modules/@types/serve-static": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", - "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", "@types/node": "*", - "@types/send": "*" + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" } }, "node_modules/@types/triple-beam": { @@ -3330,9 +3437,9 @@ } }, "node_modules/ansi-regex": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", - "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "license": "MIT", "engines": { "node": ">=12" @@ -3425,9 +3532,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", - "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.0.tgz", + "integrity": "sha512-zt40Pz4zcRXra9CVV31KeyofwiNvAbJ5B6YPz9pMJ+yOSLikvPT4Yi5LjfgjRa9CawVYBaD1JQzIVcIvBejKeA==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -3488,6 +3595,15 @@ ], "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.20.tgz", + "integrity": "sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -3506,6 +3622,15 @@ "bcrypt": "bin/bcrypt" } }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -3612,9 +3737,9 @@ "license": "ISC" }, "node_modules/browserslist": { - "version": "4.25.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.4.tgz", - "integrity": "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", + "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", "funding": [ { "type": "opencollective", @@ -3631,10 +3756,11 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001737", - "electron-to-chromium": "^1.5.211", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.8.19", + "caniuse-lite": "^1.0.30001751", + "electron-to-chromium": "^1.5.238", + "node-releases": "^2.0.26", + "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" @@ -3857,9 +3983,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001739", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz", - "integrity": "sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==", + "version": "1.0.30001751", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz", + "integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==", "funding": [ { "type": "opencollective", @@ -4128,41 +4254,6 @@ "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", "license": "MIT" }, - "node_modules/colorspace": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", - "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", - "license": "MIT", - "dependencies": { - "color": "^3.1.3", - "text-hex": "1.0.x" - } - }, - "node_modules/colorspace/node_modules/color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" - } - }, - "node_modules/colorspace/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/colorspace/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT" - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -4429,9 +4520,9 @@ } }, "node_modules/css-declaration-sorter": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", - "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.0.tgz", + "integrity": "sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ==", "license": "ISC", "engines": { "node": "^14 || ^16 || >=18" @@ -4457,14 +4548,12 @@ } }, "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "mdn-data": "2.0.30", + "mdn-data": "2.12.2", "source-map-js": "^1.0.1" }, "engines": { @@ -4790,9 +4879,9 @@ } }, "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", "engines": { "node": ">=8" @@ -4902,9 +4991,9 @@ } }, "node_modules/dompurify": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", - "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz", + "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -4972,9 +5061,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.213", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.213.tgz", - "integrity": "sha512-xr9eRzSLNa4neDO0xVFrkXu3vyIzG4Ay08dApecw42Z1NbmCt+keEpXdvlYGVe0wtvY5dhW0Ay0lY0IOfsCg0Q==", + "version": "1.5.240", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.240.tgz", + "integrity": "sha512-OBwbZjWgrCOH+g6uJsA2/7Twpas2OlepS9uvByJjR2datRDuKGYeD+nP8lBBks2qnB7bGJNHDUx7c/YLaT3QMQ==", "license": "ISC" }, "node_modules/emitter-component": { @@ -5062,9 +5151,9 @@ } }, "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -5116,9 +5205,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", - "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", + "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -5129,32 +5218,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.10", - "@esbuild/android-arm": "0.25.10", - "@esbuild/android-arm64": "0.25.10", - "@esbuild/android-x64": "0.25.10", - "@esbuild/darwin-arm64": "0.25.10", - "@esbuild/darwin-x64": "0.25.10", - "@esbuild/freebsd-arm64": "0.25.10", - "@esbuild/freebsd-x64": "0.25.10", - "@esbuild/linux-arm": "0.25.10", - "@esbuild/linux-arm64": "0.25.10", - "@esbuild/linux-ia32": "0.25.10", - "@esbuild/linux-loong64": "0.25.10", - "@esbuild/linux-mips64el": "0.25.10", - "@esbuild/linux-ppc64": "0.25.10", - "@esbuild/linux-riscv64": "0.25.10", - "@esbuild/linux-s390x": "0.25.10", - "@esbuild/linux-x64": "0.25.10", - "@esbuild/netbsd-arm64": "0.25.10", - "@esbuild/netbsd-x64": "0.25.10", - "@esbuild/openbsd-arm64": "0.25.10", - "@esbuild/openbsd-x64": "0.25.10", - "@esbuild/openharmony-arm64": "0.25.10", - "@esbuild/sunos-x64": "0.25.10", - "@esbuild/win32-arm64": "0.25.10", - "@esbuild/win32-ia32": "0.25.10", - "@esbuild/win32-x64": "0.25.10" + "@esbuild/aix-ppc64": "0.25.11", + "@esbuild/android-arm": "0.25.11", + "@esbuild/android-arm64": "0.25.11", + "@esbuild/android-x64": "0.25.11", + "@esbuild/darwin-arm64": "0.25.11", + "@esbuild/darwin-x64": "0.25.11", + "@esbuild/freebsd-arm64": "0.25.11", + "@esbuild/freebsd-x64": "0.25.11", + "@esbuild/linux-arm": "0.25.11", + "@esbuild/linux-arm64": "0.25.11", + "@esbuild/linux-ia32": "0.25.11", + "@esbuild/linux-loong64": "0.25.11", + "@esbuild/linux-mips64el": "0.25.11", + "@esbuild/linux-ppc64": "0.25.11", + "@esbuild/linux-riscv64": "0.25.11", + "@esbuild/linux-s390x": "0.25.11", + "@esbuild/linux-x64": "0.25.11", + "@esbuild/netbsd-arm64": "0.25.11", + "@esbuild/netbsd-x64": "0.25.11", + "@esbuild/openbsd-arm64": "0.25.11", + "@esbuild/openbsd-x64": "0.25.11", + "@esbuild/openharmony-arm64": "0.25.11", + "@esbuild/sunos-x64": "0.25.11", + "@esbuild/win32-arm64": "0.25.11", + "@esbuild/win32-ia32": "0.25.11", + "@esbuild/win32-x64": "0.25.11" } }, "node_modules/escalade": { @@ -5198,25 +5287,24 @@ } }, "node_modules/eslint": { - "version": "9.34.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.34.0.tgz", - "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==", + "version": "9.38.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz", + "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.1", - "@eslint/core": "^0.15.2", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.1", + "@eslint/core": "^0.16.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.34.0", - "@eslint/plugin-kit": "^0.3.5", + "@eslint/js": "9.38.0", + "@eslint/plugin-kit": "^0.4.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", @@ -5570,9 +5658,9 @@ "license": "MIT" }, "node_modules/fast-xml-parser": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.3.tgz", - "integrity": "sha512-OdCYfRqfpuLUFonTNjvd30rCBZUneHpSQkCqfaeWQ9qrKcl6XlWeDBNVwGb+INAIxRshuN2jF+BE0L6gbBO2mw==", + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", "funding": [ { "type": "github", @@ -5849,19 +5937,19 @@ } }, "node_modules/gamedig": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/gamedig/-/gamedig-5.3.1.tgz", - "integrity": "sha512-RM/eCR8bAKEX+5dA9sxg4D6oQjS5t0MPsIxGquerBLSIu0f1hAVaDGC58Lp1srYwF6A1C2wYY2p9PzOrPTKf4Q==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/gamedig/-/gamedig-5.3.2.tgz", + "integrity": "sha512-R2b1LwjW783PZsHRl9M8R06UkvJwXmJ6PDKk48UukJQ9ktiwrCeAk90MAZx6nF3oA444uf7r5eHjfaYbNoSV+Q==", "license": "MIT", "dependencies": { - "fast-xml-parser": "5.2.3", + "fast-xml-parser": "5.2.5", "gbxremote": "0.2.1", "got": "13.0.0", - "iconv-lite": "0.6.3", + "iconv-lite": "0.7.0", "long": "5.3.2", "minimist": "1.2.8", "seek-bzip": "2.0.0", - "telnet-client": "2.2.5", + "telnet-client": "2.2.6", "varint": "6.0.0" }, "bin": { @@ -5948,15 +6036,19 @@ } }, "node_modules/gamedig/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/gamedig/node_modules/p-cancelable": { @@ -6074,9 +6166,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", - "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6302,9 +6394,9 @@ "license": "MIT" }, "node_modules/htmlnano": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.3.tgz", - "integrity": "sha512-mzTUHhxdfDw80X36rv5qFvjvor7r0uCwXI5mzo8CW61tImbe5Jpvl3JzPAetVh54wUYVuoa8x3qw8LFn8B3gHQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.5.tgz", + "integrity": "sha512-IXffzXq1beGQN2rsr03aIPK/rVU1jR2uwHymlAIEf97Tl5WdpG50IsQ5nWGvSGQJ+x6U7S6yac9rRiFgAg4/xQ==", "license": "MIT", "dependencies": { "@types/relateurl": "^0.2.33", @@ -6512,12 +6604,12 @@ "license": "ISC" }, "node_modules/ioredis": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.7.0.tgz", - "integrity": "sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g==", + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.8.2.tgz", + "integrity": "sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==", "license": "MIT", "dependencies": { - "@ioredis/commands": "^1.3.0", + "@ioredis/commands": "1.4.0", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", @@ -6670,16 +6762,192 @@ "license": "ISC" }, "node_modules/isomorphic-dompurify": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/isomorphic-dompurify/-/isomorphic-dompurify-2.26.0.tgz", - "integrity": "sha512-nZmoK4wKdzPs5USq4JHBiimjdKSVAOm2T1KyDoadtMPNXYHxiENd19ou4iU/V4juFM6LVgYQnpxCYmxqNP4Obw==", + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/isomorphic-dompurify/-/isomorphic-dompurify-2.30.1.tgz", + "integrity": "sha512-VJFbthRrns7BE+q3qSUJ5zxGNjuq4FqiaWXKCwnMoJbumnoQJoeOeOzP/oejKLPPtENckLWoDxGQiv5OkEFC+Q==", "license": "MIT", "dependencies": { - "dompurify": "^3.2.6", - "jsdom": "^26.1.0" + "dompurify": "^3.3.0", + "jsdom": "^27.0.1" }, "engines": { - "node": ">=18" + "node": ">=20.19.5" + } + }, + "node_modules/isomorphic-dompurify/node_modules/@asamuzakjp/css-color": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz", + "integrity": "sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==", + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "lru-cache": "^11.2.1" + } + }, + "node_modules/isomorphic-dompurify/node_modules/cssstyle": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.1.tgz", + "integrity": "sha512-g5PC9Aiph9eiczFpcgUhd9S4UUO3F+LHGRIi5NUMZ+4xtoIYbHNZwZnWA2JsFGe8OU8nl4WyaEFiZuGuxlutJQ==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^4.0.3", + "@csstools/css-syntax-patches-for-csstree": "^1.0.14", + "css-tree": "^3.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/isomorphic-dompurify/node_modules/data-urls": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", + "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/isomorphic-dompurify/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/isomorphic-dompurify/node_modules/jsdom": { + "version": "27.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.1.tgz", + "integrity": "sha512-SNSQteBL1IlV2zqhwwolaG9CwhIhTvVHWg3kTss/cLE7H/X4644mtPQqYvCfsSrGQWt9hSZcgOXX8bOZaMN+kA==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/dom-selector": "^6.7.2", + "cssstyle": "^5.3.1", + "data-urls": "^6.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.1.0", + "ws": "^8.18.3", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/isomorphic-dompurify/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/isomorphic-dompurify/node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/isomorphic-dompurify/node_modules/tldts": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.17.tgz", + "integrity": "sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==", + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.17" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/isomorphic-dompurify/node_modules/tldts-core": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.17.tgz", + "integrity": "sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==", + "license": "MIT" + }, + "node_modules/isomorphic-dompurify/node_modules/tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/isomorphic-dompurify/node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/isomorphic-dompurify/node_modules/webidl-conversions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", + "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/isomorphic-dompurify/node_modules/whatwg-url": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "license": "MIT", + "dependencies": { + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" + }, + "engines": { + "node": ">=20" } }, "node_modules/isomorphic-unfetch": { @@ -6892,14 +7160,14 @@ } }, "node_modules/juice": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/juice/-/juice-11.0.1.tgz", - "integrity": "sha512-R3KLud4l/sN9AMmFZs0QY7cugGSiKvPhGyIsufCV5nJ0MjSlngUE7k80TmFeK9I62wOXrjWBtYA1knVs2OkF8w==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/juice/-/juice-11.0.3.tgz", + "integrity": "sha512-VYjPg4WylyWyLPnSiUsJ9tnnGhRZF0vn0YD8WWwaI8FhP9+1UdRMyRDbvqPOH/nBotmLKOc+FI+Oma6FwVWfSw==", "license": "MIT", "dependencies": { - "cheerio": "^1.0.0", + "cheerio": "1.0.0", "commander": "^12.1.0", - "entities": "^4.5.0", + "entities": "^7.0.0", "mensch": "^0.3.4", "slick": "^1.12.2", "web-resource-inliner": "^7.0.0" @@ -6912,34 +7180,46 @@ } }, "node_modules/juice/node_modules/cheerio": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz", - "integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", + "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", "license": "MIT", "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "encoding-sniffer": "^0.2.1", - "htmlparser2": "^10.0.0", - "parse5": "^7.3.0", - "parse5-htmlparser2-tree-adapter": "^7.1.0", + "domutils": "^3.1.0", + "encoding-sniffer": "^0.2.0", + "htmlparser2": "^9.1.0", + "parse5": "^7.1.2", + "parse5-htmlparser2-tree-adapter": "^7.0.0", "parse5-parser-stream": "^7.1.2", - "undici": "^7.12.0", + "undici": "^6.19.5", "whatwg-mimetype": "^4.0.0" }, "engines": { - "node": ">=20.18.1" + "node": ">=18.17" }, "funding": { "url": "https://github.com/cheeriojs/cheerio?sponsor=1" } }, + "node_modules/juice/node_modules/entities": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.0.tgz", + "integrity": "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/juice/node_modules/htmlparser2": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", - "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -6951,14 +7231,14 @@ "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", - "domutils": "^3.2.1", - "entities": "^6.0.0" + "domutils": "^3.1.0", + "entities": "^4.5.0" } }, "node_modules/juice/node_modules/htmlparser2/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -7218,9 +7498,9 @@ "license": "ISC" }, "node_modules/luxon": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.1.tgz", - "integrity": "sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", "license": "MIT", "engines": { "node": ">=12" @@ -7263,12 +7543,10 @@ } }, "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "license": "CC0-1.0", - "optional": true, - "peer": true + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "license": "CC0-1.0" }, "node_modules/media-typer": { "version": "0.3.0", @@ -7938,14 +8216,14 @@ } }, "node_modules/mongodb": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.19.0.tgz", - "integrity": "sha512-H3GtYujOJdeKIMLKBT9PwlDhGrQfplABNF1G904w6r5ZXKWyv77aB0X9B+rhmaAwjtllHzaEkvi9mkGVZxs2Bw==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", + "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==", "license": "Apache-2.0", "dependencies": { - "@mongodb-js/saslprep": "^1.1.9", + "@mongodb-js/saslprep": "^1.3.0", "bson": "^6.10.4", - "mongodb-connection-string-url": "^3.0.0" + "mongodb-connection-string-url": "^3.0.2" }, "engines": { "node": ">=16.20.1" @@ -7994,14 +8272,14 @@ } }, "node_modules/mongoose": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.18.0.tgz", - "integrity": "sha512-3TixPihQKBdyaYDeJqRjzgb86KbilEH07JmzV8SoSjgoskNTpa6oTBmDxeoF9p8YnWQoz7shnCyPkSV/48y3yw==", + "version": "8.19.2", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.19.2.tgz", + "integrity": "sha512-ww2T4dBV+suCbOfG5YPwj9pLCfUVyj8FEA1D3Ux1HHqutpLxGyOYEPU06iPRBW4cKr3PJfOSYsIpHWPTkz5zig==", "license": "MIT", "dependencies": { "bson": "^6.10.4", "kareem": "2.6.3", - "mongodb": "~6.18.0", + "mongodb": "~6.20.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -8015,52 +8293,6 @@ "url": "https://opencollective.com/mongoose" } }, - "node_modules/mongoose/node_modules/mongodb": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.18.0.tgz", - "integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==", - "license": "Apache-2.0", - "dependencies": { - "@mongodb-js/saslprep": "^1.1.9", - "bson": "^6.10.4", - "mongodb-connection-string-url": "^3.0.0" - }, - "engines": { - "node": ">=16.20.1" - }, - "peerDependencies": { - "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", - "gcp-metadata": "^5.2.0", - "kerberos": "^2.0.1", - "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", - "socks": "^2.7.1" - }, - "peerDependenciesMeta": { - "@aws-sdk/credential-providers": { - "optional": true - }, - "@mongodb-js/zstd": { - "optional": true - }, - "gcp-metadata": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "snappy": { - "optional": true - }, - "socks": { - "optional": true - } - } - }, "node_modules/mpath": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", @@ -8300,9 +8532,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz", + "integrity": "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==", "license": "MIT" }, "node_modules/nodemailer": { @@ -8324,9 +8556,9 @@ } }, "node_modules/normalize-url": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.2.tgz", - "integrity": "sha512-Ee/R3SyN4BuynXcnTaekmaVdbDAEiNrHqjQIA37mHU8G9pf7aaAD4ZX3XjBLo6rsdcxA/gtkcNYZLt30ACgynw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz", + "integrity": "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==", "license": "MIT", "engines": { "node": ">=14.16" @@ -8354,9 +8586,9 @@ "license": "MIT" }, "node_modules/nwsapi": { - "version": "2.2.21", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.21.tgz", - "integrity": "sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==", + "version": "2.2.22", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.22.tgz", + "integrity": "sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==", "license": "MIT" }, "node_modules/object-assign": { @@ -9093,59 +9325,6 @@ "postcss": "^8.4.32" } }, - "node_modules/postcss-svgo/node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", - "license": "MIT", - "engines": { - "node": ">=16" - } - }, - "node_modules/postcss-svgo/node_modules/css-tree": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", - "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", - "license": "MIT", - "dependencies": { - "mdn-data": "2.12.2", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/postcss-svgo/node_modules/mdn-data": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", - "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", - "license": "CC0-1.0" - }, - "node_modules/postcss-svgo/node_modules/svgo": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", - "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", - "license": "MIT", - "dependencies": { - "commander": "^11.1.0", - "css-select": "^5.1.0", - "css-tree": "^3.0.1", - "css-what": "^6.1.0", - "csso": "^5.0.5", - "picocolors": "^1.1.1", - "sax": "^1.4.1" - }, - "bin": { - "svgo": "bin/svgo.js" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/svgo" - } - }, "node_modules/postcss-unique-selectors": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.4.tgz", @@ -9168,9 +9347,9 @@ "license": "MIT" }, "node_modules/posthtml": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz", - "integrity": "sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==", + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.7.tgz", + "integrity": "sha512-7Hc+IvlQ7hlaIfQFZnxlRl0jnpWq2qwibORBhQYIb0QbNtuicc5ZxvKkVT71HJ4Py1wSZ/3VR1r8LfkCtoCzhw==", "license": "MIT", "dependencies": { "posthtml-parser": "^0.11.0", @@ -9504,6 +9683,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-alpn": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", @@ -9626,9 +9814,9 @@ } }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -9867,18 +10055,18 @@ } }, "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", "license": "MIT", "dependencies": { "is-arrayish": "^0.3.1" } }, "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", "license": "MIT" }, "node_modules/sinon": { @@ -10131,9 +10319,9 @@ } }, "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -10209,15 +10397,27 @@ } }, "node_modules/super-simple-scheduler": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.4.1.tgz", - "integrity": "sha512-jKVwxdRBXOHrUstA2pvO4jBZIA3CUinKiQ6k5C2Wn5tscoV3srppPxW0zG3VNKTCaGxo3yPn6Zaf3IDVBcAwRA==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.4.4.tgz", + "integrity": "sha512-o99zHFrrFECwj1FFE+ns4su5hKSYif+SUNB3fUJbaRWGkwLLoj19TRuRJYp72TYt9eDiCii68UBuKiLF4tlkMg==", "license": "MIT", "dependencies": { "uuid": "11.1.0", "winston": "3.17.0" } }, + "node_modules/super-simple-scheduler/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==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/super-simple-scheduler/node_modules/uuid": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", @@ -10231,6 +10431,28 @@ "uuid": "dist/esm/bin/uuid" } }, + "node_modules/super-simple-scheduler/node_modules/winston": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -10245,26 +10467,24 @@ } }, "node_modules/svgo": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", - "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", + "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", + "commander": "^11.1.0", "css-select": "^5.1.0", - "css-tree": "^2.3.1", + "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1", + "sax": "^1.4.1" }, "bin": { - "svgo": "bin/svgo" + "svgo": "bin/svgo.js" }, "engines": { - "node": ">=14.0.0" + "node": ">=16" }, "funding": { "type": "opencollective", @@ -10272,20 +10492,18 @@ } }, "node_modules/svgo/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "license": "MIT", - "optional": true, - "peer": true, "engines": { - "node": ">= 10" + "node": ">=16" } }, "node_modules/swagger-ui-dist": { - "version": "5.28.1", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.28.1.tgz", - "integrity": "sha512-IvPrtNi8MvjiuDgoSmPYgg27Lvu38fnLD1OSd8Y103xXsPAqezVNnNeHnVCZ/d+CMXJblflGaIyHxAYIF3O71w==", + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.30.0.tgz", + "integrity": "sha512-BoiDSeT9PCtHfDYMgX5UpB/qTQy44UoSFRmzHqvhGfgxzEVPHxIW78a+HMLUIHnSFu3z63wjtbq6L6+Rto20Rw==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" @@ -10313,9 +10531,9 @@ "license": "MIT" }, "node_modules/tar-fs": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", - "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", "license": "MIT", "dependencies": { "chownr": "^1.1.1", @@ -10341,9 +10559,9 @@ } }, "node_modules/telnet-client": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/telnet-client/-/telnet-client-2.2.5.tgz", - "integrity": "sha512-X5xEkmKHgpCpngnH7QnkFX87UyBErauHsjzlCGVp87MbhnmHoaAeacuALGfqovHh3MXAfrpPs+g30PgyBpy8Jw==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/telnet-client/-/telnet-client-2.2.6.tgz", + "integrity": "sha512-ZUYrLsPtQupQww3eSEORDVOb6ztdtKEghya6TVXPo2tg/UQq2pn5rHhvwuUvyYpbnsoqdNY1fyD1GNkXHR8dYA==", "license": "MIT", "dependencies": { "net": "^1.0.2", @@ -10587,18 +10805,18 @@ } }, "node_modules/undici": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.15.0.tgz", - "integrity": "sha512-7oZJCPvvMvTd0OlqWsIxTuItTpJBpU1tcbVl24FMn3xt3+VSunwUasmfPJRE57oNO1KsZ4PgA1xTdAX4hq8NyQ==", + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz", + "integrity": "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==", "license": "MIT", "engines": { - "node": ">=20.18.1" + "node": ">=18.17" } }, "node_modules/undici-types": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", - "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "license": "MIT" }, "node_modules/unfetch": { @@ -10620,9 +10838,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", "funding": [ { "type": "opencollective", @@ -10943,13 +11161,13 @@ } }, "node_modules/winston": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", - "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "version": "3.18.3", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.18.3.tgz", + "integrity": "sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==", "license": "MIT", "dependencies": { "@colors/colors": "^1.6.0", - "@dabh/diagnostics": "^2.0.2", + "@dabh/diagnostics": "^2.0.8", "async": "^3.2.3", "is-stream": "^2.0.0", "logform": "^2.7.0", @@ -11090,9 +11308,9 @@ } }, "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "license": "MIT", "engines": { "node": ">=12" diff --git a/server/package.json b/server/package.json index 74bd55804..380755b5b 100755 --- a/server/package.json +++ b/server/package.json @@ -51,7 +51,7 @@ "ping": "0.4.4", "sharp": "0.33.5", "ssl-checker": "2.0.10", - "super-simple-scheduler": "1.4.1", + "super-simple-scheduler": "1.4.4", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, From 931104fbcfb9c76f7c7a5757f191a140bab4b325 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 Oct 2025 09:45:21 -0700 Subject: [PATCH 101/103] type error --- server/src/service/v2/business/MonitorService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/service/v2/business/MonitorService.ts b/server/src/service/v2/business/MonitorService.ts index c0570236b..fb338c348 100644 --- a/server/src/service/v2/business/MonitorService.ts +++ b/server/src/service/v2/business/MonitorService.ts @@ -381,7 +381,7 @@ class MonitorService implements IMonitorService { // Get monitor stats const monitorStats = await MonitorStats.findOne({ monitorId: monitor._id, - }).lean(); + }); if (!monitorStats) { throw new ApiError("Monitor stats not found", 404); From c9e1d35daf0f67bf58b82486ea61e6070a5d1f1f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 Oct 2025 10:27:45 -0700 Subject: [PATCH 102/103] bump version --- client/vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/vite.config.ts b/client/vite.config.ts index 4c60ce2a4..5a6b428c7 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -7,7 +7,7 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); export default defineConfig(({}) => { - let version = "3.2.0"; + let version = "3.2.1"; return { base: "/", From 32e4f7514d51d88a938bbfbdb35d8ad49225b044 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 27 Oct 2025 17:29:47 +0000 Subject: [PATCH 103/103] feat: translations updated from POEditor --- client/src/locales/en.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 214a28221..13d4a7731 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -736,16 +736,6 @@ "webhookLabel": "Discord Webhook URL", "webhookPlaceholder": "https://your-server.com/webhook" }, - "matrixSettings": { - "title": "Matrix", - "description": "Configure your Matrix notification here", - "homeserverLabel": "Homeserver URL", - "homeserverPlaceholder": "https://matrix.org", - "roomIdLabel": "Room ID", - "roomIdPlaceholder": "!QMdRCpUIfLwsfjxye6:home.server", - "accessTokenLabel": "Access Token", - "accessTokenPlaceholder": "Your Matrix access token" - }, "webhookSettings": { "title": "Webhook", "description": "Configure your webhook here",