diff --git a/.gitignore b/.gitignore index 997dff9c5..74015c392 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .idea .vscode -.VSCodeCounter \ No newline at end of file +.VSCodeCounter +*.sh +mongo diff --git a/client/.gitignore b/client/.gitignore index 83f6fe9ed..23b6e49af 100644 --- a/client/.gitignore +++ b/client/.gitignore @@ -28,4 +28,5 @@ dist-ssr # Scripts *.sh -!env.sh \ No newline at end of file +!env.sh +.env.development \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json index 7cf0e8631..e917619ae 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -97,6 +97,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -408,6 +409,7 @@ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -451,6 +453,7 @@ "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -496,6 +499,262 @@ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", "license": "MIT" }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/linux-x64": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", @@ -512,6 +771,134 @@ "node": ">=18" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", @@ -989,6 +1376,7 @@ "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.11.tgz", "integrity": "sha512-k2D3FLJS+/qD0qnd6ZlAjGFvaaxe1Dl10NyvpeDzIebMuYdn8VqYe6XBgGueEAtnzSJM4V03VD9kb5Fi24dnTA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.26.0", "@mui/core-downloads-tracker": "^6.4.11", @@ -1153,7 +1541,6 @@ "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.1.0.tgz", "integrity": "sha512-4Kck4jxhqF6YxNwJdSae1WgDfXVg0lIH6JVJ7gtuFfuKcQCgomJxPvUEOySTFRPz1IZzwz5OAcToskRdffElDA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.27.1", "@mui/utils": "^7.1.0", @@ -1181,7 +1568,6 @@ "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.0.tgz", "integrity": "sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.27.1", "@mui/types": "^7.4.2", @@ -1212,7 +1598,6 @@ "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.1.0.tgz", "integrity": "sha512-m0mJ0c6iRC+f9hMeRe0W7zZX1wme3oUX0+XTVHjPG7DJz6OdQ6K/ggEOq7ZdwilcpdsDUwwMfOmvO71qDkYd2w==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.27.1", "@emotion/cache": "^11.13.5", @@ -1288,7 +1673,6 @@ "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.0.tgz", "integrity": "sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.27.1", "@mui/types": "^7.4.2", @@ -1696,6 +2080,123 @@ } } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz", + "integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz", + "integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz", + "integrity": "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz", + "integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz", + "integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz", + "integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz", + "integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz", + "integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz", + "integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-arm64-musl": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz", @@ -1709,6 +2210,71 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz", + "integrity": "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz", + "integrity": "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz", + "integrity": "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz", + "integrity": "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz", + "integrity": "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz", @@ -1735,6 +2301,45 @@ "linux" ] }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz", + "integrity": "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz", + "integrity": "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz", + "integrity": "sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -2133,6 +2738,7 @@ "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.12.0" } @@ -2154,6 +2760,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.22.tgz", "integrity": "sha512-vUhG0YmQZ7kL/tmKLrD3g5zXbXXreZXB3pmROW8bg3CnLnpjkRVwUlLne7Ufa2r9yJ8+/6B73RzhAek5TBKh2Q==", "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -2217,6 +2824,7 @@ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2523,6 +3131,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001716", "electron-to-chromium": "^1.5.149", @@ -2935,7 +3544,8 @@ "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/debug": { "version": "4.4.1", @@ -3342,6 +3952,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -4149,6 +4760,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.27.6" }, @@ -5416,6 +6028,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -5429,6 +6042,7 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.63.0.tgz", "integrity": "sha512-ZwueDMvUeucovM2VjkCf7zIHcs1aAlDimZu2Hvel5C5907gUzMpm4xCrQXtRzCvsBqFjonB4m3x4LzCFI1ZKWA==", "license": "MIT", + "peer": true, "engines": { "node": ">=18.0.0" }, @@ -5632,7 +6246,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-persist": { "version": "6.0.0", @@ -5770,6 +6385,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.0.tgz", "integrity": "sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.7" }, @@ -6405,6 +7021,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6524,6 +7141,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz", "integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -6744,20 +7362,6 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "license": "ISC" }, - "node_modules/yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/client/src/Components/v1/ActionsMenu/index.jsx b/client/src/Components/v1/ActionsMenu/index.jsx index 229c7c875..5cd0dfa5b 100644 --- a/client/src/Components/v1/ActionsMenu/index.jsx +++ b/client/src/Components/v1/ActionsMenu/index.jsx @@ -12,7 +12,7 @@ import { useNavigate } from "react-router-dom"; import { createToast } from "../../../Utils/toastUtils.jsx"; import PropTypes from "prop-types"; -import { usePauseMonitor, useDeleteMonitor } from "../../../Hooks/v1/monitorHooks.js"; +import { usePauseMonitor, useDeleteMonitor } from "../../../Hooks/monitorHooks.js"; const ActionsMenu = ({ monitor, @@ -31,7 +31,8 @@ const ActionsMenu = ({ const handleRemove = async (event) => { event.preventDefault(); event.stopPropagation(); - let monitor = { _id: actions.id }; + let monitor = { id: actions.id }; + console.log(monitor); await deleteMonitor({ monitor }); updateRowCallback(); }; @@ -39,7 +40,7 @@ const ActionsMenu = ({ const handlePause = async () => { try { setIsLoading(true); - await pauseMonitor({ monitorId: monitor._id }); + await pauseMonitor({ monitorId: monitor.id }); pauseCallback(); } catch (error) { createToast({ body: "Failed to pause monitor." }); @@ -72,7 +73,7 @@ const ActionsMenu = ({ aria-label="monitor actions" onClick={(event) => { event.stopPropagation(); - openMenu(event, monitor._id, monitor.type === "ping" ? null : monitor.url); + openMenu(event, monitor.id, monitor.type === "ping" ? null : monitor.url); }} sx={{ "&:focus": { @@ -197,6 +198,7 @@ const ActionsMenu = ({ confirmationButtonLabel="Delete" /* Do we need stop propagation? */ onConfirm={(e) => { + console.log(e); e.stopPropagation(); handleRemove(e); }} @@ -210,7 +212,7 @@ const ActionsMenu = ({ ActionsMenu.propTypes = { monitor: PropTypes.shape({ - _id: PropTypes.string, + id: PropTypes.string, url: PropTypes.string, type: PropTypes.string, isActive: PropTypes.bool, diff --git a/client/src/Components/v1/Charts/BarChart/index.jsx b/client/src/Components/v1/Charts/BarChart/index.jsx index 2bf8e1798..9f0ed8aa6 100644 --- a/client/src/Components/v1/Charts/BarChart/index.jsx +++ b/client/src/Components/v1/Charts/BarChart/index.jsx @@ -103,7 +103,7 @@ const BarChart = ({ checks = [] }) => { } placement="top" - key={`check-${check?._id}`} + key={`check-${check?.id}`} slotProps={{ popper: { className: "bar-tooltip", @@ -145,7 +145,7 @@ const BarChart = ({ checks = [] }) => { position="relative" width="9px" height="100%" - backgroundColor={theme.palette.primary.lowContrast} // CAIO_REVIEW + backgroundColor={theme.palette.primary.lowContrast} sx={{ borderRadius: theme.spacing(1.5), /* diff --git a/client/src/Components/v1/Inputs/Search/index.jsx b/client/src/Components/v1/Inputs/Search/index.jsx index 7a3771d47..8138ee6f4 100644 --- a/client/src/Components/v1/Inputs/Search/index.jsx +++ b/client/src/Components/v1/Inputs/Search/index.jsx @@ -85,7 +85,12 @@ const Search = ({ const enhancedOptions = React.useMemo(() => { return multiple && isAdorned ? [ - { [filteredBy]: t("selectAll"), isSelectAll: true, _id: "select_all" }, + { + [filteredBy]: t("selectAll"), + isSelectAll: true, + _id: "select_all", + id: "select_all", + }, ...options, ] : options; @@ -93,7 +98,7 @@ const Search = ({ const isOptionSelected = (option) => { if (!multiple && !isAdorned) return false; if (Array.isArray(value)) { - return value.some((item) => item._id === option._id); + return value.some((item) => (item._id ?? item.id) === (option._id ?? option.id)); } return false; }; @@ -145,7 +150,9 @@ const Search = ({ disableClearable options={enhancedOptions} getOptionLabel={(option) => option[filteredBy]} - isOptionEqualToValue={(option, value) => option._id === value._id} // Compare by unique identifier + isOptionEqualToValue={(option, value) => + (option._id ?? option.id) === (value?._id ?? value?.id) + } // Compare by unique identifier renderInput={(params) => ( { - return option._id; + return option._id ?? option.id; }} renderOption={(props, option) => { const { key, ...optionProps } = props; diff --git a/client/src/Components/v1/Inputs/Select/index.jsx b/client/src/Components/v1/Inputs/Select/index.jsx index 37ce30dc1..f952b6818 100644 --- a/client/src/Components/v1/Inputs/Select/index.jsx +++ b/client/src/Components/v1/Inputs/Select/index.jsx @@ -62,6 +62,7 @@ const Select = ({ fieldWrapperSx = {}, }) => { const theme = useTheme(); + const getItemValue = (item) => item?._id ?? item?.id; const itemStyles = { fontSize: "var(--env-var-font-size-medium)", color: theme.palette.primary.contrastTextTertiary, @@ -123,7 +124,7 @@ const Select = ({ ...sx, }} renderValue={(selected) => { - const selectedItem = items.find((item) => item._id === selected); + const selectedItem = items.find((item) => getItemValue(item) === selected); const displayName = selectedItem ? selectedItem.name : placeholder; return ( )} - {items.map((item) => ( - - {item.name} - - ))} + {items + .map((item) => { + const itemValue = getItemValue(item); + if (itemValue === undefined || itemValue === null) { + return null; + } + return ( + + {item.name} + + ); + }) + .filter(Boolean)} ); @@ -179,8 +188,8 @@ Select.propTypes = { .isRequired, items: PropTypes.arrayOf( PropTypes.shape({ - _id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]) - .isRequired, + _id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]), + id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]), name: PropTypes.string.isRequired, }) diff --git a/client/src/Components/v1/MonitorActions/index.jsx b/client/src/Components/v1/MonitorActions/index.jsx index 23cebc972..624d34b9a 100644 --- a/client/src/Components/v1/MonitorActions/index.jsx +++ b/client/src/Components/v1/MonitorActions/index.jsx @@ -11,7 +11,7 @@ import MenuList from "@mui/material/MenuList"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { createToast } from "../../../Utils/toastUtils.jsx"; -import { useExportMonitors } from "../../../Hooks/v1/monitorHooks.js"; +import { useExportMonitors } from "../../../Hooks/monitorHooks.js"; const MonitorActions = ({ isLoading }) => { const [open, setOpen] = React.useState(false); diff --git a/client/src/Components/v1/MonitorDetailsControlHeader/index.jsx b/client/src/Components/v1/MonitorDetailsControlHeader/index.jsx index ae565f3bb..bb4399b85 100644 --- a/client/src/Components/v1/MonitorDetailsControlHeader/index.jsx +++ b/client/src/Components/v1/MonitorDetailsControlHeader/index.jsx @@ -12,10 +12,10 @@ import EmailIcon from "@mui/icons-material/Email"; import PropTypes from "prop-types"; import { useNavigate } from "react-router-dom"; import { useTheme } from "@mui/material/styles"; -import { usePauseMonitor } from "../../../Hooks/v1/monitorHooks.js"; -import { useSendTestEmail } from "../../../Hooks/v1/useSendTestEmail.js"; +import { usePauseMonitor } from "../../../Hooks/monitorHooks.js"; +import { useSendTestEmail } from "../../../Hooks/useSendTestEmail.js"; import { useTranslation } from "react-i18next"; -import { useTestAllNotifications } from "../../../Hooks/v1/useNotifications.js"; +import { useTestAllNotifications } from "../../../Hooks/useNotifications.js"; /** * MonitorDetailsControlHeader component displays the control header for monitor details. * It includes status display, pause/resume button, and a configure button for admins. diff --git a/client/src/Components/v1/MonitorDetailsControlHeader/status.jsx b/client/src/Components/v1/MonitorDetailsControlHeader/status.jsx index d9b7afc40..febf449d4 100644 --- a/client/src/Components/v1/MonitorDetailsControlHeader/status.jsx +++ b/client/src/Components/v1/MonitorDetailsControlHeader/status.jsx @@ -7,7 +7,7 @@ import Dot from "../Dot/index.jsx"; import { formatDurationRounded } from "../../../Utils/timeUtils.js"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; -import { useMonitorUtils } from "../../../Hooks/v1/useMonitorUtils.js"; +import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js"; import { formatMonitorUrl } from "../../../Utils/utils.js"; /** * Status component displays the status information of a monitor. diff --git a/client/src/Components/v1/PageStateWrapper/index.jsx b/client/src/Components/v1/PageStateWrapper/index.jsx index ac97704df..08adbe514 100644 --- a/client/src/Components/v1/PageStateWrapper/index.jsx +++ b/client/src/Components/v1/PageStateWrapper/index.jsx @@ -3,7 +3,7 @@ import Fallback from "../Fallback/index.jsx"; import { Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import { useIsAdmin } from "../../../Hooks/v1/useIsAdmin.js"; +import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; import PropTypes from "prop-types"; const PageStateWrapper = ({ networkError, diff --git a/client/src/Components/v1/StatBox/index.jsx b/client/src/Components/v1/StatBox/index.jsx index 1b5f1cf6f..e72f947b3 100644 --- a/client/src/Components/v1/StatBox/index.jsx +++ b/client/src/Components/v1/StatBox/index.jsx @@ -2,7 +2,7 @@ import { Stack, Typography } from "@mui/material"; import Image from "../Image/index.jsx"; import { useTheme } from "@mui/material/styles"; import PropTypes from "prop-types"; -import { useMonitorUtils } from "../../../Hooks/v1/useMonitorUtils.js"; +import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js"; /** * StatBox Component diff --git a/client/src/Hooks/v1/checkHooks.js b/client/src/Hooks/checkHooks.js similarity index 97% rename from client/src/Hooks/v1/checkHooks.js rename to client/src/Hooks/checkHooks.js index 96fd1fe1c..b8af32073 100644 --- a/client/src/Hooks/v1/checkHooks.js +++ b/client/src/Hooks/checkHooks.js @@ -1,6 +1,6 @@ import { useState, useEffect } from "react"; -import { networkService } from "../../main.jsx"; -import { createToast } from "../../Utils/toastUtils.jsx"; +import { networkService } from "../main.jsx"; +import { createToast } from "../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; const useFetchChecksTeam = ({ diff --git a/client/src/Hooks/v1/inviteHooks.js b/client/src/Hooks/inviteHooks.js similarity index 97% rename from client/src/Hooks/v1/inviteHooks.js rename to client/src/Hooks/inviteHooks.js index b6a0851af..c862eec31 100644 --- a/client/src/Hooks/v1/inviteHooks.js +++ b/client/src/Hooks/inviteHooks.js @@ -1,5 +1,5 @@ import { useState } from "react"; -import { networkService } from "../../main.jsx"; +import { networkService } from "../main.jsx"; import { useTranslation } from "react-i18next"; const CLIENT_HOST = import.meta.env.VITE_APP_CLIENT_HOST; diff --git a/client/src/Hooks/v1/logHooks.js b/client/src/Hooks/logHooks.js similarity index 96% rename from client/src/Hooks/v1/logHooks.js rename to client/src/Hooks/logHooks.js index ff4d13872..d5162de5a 100644 --- a/client/src/Hooks/v1/logHooks.js +++ b/client/src/Hooks/logHooks.js @@ -1,6 +1,6 @@ import { useState, useEffect } from "react"; -import { networkService } from "../../main.jsx"; -import { createToast } from "../../Utils/toastUtils.jsx"; +import { networkService } from "../main.jsx"; +import { createToast } from "../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; const useFetchLogs = () => { diff --git a/client/src/Hooks/v1/monitorHooks.js b/client/src/Hooks/monitorHooks.js similarity index 85% rename from client/src/Hooks/v1/monitorHooks.js rename to client/src/Hooks/monitorHooks.js index 8acbea608..bc391ebf6 100644 --- a/client/src/Hooks/v1/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -1,12 +1,12 @@ import { useEffect, useState } from "react"; -import { networkService } from "../../main.jsx"; -import { createToast } from "../../Utils/toastUtils.jsx"; +import { networkService } from "../main.jsx"; +import { createToast } from "../Utils/toastUtils.jsx"; import { useTheme } from "@emotion/react"; import { useMonitorUtils } from "./useMonitorUtils.js"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; -const useFetchMonitorsWithSummary = ({ types, monitorUpdateTrigger }) => { +export const useFetchMonitorsWithSummary = ({ types, monitorUpdateTrigger }) => { const [isLoading, setIsLoading] = useState(false); const [monitors, setMonitors] = useState(undefined); const [monitorsSummary, setMonitorsSummary] = useState(undefined); @@ -37,7 +37,7 @@ const useFetchMonitorsWithSummary = ({ types, monitorUpdateTrigger }) => { return [monitors, monitorsSummary, isLoading, networkError]; }; -const useFetchMonitorsWithChecks = ({ +export const useFetchMonitorsWithChecks = ({ types, limit, page, @@ -67,12 +67,13 @@ const useFetchMonitorsWithChecks = ({ field, order, }); + const { count, monitors } = res?.data?.data ?? {}; const mappedMonitors = monitors.map((monitor) => getMonitorWithPercentage(monitor, theme) ); setMonitors(mappedMonitors); - setCount(count?.monitorsCount ?? 0); + setCount(count || 0); } catch (error) { console.error(error); setNetworkError(true); @@ -99,22 +100,9 @@ const useFetchMonitorsWithChecks = ({ return [monitors, count, isLoading, networkError]; }; -const useFetchMonitorsByTeamId = ({ - types, - limit, - page, - rowsPerPage, - filter, - field, - order, - checkOrder, - normalize, - status, - updateTrigger, -}) => { +export const useFetchMonitorsByTeamId = ({ types, filter, updateTrigger }) => { const [isLoading, setIsLoading] = useState(false); const [monitors, setMonitors] = useState(undefined); - const [summary, setSummary] = useState(undefined); const [networkError, setNetworkError] = useState(false); useEffect(() => { @@ -122,20 +110,11 @@ const useFetchMonitorsByTeamId = ({ try { setIsLoading(true); const res = await networkService.getMonitorsByTeamId({ - limit, types, - page, - rowsPerPage, filter, - field, - order, - checkOrder, - status, - normalize, }); - if (res?.data?.data?.filteredMonitors) { - setMonitors(res.data.data.filteredMonitors); - setSummary(res.data.data.summary); + if (res?.data?.data) { + setMonitors(res.data.data); } } catch (error) { setNetworkError(true); @@ -147,23 +126,11 @@ const useFetchMonitorsByTeamId = ({ } }; fetchMonitors(); - }, [ - types, - limit, - page, - rowsPerPage, - filter, - field, - order, - updateTrigger, - checkOrder, - normalize, - status, - ]); - return [monitors, summary, isLoading, networkError]; + }, [types, filter, updateTrigger]); + return [monitors, isLoading, networkError]; }; -const useFetchStatsByMonitorId = ({ +export const useFetchStatsByMonitorId = ({ monitorId, sortOrder, limit, @@ -202,7 +169,7 @@ const useFetchStatsByMonitorId = ({ return [monitor, audits, isLoading, networkError]; }; -const useFetchMonitorGames = ({ setGames, updateTrigger }) => { +export const useFetchMonitorGames = ({ setGames, updateTrigger }) => { const [isLoading, setIsLoading] = useState(true); useEffect(() => { const fetchGames = async () => { @@ -221,7 +188,7 @@ const useFetchMonitorGames = ({ setGames, updateTrigger }) => { return [isLoading]; }; -const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => { +export const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => { const [isLoading, setIsLoading] = useState(true); useEffect(() => { if (typeof monitorId === "undefined") { @@ -244,7 +211,7 @@ const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => { return [isLoading]; }; -const useFetchHardwareMonitorById = ({ monitorId, dateRange, updateTrigger }) => { +export const useFetchHardwareMonitorById = ({ monitorId, dateRange, updateTrigger }) => { const [isLoading, setIsLoading] = useState(true); const [networkError, setNetworkError] = useState(false); const [monitor, setMonitor] = useState(undefined); @@ -270,8 +237,34 @@ const useFetchHardwareMonitorById = ({ monitorId, dateRange, updateTrigger }) => }, [monitorId, dateRange, updateTrigger]); return [monitor, isLoading, networkError]; }; +export const useFetchPageSpeedMonitorById = ({ monitorId, dateRange, updateTrigger }) => { + const [isLoading, setIsLoading] = useState(true); + const [networkError, setNetworkError] = useState(false); + const [monitor, setMonitor] = useState(undefined); -const useFetchUptimeMonitorById = ({ monitorId, dateRange, trigger }) => { + useEffect(() => { + const fetchMonitor = async () => { + try { + if (!monitorId) { + return { monitor: undefined, isLoading: false, networkError: undefined }; + } + const response = await networkService.getPageSpeedDetailsByMonitorId({ + monitorId: monitorId, + dateRange: dateRange, + }); + setMonitor(response.data.data); + } catch (error) { + setNetworkError(true); + } finally { + setIsLoading(false); + } + }; + fetchMonitor(); + }, [monitorId, dateRange, updateTrigger]); + return [monitor, isLoading, networkError]; +}; + +export const useFetchUptimeMonitorById = ({ monitorId, dateRange, trigger }) => { const [networkError, setNetworkError] = useState(false); const [isLoading, setIsLoading] = useState(true); const [monitor, setMonitor] = useState(undefined); @@ -299,7 +292,7 @@ const useFetchUptimeMonitorById = ({ monitorId, dateRange, trigger }) => { return [monitor, monitorStats, isLoading, networkError]; }; -const useCreateMonitor = () => { +export const useCreateMonitor = () => { const [isLoading, setIsLoading] = useState(false); const navigate = useNavigate(); const createMonitor = async ({ monitor, redirect }) => { @@ -319,7 +312,7 @@ const useCreateMonitor = () => { return [createMonitor, isLoading]; }; -const useFetchGlobalSettings = () => { +export const useFetchGlobalSettings = () => { const [isLoading, setIsLoading] = useState(true); const [globalSettings, setGlobalSettings] = useState(undefined); useEffect(() => { @@ -341,13 +334,14 @@ const useFetchGlobalSettings = () => { return [globalSettings, isLoading]; }; -const useDeleteMonitor = () => { +export const useDeleteMonitor = () => { const [isLoading, setIsLoading] = useState(false); const navigate = useNavigate(); const deleteMonitor = async ({ monitor, redirect }) => { try { setIsLoading(true); - await networkService.deleteMonitorById({ monitorId: monitor._id }); + console.log(monitor); + await networkService.deleteMonitorById({ monitorId: monitor.id }); createToast({ body: "Monitor deleted successfully!" }); if (redirect) { navigate(redirect); @@ -361,7 +355,7 @@ const useDeleteMonitor = () => { return [deleteMonitor, isLoading]; }; -const useUpdateMonitor = () => { +export const useUpdateMonitor = () => { const [isLoading, setIsLoading] = useState(false); const navigate = useNavigate(); const updateMonitor = async ({ monitor, redirect }) => { @@ -408,7 +402,7 @@ const useUpdateMonitor = () => { return [updateMonitor, isLoading]; }; -const usePauseMonitor = () => { +export const usePauseMonitor = () => { const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(undefined); const pauseMonitor = async ({ monitorId, triggerUpdate }) => { @@ -431,7 +425,7 @@ const usePauseMonitor = () => { return [pauseMonitor, isLoading, error]; }; -const useAddDemoMonitors = () => { +export const useAddDemoMonitors = () => { const [isLoading, setIsLoading] = useState(false); const { t } = useTranslation(); const addDemoMonitors = async () => { @@ -448,7 +442,7 @@ const useAddDemoMonitors = () => { return [addDemoMonitors, isLoading]; }; -const useDeleteAllMonitors = () => { +export const useDeleteAllMonitors = () => { const [isLoading, setIsLoading] = useState(false); const { t } = useTranslation(); const deleteAllMonitors = async () => { @@ -465,7 +459,7 @@ const useDeleteAllMonitors = () => { return [deleteAllMonitors, isLoading]; }; -const useDeleteMonitorStats = () => { +export const useDeleteMonitorStats = () => { const { t } = useTranslation(); const [isLoading, setIsLoading] = useState(false); const deleteMonitorStats = async () => { @@ -483,7 +477,7 @@ const useDeleteMonitorStats = () => { return [deleteMonitorStats, isLoading]; }; -const useCreateBulkMonitors = () => { +export const useCreateBulkMonitors = () => { const [isLoading, setIsLoading] = useState(false); const createBulkMonitors = async (file, user) => { @@ -506,7 +500,7 @@ const useCreateBulkMonitors = () => { return [createBulkMonitors, isLoading]; }; -const useExportMonitors = () => { +export const useExportMonitors = () => { const [isLoading, setIsLoading] = useState(false); const { t } = useTranslation(); @@ -539,7 +533,7 @@ const useExportMonitors = () => { return [exportMonitors, isLoading]; }; -const useFetchJson = () => { +export const useFetchJson = () => { const [isLoading, setIsLoading] = useState(false); const fetchJson = async () => { try { @@ -555,25 +549,3 @@ const useFetchJson = () => { }; return [fetchJson, isLoading]; }; - -export { - useFetchMonitorsWithSummary, - useFetchMonitorsWithChecks, - useFetchMonitorsByTeamId, - useFetchStatsByMonitorId, - useFetchMonitorById, - useFetchUptimeMonitorById, - useFetchHardwareMonitorById, - useCreateMonitor, - useFetchGlobalSettings, - useDeleteMonitor, - useUpdateMonitor, - usePauseMonitor, - useAddDemoMonitors, - useDeleteAllMonitors, - useDeleteMonitorStats, - useCreateBulkMonitors, - useExportMonitors, - useFetchMonitorGames, - useFetchJson, -}; diff --git a/client/src/Hooks/v1/settingsHooks.js b/client/src/Hooks/settingsHooks.js similarity index 89% rename from client/src/Hooks/v1/settingsHooks.js rename to client/src/Hooks/settingsHooks.js index 787e46ff9..8c4bef385 100644 --- a/client/src/Hooks/v1/settingsHooks.js +++ b/client/src/Hooks/settingsHooks.js @@ -1,6 +1,6 @@ import { useState, useEffect } from "react"; -import { networkService } from "../../main.jsx"; -import { createToast } from "../../Utils/toastUtils.jsx"; +import { networkService } from "../main.jsx"; +import { createToast } from "../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; const useFetchSettings = ({ setSettingsData, setIsApiKeySet, setIsEmailPasswordSet }) => { @@ -42,11 +42,11 @@ const useSaveSettings = ({ setIsLoading(true); try { const settingsResponse = await networkService.updateAppSettings({ settings }); - if (settings.checkTTL) { - await networkService.updateChecksTTL({ - ttl: settings.checkTTL, - }); - } + // if (settings.checkTTL) { + // await networkService.updateChecksTTL({ + // ttl: settings.checkTTL, + // }); + // } setIsApiKeySet(settingsResponse.data.data.pagespeedKeySet); setIsEmailPasswordSet(settingsResponse.data.data.emailPasswordSet); if (settingsResponse.data.data.pagespeedKeySet === true) { diff --git a/client/src/Hooks/v1/useIsAdmin.js b/client/src/Hooks/useIsAdmin.js similarity index 100% rename from client/src/Hooks/v1/useIsAdmin.js rename to client/src/Hooks/useIsAdmin.js diff --git a/client/src/Hooks/v1/useMonitorUtils.js b/client/src/Hooks/useMonitorUtils.js similarity index 100% rename from client/src/Hooks/v1/useMonitorUtils.js rename to client/src/Hooks/useMonitorUtils.js diff --git a/client/src/Hooks/v1/useNotifications.js b/client/src/Hooks/useNotifications.js similarity index 96% rename from client/src/Hooks/v1/useNotifications.js rename to client/src/Hooks/useNotifications.js index d1b4a6652..5f2058b6a 100644 --- a/client/src/Hooks/v1/useNotifications.js +++ b/client/src/Hooks/useNotifications.js @@ -1,9 +1,9 @@ import { useState, useEffect, useCallback } from "react"; -import { createToast } from "../../Utils/toastUtils.jsx"; -import { networkService } from "../../main.jsx"; +import { createToast } from "../Utils/toastUtils.jsx"; +import { networkService } from "../main.jsx"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; -import { NOTIFICATION_TYPES } from "../../Pages/v1/Notifications/utils.js"; +import { NOTIFICATION_TYPES } from "../Pages/Notifications/utils.js"; const useCreateNotification = () => { const navigate = useNavigate(); diff --git a/client/src/Hooks/v1/useSendTestEmail.js b/client/src/Hooks/useSendTestEmail.js similarity index 93% rename from client/src/Hooks/v1/useSendTestEmail.js rename to client/src/Hooks/useSendTestEmail.js index 0be77954a..7223a489a 100644 --- a/client/src/Hooks/v1/useSendTestEmail.js +++ b/client/src/Hooks/useSendTestEmail.js @@ -1,7 +1,7 @@ import { useState } from "react"; -import { networkService } from "../../main.jsx"; +import { networkService } from "../main.jsx"; import { useSelector } from "react-redux"; -import { createToast } from "../../Utils/toastUtils.jsx"; +import { createToast } from "../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; const useSendTestEmail = () => { diff --git a/client/src/Hooks/v1/userHooks.js b/client/src/Hooks/userHooks.js similarity index 94% rename from client/src/Hooks/v1/userHooks.js rename to client/src/Hooks/userHooks.js index f9c454450..3d822f979 100644 --- a/client/src/Hooks/v1/userHooks.js +++ b/client/src/Hooks/userHooks.js @@ -1,6 +1,6 @@ import { useEffect, useState, useCallback } from "react"; -import { networkService } from "../../main.jsx"; -import { createToast } from "../../Utils/toastUtils.jsx"; +import { networkService } from "../main.jsx"; +import { createToast } from "../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; export const useGetUser = (userId) => { diff --git a/client/src/Pages/v1/About/index.jsx b/client/src/Pages/About/index.jsx similarity index 100% rename from client/src/Pages/v1/About/index.jsx rename to client/src/Pages/About/index.jsx diff --git a/client/src/Pages/v1/Account/EditUser/hooks/editUser.js b/client/src/Pages/Account/EditUser/hooks/editUser.js similarity index 91% rename from client/src/Pages/v1/Account/EditUser/hooks/editUser.js rename to client/src/Pages/Account/EditUser/hooks/editUser.js index 00660aead..aeb50ffff 100644 --- a/client/src/Pages/v1/Account/EditUser/hooks/editUser.js +++ b/client/src/Pages/Account/EditUser/hooks/editUser.js @@ -1,8 +1,8 @@ import { useState, useEffect } from "react"; -import { ROLES } from "../../../../../Utils/roleUtils.js"; -import { editUserValidation } from "../../../../../Validation/validation.js"; +import { ROLES } from "../../../../Utils/roleUtils.js"; +import { editUserValidation } from "../../../../Validation/validation.js"; import Joi from "joi"; -import { createToast } from "../../../../../Utils/toastUtils.jsx"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; export const useEditUserForm = (user) => { const [searchInput, setSearchInput] = useState(""); diff --git a/client/src/Pages/v1/Account/EditUser/index.jsx b/client/src/Pages/Account/EditUser/index.jsx similarity index 96% rename from client/src/Pages/v1/Account/EditUser/index.jsx rename to client/src/Pages/Account/EditUser/index.jsx index 674a522b8..2b2ac5128 100644 --- a/client/src/Pages/v1/Account/EditUser/index.jsx +++ b/client/src/Pages/Account/EditUser/index.jsx @@ -11,8 +11,8 @@ import ChangePasswordModal from "@/Pages/Account/components/ChangePasswordModal/ import { useParams } from "react-router-dom"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import { useGetUser, useEditUser } from "../../../../Hooks/v1/userHooks.js"; -import { EDITABLE_ROLES, ROLES } from "../../../../Utils/roleUtils.js"; +import { useGetUser, useEditUser } from "../../../Hooks/userHooks.js"; +import { EDITABLE_ROLES, ROLES } from "../../../Utils/roleUtils.js"; import { useEditUserForm, useValidateEditUserForm } from "./hooks/editUser.js"; import { useSelector } from "react-redux"; diff --git a/client/src/Pages/v1/Account/components/AddMemberMenu/index.jsx b/client/src/Pages/Account/components/AddMemberMenu/index.jsx similarity index 100% rename from client/src/Pages/v1/Account/components/AddMemberMenu/index.jsx rename to client/src/Pages/Account/components/AddMemberMenu/index.jsx diff --git a/client/src/Pages/v1/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx b/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx similarity index 93% rename from client/src/Pages/v1/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx rename to client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx index 58db019a3..1237777ff 100644 --- a/client/src/Pages/v1/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx +++ b/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { newOrChangedCredentials } from "../../../../../../Validation/validation.js"; +import { newOrChangedCredentials } from "../../../../../Validation/validation.js"; import { useTranslation } from "react-i18next"; const useAddTeamMember = () => { const { t } = useTranslation(); diff --git a/client/src/Pages/v1/Account/components/AddTeamMember/index.jsx b/client/src/Pages/Account/components/AddTeamMember/index.jsx similarity index 97% rename from client/src/Pages/v1/Account/components/AddTeamMember/index.jsx rename to client/src/Pages/Account/components/AddTeamMember/index.jsx index 219658b63..e5280af16 100644 --- a/client/src/Pages/v1/Account/components/AddTeamMember/index.jsx +++ b/client/src/Pages/Account/components/AddTeamMember/index.jsx @@ -2,10 +2,10 @@ import { Button, Stack } from "@mui/material"; import { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx"; import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import Select from "@/Components/v1/Inputs/Select/index.jsx"; -import { useGetInviteToken } from "../../../../../Hooks/v1/inviteHooks.js"; +import { useGetInviteToken } from "../../../../Hooks/inviteHooks.js"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import { createToast } from "../../../../../Utils/toastUtils.jsx"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; import { useState } from "react"; import PasswordTooltip from "../../../Auth/components/PasswordTooltip.jsx"; import useAddTeamMember from "./hooks/useAddTeamMember.jsx"; diff --git a/client/src/Pages/Account/components/ChangePasswordModal/index.jsx b/client/src/Pages/Account/components/ChangePasswordModal/index.jsx index cc284a675..725a9b00e 100644 --- a/client/src/Pages/Account/components/ChangePasswordModal/index.jsx +++ b/client/src/Pages/Account/components/ChangePasswordModal/index.jsx @@ -2,12 +2,12 @@ import { useState } from "react"; import { Button, Stack } from "@mui/material"; import { GenericDialog } from "@/Components/v1/Dialog/genericDialog"; import TextInput from "@/Components/v1/Inputs/TextInput"; -import PasswordTooltip from "@/Pages/v1/Auth/components/PasswordTooltip"; +import PasswordTooltip from "@/Pages/Auth/components/PasswordTooltip"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import { createToast } from "../../../../Utils/toastUtils"; import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments"; -import usePasswordFeedback from "@/Pages/v1/Auth/hooks/usePasswordFeedback"; +import usePasswordFeedback from "@/Pages/Auth/hooks/usePasswordFeedback"; import PropTypes from "prop-types"; const ChangePasswordModal = ({ isSaving, isLoading, changePassword }) => { diff --git a/client/src/Pages/v1/Account/components/PasswordPanel.jsx b/client/src/Pages/Account/components/PasswordPanel.jsx similarity index 95% rename from client/src/Pages/v1/Account/components/PasswordPanel.jsx rename to client/src/Pages/Account/components/PasswordPanel.jsx index ca2af9205..f54c61a16 100644 --- a/client/src/Pages/v1/Account/components/PasswordPanel.jsx +++ b/client/src/Pages/Account/components/PasswordPanel.jsx @@ -4,12 +4,12 @@ import { useTheme } from "@emotion/react"; import { Box, Stack, Typography, Button } from "@mui/material"; import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; -import { newOrChangedCredentials } from "../../../../Validation/validation.js"; +import { newOrChangedCredentials } from "../../../Validation/validation.js"; import Alert from "@/Components/v1/Alert/index.jsx"; -import { update } from "../../../../Features/Auth/authSlice.js"; +import { update } from "../../../Features/Auth/authSlice.js"; import { useDispatch, useSelector } from "react-redux"; -import { createToast } from "../../../../Utils/toastUtils.jsx"; -import { getTouchedFieldErrors } from "../../../../Validation/error.js"; +import { createToast } from "../../../Utils/toastUtils.jsx"; +import { getTouchedFieldErrors } from "../../../Validation/error.js"; import { useTranslation } from "react-i18next"; const defaultPasswordsState = { diff --git a/client/src/Pages/v1/Account/components/ProfilePanel.jsx b/client/src/Pages/Account/components/ProfilePanel.jsx similarity index 97% rename from client/src/Pages/v1/Account/components/ProfilePanel.jsx rename to client/src/Pages/Account/components/ProfilePanel.jsx index 2035861a2..d59923ae4 100644 --- a/client/src/Pages/v1/Account/components/ProfilePanel.jsx +++ b/client/src/Pages/Account/components/ProfilePanel.jsx @@ -5,15 +5,11 @@ import { Box, Button, Divider, Stack, Typography } from "@mui/material"; import Avatar from "@/Components/v1/Avatar/index.jsx"; import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import ImageUpload from "@/Components/v1/Inputs/ImageUpload/index.jsx"; -import { newOrChangedCredentials } from "../../../../Validation/validation.js"; +import { newOrChangedCredentials } from "../../../Validation/validation.js"; import { useDispatch, useSelector } from "react-redux"; -import { - clearAuthState, - deleteUser, - update, -} from "../../../../Features/Auth/authSlice.js"; -import { createToast } from "../../../../Utils/toastUtils.jsx"; -import { logger } from "../../../../Utils/Logger.js"; +import { clearAuthState, deleteUser, update } from "../../../Features/Auth/authSlice.js"; +import { createToast } from "../../../Utils/toastUtils.jsx"; +import { logger } from "../../../Utils/Logger.js"; import { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx"; import Dialog from "@/Components/v1/Dialog/index.jsx"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Account/components/RoleTable/index.jsx b/client/src/Pages/Account/components/RoleTable/index.jsx similarity index 93% rename from client/src/Pages/v1/Account/components/RoleTable/index.jsx rename to client/src/Pages/Account/components/RoleTable/index.jsx index 02dcaaf0d..5eea33685 100644 --- a/client/src/Pages/v1/Account/components/RoleTable/index.jsx +++ b/client/src/Pages/Account/components/RoleTable/index.jsx @@ -3,7 +3,7 @@ import DataTable from "@/Components/v1/Table/index.jsx"; import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded"; import { useTranslation } from "react-i18next"; -import { ROLES } from "../../../../../Utils/roleUtils.js"; +import { ROLES } from "../../../../Utils/roleUtils.js"; const RoleTable = ({ roles, handleDeleteRole }) => { const { t } = useTranslation(); diff --git a/client/src/Pages/v1/Account/components/TeamPanel.jsx b/client/src/Pages/Account/components/TeamPanel.jsx similarity index 96% rename from client/src/Pages/v1/Account/components/TeamPanel.jsx rename to client/src/Pages/Account/components/TeamPanel.jsx index 02918eb30..03a6145a5 100644 --- a/client/src/Pages/v1/Account/components/TeamPanel.jsx +++ b/client/src/Pages/Account/components/TeamPanel.jsx @@ -4,16 +4,16 @@ import { Button, ButtonGroup, Stack, Typography } from "@mui/material"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; -import { newOrChangedCredentials } from "../../../../Validation/validation.js"; -import { networkService } from "../../../../main.jsx"; -import { createToast } from "../../../../Utils/toastUtils.jsx"; +import { newOrChangedCredentials } from "../../../Validation/validation.js"; +import { networkService } from "../../../main.jsx"; +import { createToast } from "../../../Utils/toastUtils.jsx"; import Select from "@/Components/v1/Inputs/Select/index.jsx"; import { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx"; import AddTeamMember from "./AddTeamMember/index.jsx"; import DataTable from "@/Components/v1/Table/index.jsx"; -import { useGetInviteToken } from "../../../../Hooks/v1/inviteHooks.js"; +import { useGetInviteToken } from "../../../Hooks/inviteHooks.js"; import { useNavigate } from "react-router-dom"; -import { useIsSuperAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; +import { useIsSuperAdmin } from "@/Hooks/useIsAdmin.js"; import AddMemberMenu from "./AddMemberMenu/index.jsx"; /** * TeamPanel component manages the organization and team members, diff --git a/client/src/Pages/v1/Account/index.jsx b/client/src/Pages/Account/index.jsx similarity index 100% rename from client/src/Pages/v1/Account/index.jsx rename to client/src/Pages/Account/index.jsx diff --git a/client/src/Pages/v1/Auth/CheckEmail.jsx b/client/src/Pages/Auth/CheckEmail.jsx similarity index 94% rename from client/src/Pages/v1/Auth/CheckEmail.jsx rename to client/src/Pages/Auth/CheckEmail.jsx index c12a84804..6aee7a470 100644 --- a/client/src/Pages/v1/Auth/CheckEmail.jsx +++ b/client/src/Pages/Auth/CheckEmail.jsx @@ -3,12 +3,12 @@ import { useEffect, useState } from "react"; import { useTheme } from "@emotion/react"; import { useDispatch } from "react-redux"; import { useNavigate } from "react-router-dom"; -import { createToast } from "../../../Utils/toastUtils.jsx"; -import { forgotPassword } from "../../../Features/Auth/authSlice.js"; +import { createToast } from "../../Utils/toastUtils.jsx"; +import { forgotPassword } from "../../Features/Auth/authSlice.js"; import { Trans, useTranslation } from "react-i18next"; -import Background from "../../../assets/Images/background-grid.svg?react"; -import EmailIcon from "../../../assets/icons/email.svg?react"; -import Logo from "../../../assets/icons/checkmate-icon.svg?react"; +import Background from "@/assets/Images/background-grid.svg?react"; +import EmailIcon from "@/assets/icons/email.svg?react"; +import Logo from "@/assets/icons/checkmate-icon.svg?react"; import IconBox from "@/Components/v1/IconBox/index.jsx"; import "./index.css"; diff --git a/client/src/Pages/v1/Auth/ForgotPassword.jsx b/client/src/Pages/Auth/ForgotPassword.jsx similarity index 93% rename from client/src/Pages/v1/Auth/ForgotPassword.jsx rename to client/src/Pages/Auth/ForgotPassword.jsx index 8cbd85d8a..0a89b2987 100644 --- a/client/src/Pages/v1/Auth/ForgotPassword.jsx +++ b/client/src/Pages/Auth/ForgotPassword.jsx @@ -1,15 +1,15 @@ import { Box, Stack, Typography, Button } from "@mui/material"; import { useTheme } from "@emotion/react"; -import { createToast } from "../../../Utils/toastUtils.jsx"; +import { createToast } from "../../Utils/toastUtils.jsx"; import { useDispatch, useSelector } from "react-redux"; -import { forgotPassword } from "../../../Features/Auth/authSlice.js"; +import { forgotPassword } from "../../Features/Auth/authSlice.js"; import { useEffect, useState } from "react"; -import { newOrChangedCredentials } from "../../../Validation/validation.js"; +import { newOrChangedCredentials } from "../../Validation/validation.js"; import { useNavigate } from "react-router-dom"; import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; -import Logo from "../../../assets/icons/checkmate-icon.svg?react"; -import Key from "../../../assets/icons/key.svg?react"; -import Background from "../../../assets/Images/background-grid.svg?react"; +import Logo from "@/assets/icons/checkmate-icon.svg?react"; +import Key from "@/assets/icons/key.svg?react"; +import Background from "@/assets/Images/background-grid.svg?react"; import IconBox from "@/Components/v1/IconBox/index.jsx"; import { Trans, useTranslation } from "react-i18next"; import "./index.css"; diff --git a/client/src/Pages/v1/Auth/Login/hooks/useLoadingSubmit.jsx b/client/src/Pages/Auth/Login/hooks/useLoadingSubmit.jsx similarity index 100% rename from client/src/Pages/v1/Auth/Login/hooks/useLoadingSubmit.jsx rename to client/src/Pages/Auth/Login/hooks/useLoadingSubmit.jsx diff --git a/client/src/Pages/v1/Auth/Login/hooks/useLoginForm.jsx b/client/src/Pages/Auth/Login/hooks/useLoginForm.jsx similarity index 100% rename from client/src/Pages/v1/Auth/Login/hooks/useLoginForm.jsx rename to client/src/Pages/Auth/Login/hooks/useLoginForm.jsx diff --git a/client/src/Pages/v1/Auth/Login/hooks/useLoginSubmit.jsx b/client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx similarity index 87% rename from client/src/Pages/v1/Auth/Login/hooks/useLoginSubmit.jsx rename to client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx index 0eed9bd07..b009793d5 100644 --- a/client/src/Pages/v1/Auth/Login/hooks/useLoginSubmit.jsx +++ b/client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx @@ -1,7 +1,7 @@ import { useDispatch } from "react-redux"; -import { login } from "../../../../../Features/Auth/authSlice.js"; +import { login } from "../../../../Features/Auth/authSlice.js"; import { useNavigate } from "react-router-dom"; -import { createToast } from "../../../../../Utils/toastUtils.jsx"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; const useLoginSubmit = () => { diff --git a/client/src/Pages/v1/Auth/Login/hooks/useValidateLoginForm.jsx b/client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx similarity index 90% rename from client/src/Pages/v1/Auth/Login/hooks/useValidateLoginForm.jsx rename to client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx index ca9bef879..0872213ad 100644 --- a/client/src/Pages/v1/Auth/Login/hooks/useValidateLoginForm.jsx +++ b/client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { loginCredentials } from "../../../../../Validation/validation.js"; +import { loginCredentials } from "../../../../Validation/validation.js"; const useValidateLoginForm = () => { const [errors, setErrors] = useState({ diff --git a/client/src/Pages/v1/Auth/Login/index.jsx b/client/src/Pages/Auth/Login/index.jsx similarity index 100% rename from client/src/Pages/v1/Auth/Login/index.jsx rename to client/src/Pages/Auth/Login/index.jsx diff --git a/client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx b/client/src/Pages/Auth/NewPasswordConfirmed.jsx similarity index 92% rename from client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx rename to client/src/Pages/Auth/NewPasswordConfirmed.jsx index 629494098..53b352853 100644 --- a/client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx +++ b/client/src/Pages/Auth/NewPasswordConfirmed.jsx @@ -2,10 +2,10 @@ import { Box, Button, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; import { useDispatch } from "react-redux"; -import { clearAuthState } from "../../../Features/Auth/authSlice.js"; -import Background from "../../../assets/Images/background-grid.svg?react"; -import ConfirmIcon from "../../../assets/icons/check-outlined.svg?react"; -import Logo from "../../../assets/icons/checkmate-icon.svg?react"; +import { clearAuthState } from "../../Features/Auth/authSlice.js"; +import Background from "@/assets/Images/background-grid.svg?react"; +import ConfirmIcon from "@/assets/icons/check-outlined.svg?react"; +import Logo from "@/assets/icons/checkmate-icon.svg?react"; import IconBox from "@/Components/v1/IconBox/index.jsx"; import { Trans, useTranslation } from "react-i18next"; import "./index.css"; diff --git a/client/src/Pages/v1/Auth/Register/index.jsx b/client/src/Pages/Auth/Register/index.jsx similarity index 97% rename from client/src/Pages/v1/Auth/Register/index.jsx rename to client/src/Pages/Auth/Register/index.jsx index d6515b85c..3345d8aa0 100644 --- a/client/src/Pages/v1/Auth/Register/index.jsx +++ b/client/src/Pages/Auth/Register/index.jsx @@ -13,11 +13,11 @@ import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { useDispatch, useSelector } from "react-redux"; import { useParams } from "react-router-dom"; -import { networkService } from "../../../../main.jsx"; -import { newOrChangedCredentials } from "../../../../Validation/validation.js"; -import { register } from "../../../../Features/Auth/authSlice.js"; +import { networkService } from "../../../main.jsx"; +import { newOrChangedCredentials } from "../../../Validation/validation.js"; +import { register } from "../../../Features/Auth/authSlice.js"; import AuthPageWrapper from "../components/AuthPageWrapper.jsx"; -import { createToast } from "../../../../Utils/toastUtils.jsx"; +import { createToast } from "../../../Utils/toastUtils.jsx"; import PropTypes from "prop-types"; const getFeedbackStatus = (form, errors, field, criteria) => { diff --git a/client/src/Pages/v1/Auth/SetNewPassword.jsx b/client/src/Pages/Auth/SetNewPassword.jsx similarity index 95% rename from client/src/Pages/v1/Auth/SetNewPassword.jsx rename to client/src/Pages/Auth/SetNewPassword.jsx index 45f3351f3..6525c3f27 100644 --- a/client/src/Pages/v1/Auth/SetNewPassword.jsx +++ b/client/src/Pages/Auth/SetNewPassword.jsx @@ -4,16 +4,16 @@ import { useNavigate } from "react-router-dom"; import { useParams } from "react-router-dom"; import { useTheme } from "@emotion/react"; import { Box, Stack, Typography, Button } from "@mui/material"; -import { setNewPassword } from "../../../Features/Auth/authSlice.js"; -import { createToast } from "../../../Utils/toastUtils.jsx"; -import { newOrChangedCredentials } from "../../../Validation/validation.js"; +import { setNewPassword } from "../../Features/Auth/authSlice.js"; +import { createToast } from "../../Utils/toastUtils.jsx"; +import { newOrChangedCredentials } from "../../Validation/validation.js"; import Check from "@/Components/v1/Check/Check.jsx"; import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; import IconBox from "@/Components/v1/IconBox/index.jsx"; -import LockIcon from "../../../assets/icons/lock.svg?react"; -import Logo from "../../../assets/icons/checkmate-icon.svg?react"; -import Background from "../../../assets/Images/background-grid.svg?react"; +import LockIcon from "@/assets/icons/lock.svg?react"; +import Logo from "@/assets/icons/checkmate-icon.svg?react"; +import Background from "@/assets/Images/background-grid.svg?react"; import "./index.css"; import { useValidatePassword } from "./hooks/useValidatePassword.jsx"; import { Trans, useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Auth/components/AuthHeader.jsx b/client/src/Pages/Auth/components/AuthHeader.jsx similarity index 87% rename from client/src/Pages/v1/Auth/components/AuthHeader.jsx rename to client/src/Pages/Auth/components/AuthHeader.jsx index a34e83d6a..56b69ef14 100644 --- a/client/src/Pages/v1/Auth/components/AuthHeader.jsx +++ b/client/src/Pages/Auth/components/AuthHeader.jsx @@ -1,8 +1,8 @@ // Components import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import Logo from "../../../../assets/icons/checkmate-icon.svg?react"; -import LanguageSelector from "../../../../Components/LanguageSelector.jsx"; +import Logo from "@/assets/icons/checkmate-icon.svg?react"; +import LanguageSelector from "@/Components/LanguageSelector.jsx"; import ThemeSwitch from "@/Components/v1/ThemeSwitch/index.jsx"; // Utils diff --git a/client/src/Pages/v1/Auth/components/AuthPageWrapper.jsx b/client/src/Pages/Auth/components/AuthPageWrapper.jsx similarity index 93% rename from client/src/Pages/v1/Auth/components/AuthPageWrapper.jsx rename to client/src/Pages/Auth/components/AuthPageWrapper.jsx index 7e358e6ab..a5fbe311b 100644 --- a/client/src/Pages/v1/Auth/components/AuthPageWrapper.jsx +++ b/client/src/Pages/Auth/components/AuthPageWrapper.jsx @@ -1,10 +1,10 @@ -import Background from "../../../../assets/Images/background-grid.svg?react"; +import Background from "@/assets/Images/background-grid.svg?react"; import Stack from "@mui/material/Stack"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import AuthHeader from "./AuthHeader.jsx"; import { useTheme } from "@mui/material/styles"; -import Logo from "../../../../assets/icons/checkmate-icon.svg?react"; +import Logo from "@/assets/icons/checkmate-icon.svg?react"; import PropTypes from "prop-types"; const AuthPageWrapper = ({ children, heading, welcome }) => { diff --git a/client/src/Pages/v1/Auth/components/PasswordTooltip.jsx b/client/src/Pages/Auth/components/PasswordTooltip.jsx similarity index 100% rename from client/src/Pages/v1/Auth/components/PasswordTooltip.jsx rename to client/src/Pages/Auth/components/PasswordTooltip.jsx diff --git a/client/src/Pages/v1/Auth/hooks/usePasswordFeedback.jsx b/client/src/Pages/Auth/hooks/usePasswordFeedback.jsx similarity index 96% rename from client/src/Pages/v1/Auth/hooks/usePasswordFeedback.jsx rename to client/src/Pages/Auth/hooks/usePasswordFeedback.jsx index 3c199e25f..73e8fb738 100644 --- a/client/src/Pages/v1/Auth/hooks/usePasswordFeedback.jsx +++ b/client/src/Pages/Auth/hooks/usePasswordFeedback.jsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { newOrChangedCredentials } from "../../../../Validation/validation.js"; +import { newOrChangedCredentials } from "../../../Validation/validation.js"; const usePasswordFeedback = () => { const [feedback, setFeedback] = useState({}); diff --git a/client/src/Pages/v1/Auth/hooks/useValidatePassword.jsx b/client/src/Pages/Auth/hooks/useValidatePassword.jsx similarity index 96% rename from client/src/Pages/v1/Auth/hooks/useValidatePassword.jsx rename to client/src/Pages/Auth/hooks/useValidatePassword.jsx index 231b8eeb8..014804d35 100644 --- a/client/src/Pages/v1/Auth/hooks/useValidatePassword.jsx +++ b/client/src/Pages/Auth/hooks/useValidatePassword.jsx @@ -1,5 +1,5 @@ import { useMemo, useState } from "react"; -import { newOrChangedCredentials } from "../../../../Validation/validation.js"; +import { newOrChangedCredentials } from "../../../Validation/validation.js"; const getFeedbackStatus = (form, errors, field, criteria) => { const fieldErrors = errors[field]; diff --git a/client/src/Pages/v1/Auth/index.css b/client/src/Pages/Auth/index.css similarity index 100% rename from client/src/Pages/v1/Auth/index.css rename to client/src/Pages/Auth/index.css diff --git a/client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx b/client/src/Pages/Incidents/Components/IncidentTable/index.jsx similarity index 95% rename from client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx rename to client/src/Pages/Incidents/Components/IncidentTable/index.jsx index df457c0e2..e066049e6 100644 --- a/client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx +++ b/client/src/Pages/Incidents/Components/IncidentTable/index.jsx @@ -13,14 +13,16 @@ import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx"; //Utils -import { formatDateWithTz } from "../../../../../Utils/timeUtils.js"; +import { formatDateWithTz } from "@/Utils/timeUtils.js"; import { useSelector } from "react-redux"; import { useState } from "react"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; -import { useFetchChecksTeam } from "../../../../../Hooks/v1/checkHooks.js"; -import { useFetchChecksByMonitor } from "../../../../../Hooks/v1/checkHooks.js"; -import { useResolveIncident } from "../../../../../Hooks/v1/checkHooks.js"; +import { + useFetchChecksTeam, + useFetchChecksByMonitor, + useResolveIncident, +} from "@/Hooks/checkHooks.js"; import { Button, Typography, useTheme } from "@mui/material"; import { lighten } from "@mui/material/styles"; diff --git a/client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx b/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx similarity index 100% rename from client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx rename to client/src/Pages/Incidents/Components/OptionsHeader/index.jsx diff --git a/client/src/Pages/v1/Incidents/Components/OptionsHeader/skeleton.jsx b/client/src/Pages/Incidents/Components/OptionsHeader/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/Incidents/Components/OptionsHeader/skeleton.jsx rename to client/src/Pages/Incidents/Components/OptionsHeader/skeleton.jsx diff --git a/client/src/Pages/v1/Incidents/Components/StatusBoxes/StatusBox.jsx b/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx similarity index 87% rename from client/src/Pages/v1/Incidents/Components/StatusBoxes/StatusBox.jsx rename to client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx index cd7078e6c..c27c5948c 100644 --- a/client/src/Pages/v1/Incidents/Components/StatusBoxes/StatusBox.jsx +++ b/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx @@ -1,11 +1,11 @@ import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { Box, Stack, Typography } from "@mui/material"; -import Background from "../../../../../assets/Images/background-grid.svg?react"; -import AlertIcon from "../../../../../assets/icons/alert-icon.svg?react"; -import CheckIcon from "../../../../../assets/icons/check-icon.svg?react"; -import CloseIcon from "../../../../../assets/icons/close-icon.svg?react"; -import WarningIcon from "../../../../../assets/icons/warning-icon.svg?react"; +import Background from "@/assets/Images/background-grid.svg?react"; +import AlertIcon from "@/assets/icons/alert-icon.svg?react"; +import CheckIcon from "@/assets/icons/check-icon.svg?react"; +import CloseIcon from "@/assets/icons/close-icon.svg?react"; +import WarningIcon from "@/assets/icons/warning-icon.svg?react"; const StatusBox = ({ title, value, status }) => { const theme = useTheme(); diff --git a/client/src/Pages/v1/Incidents/Components/StatusBoxes/index.jsx b/client/src/Pages/Incidents/Components/StatusBoxes/index.jsx similarity index 100% rename from client/src/Pages/v1/Incidents/Components/StatusBoxes/index.jsx rename to client/src/Pages/Incidents/Components/StatusBoxes/index.jsx diff --git a/client/src/Pages/v1/Incidents/Components/StatusBoxes/skeleton.jsx b/client/src/Pages/Incidents/Components/StatusBoxes/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/Incidents/Components/StatusBoxes/skeleton.jsx rename to client/src/Pages/Incidents/Components/StatusBoxes/skeleton.jsx diff --git a/client/src/Pages/v1/Incidents/index.jsx b/client/src/Pages/Incidents/index.jsx similarity index 93% rename from client/src/Pages/v1/Incidents/index.jsx rename to client/src/Pages/Incidents/index.jsx index 91cab2a40..698fc4a2f 100644 --- a/client/src/Pages/v1/Incidents/index.jsx +++ b/client/src/Pages/Incidents/index.jsx @@ -9,9 +9,11 @@ import { Box, Button } from "@mui/material"; //Utils import { useTheme } from "@emotion/react"; -import { useFetchMonitorsByTeamId } from "../../../Hooks/v1/monitorHooks.js"; -import { useFetchChecksSummaryByTeamId } from "../../../Hooks/v1/checkHooks.js"; -import { useAcknowledgeChecks } from "../../../Hooks/v1/checkHooks.js"; +import { useFetchMonitorsByTeamId } from "@/Hooks/monitorHooks.js"; +import { + useFetchChecksSummaryByTeamId, + useAcknowledgeChecks, +} from "@/Hooks/checkHooks.js"; import { useState, useEffect } from "react"; import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Incidents2/Components/ActiveIncidentsPanel/index.jsx b/client/src/Pages/Incidents2/Components/ActiveIncidentsPanel/index.jsx similarity index 100% rename from client/src/Pages/v1/Incidents2/Components/ActiveIncidentsPanel/index.jsx rename to client/src/Pages/Incidents2/Components/ActiveIncidentsPanel/index.jsx diff --git a/client/src/Pages/v1/Incidents2/Components/IncidentTable/index.jsx b/client/src/Pages/Incidents2/Components/IncidentTable/index.jsx similarity index 98% rename from client/src/Pages/v1/Incidents2/Components/IncidentTable/index.jsx rename to client/src/Pages/Incidents2/Components/IncidentTable/index.jsx index 8cac4e07b..d613e15c7 100644 --- a/client/src/Pages/v1/Incidents2/Components/IncidentTable/index.jsx +++ b/client/src/Pages/Incidents2/Components/IncidentTable/index.jsx @@ -8,7 +8,7 @@ import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx"; //Utils -import { formatDateWithTz } from "../../../../../Utils/timeUtils.js"; +import { formatDateWithTz } from "../../../../Utils/timeUtils.js"; import { useSelector } from "react-redux"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Incidents2/Components/IncidentsSummaryPanel/index.jsx b/client/src/Pages/Incidents2/Components/IncidentsSummaryPanel/index.jsx similarity index 100% rename from client/src/Pages/v1/Incidents2/Components/IncidentsSummaryPanel/index.jsx rename to client/src/Pages/Incidents2/Components/IncidentsSummaryPanel/index.jsx diff --git a/client/src/Pages/v1/Incidents2/Components/IncidentsSummaryPanel/skeleton.jsx b/client/src/Pages/Incidents2/Components/IncidentsSummaryPanel/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/Incidents2/Components/IncidentsSummaryPanel/skeleton.jsx rename to client/src/Pages/Incidents2/Components/IncidentsSummaryPanel/skeleton.jsx diff --git a/client/src/Pages/v1/Incidents2/Components/LatestIncidentsPanel/IncidentItem.jsx b/client/src/Pages/Incidents2/Components/LatestIncidentsPanel/IncidentItem.jsx similarity index 100% rename from client/src/Pages/v1/Incidents2/Components/LatestIncidentsPanel/IncidentItem.jsx rename to client/src/Pages/Incidents2/Components/LatestIncidentsPanel/IncidentItem.jsx diff --git a/client/src/Pages/v1/Incidents2/Components/LatestIncidentsPanel/index.jsx b/client/src/Pages/Incidents2/Components/LatestIncidentsPanel/index.jsx similarity index 100% rename from client/src/Pages/v1/Incidents2/Components/LatestIncidentsPanel/index.jsx rename to client/src/Pages/Incidents2/Components/LatestIncidentsPanel/index.jsx diff --git a/client/src/Pages/v1/Incidents2/Components/OptionsHeader/index.jsx b/client/src/Pages/Incidents2/Components/OptionsHeader/index.jsx similarity index 100% rename from client/src/Pages/v1/Incidents2/Components/OptionsHeader/index.jsx rename to client/src/Pages/Incidents2/Components/OptionsHeader/index.jsx diff --git a/client/src/Pages/v1/Incidents2/Components/StatisticsPanel/index.jsx b/client/src/Pages/Incidents2/Components/StatisticsPanel/index.jsx similarity index 100% rename from client/src/Pages/v1/Incidents2/Components/StatisticsPanel/index.jsx rename to client/src/Pages/Incidents2/Components/StatisticsPanel/index.jsx diff --git a/client/src/Pages/v1/Incidents2/Components/SummaryCard/index.jsx b/client/src/Pages/Incidents2/Components/SummaryCard/index.jsx similarity index 100% rename from client/src/Pages/v1/Incidents2/Components/SummaryCard/index.jsx rename to client/src/Pages/Incidents2/Components/SummaryCard/index.jsx diff --git a/client/src/Pages/v1/Incidents2/hooks/useFetchIncidents.js b/client/src/Pages/Incidents2/hooks/useFetchIncidents.js similarity index 97% rename from client/src/Pages/v1/Incidents2/hooks/useFetchIncidents.js rename to client/src/Pages/Incidents2/hooks/useFetchIncidents.js index a67f6b7ee..ab22c42e0 100644 --- a/client/src/Pages/v1/Incidents2/hooks/useFetchIncidents.js +++ b/client/src/Pages/Incidents2/hooks/useFetchIncidents.js @@ -1,6 +1,6 @@ import { useState, useCallback } from "react"; -import { networkService } from "../../../../main.jsx"; -import { createToast } from "../../../../Utils/toastUtils.jsx"; +import { networkService } from "../../../main.jsx"; +import { createToast } from "../../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; /** * Hook to fetch and manage incidents diff --git a/client/src/Pages/v1/Incidents2/index.jsx b/client/src/Pages/Incidents2/index.jsx similarity index 95% rename from client/src/Pages/v1/Incidents2/index.jsx rename to client/src/Pages/Incidents2/index.jsx index 2983a38e8..966388b51 100644 --- a/client/src/Pages/v1/Incidents2/index.jsx +++ b/client/src/Pages/Incidents2/index.jsx @@ -14,7 +14,7 @@ import { useTranslation } from "react-i18next"; // Hooks import useFetchIncidents from "./hooks/useFetchIncidents.js"; -import { useFetchMonitorsByTeamId } from "../../../Hooks/v1/monitorHooks.js"; +import { useFetchMonitorsByTeamId } from "../../Hooks/monitorHooks.js"; const Incidents2 = () => { const { t } = useTranslation(); @@ -34,7 +34,7 @@ const Incidents2 = () => { setUpdateTrigger((prev) => !prev); }; - const [monitors, , isLoadingMonitors, monitorsNetworkError] = useFetchMonitorsByTeamId( + const [monitors, isLoadingMonitors, monitorsNetworkError] = useFetchMonitorsByTeamId( {} ); diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx b/client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx similarity index 96% rename from client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx rename to client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx index 5fe1d8984..64f82cec2 100644 --- a/client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx +++ b/client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx @@ -1,7 +1,7 @@ import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; import { Box, Stack, Typography } from "@mui/material"; import { CustomThreshold } from "./CustomThreshold/index.jsx"; -import { capitalizeFirstLetter } from "../../../../../Utils/stringUtils.js"; +import { capitalizeFirstLetter } from "../../../../Utils/stringUtils.js"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import PropTypes from "prop-types"; diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/CustomThreshold/index.jsx b/client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Create/Components/CustomThreshold/index.jsx rename to client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/DiskSelection.jsx b/client/src/Pages/Infrastructure/Create/Components/DiskSelection.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Create/Components/DiskSelection.jsx rename to client/src/Pages/Infrastructure/Create/Components/DiskSelection.jsx diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/MonitorActionButtons.jsx b/client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Create/Components/MonitorActionButtons.jsx rename to client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx b/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx similarity index 95% rename from client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx rename to client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx index 570f38a7e..8fa6f4ff1 100644 --- a/client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx +++ b/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx @@ -1,5 +1,5 @@ import { Box, Stack, Tooltip, Typography } from "@mui/material"; -import { useMonitorUtils } from "../../../../../Hooks/v1/useMonitorUtils.js"; +import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils.js"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import PulseDot from "@/Components/v1/Animated/PulseDot.jsx"; diff --git a/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx rename to client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx diff --git a/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx similarity index 96% rename from client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx rename to client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx index 15a5f3c5d..c5c7aacf2 100644 --- a/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx +++ b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx @@ -1,7 +1,4 @@ -import { - useCreateMonitor, - useUpdateMonitor, -} from "../../../../../Hooks/v1/monitorHooks.js"; +import { useCreateMonitor, useUpdateMonitor } from "../../../../Hooks/monitorHooks.js"; const useInfrastructureSubmit = () => { const [createMonitor, isCreating] = useCreateMonitor(); const [updateMonitor, isUpdating] = useUpdateMonitor(); diff --git a/client/src/Pages/v1/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx b/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx similarity index 85% rename from client/src/Pages/v1/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx rename to client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx index 73097d0d8..f7665e65f 100644 --- a/client/src/Pages/v1/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx +++ b/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx @@ -1,6 +1,6 @@ import { useState } from "react"; -import { infrastructureMonitorValidation } from "../../../../../Validation/validation.js"; -import { createToast } from "../../../../../Utils/toastUtils.jsx"; +import { infrastructureMonitorValidation } from "../../../../Validation/validation.js"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; const useValidateInfrastructureForm = () => { const [errors, setErrors] = useState({}); diff --git a/client/src/Pages/v1/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx similarity index 97% rename from client/src/Pages/v1/Infrastructure/Create/index.jsx rename to client/src/Pages/Infrastructure/Create/index.jsx index c74d49816..b27c11cd4 100644 --- a/client/src/Pages/v1/Infrastructure/Create/index.jsx +++ b/client/src/Pages/Infrastructure/Create/index.jsx @@ -13,8 +13,8 @@ import CustomAlertsSection from "./Components/CustomAlertsSection.jsx"; import DiskSelection from "./Components/DiskSelection.jsx"; // Utils import NotificationsConfig from "@/Components/v1/NotificationConfig/index.jsx"; -import { useGetNotificationsByTeamId } from "../../../../Hooks/v1/useNotifications.js"; -import { networkService } from "../../../../Utils/NetworkService"; +import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications.js"; +import { networkService } from "../../../Utils/NetworkService.js"; import { useParams } from "react-router-dom"; import { useState, useEffect } from "react"; import { useTheme } from "@emotion/react"; @@ -24,7 +24,7 @@ import { useFetchGlobalSettings, useFetchHardwareMonitorById, usePauseMonitor, -} from "../../../../Hooks/v1/monitorHooks.js"; +} from "../../../Hooks/monitorHooks.js"; import useInfrastructureMonitorForm from "./hooks/useInfrastructureMonitorForm.jsx"; import useValidateInfrastructureForm from "./hooks/useValidateInfrastructureForm.jsx"; import useInfrastructureSubmit from "./hooks/useInfrastructureSubmit.jsx"; @@ -150,7 +150,8 @@ const CreateInfrastructureMonitor = () => { const handleRemove = async (event) => { event.preventDefault(); - await deleteMonitor({ monitor, redirect: "/infrastructure" }); + const TEMP_MONITOR = { id: monitor._id }; + await deleteMonitor({ monitor: TEMP_MONITOR, redirect: "/infrastructure" }); }; const isBusy = diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx b/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx rename to client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/index.jsx b/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/index.jsx rename to client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx b/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx rename to client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/BaseContainer/index.jsx b/client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Components/BaseContainer/index.jsx rename to client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx b/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx rename to client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx b/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx rename to client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx b/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx rename to client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx rename to client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx rename to client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/index.jsx rename to client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/skeleton.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/skeleton.jsx rename to client/src/Pages/Infrastructure/Details/Components/NetworkStats/skeleton.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx b/client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx similarity index 97% rename from client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx rename to client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx index 7f09e82d5..ef91709cf 100644 --- a/client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx @@ -4,7 +4,7 @@ import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx"; import StatBox from "@/Components/v1/StatBox/index.jsx"; //Utils -import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; +import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils.js"; import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Infrastructure/Details/Hooks/useHardwareUtils.jsx b/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Details/Hooks/useHardwareUtils.jsx rename to client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx diff --git a/client/src/Pages/v1/Infrastructure/Details/index.jsx b/client/src/Pages/Infrastructure/Details/index.jsx similarity index 96% rename from client/src/Pages/v1/Infrastructure/Details/index.jsx rename to client/src/Pages/Infrastructure/Details/index.jsx index f9c51c27d..62a5c2642 100644 --- a/client/src/Pages/v1/Infrastructure/Details/index.jsx +++ b/client/src/Pages/Infrastructure/Details/index.jsx @@ -13,8 +13,8 @@ import TabContext from "@mui/lab/TabContext"; // Utils import { useTheme } from "@emotion/react"; -import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; -import { useFetchHardwareMonitorById } from "../../../../Hooks/v1/monitorHooks.js"; +import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; +import { useFetchHardwareMonitorById } from "../../../Hooks/monitorHooks.js"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { useParams } from "react-router-dom"; diff --git a/client/src/Pages/v1/Infrastructure/Monitors/Components/Filters/index.jsx b/client/src/Pages/Infrastructure/Monitors/Components/Filters/index.jsx similarity index 100% rename from client/src/Pages/v1/Infrastructure/Monitors/Components/Filters/index.jsx rename to client/src/Pages/Infrastructure/Monitors/Components/Filters/index.jsx diff --git a/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx similarity index 95% rename from client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx rename to client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx index cc2cd5f98..5f0360312 100644 --- a/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx +++ b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx @@ -7,12 +7,12 @@ import { Stack } from "@mui/material"; import { InfrastructureMenu } from "../MonitorsTableMenu/index.jsx"; import LoadingSpinner from "../../../../Uptime/Monitors/Components/LoadingSpinner/index.jsx"; // Assets -import CPUChipIcon from "../../../../../../assets/icons/cpu-chip.svg?react"; +import CPUChipIcon from "@/assets/icons/cpu-chip.svg?react"; import CustomGauge from "@/Components/v1/Charts/CustomGauge/index.jsx"; // Utils import { useTheme } from "@emotion/react"; -import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; +import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils.js"; import { useNavigate } from "react-router-dom"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; @@ -115,7 +115,7 @@ const MonitorsTable = ({ : theme.palette.success.main; return { - id: monitor._id, + id: monitor.id, name: monitor.name, url: monitor.url, processor, diff --git a/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx similarity index 94% rename from client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx rename to client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx index d3af34d00..39aa84e90 100644 --- a/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx +++ b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx @@ -3,13 +3,13 @@ import { useRef, useState } from "react"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import { createToast } from "../../../../../../Utils/toastUtils.jsx"; +import { createToast } from "@/Utils/toastUtils.jsx"; import { IconButton, Menu, MenuItem } from "@mui/material"; -import Settings from "../../../../../../assets/icons/settings-bold.svg?react"; +import Settings from "@/assets/icons/settings-bold.svg?react"; import PropTypes from "prop-types"; import Dialog from "@/Components/v1/Dialog/index.jsx"; -import { networkService } from "../../../../../../Utils/NetworkService.js"; -import { usePauseMonitor } from "../../../../../../Hooks/v1/monitorHooks.js"; +import { networkService } from "@/Utils/NetworkService.js"; +import { usePauseMonitor } from "@/Hooks/monitorHooks.js"; import { useTranslation } from "react-i18next"; /** diff --git a/client/src/Pages/v1/Infrastructure/Monitors/index.jsx b/client/src/Pages/Infrastructure/Monitors/index.jsx similarity index 89% rename from client/src/Pages/v1/Infrastructure/Monitors/index.jsx rename to client/src/Pages/Infrastructure/Monitors/index.jsx index 6c3a657ab..b25636fa9 100644 --- a/client/src/Pages/v1/Infrastructure/Monitors/index.jsx +++ b/client/src/Pages/Infrastructure/Monitors/index.jsx @@ -11,11 +11,12 @@ import SearchComponent from "../../Uptime/Monitors/Components/SearchComponent/in // Utils import { useTheme } from "@emotion/react"; import { useEffect, useState } from "react"; -import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; +import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; import { useTranslation } from "react-i18next"; -import { useFetchMonitorsByTeamId } from "../../../../Hooks/v1/monitorHooks.js"; +import { useFetchMonitorsWithChecks } from "@/Hooks/monitorHooks.js"; + import { useDispatch, useSelector } from "react-redux"; -import { setRowsPerPage } from "../../../../Features/UI/uiSlice.js"; +import { setRowsPerPage } from "../../../Features/UI/uiSlice.js"; // Constants const TYPES = ["hardware"]; const BREADCRUMBS = [{ name: `infrastructure`, path: "/infrastructure" }]; @@ -70,14 +71,14 @@ const InfrastructureMonitors = () => { const field = toFilterStatus !== undefined ? "status" : undefined; - const [monitors, summary, isLoading, networkError] = useFetchMonitorsByTeamId({ - limit: 1, + const [monitors, count, isLoading, networkError] = useFetchMonitorsWithChecks({ types: TYPES, - page, + limit: 1, + page: page, field: field, filter: toFilterStatus ?? search, - rowsPerPage, - updateTrigger, + rowsPerPage: rowsPerPage, + monitorUpdateTrigger: updateTrigger, }); return ( @@ -99,7 +100,7 @@ const InfrastructureMonitors = () => { { isSearching={isSearching} /> { @@ -26,12 +26,12 @@ const Diagnostics = () => { status="up" heading={t("status")} subHeading={ - error + error ? t("logsPage.logLevelSelect.values.error") - : isLoading + : isLoading ? t("commonSaving") - : diagnostics - ? t("diagnosticsPage.diagnosticDescription") + : diagnostics + ? t("diagnosticsPage.diagnosticDescription") : t("general.noOptionsFound", { unit: "data" }) } /> diff --git a/client/src/Pages/v1/Logs/Diagnostics/utils/utils.js b/client/src/Pages/Logs/Diagnostics/utils/utils.js similarity index 100% rename from client/src/Pages/v1/Logs/Diagnostics/utils/utils.js rename to client/src/Pages/Logs/Diagnostics/utils/utils.js diff --git a/client/src/Pages/v1/Logs/Logs/index.jsx b/client/src/Pages/Logs/Logs/index.jsx similarity index 97% rename from client/src/Pages/v1/Logs/Logs/index.jsx rename to client/src/Pages/Logs/Logs/index.jsx index 64c4cb97a..c44a83ed9 100644 --- a/client/src/Pages/v1/Logs/Logs/index.jsx +++ b/client/src/Pages/Logs/Logs/index.jsx @@ -4,7 +4,7 @@ import Select from "@/Components/v1/Inputs/Select/index.jsx"; import Typography from "@mui/material/Typography"; import Divider from "@mui/material/Divider"; -import { useFetchLogs } from "../../../../Hooks/v1/logHooks.js"; +import { useFetchLogs } from "../../../Hooks/logHooks.js"; import { useTheme } from "@emotion/react"; import { useState } from "react"; import { useTranslation } from "react-i18next"; @@ -51,7 +51,6 @@ const Logs = () => { ]; return ( - { MonitorListItem.propTypes = { monitor: PropTypes.shape({ - _id: PropTypes.string.isRequired, + id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, }).isRequired, onDelete: PropTypes.func.isRequired, @@ -33,7 +33,7 @@ MonitorListItem.propTypes = { const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => { const onDelete = (monitorToDelete) => { const newMonitors = selectedMonitors.filter( - (monitor) => monitor._id !== monitorToDelete._id + (monitor) => monitor.id !== monitorToDelete.id ); setSelectedMonitors(newMonitors); }; @@ -47,7 +47,7 @@ const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => { > {selectedMonitors?.map((monitor) => ( @@ -59,7 +59,7 @@ const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => { MonitorList.propTypes = { selectedMonitors: PropTypes.arrayOf( PropTypes.shape({ - _id: PropTypes.string.isRequired, + id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, }) ).isRequired, diff --git a/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx similarity index 100% rename from client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx rename to client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx diff --git a/client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx b/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx similarity index 81% rename from client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx rename to client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx index a75cbe206..22a61084d 100644 --- a/client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx +++ b/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx @@ -1,4 +1,4 @@ -import { networkService } from "../../../../../main.jsx"; +import { networkService } from "../../../../main.jsx"; import dayjs from "dayjs"; import { MS_PER_SECOND, @@ -6,7 +6,7 @@ import { MS_PER_HOUR, MS_PER_DAY, MS_PER_WEEK, -} from "../../../../../Utils/timeUtils.js"; +} from "../../../../Utils/timeUtils.js"; const useMaintenanceActions = () => { const MS_LOOKUP = { @@ -22,6 +22,12 @@ const useMaintenanceActions = () => { weekly: MS_PER_DAY * 7, }; const handleSubmitForm = async (maintenanceWindowId, form) => { + const toStringId = (monitor) => { + if (!monitor) { + return ""; + } + return monitor._id ?? monitor.id ?? ""; + }; const start = dayjs(form.startDate) .set("hour", form.startTime.hour()) .set("minute", form.startTime.minute()); @@ -33,7 +39,7 @@ const useMaintenanceActions = () => { const repeat = REPEAT_LOOKUP[form.repeat]; const submit = { - monitors: form.monitors.map((monitor) => monitor._id), + monitors: form.monitors.map((monitor) => toStringId(monitor)).filter(Boolean), name: form.name, start: start.toISOString(), end: end.toISOString(), diff --git a/client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx b/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx similarity index 88% rename from client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx rename to client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx index d21906e41..3414334a7 100644 --- a/client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx +++ b/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx @@ -1,4 +1,4 @@ -import { networkService } from "../../../../../main.jsx"; +import { networkService } from "../../../../main.jsx"; import dayjs from "dayjs"; import { MS_PER_SECOND, @@ -6,7 +6,7 @@ import { MS_PER_HOUR, MS_PER_DAY, MS_PER_WEEK, -} from "../../../../../Utils/timeUtils.js"; +} from "../../../../Utils/timeUtils.js"; const useMaintenanceData = () => { const REVERSE_REPEAT_LOOKUP = { 0: "none", @@ -41,7 +41,7 @@ const useMaintenanceData = () => { limit: null, types: ["http", "ping", "pagespeed", "port"], }); - const fetchedMonitors = response.data.data.monitors; + const fetchedMonitors = response.data.data; return fetchedMonitors; }; @@ -55,7 +55,9 @@ const useMaintenanceData = () => { const endTime = dayjs(end); const durationInMs = endTime.diff(startTime, "milliseconds").toString(); const { duration, durationUnit } = getDurationAndUnit(durationInMs); - const monitor = monitorList.find((monitor) => monitor._id === monitorId); + const monitor = monitorList.find( + (monitor) => (monitor._id ?? monitor.id) === monitorId + ); const maintenanceWindowInformation = { name, repeat: REVERSE_REPEAT_LOOKUP[repeat], diff --git a/client/src/Pages/v1/Maintenance/CreateMaintenance/index.css b/client/src/Pages/Maintenance/CreateMaintenance/index.css similarity index 100% rename from client/src/Pages/v1/Maintenance/CreateMaintenance/index.css rename to client/src/Pages/Maintenance/CreateMaintenance/index.css diff --git a/client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx similarity index 97% rename from client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx rename to client/src/Pages/Maintenance/CreateMaintenance/index.jsx index d0e7b03cd..759679a2d 100644 --- a/client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx +++ b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx @@ -7,8 +7,8 @@ import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { LocalizationProvider } from "@mui/x-date-pickers"; import { DatePicker } from "@mui/x-date-pickers/DatePicker"; import { MobileTimePicker } from "@mui/x-date-pickers/MobileTimePicker"; -import { maintenanceWindowValidation } from "../../../../Validation/validation.js"; -import { createToast } from "../../../../Utils/toastUtils.jsx"; +import { maintenanceWindowValidation } from "@/Validation/validation.js"; +import { createToast } from "@/Utils/toastUtils.jsx"; import MonitorList from "./Components/MonitorList/index.jsx"; import ConfigSelect from "./Components/ConfigSelect/index.jsx"; import useMaintenanceData from "./hooks/useMaintenanceData.jsx"; @@ -16,13 +16,13 @@ import useMaintenanceActions from "./hooks/useMaintenanceActions.jsx"; import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; -import CalendarIcon from "../../../../assets/icons/calendar.svg?react"; +import CalendarIcon from "@/assets/icons/calendar.svg?react"; import "./index.css"; import Search from "@/Components/v1/Inputs/Search/index.jsx"; -import { logger } from "../../../../Utils/Logger.js"; +import { logger } from "@/Utils/Logger.js"; import { useNavigate, useParams } from "react-router-dom"; -import { buildErrors, hasValidationErrors } from "../../../../Validation/error.js"; +import { buildErrors, hasValidationErrors } from "@/Validation/error.js"; import { useTranslation } from "react-i18next"; import dayjs from "dayjs"; @@ -108,6 +108,7 @@ const CreateMaintenance = () => { const handleSubmit = async () => { if (hasValidationErrors(form, maintenanceWindowValidation, setErrors)) return; + const request = handleSubmitForm(maintenanceWindowId, form); try { setIsLoading(true); diff --git a/client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx b/client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx similarity index 93% rename from client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx rename to client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx index b2654da49..38a3d8e64 100644 --- a/client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx +++ b/client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx @@ -3,11 +3,11 @@ import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; import { useSelector } from "react-redux"; import { IconButton, Menu, MenuItem } from "@mui/material"; -import { logger } from "../../../../../Utils/Logger.js"; -import Settings from "../../../../../assets/icons/settings-bold.svg?react"; +import { logger } from "@/Utils/Logger.js"; +import Settings from "@/assets/icons/settings-bold.svg?react"; import PropTypes from "prop-types"; -import { networkService } from "../../../../../main.jsx"; -import { createToast } from "../../../../../Utils/toastUtils.jsx"; +import { networkService } from "../../../../main.jsx"; +import { createToast } from "@/Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; import Dialog from "@/Components/v1/Dialog/index.jsx"; diff --git a/client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx b/client/src/Pages/Maintenance/MaintenanceTable/index.jsx similarity index 97% rename from client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx rename to client/src/Pages/Maintenance/MaintenanceTable/index.jsx index f1d44f944..faeb2072c 100644 --- a/client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx +++ b/client/src/Pages/Maintenance/MaintenanceTable/index.jsx @@ -7,9 +7,9 @@ import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded"; import ActionsMenu from "./ActionsMenu/index.jsx"; import { memo } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { formatDurationRounded } from "../../../../Utils/timeUtils.js"; +import { formatDurationRounded } from "../../../Utils/timeUtils.js"; import { StatusLabel } from "@/Components/v1/Label/index.jsx"; -import { setRowsPerPage } from "../../../../Features/UI/uiSlice.js"; +import { setRowsPerPage } from "../../../Features/UI/uiSlice.js"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; diff --git a/client/src/Pages/v1/Maintenance/index.css b/client/src/Pages/Maintenance/index.css similarity index 100% rename from client/src/Pages/v1/Maintenance/index.css rename to client/src/Pages/Maintenance/index.css diff --git a/client/src/Pages/v1/Maintenance/index.jsx b/client/src/Pages/Maintenance/index.jsx similarity index 98% rename from client/src/Pages/v1/Maintenance/index.jsx rename to client/src/Pages/Maintenance/index.jsx index cba0e9f18..b49cd9f6e 100644 --- a/client/src/Pages/v1/Maintenance/index.jsx +++ b/client/src/Pages/Maintenance/index.jsx @@ -4,7 +4,7 @@ import { useState, useEffect } from "react"; import "./index.css"; import MaintenanceTable from "./MaintenanceTable/index.jsx"; import { useSelector } from "react-redux"; -import { networkService } from "../../../main.jsx"; +import { networkService } from "../../main.jsx"; import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/NotFound/index.jsx b/client/src/Pages/NotFound/index.jsx similarity index 96% rename from client/src/Pages/v1/NotFound/index.jsx rename to client/src/Pages/NotFound/index.jsx index 48825c9f6..27e3173b0 100644 --- a/client/src/Pages/v1/NotFound/index.jsx +++ b/client/src/Pages/NotFound/index.jsx @@ -1,5 +1,5 @@ import PropTypes from "prop-types"; -import NotFoundSvg from "../../../assets/Images/sushi_404.svg"; +import NotFoundSvg from "@/assets/Images/sushi_404.svg"; import { Button, Stack, Typography } from "@mui/material"; import { useNavigate } from "react-router-dom"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/Notifications/components/ActionMenu.jsx b/client/src/Pages/Notifications/components/ActionMenu.jsx similarity index 100% rename from client/src/Pages/v1/Notifications/components/ActionMenu.jsx rename to client/src/Pages/Notifications/components/ActionMenu.jsx diff --git a/client/src/Pages/v1/Notifications/create/index.jsx b/client/src/Pages/Notifications/create/index.jsx similarity index 97% rename from client/src/Pages/v1/Notifications/create/index.jsx rename to client/src/Pages/Notifications/create/index.jsx index b1c50493c..11bf2a95d 100644 --- a/client/src/Pages/v1/Notifications/create/index.jsx +++ b/client/src/Pages/Notifications/create/index.jsx @@ -18,9 +18,9 @@ import { useEditNotification, useTestNotification, useDeleteNotification, -} from "../../../../Hooks/v1/useNotifications.js"; -import { notificationValidation } from "../../../../Validation/validation.js"; -import { createToast } from "../../../../Utils/toastUtils.jsx"; +} from "../../../Hooks/useNotifications.js"; +import { notificationValidation } from "../../../Validation/validation.js"; +import { createToast } from "../../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; import { useParams, useNavigate } from "react-router-dom"; import { diff --git a/client/src/Pages/v1/Notifications/index.jsx b/client/src/Pages/Notifications/index.jsx similarity index 98% rename from client/src/Pages/v1/Notifications/index.jsx rename to client/src/Pages/Notifications/index.jsx index 78ec6e9bb..8ebdb22ec 100644 --- a/client/src/Pages/v1/Notifications/index.jsx +++ b/client/src/Pages/Notifications/index.jsx @@ -14,7 +14,7 @@ import { useNavigate } from "react-router-dom"; import { useGetNotificationsByTeamId, useDeleteNotification, -} from "../../../Hooks/v1/useNotifications.js"; +} from "../../Hooks/useNotifications.js"; import { useTranslation } from "react-i18next"; const Notifications = () => { diff --git a/client/src/Pages/v1/Notifications/utils.js b/client/src/Pages/Notifications/utils.js similarity index 100% rename from client/src/Pages/v1/Notifications/utils.js rename to client/src/Pages/Notifications/utils.js diff --git a/client/src/Pages/v1/PageSpeed/Create/index.css b/client/src/Pages/PageSpeed/Create/index.css similarity index 100% rename from client/src/Pages/v1/PageSpeed/Create/index.css rename to client/src/Pages/PageSpeed/Create/index.css diff --git a/client/src/Pages/v1/PageSpeed/Create/index.jsx b/client/src/Pages/PageSpeed/Create/index.jsx similarity index 96% rename from client/src/Pages/v1/PageSpeed/Create/index.jsx rename to client/src/Pages/PageSpeed/Create/index.jsx index cb856bd37..2cce26159 100644 --- a/client/src/Pages/v1/PageSpeed/Create/index.jsx +++ b/client/src/Pages/PageSpeed/Create/index.jsx @@ -16,22 +16,22 @@ import Radio from "@/Components/v1/Inputs/Radio/index.jsx"; // Utils import { useState, useEffect } from "react"; import { useSelector } from "react-redux"; -import { monitorValidation } from "../../../../Validation/validation.js"; -import { parseDomainName } from "../../../../Utils/monitorUtils.js"; +import { monitorValidation } from "../../../Validation/validation.js"; +import { parseDomainName } from "../../../Utils/monitorUtils.js"; import { useTranslation } from "react-i18next"; -import { useGetNotificationsByTeamId } from "../../../../Hooks/v1/useNotifications.js"; +import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications.js"; import { useTheme } from "@emotion/react"; -import { createToast } from "../../../../Utils/toastUtils.jsx"; +import { createToast } from "../../../Utils/toastUtils.jsx"; import { useParams } from "react-router"; -import { useMonitorUtils } from "../../../../Hooks/v1/useMonitorUtils.js"; +import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js"; import { useCreateMonitor, useFetchMonitorById, useDeleteMonitor, useUpdateMonitor, usePauseMonitor, -} from "../../../../Hooks/v1/monitorHooks.js"; +} from "../../../Hooks/monitorHooks.js"; const PageSpeedSetup = () => { const { monitorId } = useParams(); @@ -178,7 +178,8 @@ const PageSpeedSetup = () => { const handleRemove = async (event) => { event.preventDefault(); - await deleteMonitor({ monitor, redirect: "/pagespeed" }); + const TEMP_MONITOR = { id: monitor._id }; + await deleteMonitor({ monitor: TEMP_MONITOR, redirect: "/pagespeed" }); }; const isBusy = isLoading || isCreating || isDeleting || isUpdating || isPausing; diff --git a/client/src/Pages/v1/PageSpeed/Create/skeleton.jsx b/client/src/Pages/PageSpeed/Create/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/PageSpeed/Create/skeleton.jsx rename to client/src/Pages/PageSpeed/Create/skeleton.jsx diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChart.jsx b/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx similarity index 99% rename from client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChart.jsx rename to client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx index 5c79cc06c..06b92c933 100644 --- a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChart.jsx +++ b/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx @@ -11,7 +11,7 @@ import { import { useTheme } from "@emotion/react"; import { useMemo, useState } from "react"; import { Box, Stack, Typography } from "@mui/material"; -import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js"; +import { formatDateWithTz } from "../../../../../Utils/timeUtils.js"; import { useSelector } from "react-redux"; const config = { diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx b/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx similarity index 94% rename from client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx rename to client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx index c0da1600f..113e4d618 100644 --- a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx +++ b/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx @@ -1,6 +1,6 @@ import { Box, Typography, Divider } from "@mui/material"; import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx"; -import MetricsIcon from "../../../../../../assets/icons/ruler-icon.svg?react"; +import MetricsIcon from "@/assets/icons/ruler-icon.svg?react"; import LegendBox from "@/Components/v1/Charts/LegendBox/index.jsx"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChart.jsx b/client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx similarity index 100% rename from client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChart.jsx rename to client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx b/client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx similarity index 96% rename from client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx rename to client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx index 1297bd699..43e2371b4 100644 --- a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx +++ b/client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx @@ -1,7 +1,7 @@ import { Stack, Box, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import LegendBox from "@/Components/v1/Charts/LegendBox/index.jsx"; -import SpeedometerIcon from "../../../../../../assets/icons/speedometer-icon.svg?react"; +import SpeedometerIcon from "@/assets/icons/speedometer-icon.svg?react"; import PropTypes from "prop-types"; const PieChartLegend = ({ audits }) => { diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx b/client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx similarity index 93% rename from client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx rename to client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx index 1281a396e..aa88d57e5 100644 --- a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx +++ b/client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx @@ -2,7 +2,7 @@ import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx"; import AreaChart from "../Charts/AreaChart.jsx"; import AreaChartLegend from "../Charts/AreaChartLegend.jsx"; import SkeletonLayout from "./skeleton.jsx"; -import ScoreIcon from "../../../../../../assets/icons/monitor-graph-line.svg?react"; +import ScoreIcon from "@/assets/icons/monitor-graph-line.svg?react"; import { Stack } from "@mui/material"; import PropTypes from "prop-types"; diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx b/client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx rename to client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx b/client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx similarity index 91% rename from client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx rename to client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx index ff265317c..493d1ece6 100644 --- a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx +++ b/client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx @@ -1,7 +1,7 @@ import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx"; import StatBox from "@/Components/v1/StatBox/index.jsx"; import { Typography } from "@mui/material"; -import { getHumanReadableDuration } from "../../../../../../Utils/timeUtils.js"; +import { getHumanReadableDuration } from "../../../../../Utils/timeUtils.js"; import { useTranslation } from "react-i18next"; const PageSpeedStatusBoxes = ({ shouldRender, monitor }) => { diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx b/client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx similarity index 93% rename from client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx rename to client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx index 27f9e885b..c2eecaa6a 100644 --- a/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx +++ b/client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx @@ -1,5 +1,5 @@ import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx"; -import PerformanceIcon from "../../../../../../assets/icons/performance-report.svg?react"; +import PerformanceIcon from "@/assets/icons/performance-report.svg?react"; import PieChart from "../Charts/PieChart.jsx"; import { Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx b/client/src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx rename to client/src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx diff --git a/client/src/Pages/v1/PageSpeed/Details/index.jsx b/client/src/Pages/PageSpeed/Details/index.jsx similarity index 88% rename from client/src/Pages/v1/PageSpeed/Details/index.jsx rename to client/src/Pages/PageSpeed/Details/index.jsx index 836f0ca37..df411048a 100644 --- a/client/src/Pages/v1/PageSpeed/Details/index.jsx +++ b/client/src/Pages/PageSpeed/Details/index.jsx @@ -9,16 +9,15 @@ import PerformanceReport from "./Components/PerformanceReport/index.jsx"; import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; // Utils import { useTheme } from "@emotion/react"; -import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; +import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; import { useParams } from "react-router-dom"; -import { useFetchStatsByMonitorId } from "../../../../Hooks/v1/monitorHooks.js"; +import { useFetchPageSpeedMonitorById } from "../../../Hooks/monitorHooks.js"; import { useState } from "react"; import { useTranslation } from "react-i18next"; // Constants const BREADCRUMBS = [ { name: "pagespeed", path: "/pagespeed" }, { name: "details", path: `` }, - // { name: "details", path: `/pagespeed/${monitorId}` }, // Not needed? ]; const PageSpeedDetails = () => { @@ -36,13 +35,9 @@ const PageSpeedDetails = () => { }); const [trigger, setTrigger] = useState(false); // Network - const [monitor, audits, isLoading, networkError] = useFetchStatsByMonitorId({ + const [monitor, isLoading, networkError] = useFetchPageSpeedMonitorById({ monitorId, - sortOrder: "desc", - limit: 50, dateRange: "day", - numToDisplay: null, - normalize: null, updateTrigger: trigger, }); @@ -116,7 +111,7 @@ const PageSpeedDetails = () => { /> ); diff --git a/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx b/client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx similarity index 96% rename from client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx rename to client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx index 4e551526b..8a6444d1b 100644 --- a/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx +++ b/client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx @@ -1,17 +1,14 @@ import PropTypes from "prop-types"; // import PageSpeedIcon from "../../../../assets/icons/page-speed.svg?react"; -import PageSpeedIcon from "../../../../../../assets/icons/page-speed.svg?react"; +import PageSpeedIcon from "@/assets/icons/page-speed.svg?react"; import { StatusLabel } from "@/Components/v1/Label/index.jsx"; import { Box, Grid, Stack, Typography } from "@mui/material"; import { useNavigate } from "react-router-dom"; import { useTheme } from "@emotion/react"; import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip } from "recharts"; import { useSelector } from "react-redux"; -import { - formatDateWithTz, - formatDurationSplit, -} from "../../../../../../Utils/timeUtils.js"; -import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; +import { formatDateWithTz, formatDurationSplit } from "@/Utils/timeUtils.js"; +import { useMonitorUtils } from "@/Hooks/useMonitorUtils.js"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import IconBox from "@/Components/v1/IconBox/index.jsx"; @@ -224,7 +221,7 @@ const Card = ({ monitor }) => { navigate(`/pagespeed/${monitor._id}`)} + onClick={() => navigate(`/pagespeed/${monitor.id}`)} border={1} borderColor={theme.palette.primary.lowContrast} borderRadius={theme.shape.borderRadius} diff --git a/client/src/Pages/v1/PageSpeed/Monitors/Components/MonitorGrid/index.jsx b/client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx similarity index 100% rename from client/src/Pages/v1/PageSpeed/Monitors/Components/MonitorGrid/index.jsx rename to client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx diff --git a/client/src/Pages/v1/PageSpeed/Monitors/index.jsx b/client/src/Pages/PageSpeed/Monitors/index.jsx similarity index 70% rename from client/src/Pages/v1/PageSpeed/Monitors/index.jsx rename to client/src/Pages/PageSpeed/Monitors/index.jsx index 7485a72c9..4530a41e9 100644 --- a/client/src/Pages/v1/PageSpeed/Monitors/index.jsx +++ b/client/src/Pages/PageSpeed/Monitors/index.jsx @@ -10,9 +10,9 @@ import FallbackPageSpeedWarning from "@/Components/v1/Fallback/FallbackPageSpeed // Utils import { useTheme } from "@emotion/react"; -import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; -import { useFetchMonitorsByTeamId } from "../../../../Hooks/v1/monitorHooks.js"; -import { useFetchSettings } from "../../../../Hooks/v1/settingsHooks.js"; +import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; +import { useFetchMonitorsWithChecks } from "@/Hooks/monitorHooks.js"; +import { useFetchSettings } from "@/Hooks/settingsHooks.js"; // Constants const BREADCRUMBS = [{ name: `pagespeed`, path: "/pagespeed" }]; const TYPES = ["pagespeed"]; @@ -20,14 +20,20 @@ const PageSpeed = () => { const theme = useTheme(); const isAdmin = useIsAdmin(); - const [monitors, monitorsSummary, isLoading, networkError] = useFetchMonitorsByTeamId({ - limit: 10, + const [ + monitorsWithChecks, + monitorsWithChecksCount, + monitorsWithChecksIsLoading, + monitorsWithChecksNetworkError, + ] = useFetchMonitorsWithChecks({ types: TYPES, + limit: 10, page: null, rowsPerPage: null, filter: null, field: null, order: null, + monitorUpdateTrigger: null, }); const [settingsData, setSettingsData] = useState(undefined); @@ -40,9 +46,9 @@ const PageSpeed = () => { return ( <> { diff --git a/client/src/Pages/v1/ServerUnreachable.jsx b/client/src/Pages/ServerUnreachable.jsx similarity index 92% rename from client/src/Pages/v1/ServerUnreachable.jsx rename to client/src/Pages/ServerUnreachable.jsx index 8230f473b..bfbbfa90a 100644 --- a/client/src/Pages/v1/ServerUnreachable.jsx +++ b/client/src/Pages/ServerUnreachable.jsx @@ -2,14 +2,14 @@ import React, { useState } from "react"; import { Box, Typography, Button, Stack } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import { networkService } from "../../Utils/NetworkService.js"; +import { networkService } from "@/Utils/NetworkService.js"; import Alert from "@/Components/v1/Alert/index.jsx"; -import { createToast } from "../../Utils/toastUtils.jsx"; +import { createToast } from "@/Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; -import Background from "../../assets/Images/background-grid.svg?react"; -import Logo from "../../assets/icons/checkmate-icon.svg?react"; +import Background from "@/assets/Images/background-grid.svg?react"; +import Logo from "@/assets/icons/checkmate-icon.svg?react"; import ThemeSwitch from "@/Components/v1/ThemeSwitch/index.jsx"; -import LanguageSelector from "../../Components/LanguageSelector.jsx"; +import LanguageSelector from "@/Components/LanguageSelector.jsx"; const ServerUnreachable = () => { const theme = useTheme(); diff --git a/client/src/Pages/v1/Settings/SettingsAbout.jsx b/client/src/Pages/Settings/SettingsAbout.jsx similarity index 100% rename from client/src/Pages/v1/Settings/SettingsAbout.jsx rename to client/src/Pages/Settings/SettingsAbout.jsx diff --git a/client/src/Pages/v1/Settings/SettingsDemoMonitors.jsx b/client/src/Pages/Settings/SettingsDemoMonitors.jsx similarity index 100% rename from client/src/Pages/v1/Settings/SettingsDemoMonitors.jsx rename to client/src/Pages/Settings/SettingsDemoMonitors.jsx diff --git a/client/src/Pages/v1/Settings/SettingsEmail.jsx b/client/src/Pages/Settings/SettingsEmail.jsx similarity index 98% rename from client/src/Pages/v1/Settings/SettingsEmail.jsx rename to client/src/Pages/Settings/SettingsEmail.jsx index 7b5ad175e..6f0b02137 100644 --- a/client/src/Pages/v1/Settings/SettingsEmail.jsx +++ b/client/src/Pages/Settings/SettingsEmail.jsx @@ -12,8 +12,8 @@ import { PropTypes } from "prop-types"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; -import { useSendTestEmail } from "../../../Hooks/v1/useSendTestEmail.js"; -import { createToast } from "../../../Utils/toastUtils.jsx"; +import { useSendTestEmail } from "@/Hooks/useSendTestEmail.js"; +import { createToast } from "@/Utils/toastUtils.jsx"; const SettingsEmail = ({ isAdmin, diff --git a/client/src/Pages/v1/Settings/SettingsExport.jsx b/client/src/Pages/Settings/SettingsExport.jsx similarity index 100% rename from client/src/Pages/v1/Settings/SettingsExport.jsx rename to client/src/Pages/Settings/SettingsExport.jsx diff --git a/client/src/Pages/v1/Settings/SettingsGlobalThresholds.jsx b/client/src/Pages/Settings/SettingsGlobalThresholds.jsx similarity index 100% rename from client/src/Pages/v1/Settings/SettingsGlobalThresholds.jsx rename to client/src/Pages/Settings/SettingsGlobalThresholds.jsx diff --git a/client/src/Pages/v1/Settings/SettingsPagespeed.jsx b/client/src/Pages/Settings/SettingsPagespeed.jsx similarity index 100% rename from client/src/Pages/v1/Settings/SettingsPagespeed.jsx rename to client/src/Pages/Settings/SettingsPagespeed.jsx diff --git a/client/src/Pages/v1/Settings/SettingsStats.jsx b/client/src/Pages/Settings/SettingsStats.jsx similarity index 86% rename from client/src/Pages/v1/Settings/SettingsStats.jsx rename to client/src/Pages/Settings/SettingsStats.jsx index b68006ab2..690b306be 100644 --- a/client/src/Pages/v1/Settings/SettingsStats.jsx +++ b/client/src/Pages/Settings/SettingsStats.jsx @@ -36,16 +36,6 @@ const SettingsStats = ({ isAdmin, HEADING_SX, handleChange, settingsData, errors - {t("settingsPage.statsSettings.clearAllStatsDescription")} diff --git a/client/src/Pages/v1/Settings/SettingsTimeZone.jsx b/client/src/Pages/Settings/SettingsTimeZone.jsx similarity index 97% rename from client/src/Pages/v1/Settings/SettingsTimeZone.jsx rename to client/src/Pages/Settings/SettingsTimeZone.jsx index f684c871d..8ed7e5b3b 100644 --- a/client/src/Pages/v1/Settings/SettingsTimeZone.jsx +++ b/client/src/Pages/Settings/SettingsTimeZone.jsx @@ -3,7 +3,7 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; import Search from "@/Components/v1/Inputs/Search/index.jsx"; -import timezones from "../../../Utils/timezones.json"; +import timezones from "@/Utils/timezones.json"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/Settings/SettingsUI.jsx b/client/src/Pages/Settings/SettingsUI.jsx similarity index 100% rename from client/src/Pages/v1/Settings/SettingsUI.jsx rename to client/src/Pages/Settings/SettingsUI.jsx diff --git a/client/src/Pages/v1/Settings/SettingsURL.jsx b/client/src/Pages/Settings/SettingsURL.jsx similarity index 100% rename from client/src/Pages/v1/Settings/SettingsURL.jsx rename to client/src/Pages/Settings/SettingsURL.jsx diff --git a/client/src/Pages/v1/Settings/index.jsx b/client/src/Pages/Settings/index.jsx similarity index 95% rename from client/src/Pages/v1/Settings/index.jsx rename to client/src/Pages/Settings/index.jsx index b6137121b..27cc6bb21 100644 --- a/client/src/Pages/v1/Settings/index.jsx +++ b/client/src/Pages/Settings/index.jsx @@ -12,27 +12,22 @@ import SettingsGlobalThresholds from "./SettingsGlobalThresholds.jsx"; import SettingsExport from "./SettingsExport.jsx"; import Button from "@mui/material/Button"; // Utils -import { settingsValidation } from "../../../Validation/validation.js"; +import { settingsValidation } from "@/Validation/validation.js"; import { useState } from "react"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import { useSelector, useDispatch } from "react-redux"; -import { - setTimezone, - setMode, - setLanguage, - setShowURL, -} from "../../../Features/UI/uiSlice.js"; +import { setTimezone, setMode, setLanguage, setShowURL } from "@/Features/UI/uiSlice.js"; import SettingsStats from "./SettingsStats.jsx"; -import { useFetchSettings, useSaveSettings } from "../../../Hooks/v1/settingsHooks.js"; -import { useIsAdmin } from "../../../Hooks/v1/useIsAdmin.js"; +import { useFetchSettings, useSaveSettings } from "@/Hooks/settingsHooks.js"; +import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; import { useAddDemoMonitors, useDeleteAllMonitors, useDeleteMonitorStats, useFetchJson, -} from "../../../Hooks/v1/monitorHooks.js"; +} from "@/Hooks/monitorHooks.js"; // Constants const BREADCRUMBS = [{ name: `Settings`, path: "/settings" }]; diff --git a/client/src/Pages/v1/StatusPage/Create/Components/MonitorList/index.jsx b/client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx similarity index 95% rename from client/src/Pages/v1/StatusPage/Create/Components/MonitorList/index.jsx rename to client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx index 5484202d5..d2a13b2eb 100644 --- a/client/src/Pages/v1/StatusPage/Create/Components/MonitorList/index.jsx +++ b/client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx @@ -43,7 +43,7 @@ const MonitorListItem = ({ const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => { const onDelete = (monitorToDelete) => { const newMonitors = selectedMonitors.filter( - (monitor) => monitor._id !== monitorToDelete._id + (monitor) => monitor.id !== monitorToDelete.id ); setSelectedMonitors(newMonitors); }; @@ -80,8 +80,8 @@ const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => { > {selectedMonitors?.map((monitor, index) => ( {(provided, snapshot) => ( diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx b/client/src/Pages/StatusPage/Create/Components/Progress/index.jsx similarity index 92% rename from client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx rename to client/src/Pages/StatusPage/Create/Components/Progress/index.jsx index 232a12f1d..2d9731ea0 100644 --- a/client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx +++ b/client/src/Pages/StatusPage/Create/Components/Progress/index.jsx @@ -2,7 +2,7 @@ import { Button, Box } from "@mui/material"; import ProgressUpload from "@/Components/v1/ProgressBars/index.jsx"; import ImageIcon from "@mui/icons-material/Image"; import { useTranslation } from "react-i18next"; -import { formatBytes } from "../../../../../../Utils/fileUtils.js"; +import { formatBytes } from "../../../../../Utils/fileUtils.js"; const Progress = ({ isLoading, progressValue, logo, logoType, removeLogo, errors }) => { const { t } = useTranslation(); if (isLoading) { diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Skeleton/index.jsx b/client/src/Pages/StatusPage/Create/Components/Skeleton/index.jsx similarity index 100% rename from client/src/Pages/v1/StatusPage/Create/Components/Skeleton/index.jsx rename to client/src/Pages/StatusPage/Create/Components/Skeleton/index.jsx diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/ConfigStack.jsx similarity index 100% rename from client/src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx rename to client/src/Pages/StatusPage/Create/Components/Tabs/ConfigStack.jsx diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx similarity index 99% rename from client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx rename to client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx index d11825e69..e84de16dc 100644 --- a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx +++ b/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx @@ -24,7 +24,7 @@ const Content = ({ // Handlers const handleMonitorsChange = (selectedMonitors) => { handleFormChange({ - target: { name: "monitors", value: selectedMonitors.map((monitor) => monitor._id) }, + target: { name: "monitors", value: selectedMonitors.map((monitor) => monitor.id) }, }); setSelectedMonitors(selectedMonitors); }; diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx similarity index 98% rename from client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx rename to client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx index a668a9db8..d4c583d3a 100644 --- a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx +++ b/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx @@ -11,7 +11,7 @@ import Progress from "../Progress/index.jsx"; // Utils import { useTheme } from "@emotion/react"; -import timezones from "../../../../../../Utils/timezones.json"; +import timezones from "../../../../../Utils/timezones.json"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; import { useMemo, useState, useCallback } from "react"; diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx similarity index 100% rename from client/src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx rename to client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx diff --git a/client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx b/client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx similarity index 83% rename from client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx rename to client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx index 808598052..dac5997f9 100644 --- a/client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx +++ b/client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx @@ -1,6 +1,6 @@ import { useState } from "react"; -import { networkService } from "../../../../../main.jsx"; -import { createToast } from "../../../../../Utils/toastUtils.jsx"; +import { networkService } from "../../../../main.jsx"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; const useCreateStatusPage = (isCreate, url) => { const [isLoading, setIsLoading] = useState(false); diff --git a/client/src/Pages/v1/StatusPage/Create/Hooks/useMonitorsFetch.jsx b/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx similarity index 83% rename from client/src/Pages/v1/StatusPage/Create/Hooks/useMonitorsFetch.jsx rename to client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx index 81fefd65c..03ebb07bf 100644 --- a/client/src/Pages/v1/StatusPage/Create/Hooks/useMonitorsFetch.jsx +++ b/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; -import { networkService } from "../../../../../main.jsx"; +import { networkService } from "../../../../main.jsx"; import { useSelector } from "react-redux"; -import { createToast } from "../../../../../Utils/toastUtils.jsx"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; const useMonitorsFetch = () => { const { user } = useSelector((state) => state.auth); @@ -16,7 +16,7 @@ const useMonitorsFetch = () => { limit: null, // donot return any checks for the monitors types: ["http", "ping", "port", "game"], // include game servers in status page monitor selection }); - setMonitors(response.data.data.monitors); + setMonitors(response.data.data); } catch (error) { setNetworkError(true); createToast({ body: error.message }); diff --git a/client/src/Pages/v1/StatusPage/Create/index.jsx b/client/src/Pages/StatusPage/Create/index.jsx similarity index 96% rename from client/src/Pages/v1/StatusPage/Create/index.jsx rename to client/src/Pages/StatusPage/Create/index.jsx index d9fafed70..f8fcf0e91 100644 --- a/client/src/Pages/v1/StatusPage/Create/index.jsx +++ b/client/src/Pages/StatusPage/Create/index.jsx @@ -8,11 +8,11 @@ import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; //Utils import { useTheme } from "@emotion/react"; import { useState, useEffect, useRef, useCallback } from "react"; -import { statusPageValidation } from "../../../../Validation/validation.js"; -import { buildErrors } from "../../../../Validation/error.js"; +import { statusPageValidation } from "../../../Validation/validation.js"; +import { buildErrors } from "../../../Validation/error.js"; import { useMonitorsFetch } from "./Hooks/useMonitorsFetch.jsx"; import { useCreateStatusPage } from "./Hooks/useCreateStatusPage.jsx"; -import { createToast } from "../../../../Utils/toastUtils.jsx"; +import { createToast } from "../../../Utils/toastUtils.jsx"; import { useNavigate } from "react-router-dom"; import { useStatusPageFetch } from "../Status/Hooks/useStatusPageFetch.jsx"; import { useParams } from "react-router-dom"; @@ -62,7 +62,6 @@ const CreateStatusPage = () => { useStatusPageFetch(isCreate, url); const [deleteStatusPage, isDeleting] = useStatusPageDelete(fetchStatusPage, url); - console.log(JSON.stringify(form, null, 2)); // Handlers const handleFormChange = (e) => { let { type, name, value, checked } = e.target; @@ -144,6 +143,8 @@ const CreateStatusPage = () => { ...form, logo: { type: form.logo?.type ?? null, size: form.logo?.size ?? null }, }; + + console.log(toSubmit); const { error } = statusPageValidation.validate(toSubmit, { abortEarly: false, }); @@ -205,7 +206,7 @@ const CreateStatusPage = () => { companyName: statusPage?.companyName, isPublished: statusPage?.isPublished, timezone: statusPage?.timezone, - monitors: statusPageMonitors.map((monitor) => monitor._id), + monitors: statusPageMonitors.map((monitor) => monitor.id), color: statusPage?.color, logo: newLogo, showCharts: statusPage?.showCharts ?? true, diff --git a/client/src/Pages/v1/StatusPage/Status/Components/AdminLink/index.jsx b/client/src/Pages/StatusPage/Status/Components/AdminLink/index.jsx similarity index 100% rename from client/src/Pages/v1/StatusPage/Status/Components/AdminLink/index.jsx rename to client/src/Pages/StatusPage/Status/Components/AdminLink/index.jsx diff --git a/client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx b/client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx similarity index 97% rename from client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx rename to client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx index c56aa40cc..60a4882f5 100644 --- a/client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx +++ b/client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx @@ -1,7 +1,7 @@ // Components import { Box, Stack, Typography, Button } from "@mui/material"; import Image from "@/Components/v1/Image/index.jsx"; -import SettingsIcon from "../../../../../../assets/icons/settings-bold.svg?react"; +import SettingsIcon from "@/assets/icons/settings-bold.svg?react"; import ThemeSwitch from "@/Components/v1/ThemeSwitch/index.jsx"; import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward"; //Utils diff --git a/client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx b/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx similarity index 92% rename from client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx rename to client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx index 1628b3d44..33f771233 100644 --- a/client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx +++ b/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx @@ -6,7 +6,7 @@ import { StatusLabel } from "@/Components/v1/Label/index.jsx"; //Utils import { useTheme } from "@mui/material/styles"; -import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; +import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils.js"; import PropTypes from "prop-types"; import { useSelector } from "react-redux"; @@ -28,14 +28,14 @@ const MonitorsList = ({ const status = determineState(monitor); return ( { const [isLoading, setIsLoading] = useState(true); diff --git a/client/src/Pages/v1/StatusPage/Status/index.jsx b/client/src/Pages/StatusPage/Status/index.jsx similarity index 98% rename from client/src/Pages/v1/StatusPage/Status/index.jsx rename to client/src/Pages/StatusPage/Status/index.jsx index e700bbbd9..adcd9c2c7 100644 --- a/client/src/Pages/v1/StatusPage/Status/index.jsx +++ b/client/src/Pages/StatusPage/Status/index.jsx @@ -12,7 +12,7 @@ import TextLink from "@/Components/v1/TextLink/index.jsx"; // Utils import { useStatusPageFetch } from "./Hooks/useStatusPageFetch.jsx"; import { useTheme } from "@emotion/react"; -import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; +import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; import { useLocation } from "react-router-dom"; import { useParams } from "react-router-dom"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx b/client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx similarity index 100% rename from client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx rename to client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx diff --git a/client/src/Pages/v1/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx b/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx similarity index 86% rename from client/src/Pages/v1/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx rename to client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx index 89df1b84d..839ff4cdf 100644 --- a/client/src/Pages/v1/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx +++ b/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx @@ -1,7 +1,7 @@ import { useState, useEffect } from "react"; -import { networkService } from "../../../../../main.jsx"; +import { networkService } from "../../../../main.jsx"; import { useSelector } from "react-redux"; -import { createToast } from "../../../../../Utils/toastUtils.jsx"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; const useStatusPagesFetch = () => { const { user } = useSelector((state) => state.auth); diff --git a/client/src/Pages/v1/StatusPage/StatusPages/index.jsx b/client/src/Pages/StatusPage/StatusPages/index.jsx similarity index 95% rename from client/src/Pages/v1/StatusPage/StatusPages/index.jsx rename to client/src/Pages/StatusPage/StatusPages/index.jsx index 3c60fe950..7137d01ad 100644 --- a/client/src/Pages/v1/StatusPage/StatusPages/index.jsx +++ b/client/src/Pages/StatusPage/StatusPages/index.jsx @@ -7,7 +7,7 @@ import PageStateWrapper from "@/Components/v1/PageStateWrapper/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { useStatusPagesFetch } from "./Hooks/useStatusPagesFetch.jsx"; -import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; +import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; const BREADCRUMBS = [{ name: `Status Pages`, path: "" }]; const StatusPages = () => { diff --git a/client/src/Pages/v1/Uptime/BulkImport/Upload.jsx b/client/src/Pages/Uptime/BulkImport/Upload.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/BulkImport/Upload.jsx rename to client/src/Pages/Uptime/BulkImport/Upload.jsx diff --git a/client/src/Pages/v1/Uptime/BulkImport/index.jsx b/client/src/Pages/Uptime/BulkImport/index.jsx similarity index 95% rename from client/src/Pages/v1/Uptime/BulkImport/index.jsx rename to client/src/Pages/Uptime/BulkImport/index.jsx index 9f3efcd67..6f2f7bcb5 100644 --- a/client/src/Pages/v1/Uptime/BulkImport/index.jsx +++ b/client/src/Pages/Uptime/BulkImport/index.jsx @@ -5,14 +5,14 @@ import { useState } from "react"; import { Box, Stack, Typography, Button, Link } from "@mui/material"; //Components -import { createToast } from "../../../../Utils/toastUtils.jsx"; +import { createToast } from "../../../Utils/toastUtils.jsx"; import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; import UploadFile from "./Upload.jsx"; import { useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; import { Trans, useTranslation } from "react-i18next"; -import { useCreateBulkMonitors } from "../../../../Hooks/v1/monitorHooks.js"; +import { useCreateBulkMonitors } from "../../../Hooks/monitorHooks.js"; const BulkImport = () => { const theme = useTheme(); diff --git a/client/src/Pages/v1/Uptime/Create/index.css b/client/src/Pages/Uptime/Create/index.css similarity index 100% rename from client/src/Pages/v1/Uptime/Create/index.css rename to client/src/Pages/Uptime/Create/index.css diff --git a/client/src/Pages/v1/Uptime/Create/index.jsx b/client/src/Pages/Uptime/Create/index.jsx similarity index 98% rename from client/src/Pages/v1/Uptime/Create/index.jsx rename to client/src/Pages/Uptime/Create/index.jsx index 003869b10..4f847db4e 100644 --- a/client/src/Pages/v1/Uptime/Create/index.jsx +++ b/client/src/Pages/Uptime/Create/index.jsx @@ -26,14 +26,14 @@ import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { monitorValidation } from "../../../../Validation/validation.js"; -import { createToast } from "../../../../Utils/toastUtils.jsx"; +import { monitorValidation } from "../../../Validation/validation.js"; +import { createToast } from "../../../Utils/toastUtils.jsx"; import { PauseOutlined as PauseOutlinedIcon, PlayArrowOutlined as PlayArrowOutlinedIcon, } from "@mui/icons-material"; -import { useMonitorUtils } from "../../../../Hooks/v1/useMonitorUtils.js"; -import { useGetNotificationsByTeamId } from "../../../../Hooks/v1/useNotifications.js"; +import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js"; +import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications.js"; import { useParams } from "react-router-dom"; import { useCreateMonitor, @@ -42,7 +42,7 @@ import { usePauseMonitor, useFetchMonitorById, useFetchMonitorGames, -} from "../../../../Hooks/v1/monitorHooks.js"; +} from "../../../Hooks/monitorHooks.js"; /** * Create page renders monitor creation or configuration views. @@ -292,7 +292,8 @@ const UptimeCreate = ({ isClone = false }) => { const handleRemove = async (event) => { event.preventDefault(); - await deleteMonitor({ monitor, redirect: "/uptime" }); + const TEMP_MONITOR = { id: monitor._id }; + await deleteMonitor({ monitor: TEMP_MONITOR, redirect: "/uptime" }); }; const isBusy = isLoading || isCreating || isDeleting || isUpdating || isPausing; diff --git a/client/src/Pages/v1/Uptime/Create/skeleton.jsx b/client/src/Pages/Uptime/Create/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Create/skeleton.jsx rename to client/src/Pages/Uptime/Create/skeleton.jsx diff --git a/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx b/client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx similarity index 92% rename from client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx rename to client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx index 45f35a42e..582c7891f 100644 --- a/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx +++ b/client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx @@ -1,15 +1,15 @@ // Components import { Stack, Typography, Box } from "@mui/material"; import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx"; -import UptimeIcon from "../../../../../../assets/icons/uptime-icon.svg?react"; -import IncidentsIcon from "../../../../../../assets/icons/incidents.svg?react"; -import AverageResponseIcon from "../../../../../../assets/icons/average-response-icon.svg?react"; +import UptimeIcon from "@/assets/icons/uptime-icon.svg?react"; +import IncidentsIcon from "@/assets/icons/incidents.svg?react"; +import AverageResponseIcon from "@/assets/icons/average-response-icon.svg?react"; import UpBarChart from "../Charts/UpBarChart.jsx"; import DownBarChart from "../Charts/DownBarChart.jsx"; import ResponseGaugeChart from "../Charts/ResponseGaugeChart.jsx"; import SkeletonLayout from "./skeleton.jsx"; // Utils -import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js"; +import { formatDateWithTz } from "../../../../../Utils/timeUtils.js"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { useState } from "react"; diff --git a/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/skeleton.jsx b/client/src/Pages/Uptime/Details/Components/ChartBoxes/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/skeleton.jsx rename to client/src/Pages/Uptime/Details/Components/ChartBoxes/skeleton.jsx diff --git a/client/src/Pages/v1/Uptime/Details/Components/Charts/CustomLabels.jsx b/client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx similarity index 93% rename from client/src/Pages/v1/Uptime/Details/Components/Charts/CustomLabels.jsx rename to client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx index c9c1ff58d..472c8d058 100644 --- a/client/src/Pages/v1/Uptime/Details/Components/Charts/CustomLabels.jsx +++ b/client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx @@ -1,6 +1,6 @@ import PropTypes from "prop-types"; import { useSelector } from "react-redux"; -import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js"; +import { formatDateWithTz } from "../../../../../Utils/timeUtils.js"; const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint, type }) => { const uiTimezone = useSelector((state) => state.ui.timezone); diff --git a/client/src/Pages/v1/Uptime/Details/Components/Charts/DownBarChart.jsx b/client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Details/Components/Charts/DownBarChart.jsx rename to client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx diff --git a/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx b/client/src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx rename to client/src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx diff --git a/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx b/client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx similarity index 89% rename from client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx rename to client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx index daa7b4f53..861b600d4 100644 --- a/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx +++ b/client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx @@ -1,6 +1,6 @@ import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx"; import MonitorDetailsAreaChart from "@/Components/v1/Charts/MonitorDetailsAreaChart/index.jsx"; -import ResponseTimeIcon from "../../../../../../assets/icons/response-time-icon.svg?react"; +import ResponseTimeIcon from "@/assets/icons/response-time-icon.svg?react"; import SkeletonLayout from "./ResponseTimeChartSkeleton.jsx"; import PropTypes from "prop-types"; diff --git a/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx b/client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx rename to client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx diff --git a/client/src/Pages/v1/Uptime/Details/Components/Charts/UpBarChart.jsx b/client/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Details/Components/Charts/UpBarChart.jsx rename to client/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx diff --git a/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx b/client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx similarity index 97% rename from client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx rename to client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx index 0d0defc3b..0715561d4 100644 --- a/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx +++ b/client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx @@ -1,11 +1,11 @@ import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx"; import PropTypes from "prop-types"; -import HistoryIcon from "../../../../../../assets/icons/history-icon.svg?react"; +import HistoryIcon from "@/assets/icons/history-icon.svg?react"; import Table from "@/Components/v1/Table/index.jsx"; import TablePagination from "@/Components/v1/Table/TablePagination/index.jsx"; import { StatusLabel } from "@/Components/v1/Label/index.jsx"; import { useTranslation } from "react-i18next"; -import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js"; +import { formatDateWithTz } from "../../../../../Utils/timeUtils.js"; import SkeletonLayout from "./skeleton.jsx"; import Tooltip from "@mui/material/Tooltip"; import Box from "@mui/material/Box"; diff --git a/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/skeleton.jsx b/client/src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Details/Components/ResponseTable/skeleton.jsx rename to client/src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx diff --git a/client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx b/client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx similarity index 92% rename from client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx rename to client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx index a7b971839..0bfdc1096 100644 --- a/client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx +++ b/client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx @@ -2,10 +2,10 @@ import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx"; import StatBox from "@/Components/v1/StatBox/index.jsx"; import PropTypes from "prop-types"; -import { getHumanReadableDuration } from "../../../../../../Utils/timeUtils.js"; +import { getHumanReadableDuration } from "../../../../../Utils/timeUtils.js"; import { useTheme } from "@mui/material/styles"; import { Typography } from "@mui/material"; -import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; +import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils.js"; const UptimeStatusBoxes = ({ isLoading = false, diff --git a/client/src/Pages/v1/Uptime/Details/Hooks/useCertificateFetch.jsx b/client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx similarity index 84% rename from client/src/Pages/v1/Uptime/Details/Hooks/useCertificateFetch.jsx rename to client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx index f85111f97..e74a99d82 100644 --- a/client/src/Pages/v1/Uptime/Details/Hooks/useCertificateFetch.jsx +++ b/client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx @@ -1,7 +1,7 @@ -import { logger } from "../../../../../Utils/Logger.js"; +import { logger } from "../../../../Utils/Logger.js"; import { useEffect, useState } from "react"; -import { networkService } from "../../../../../main.jsx"; -import { formatDateWithTz } from "../../../../../Utils/timeUtils.js"; +import { networkService } from "../../../../main.jsx"; +import { formatDateWithTz } from "../../../../Utils/timeUtils.js"; const useCertificateFetch = ({ monitor, diff --git a/client/src/Pages/v1/Uptime/Details/index.jsx b/client/src/Pages/Uptime/Details/index.jsx similarity index 95% rename from client/src/Pages/v1/Uptime/Details/index.jsx rename to client/src/Pages/Uptime/Details/index.jsx index f5e283495..144ceb4dc 100644 --- a/client/src/Pages/v1/Uptime/Details/index.jsx +++ b/client/src/Pages/Uptime/Details/index.jsx @@ -15,10 +15,10 @@ import { useState } from "react"; import { useParams } from "react-router-dom"; import { useSelector } from "react-redux"; import { useTheme } from "@emotion/react"; -import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; -import { useFetchUptimeMonitorById } from "../../../../Hooks/v1/monitorHooks.js"; +import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; +import { useFetchUptimeMonitorById } from "../../../Hooks/monitorHooks.js"; import useCertificateFetch from "./Hooks/useCertificateFetch.jsx"; -import { useFetchChecksByMonitor } from "../../../../Hooks/v1/checkHooks.js"; +import { useFetchChecksByMonitor } from "../../../Hooks/checkHooks.js"; import { useTranslation } from "react-i18next"; // Constants diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx b/client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx rename to client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/LoadingSpinner/index.jsx b/client/src/Pages/Uptime/Monitors/Components/LoadingSpinner/index.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Monitors/Components/LoadingSpinner/index.jsx rename to client/src/Pages/Uptime/Monitors/Components/LoadingSpinner/index.jsx diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx b/client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx rename to client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/Skeleton/index.jsx b/client/src/Pages/Uptime/Monitors/Components/Skeleton/index.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Monitors/Components/Skeleton/index.jsx rename to client/src/Pages/Uptime/Monitors/Components/Skeleton/index.jsx diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/index.jsx b/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/index.jsx rename to client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx b/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx rename to client/src/Pages/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx b/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx similarity index 90% rename from client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx rename to client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx index c745e7935..c9015816a 100644 --- a/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx +++ b/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx @@ -1,9 +1,9 @@ import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { Box, Stack, Typography } from "@mui/material"; -import Arrow from "../../../../../../assets/icons/top-right-arrow.svg?react"; -import Background from "../../../../../../assets/Images/background-grid.svg?react"; -import ClockSnooze from "../../../../../../assets/icons/clock-snooze.svg?react"; +import Arrow from "@/assets/icons/top-right-arrow.svg?react"; +import Background from "@/assets/Images/background-grid.svg?react"; +import ClockSnooze from "@/assets/icons/clock-snooze.svg?react"; const StatusBox = ({ title, value, status }) => { const theme = useTheme(); diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx b/client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx similarity index 97% rename from client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx rename to client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx index 9cf2d247d..fe220251d 100644 --- a/client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx +++ b/client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx @@ -13,7 +13,7 @@ import TableSkeleton from "@/Components/v1/Table/skeleton.jsx"; // Utils import { useTheme } from "@emotion/react"; -import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; +import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils.js"; import { useNavigate } from "react-router-dom"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; @@ -24,13 +24,13 @@ import { useTranslation } from "react-i18next"; * @param {boolean} props.isAdmin - Whether the current user has admin privileges * @param {boolean} props.isLoading - Loading state of the table * @param {Array<{ - * _id: string, + * id: string, * url: string, * title: string, * percentage: number, * percentageColor: string, * monitor: { - * _id: string, + * id: string, * type: string, * checks: Array * } @@ -101,7 +101,7 @@ const UptimeDataTable = ({ ), render: (row) => ( { - navigate(`/uptime/${row._id}`); + navigate(`/uptime/${row.id}`); }, emptyView: "No monitors found", }} diff --git a/client/src/Pages/v1/Uptime/Monitors/Hooks/useDebounce.jsx b/client/src/Pages/Uptime/Monitors/Hooks/useDebounce.jsx similarity index 100% rename from client/src/Pages/v1/Uptime/Monitors/Hooks/useDebounce.jsx rename to client/src/Pages/Uptime/Monitors/Hooks/useDebounce.jsx diff --git a/client/src/Pages/v1/Uptime/Monitors/index.jsx b/client/src/Pages/Uptime/Monitors/index.jsx similarity index 96% rename from client/src/Pages/v1/Uptime/Monitors/index.jsx rename to client/src/Pages/Uptime/Monitors/index.jsx index cacfc23b4..fb8d6246b 100644 --- a/client/src/Pages/v1/Uptime/Monitors/index.jsx +++ b/client/src/Pages/Uptime/Monitors/index.jsx @@ -5,7 +5,7 @@ // Components import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; -import Greeting from "../../../../Utils/greeting.jsx"; +import Greeting from "../../../Utils/greeting.jsx"; import StatusBoxes from "./Components/StatusBoxes/index.jsx"; import UptimeDataTable from "./Components/UptimeDataTable/index.jsx"; import Pagination from "@/Components/v1/Table/TablePagination/index.jsx"; @@ -20,16 +20,16 @@ import MonitorCountHeader from "@/Components/v1/MonitorCountHeader/index.jsx"; import { Stack, Box, Button } from "@mui/material"; // Utils import { useState, useCallback, useEffect } from "react"; -import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; +import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; import { useSelector, useDispatch } from "react-redux"; -import { setRowsPerPage } from "../../../../Features/UI/uiSlice.js"; +import { setRowsPerPage } from "../../../Features/UI/uiSlice.js"; import PropTypes from "prop-types"; import { useFetchMonitorsWithSummary, useFetchMonitorsWithChecks, -} from "../../../../Hooks/v1/monitorHooks.js"; +} from "@/Hooks/monitorHooks.js"; import { useTranslation } from "react-i18next"; const TYPES = ["http", "ping", "docker", "port", "game"]; diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index 255d913c1..d9f6d58cb 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -1,58 +1,58 @@ import { useSelector } from "react-redux"; import { Navigate, Route, Routes as LibRoutes } from "react-router"; import HomeLayout from "@/Components/v1/Layouts/HomeLayout"; -import NotFound from "../Pages/v1/NotFound"; +import NotFound from "../Pages/NotFound/index.jsx"; // Auth -import AuthLogin from "../Pages/v1/Auth/Login"; -import AuthRegister from "../Pages/v1/Auth/Register/"; -import AuthForgotPassword from "../Pages/v1/Auth/ForgotPassword"; -import AuthCheckEmail from "../Pages/v1/Auth/CheckEmail"; -import AuthSetNewPassword from "../Pages/v1/Auth/SetNewPassword"; -import AuthNewPasswordConfirmed from "../Pages/v1/Auth/NewPasswordConfirmed"; +import AuthLogin from "../Pages/Auth/Login/index.jsx"; +import AuthRegister from "../Pages/Auth/Register/index.jsx"; +import AuthForgotPassword from "../Pages/Auth/ForgotPassword.jsx"; +import AuthCheckEmail from "../Pages/Auth/CheckEmail.jsx"; +import AuthSetNewPassword from "../Pages/Auth/SetNewPassword.jsx"; +import AuthNewPasswordConfirmed from "../Pages/Auth/NewPasswordConfirmed.jsx"; // Uptime -import Uptime from "../Pages/v1/Uptime/Monitors"; -import UptimeDetails from "../Pages/v1/Uptime/Details"; -import UptimeCreate from "../Pages/v1/Uptime/Create"; +import Uptime from "../Pages/Uptime/Monitors/index.jsx"; +import UptimeDetails from "../Pages/Uptime/Details/index.jsx"; +import UptimeCreate from "../Pages/Uptime/Create/index.jsx"; // PageSpeed -import PageSpeed from "../Pages/v1/PageSpeed/Monitors"; -import PageSpeedDetails from "../Pages/v1/PageSpeed/Details"; -import PageSpeedCreate from "../Pages/v1/PageSpeed/Create"; +import PageSpeed from "../Pages/PageSpeed/Monitors/index.jsx"; +import PageSpeedDetails from "../Pages/PageSpeed/Details/index.jsx"; +import PageSpeedCreate from "../Pages/PageSpeed/Create/index.jsx"; // Infrastructure -import Infrastructure from "../Pages/v1/Infrastructure/Monitors"; -import InfrastructureCreate from "../Pages/v1/Infrastructure/Create"; -import InfrastructureDetails from "../Pages/v1/Infrastructure/Details"; +import Infrastructure from "../Pages/Infrastructure/Monitors/index.jsx"; +import InfrastructureCreate from "../Pages/Infrastructure/Create/index.jsx"; +import InfrastructureDetails from "../Pages/Infrastructure/Details/index.jsx"; // Server Status -import ServerUnreachable from "../Pages/v1/ServerUnreachable.jsx"; +import ServerUnreachable from "../Pages/ServerUnreachable.jsx"; // Incidents -import Checks from "../Pages/v1/Incidents"; -import Incidents2 from "../Pages/v1/Incidents2"; +import Checks from "../Pages/Incidents"; +import Incidents2 from "../Pages/Incidents2/index.jsx"; // Status pages -import CreateStatus from "../Pages/v1/StatusPage/Create"; -import StatusPages from "../Pages/v1/StatusPage/StatusPages"; -import Status from "../Pages/v1/StatusPage/Status"; +import CreateStatus from "../Pages/StatusPage/Create/index.jsx"; +import StatusPages from "../Pages/StatusPage/StatusPages/index.jsx"; +import Status from "../Pages/StatusPage/Status/index.jsx"; -import Notifications from "../Pages/v1/Notifications"; -import CreateNotifications from "../Pages/v1/Notifications/create"; +import Notifications from "../Pages/Notifications/index.jsx"; +import CreateNotifications from "../Pages/Notifications/create/index.jsx"; // Settings -import Account from "../Pages/v1/Account"; -import EditUser from "../Pages/v1/Account/EditUser"; -import Settings from "../Pages/v1/Settings"; +import Account from "../Pages/Account/index.jsx"; +import EditUser from "../Pages/Account/EditUser/index.jsx"; +import Settings from "../Pages/Settings"; -import Maintenance from "../Pages/v1/Maintenance"; +import Maintenance from "../Pages/Maintenance/index.jsx"; import ProtectedRoute from "../Components/v1/ProtectedRoute"; import RoleProtectedRoute from "../Components/v1/RoleProtectedRoute"; -import CreateNewMaintenanceWindow from "../Pages/v1/Maintenance/CreateMaintenance"; +import CreateNewMaintenanceWindow from "../Pages/Maintenance/CreateMaintenance/index.jsx"; import withAdminCheck from "@/Components/v1/HOC/withAdminCheck"; -import BulkImport from "../Pages/v1/Uptime/BulkImport"; -import Logs from "../Pages/v1/Logs"; +import BulkImport from "../Pages/Uptime/BulkImport/index.jsx"; +import Logs from "../Pages/Logs/index.jsx"; const Routes = () => { const mode = useSelector((state) => state.ui.mode); diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js index fe4eb7e14..bd0e37b30 100644 --- a/client/src/Utils/NetworkService.js +++ b/client/src/Utils/NetworkService.js @@ -144,20 +144,16 @@ class NetworkService { */ async getMonitorsByTeamId(config) { - const { limit, types, page, rowsPerPage, filter, field, order } = config; + const { types, filter } = config ?? {}; const params = new URLSearchParams(); - if (limit) params.append("limit", limit); if (types) { types.forEach((type) => { params.append("type", type); }); } - if (page) params.append("page", page); - if (rowsPerPage) params.append("rowsPerPage", rowsPerPage); - if (filter) params.append("filter", filter); - if (field) params.append("field", field); - if (order) params.append("order", order); + if (filter !== undefined && filter !== null && filter !== "") + params.append("filter", filter); return this.axiosInstance.get(`/monitors/team?${params.toString()}`, { headers: { @@ -202,6 +198,14 @@ class NetworkService { `/monitors/hardware/details/${config.monitorId}?${params.toString()}` ); } + async getPageSpeedDetailsByMonitorId(config) { + const params = new URLSearchParams(); + if (config.dateRange) params.append("dateRange", config.dateRange); + + return this.axiosInstance.get( + `/monitors/pagespeed/details/${config.monitorId}?${params.toString()}` + ); + } async getUptimeDetailsById(config) { const params = new URLSearchParams(); if (config.dateRange) params.append("dateRange", config.dateRange); diff --git a/docker/dist-arm/docker-compose.yaml b/docker/dist-arm/docker-compose.yaml index 0a5825b3e..9a87aa59b 100644 --- a/docker/dist-arm/docker-compose.yaml +++ b/docker/dist-arm/docker-compose.yaml @@ -15,7 +15,7 @@ services: depends_on: - mongodb mongodb: - image: mongo:4.4.18 + image: mongo:4.4.30 restart: always command: ["mongod", "--quiet", "--bind_ip_all"] ports: diff --git a/server/jest.config.ts b/server/jest.config.ts new file mode 100644 index 000000000..2bcfcbf21 --- /dev/null +++ b/server/jest.config.ts @@ -0,0 +1,20 @@ +import type { Config } from "jest"; + +const config: Config = { + rootDir: ".", + testEnvironment: "node", + extensionsToTreatAsEsm: [".ts"], + transform: { + "^.+\\.(t|j)sx?$": ["ts-jest", { useESM: true, tsconfig: "./tsconfig.jest.json" }], + }, + moduleNameMapper: { + "^@/(.*)$": "/src/$1", + }, + testMatch: ["/test/**/*.test.ts"], + setupFilesAfterEnv: [], + collectCoverageFrom: ["src/**/*.ts"], + coveragePathIgnorePatterns: ["/node_modules/", "/test/"], + clearMocks: true, +}; + +export default config; diff --git a/server/nodemon.json b/server/nodemon.json index 389209fa7..3faf346d3 100755 --- a/server/nodemon.json +++ b/server/nodemon.json @@ -1,5 +1,5 @@ { - "ignore": ["src/locales/*", "*.log", "node_modules/*"], + "ignore": ["node_modules/*"], "watch": ["src/**/*.ts", "src/**/*.js", "*.json"], "ext": "ts,js,json" } diff --git a/server/package-lock.json b/server/package-lock.json index 8de5cfc0c..11e1829b4 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -13,6 +13,7 @@ "axios": "^1.7.2", "bcryptjs": "3.0.2", "bullmq": "5.41.2", + "cacheable-lookup": "7.0.0", "compression": "1.8.1", "cookie-parser": "^1.4.7", "cors": "^2.8.5", @@ -39,25 +40,34 @@ "ping": "0.4.4", "sharp": "0.33.5", "ssl-checker": "2.0.10", - "super-simple-scheduler": "1.4.4", + "super-simple-scheduler": "1.4.5", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/dockerode": "^4.0.0", "@types/express": "5.0.3", + "@types/gamedig": "^5.0.3", + "@types/jest": "^30.0.0", + "@types/jmespath": "^0.15.2", "@types/jsonwebtoken": "9.0.10", + "@types/mjml": "^4.7.4", + "@types/multer": "^2.0.0", "@types/nodemailer": "7.0.1", + "@types/papaparse": "^5.5.2", "@types/ping": "0.4.4", "c8": "10.1.3", - "chai": "5.2.0", "eslint": "^9.17.0", "eslint-plugin-mocha": "^10.5.0", "esm": "3.2.25", "globals": "^15.14.0", - "mocha": "11.1.0", + "jest": "^30.2.0", + "nodemon": "^3.1.11", "prettier": "^3.3.3", - "sinon": "19.0.2", + "ts-jest": "^29.4.6", + "ts-node": "^10.9.2", + "tsc-alias": "1.8.16", "tsx": "4.20.5", "typescript": "5.9.2" }, @@ -830,12 +840,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -843,6 +853,174 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/compat-data": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", @@ -852,6 +1030,285 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/runtime": { "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", @@ -861,6 +1318,54 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@balena/dockerignore": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", @@ -886,6 +1391,30 @@ "node": ">=0.1.90" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", @@ -970,6 +1499,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -1014,6 +1544,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -1029,6 +1560,18 @@ "kuler": "^2.0.0" } }, + "node_modules/@emnapi/core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/runtime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.6.0.tgz", @@ -1039,6 +1582,17 @@ "tslib": "^2.4.0" } }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.11", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", @@ -2138,6 +2692,113 @@ "node": ">=12" } }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -2148,6 +2809,377 @@ "node": ">=8" } }, + "node_modules/@jest/console": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", + "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", + "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.2.0", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.2.0", + "jest-config": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-resolve-dependencies": "30.2.0", + "jest-runner": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "jest-watcher": "30.2.0", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", + "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-mock": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "30.2.0", + "jest-snapshot": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", + "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", + "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@sinonjs/fake-timers": "^13.0.0", + "@types/node": "*", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", + "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/types": "30.2.0", + "jest-mock": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", + "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@jridgewell/trace-mapping": "^0.3.25", + "@types/node": "*", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^5.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", + "slash": "^3.0.0", + "string-length": "^4.0.2", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", + "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", + "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.2.0", + "@jest/types": "30.2.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", + "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.2.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", + "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.2.0", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", + "chalk": "^4.1.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.2.0", + "micromatch": "^4.0.8", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", + "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -2273,6 +3305,57 @@ "win32" ] }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -2283,6 +3366,19 @@ "node": ">=14" } }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -2395,6 +3491,13 @@ "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", "license": "BSD-3-Clause" }, + "node_modules/@sinclair/typebox": { + "version": "0.34.47", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.47.tgz", + "integrity": "sha512-ZGIBQ+XDvO5JQku9wmwtabcVTHJsgSWAHYtVuM9pBNNR5E88v6Jcj/llpmsjivig5X8A8HHOb4/mbEKPS5EvAw==", + "dev": true, + "license": "MIT" + }, "node_modules/@sindresorhus/is": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.1.0.tgz", @@ -2427,34 +3530,6 @@ "@sinonjs/commons": "^3.0.1" } }, - "node_modules/@sinonjs/samsam": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz", - "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "type-detect": "^4.1.0" - } - }, - "node_modules/@sinonjs/samsam/node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", - "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", - "dev": true, - "license": "(Unlicense OR Apache-2.0)" - }, "node_modules/@smithy/abort-controller": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.3.tgz", @@ -3143,6 +4218,100 @@ "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, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, "node_modules/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", @@ -3164,6 +4333,29 @@ "@types/node": "*" } }, + "node_modules/@types/docker-modem": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz", + "integrity": "sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/ssh2": "*" + } + }, + "node_modules/@types/dockerode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-4.0.0.tgz", + "integrity": "sha512-NBm/UIL1KYWivh9ihJELK4Jv0dzOQCleS0OT5JklErra8mZ/t0UQpHfTkMmPrAqtra7OyFius1EFLVs0DhiHQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/docker-modem": "*", + "@types/node": "*", + "@types/ssh2": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -3196,6 +4388,13 @@ "@types/send": "*" } }, + "node_modules/@types/gamedig": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/gamedig/-/gamedig-5.0.3.tgz", + "integrity": "sha512-VLrOVRf72wVE6P0DhFtRVodiONwL51uojn6dNpP/Ks/Vdm3CDH6dZTAw74WmCDLuyvpytNy8auKzlTKfBlO8jg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", @@ -3216,6 +4415,44 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, + "node_modules/@types/jmespath": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@types/jmespath/-/jmespath-0.15.2.tgz", + "integrity": "sha512-pegh49FtNsC389Flyo9y8AfkVIZn9MMPE9yJrO9svhq6Fks2MwymULWjZqySuxmctd3ZH4/n7Mr98D+1Qo5vGA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -3241,6 +4478,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mjml": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/@types/mjml/-/mjml-4.7.4.tgz", + "integrity": "sha512-vyi1vzWgMzFMwZY7GSZYX0GU0dmtC8vLHwpgk+NWmwbwRSrlieVyJ9sn5elodwUfklJM7yGl0zQeet1brKTWaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mjml-core": "*" + } + }, + "node_modules/@types/mjml-core": { + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/@types/mjml-core/-/mjml-core-4.15.2.tgz", + "integrity": "sha512-Q7SxFXgoX979HP57DEVsRI50TV8x1V4lfCA4Up9AvfINDM5oD/X9ARgfoyX1qS987JCnDLv85JjkqAjt3hZSiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -3248,11 +4502,22 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/multer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.0.0.tgz", + "integrity": "sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "24.9.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz", "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -3268,6 +4533,16 @@ "@types/node": "*" } }, + "node_modules/@types/papaparse": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.2.tgz", + "integrity": "sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/ping": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/@types/ping/-/ping-0.4.4.tgz", @@ -3328,6 +4603,40 @@ "@types/node": "*" } }, + "node_modules/@types/ssh2": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.5.tgz", + "integrity": "sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^18.11.18" + } + }, + "node_modules/@types/ssh2/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/ssh2/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", @@ -3356,6 +4665,299 @@ "@types/webidl-conversions": "*" } }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -3384,6 +4986,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3401,6 +5004,19 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -3436,6 +5052,35 @@ "node": ">=6" } }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -3488,6 +5133,13 @@ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", "license": "MIT" }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3500,6 +5152,16 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -3509,16 +5171,6 @@ "safer-buffer": "~2.1.0" } }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -3542,6 +5194,142 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/babel-jest": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", + "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "30.2.0", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.2.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", + "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/babel__core": "^7.20.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", + "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3655,23 +5443,23 @@ } }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", @@ -3687,12 +5475,41 @@ "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -3729,13 +5546,6 @@ "node": ">=8" } }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true, - "license": "ISC" - }, "node_modules/browserslist": { "version": "4.27.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", @@ -3755,6 +5565,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -3769,6 +5580,29 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, "node_modules/bson": { "version": "6.10.4", "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", @@ -3958,16 +5792,13 @@ } }, "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, "node_modules/caniuse-api": { @@ -4002,23 +5833,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/chai": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", - "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -4036,14 +5850,14 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 16" + "node": ">=10" } }, "node_modules/cheerio": { @@ -4126,6 +5940,29 @@ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "license": "ISC" }, + "node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -4207,6 +6044,24 @@ "node": ">=0.10.0" } }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -4493,6 +6348,13 @@ "node": ">=10.0.0" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cron-parser": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", @@ -4589,6 +6451,7 @@ "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.1.tgz", "integrity": "sha512-fm4D8ti0dQmFPeF8DXSAA//btEmqCOgAc/9Oa3C1LW94h5usNrJEfrON7b4FkPZgnDEn6OUs5NdxiJZmAtGOpQ==", "license": "MIT", + "peer": true, "dependencies": { "cssnano-preset-default": "^7.0.9", "lilconfig": "^3.1.3" @@ -4769,19 +6632,6 @@ } } }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/decimal.js": { "version": "10.6.0", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", @@ -4815,14 +6665,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "node_modules/dedent": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", + "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6" + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } } }, "node_modules/deep-is": { @@ -4832,6 +6687,16 @@ "dev": true, "license": "MIT" }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/defer-to-connect": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", @@ -4887,6 +6752,16 @@ "node": ">=8" } }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", @@ -4894,15 +6769,28 @@ "license": "MIT" }, "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/docker-modem": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.6.tgz", @@ -5074,6 +6962,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -5292,6 +7193,7 @@ "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5480,6 +7382,20 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -5542,40 +7458,126 @@ "deprecated": "Use promise-toolbox/fromEvent instead", "license": "MIT" }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -5606,15 +7608,6 @@ "express": ">= 4.11" } }, - "node_modules/express/node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -5643,6 +7636,36 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -5675,6 +7698,26 @@ "fxparser": "src/cli/cli.js" } }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, "node_modules/fecha": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", @@ -5779,16 +7822,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -5913,6 +7946,13 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "license": "MIT" }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -6103,6 +8143,16 @@ "node": ">=0.10" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -6136,6 +8186,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -6179,9 +8239,9 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -6248,6 +8308,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -6285,6 +8366,13 @@ "url": "https://github.com/sindresorhus/got?sponsor=1" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, "node_modules/handlebars": { "version": "4.7.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", @@ -6355,16 +8443,6 @@ "node": ">= 0.4" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, "node_modules/helmet": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", @@ -6529,6 +8607,16 @@ "numbered": "^1.1.0" } }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -6571,6 +8659,13 @@ "node": ">= 4" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -6587,6 +8682,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -6597,6 +8712,18 @@ "node": ">=0.8.19" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -6681,6 +8808,16 @@ "node": ">=8" } }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -6708,16 +8845,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -6736,19 +8863,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -6988,6 +9102,23 @@ "node": ">=8" } }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -7003,6 +9134,21 @@ "node": ">=10" } }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/istanbul-reports": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", @@ -7032,6 +9178,600 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jest": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", + "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/core": "30.2.0", + "@jest/types": "30.2.0", + "import-local": "^3.2.0", + "jest-cli": "30.2.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", + "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.1.1", + "jest-util": "30.2.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", + "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "co": "^4.6.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "p-limit": "^3.1.0", + "pretty-format": "30.2.0", + "pure-rand": "^7.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-cli": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", + "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "yargs": "^17.7.2" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", + "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.2.0", + "@jest/types": "30.2.0", + "babel-jest": "30.2.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-circus": "30.2.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-runner": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "micromatch": "^4.0.8", + "parse-json": "^5.2.0", + "pretty-format": "30.2.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "esbuild-register": ">=3.4.0", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild-register": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", + "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", + "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "jest-util": "30.2.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", + "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-mock": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", + "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", + "micromatch": "^4.0.8", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/jest-leak-detector": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", + "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", + "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.2.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", + "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.2.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", + "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", + "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", + "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", + "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.2.0", + "@jest/environment": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-leak-detector": "30.2.0", + "jest-message-util": "30.2.0", + "jest-resolve": "30.2.0", + "jest-runtime": "30.2.0", + "jest-util": "30.2.0", + "jest-watcher": "30.2.0", + "jest-worker": "30.2.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", + "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/globals": "30.2.0", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", + "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.2.0", + "graceful-fs": "^4.2.11", + "jest-diff": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "pretty-format": "30.2.0", + "semver": "^7.7.2", + "synckit": "^0.11.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", + "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-validate": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", + "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.2.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", + "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "jest-util": "30.2.0", + "string-length": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", + "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.2.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/jmespath": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", @@ -7061,9 +9801,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -7111,6 +9851,19 @@ } } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -7137,6 +9890,19 @@ "dev": true, "license": "MIT" }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -7247,13 +10013,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/just-extend": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", - "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", - "dev": true, - "license": "MIT" - }, "node_modules/jwa": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", @@ -7266,12 +10025,12 @@ } }, "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", "license": "MIT", "dependencies": { - "jwa": "^1.4.1", + "jwa": "^1.4.2", "safe-buffer": "^5.0.1" } }, @@ -7299,6 +10058,16 @@ "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", "license": "MIT" }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -7432,23 +10201,6 @@ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "license": "MIT" }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/logform": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", @@ -7472,13 +10224,6 @@ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "license": "Apache-2.0" }, - "node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, - "license": "MIT" - }, "node_modules/lowercase-keys": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", @@ -7533,6 +10278,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -7578,6 +10340,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -7587,6 +10366,20 @@ "node": ">= 0.6" } }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -7629,6 +10422,16 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/mimic-response": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", @@ -8119,81 +10922,6 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "license": "MIT" }, - "node_modules/mocha": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz", - "integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^10.4.5", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -8370,6 +11098,20 @@ "node": ">= 6.0.0" } }, + "node_modules/mylas": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.14.tgz", + "integrity": "sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/raouldeheer" + } + }, "node_modules/nan": { "version": "2.23.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz", @@ -8395,6 +11137,22 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -8423,31 +11181,6 @@ "integrity": "sha512-kbhcj2SVVR4caaVnGLJKmlk2+f+oLkjqdKeQlmUtz6nGzOpbcobwVIeSURNgraV/v3tlmGIX82OcPCl0K6RbHQ==", "license": "MIT" }, - "node_modules/nise": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz", - "integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^13.0.1", - "@sinonjs/text-encoding": "^0.7.3", - "just-extend": "^6.2.0", - "path-to-regexp": "^8.1.0" - } - }, - "node_modules/nise/node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", @@ -8531,6 +11264,13 @@ "node-gyp-build-optional-packages-test": "build-test.js" } }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, "node_modules/node-releases": { "version": "2.0.26", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz", @@ -8546,6 +11286,58 @@ "node": ">=6.0.0" } }, + "node_modules/nodemon": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", + "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -8567,6 +11359,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -8651,6 +11456,22 @@ "fn.name": "1.x.x" } }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -8710,6 +11531,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -8820,6 +11651,16 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -8851,14 +11692,14 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, - "node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 14.16" + "node": ">=8" } }, "node_modules/picocolors": { @@ -8888,6 +11729,98 @@ "node": ">=4.0.0" } }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/plimit-lit": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz", + "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "queue-lit": "^1.5.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -8907,6 +11840,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -9325,6 +12259,40 @@ "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/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", @@ -9491,6 +12459,34 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-format": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", + "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -9540,6 +12536,13 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, "node_modules/pump": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", @@ -9559,13 +12562,30 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -9574,6 +12594,37 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/queue-lit": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", + "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -9593,16 +12644,6 @@ "dev": true, "license": "MIT" }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -9613,20 +12654,56 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -9698,6 +12775,29 @@ "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", "license": "MIT" }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -9732,12 +12832,47 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rrweb-cssom": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", "license": "MIT" }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -9774,10 +12909,13 @@ "license": "MIT" }, "node_modules/sax": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", - "license": "ISC" + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", + "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } }, "node_modules/saxes": { "version": "6.0.0", @@ -9873,16 +13011,6 @@ "node": ">= 0.8" } }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", @@ -10069,33 +13197,27 @@ "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", "license": "MIT" }, - "node_modules/sinon": { - "version": "19.0.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.2.tgz", - "integrity": "sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g==", + "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": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^13.0.2", - "@sinonjs/samsam": "^8.0.1", - "diff": "^7.0.0", - "nise": "^6.1.1", - "supports-color": "^7.2.0" + "semver": "^7.5.3" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" + "engines": { + "node": ">=10" } }, - "node_modules/sinon/node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "engines": { - "node": ">=0.3.1" + "node": ">=8" } }, "node_modules/slick": { @@ -10125,6 +13247,17 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", @@ -10140,6 +13273,13 @@ "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==", "license": "ISC" }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/ssh2": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz", @@ -10172,6 +13312,29 @@ "node": "*" } }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/standard-as-callback": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", @@ -10213,6 +13376,43 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string-to-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string-to-stream/-/string-to-stream-1.1.1.tgz", @@ -10355,6 +13555,26 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -10397,9 +13617,9 @@ } }, "node_modules/super-simple-scheduler": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.4.4.tgz", - "integrity": "sha512-o99zHFrrFECwj1FFE+ns4su5hKSYif+SUNB3fUJbaRWGkwLLoj19TRuRJYp72TYt9eDiCii68UBuKiLF4tlkMg==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.4.5.tgz", + "integrity": "sha512-xHR3a+pLywY7HtwjWj/+OaANdEwAieQhckC/i0s6OoHtVJQ4+VLO1VfkKUbxp+oSROGXcaZet8PTGONVdKNUUw==", "license": "MIT", "dependencies": { "uuid": "11.1.0", @@ -10467,24 +13687,25 @@ } }, "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==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", "license": "MIT", + "optional": true, "dependencies": { - "commander": "^11.1.0", + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", "css-select": "^5.1.0", - "css-tree": "^3.0.1", + "css-tree": "^2.3.1", "css-what": "^6.1.0", "csso": "^5.0.5", - "picocolors": "^1.1.1", - "sax": "^1.4.1" + "picocolors": "^1.0.0" }, "bin": { - "svgo": "bin/svgo.js" + "svgo": "bin/svgo" }, "engines": { - "node": ">=16" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", @@ -10492,14 +13713,36 @@ } }, "node_modules/svgo/node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "license": "MIT", + "optional": true, "engines": { - "node": ">=16" + "node": ">= 10" } }, + "node_modules/svgo/node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "license": "MIT", + "optional": true, + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/svgo/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 + }, "node_modules/swagger-ui-dist": { "version": "5.30.0", "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.30.0.tgz", @@ -10530,6 +13773,22 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "license": "MIT" }, + "node_modules/synckit": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, "node_modules/tar-fs": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", @@ -10637,6 +13896,13 @@ "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", "license": "MIT" }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -10658,6 +13924,16 @@ "node": ">=0.6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tough-cookie": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", @@ -10691,6 +13967,136 @@ "node": ">= 14.0.0" } }, + "node_modules/ts-jest": { + "version": "29.4.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", + "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.3", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tsc-alias": { + "version": "1.8.16", + "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.16.tgz", + "integrity": "sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.3", + "commander": "^9.0.0", + "get-tsconfig": "^4.10.0", + "globby": "^11.0.4", + "mylas": "^2.1.9", + "normalize-path": "^3.0.0", + "plimit-lit": "^1.2.6" + }, + "bin": { + "tsc-alias": "dist/bin/index.js" + }, + "engines": { + "node": ">=16.20.2" + } + }, + "node_modules/tsc-alias/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -10783,6 +14189,7 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10804,6 +14211,13 @@ "node": ">=0.8.0" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, "node_modules/undici": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz", @@ -10837,6 +14251,41 @@ "node": ">= 0.8" } }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", @@ -10905,6 +14354,13 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -10956,6 +14412,16 @@ "node": ">=18" } }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, "node_modules/web-resource-inliner": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-7.0.0.tgz", @@ -11224,13 +14690,6 @@ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "license": "MIT" }, - "node_modules/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -11325,6 +14784,20 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", @@ -11408,6 +14881,13 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -11435,22 +14915,6 @@ "node": ">=12" } }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/yargs/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -11492,6 +14956,16 @@ "node": ">=8" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/server/package.json b/server/package.json index 7486e81a2..91ba82988 100755 --- a/server/package.json +++ b/server/package.json @@ -5,10 +5,10 @@ "main": "index.js", "type": "module", "scripts": { - "test": "c8 mocha", - "dev": "tsx watch src/index.js", + "test": "NODE_OPTIONS=--experimental-vm-modules c8 jest --runInBand", + "dev": "nodemon --exec tsx src/index.js", "start": "node --watch ./dist/index.js", - "build": "tsc", + "build": "tsc && tsc-alias", "lint": "eslint .", "lint-fix": "eslint --fix .", "format": "prettier --write .", @@ -58,19 +58,28 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/dockerode": "^4.0.0", "@types/express": "5.0.3", + "@types/gamedig": "^5.0.3", + "@types/jest": "^30.0.0", + "@types/jmespath": "^0.15.2", "@types/jsonwebtoken": "9.0.10", + "@types/mjml": "^4.7.4", + "@types/multer": "^2.0.0", "@types/nodemailer": "7.0.1", + "@types/papaparse": "^5.5.2", "@types/ping": "0.4.4", "c8": "10.1.3", - "chai": "5.2.0", "eslint": "^9.17.0", "eslint-plugin-mocha": "^10.5.0", "esm": "3.2.25", "globals": "^15.14.0", - "mocha": "11.1.0", + "jest": "^30.2.0", + "nodemon": "^3.1.11", "prettier": "^3.3.3", - "sinon": "19.0.2", + "ts-jest": "^29.4.6", + "ts-node": "^10.9.2", + "tsc-alias": "1.8.16", "tsx": "4.20.5", "typescript": "5.9.2" } diff --git a/server/scripts/generate-checks.js b/server/scripts/generate-checks.js new file mode 100644 index 000000000..d3a33b033 --- /dev/null +++ b/server/scripts/generate-checks.js @@ -0,0 +1,176 @@ +import mongoose from "mongoose"; +import { MonitorModel } from "../dist/db/models/Monitor.js"; +import { CheckModel } from "../dist/db/models/Check.js"; + +const DEFAULT_MONITOR_ID = "000000000000000000000001"; +const DEFAULT_TEAM_ID = "0000000000000000000000aa"; +const DEFAULT_USER_ID = "0000000000000000000000bb"; +const DEFAULT_MONITOR_TYPE = "http"; +const DEFAULT_TOTAL = 1_000_000; +const DEFAULT_BATCH_SIZE = 5_000; + +const parseObjectId = (value, fallback) => { + try { + return new mongoose.Types.ObjectId(value || fallback); + } catch (error) { + console.warn(`Invalid ObjectId '${value}', falling back to '${fallback}'.`); + return new mongoose.Types.ObjectId(fallback); + } +}; + +async function ensureMonitor({ monitorId, teamId, userId, type }) { + const existing = await MonitorModel.findById(monitorId); + if (existing) { + return existing; + } + + console.log(`Monitor ${monitorId.toString()} not found, creating it.`); + const monitor = new MonitorModel({ + _id: monitorId, + userId, + teamId, + name: `Seed Monitor ${monitorId.toString()}`, + description: "Synthetic monitor for performance testing", + statusWindow: [], + statusWindowSize: 5, + statusWindowThreshold: 60, + type, + ignoreTlsErrors: false, + url: "https://example.com", + isActive: true, + interval: 60000, + alertThreshold: 5, + cpuAlertThreshold: 5, + memoryAlertThreshold: 5, + diskAlertThreshold: 5, + tempAlertThreshold: 5, + selectedDisks: [], + }); + + await monitor.save(); + return monitor; +} + +async function run() { + const mongoUri = process.env.MONGO_URI ?? "mongodb://localhost:27017/uptime_db"; + const monitorId = parseObjectId(process.env.MONITOR_ID ?? DEFAULT_MONITOR_ID, DEFAULT_MONITOR_ID); + const teamId = parseObjectId("69648b0578209af45f9ffe30"); + const userId = parseObjectId("69648b0678209af45f9ffe32"); + const monitorType = process.env.MONITOR_TYPE ?? DEFAULT_MONITOR_TYPE; + const total = Number(process.env.CHECK_TOTAL ?? DEFAULT_TOTAL); + const batchSize = Number(process.env.CHECK_BATCH_SIZE ?? DEFAULT_BATCH_SIZE); + + console.log(`Connecting to MongoDB at ${mongoUri}`); + await mongoose.connect(mongoUri); + + await ensureMonitor({ monitorId, teamId, userId, type: monitorType }); + + console.log(`Seeding ${total} checks for monitor ${monitorId.toString()} (team ${teamId.toString()}) in batches of ${batchSize}.`); + + const docs = []; + const startTime = Date.now(); + + for (let i = 0; i < total; i += 1) { + const baseTime = Date.now() - (total - i) * 1000; + const createdAt = new Date(baseTime); + docs.push({ + metadata: { + monitorId, + teamId, + type: monitorType, + }, + status: i % 50 !== 0, + statusCode: i % 50 !== 0 ? 200 : 500, + responseTime: Math.floor(Math.random() * 1000), + message: i % 50 !== 0 ? "OK" : "Error", + expiry: createdAt, + createdAt, + updatedAt: createdAt, + timings: { + start: baseTime, + socket: baseTime, + lookup: baseTime, + connect: baseTime, + secureConnect: baseTime, + upload: baseTime, + response: baseTime + 40, + end: baseTime + 45, + phases: { + wait: 0, + dns: 1, + tcp: 2, + tls: 4, + request: 0, + firstByte: 30, + download: 5, + total: 45, + }, + }, + cpu: { + physical_core: 8, + logical_core: 16, + frequency: 3600, + temperature: [50 + Math.random() * 10], + free_percent: 40, + usage_percent: Math.random() * 100, + }, + memory: { + total_bytes: 32 * 1024 ** 3, + available_bytes: 16 * 1024 ** 3, + used_bytes: 16 * 1024 ** 3, + usage_percent: Math.random() * 100, + }, + disk: [ + { + device: "/dev/sda1", + mountpoint: "/", + read_speed_bytes: Math.random() * 10_000_000, + write_speed_bytes: Math.random() * 10_000_000, + total_bytes: 512 * 1024 ** 3, + free_bytes: 128 * 1024 ** 3, + usage_percent: Math.random() * 100, + }, + ], + host: { + os: "linux", + platform: "ubuntu", + kernel_version: "5.15.0", + }, + net: [ + { + name: "eth0", + bytes_sent: Math.random() * 10_000_000, + bytes_recv: Math.random() * 10_000_000, + packets_sent: Math.random() * 1_000_000, + packets_recv: Math.random() * 1_000_000, + err_in: 0, + err_out: 0, + drop_in: 0, + drop_out: 0, + fifo_in: 0, + fifo_out: 0, + }, + ], + errors: i % 50 === 0 ? [{ metric: ["uptime"], err: "500" }] : [], + }); + + if (docs.length === batchSize) { + await CheckModel.insertMany(docs, { ordered: false }); + console.log(`Inserted ${i + 1} / ${total}`); + docs.length = 0; + } + } + + if (docs.length > 0) { + await CheckModel.insertMany(docs, { ordered: false }); + } + + await mongoose.disconnect(); + const duration = ((Date.now() - startTime) / 1000).toFixed(2); + console.log(`Finished inserting ${total} checks in ${duration}s`); +} + +run().catch((error) => { + console.error("Failed to seed checks", error); + process.exit(1); +}); diff --git a/server/src/app.js b/server/src/app.js index 4578de896..d782d07eb 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -14,7 +14,6 @@ import { sanitizeBody, sanitizeQuery } from "./middleware/v1/sanitization.js"; export const createApp = ({ services, controllers, envSettings, frontendPath, openApiSpec }) => { const allowedOrigin = envSettings.clientHost; - const app = express(); app.use(generalApiLimiter); // Static files diff --git a/server/src/config/controllers.js b/server/src/config/controllers.js deleted file mode 100644 index 5493a141b..000000000 --- a/server/src/config/controllers.js +++ /dev/null @@ -1,72 +0,0 @@ -import { createCommonDependencies } from "../controllers/v1/baseController.js"; - -// Services - -// V1 Controllers -import MonitorController from "../controllers/v1/monitorController.js"; -import AuthController from "../controllers/v1/authController.js"; -import SettingsController from "../controllers/v1/settingsController.js"; -import CheckController from "../controllers/v1/checkController.js"; -import InviteController from "../controllers/v1/inviteController.js"; -import MaintenanceWindowController from "../controllers/v1/maintenanceWindowController.js"; -import QueueController from "../controllers/v1/queueController.js"; -import LogController from "../controllers/v1/logController.js"; -import StatusPageController from "../controllers/v1/statusPageController.js"; -import NotificationController from "../controllers/v1/notificationController.js"; -import DiagnosticController from "../controllers/v1/diagnosticController.js"; -import IncidentController from "../controllers/v1/incidentController.js"; - -export const initializeControllers = (services) => { - const controllers = {}; - const commonDependencies = createCommonDependencies(services.db, services.errorService, services.logger, services.stringService); - - // V1 - controllers.authController = new AuthController(commonDependencies, { - settingsService: services.settingsService, - emailService: services.emailService, - jobQueue: services.jobQueue, - userService: services.userService, - }); - - controllers.monitorController = new MonitorController(commonDependencies, { - settingsService: services.settingsService, - jobQueue: services.jobQueue, - emailService: services.emailService, - monitorService: services.monitorService, - }); - - controllers.settingsController = new SettingsController(commonDependencies, { - settingsService: services.settingsService, - emailService: services.emailService, - }); - controllers.checkController = new CheckController(commonDependencies, { - settingsService: services.settingsService, - checkService: services.checkService, - }); - controllers.inviteController = new InviteController(commonDependencies, { - inviteService: services.inviteService, - }); - - controllers.maintenanceWindowController = new MaintenanceWindowController(commonDependencies, { - settingsService: services.settingsService, - maintenanceWindowService: services.maintenanceWindowService, - }); - controllers.queueController = new QueueController(commonDependencies, { - jobQueue: services.jobQueue, - }); - controllers.logController = new LogController(commonDependencies); - controllers.statusPageController = new StatusPageController(commonDependencies); - controllers.notificationController = new NotificationController(commonDependencies, { - notificationService: services.notificationService, - statusService: services.statusService, - }); - controllers.diagnosticController = new DiagnosticController(commonDependencies, { - diagnosticService: services.diagnosticService, - }); - - controllers.incidentController = new IncidentController(commonDependencies, { - incidentService: services.incidentService, - }); - - return controllers; -}; diff --git a/server/src/config/controllers.ts b/server/src/config/controllers.ts new file mode 100644 index 000000000..9fe834348 --- /dev/null +++ b/server/src/config/controllers.ts @@ -0,0 +1,35 @@ +import MonitorController from "../controllers/monitorController.js"; +import AuthController from "../controllers/authController.js"; +import SettingsController from "../controllers/settingsController.js"; +import CheckController from "../controllers/checkController.js"; +import InviteController from "../controllers/inviteController.js"; +import MaintenanceWindowController from "../controllers/maintenanceWindowController.js"; +import QueueController from "../controllers/queueController.js"; +import LogController from "../controllers/logController.js"; +import StatusPageController from "../controllers/statusPageController.js"; +import NotificationController from "../controllers/notificationController.js"; +import DiagnosticController from "../controllers/diagnosticController.js"; +import IncidentController from "../controllers/incidentController.js"; + +export const initializeControllers = (services: any) => { + const controllers: Record = {}; + + controllers.authController = new AuthController(services.userService); + + controllers.monitorController = new MonitorController(services.monitorService); + + controllers.settingsController = new SettingsController(services.settingsService, services.emailService, services.db); + controllers.checkController = new CheckController(services.checkService); + controllers.inviteController = new InviteController(services.inviteService); + + controllers.maintenanceWindowController = new MaintenanceWindowController(services.maintenanceWindowService); + controllers.queueController = new QueueController(services.jobQueue); + controllers.logController = new LogController(services.logger); + controllers.statusPageController = new StatusPageController(services.db); + controllers.notificationController = new NotificationController(services.notificationService, services.db); + controllers.diagnosticController = new DiagnosticController(services.diagnosticService); + + controllers.incidentController = new IncidentController(services.incidentService); + + return controllers; +}; diff --git a/server/src/config/routes.js b/server/src/config/routes.ts similarity index 92% rename from server/src/config/routes.js rename to server/src/config/routes.ts index 97b2d6a1d..303dd4eb7 100644 --- a/server/src/config/routes.js +++ b/server/src/config/routes.ts @@ -2,7 +2,7 @@ import { verifyJWT } from "../middleware/v1/verifyJWT.js"; import { authApiLimiter } from "../middleware/v1/rateLimiter.js"; import AuthRoutes from "../routes/v1/authRoute.js"; -import InviteRoutes from "../routes/v1//inviteRoute.js"; +import InviteRoutes from "../routes/v1/inviteRoute.js"; import MonitorRoutes from "../routes/v1/monitorRoute.js"; import CheckRoutes from "../routes/v1/checkRoute.js"; import SettingsRoutes from "../routes/v1/settingsRoute.js"; @@ -10,12 +10,12 @@ import MaintenanceWindowRoutes from "../routes/v1/maintenanceWindowRoute.js"; import StatusPageRoutes from "../routes/v1/statusPageRoute.js"; import QueueRoutes from "../routes/v1/queueRoute.js"; import LogRoutes from "../routes/v1/logRoutes.js"; -import DiagnosticRoutes from "../routes/v1//diagnosticRoute.js"; +import DiagnosticRoutes from "../routes/v1/diagnosticRoute.js"; import NotificationRoutes from "../routes/v1/notificationRoute.js"; import IncidentRoutes from "../routes/v1/incidentRoute.js"; -export const setupRoutes = (app, controllers) => { +export const setupRoutes = (app: any, controllers: Record) => { // V1 const authRoutes = new AuthRoutes(controllers.authController); const monitorRoutes = new MonitorRoutes(controllers.monitorController); diff --git a/server/src/config/services.js b/server/src/config/services.ts similarity index 57% rename from server/src/config/services.js rename to server/src/config/services.ts index 9744c647d..74c68c86e 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.ts @@ -1,23 +1,23 @@ -import ServiceRegistry from "../service/v1/system/serviceRegistry.js"; -import TranslationService from "../service/v1/system/translationService.js"; -import StringService from "../service/v1/system/stringService.js"; -import MongoDB from "../db/v1/MongoDB.js"; -import NetworkService from "../service/v1/infrastructure/networkService.js"; -import EmailService from "../service/v1/infrastructure/emailService.js"; -import BufferService from "../service/v1/infrastructure/bufferService.js"; -import StatusService from "../service/v1/infrastructure/statusService.js"; -import NotificationUtils from "../service/v1/infrastructure/notificationUtils.js"; -import NotificationService from "../service/v1/infrastructure/notificationService.js"; -import ErrorService from "../service/v1/infrastructure/errorService.js"; -import SuperSimpleQueueHelper from "../service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; -import SuperSimpleQueue from "../service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js"; -import UserService from "../service/v1/business/userService.js"; -import CheckService from "../service/v1/business/checkService.js"; -import DiagnosticService from "../service/v1/business/diagnosticService.js"; -import InviteService from "../service/v1/business/inviteService.js"; -import MaintenanceWindowService from "../service/v1/business/maintenanceWindowService.js"; -import MonitorService from "../service/v1/business/monitorService.js"; -import IncidentService from "../service/v1/business/incidentService.js"; +import ServiceRegistry from "../service/system/serviceRegistry.js"; +import TranslationService from "../service/system/translationService.js"; +import StringService from "../service/system/stringService.js"; +import MongoDB from "../db/MongoDB.js"; +import NetworkService from "../service/infrastructure/networkService.js"; +import EmailService from "../service/infrastructure/emailService.js"; +import BufferService from "../service/infrastructure/bufferService.js"; +import StatusService from "../service/infrastructure/statusService.js"; +import NotificationUtils from "../service/infrastructure/notificationUtils.js"; +import NotificationService from "../service/infrastructure/notificationService.js"; +import ErrorService from "../service/infrastructure/errorService.js"; +import SuperSimpleQueueHelper from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; +import SuperSimpleQueue from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js"; +import UserService from "../service/business/userService.js"; +import CheckService from "../service/business/checkService.js"; +import DiagnosticService from "../service/business/diagnosticService.js"; +import InviteService from "../service/business/inviteService.js"; +import MaintenanceWindowService from "../service/business/maintenanceWindowService.js"; +import { MonitorService } from "@/service/index.js"; +import IncidentService from "../service/business/incidentService.js"; import papaparse from "papaparse"; import axios from "axios"; import got from "got"; @@ -34,9 +34,8 @@ const { compile } = pkg; import mjml2html from "mjml"; import jwt from "jsonwebtoken"; import crypto from "crypto"; -import { games } from "gamedig"; +import { games, GameDig } from "gamedig"; import jmespath from "jmespath"; -import { GameDig } from "gamedig"; import { fileURLToPath } from "url"; import { ObjectId } from "mongodb"; @@ -47,33 +46,35 @@ import { GenerateAvatarImage } from "../utils/imageProcessing.js"; import { ParseBoolean } from "../utils/utils.js"; // Models -import Check from "../db/v1/models/Check.js"; -import Monitor from "../db/v1/models/Monitor.js"; -import User from "../db/v1/models/User.js"; -import InviteToken from "../db/v1/models/InviteToken.js"; -import StatusPage from "../db/v1/models/StatusPage.js"; -import Team from "../db/v1/models/Team.js"; -import MaintenanceWindow from "../db/v1/models/MaintenanceWindow.js"; -import MonitorStats from "../db/v1/models/MonitorStats.js"; -import Notification from "../db/v1/models/Notification.js"; -import RecoveryToken from "../db/v1/models/RecoveryToken.js"; -import AppSettings from "../db/v1/models/AppSettings.js"; -import Incident from "../db/v1/models/Incident.js"; +import Monitor from "../db/models/Monitor.js"; +import User from "../db/models/User.js"; +import InviteToken from "../db/models/InviteToken.js"; +import StatusPage from "../db/models/StatusPage.js"; +import Team from "../db/models/Team.js"; +import MaintenanceWindow from "../db/models/MaintenanceWindow.js"; +import MonitorStats from "../db/models/MonitorStats.js"; +import Notification from "../db/models/Notification.js"; +import RecoveryToken from "../db/models/RecoveryToken.js"; +import AppSettings from "../db/models/AppSettings.js"; +import Incident from "../db/models/Incident.js"; -import InviteModule from "../db/v1/modules/inviteModule.js"; -import CheckModule from "../db/v1/modules/checkModule.js"; -import StatusPageModule from "../db/v1/modules/statusPageModule.js"; -import UserModule from "../db/v1/modules/userModule.js"; -import MaintenanceWindowModule from "../db/v1/modules/maintenanceWindowModule.js"; -import MonitorModule from "../db/v1/modules/monitorModule.js"; -import NotificationModule from "../db/v1/modules/notificationModule.js"; -import RecoveryModule from "../db/v1/modules/recoveryModule.js"; -import SettingsModule from "../db/v1/modules/settingsModule.js"; -import IncidentModule from "../db/v1/modules/incidentModule.js"; +import InviteModule from "../db/modules/inviteModule.js"; +import CheckModule from "../db/modules/checkModule.js"; +import StatusPageModule from "../db/modules/statusPageModule.js"; +import UserModule from "../db/modules/userModule.js"; +import MaintenanceWindowModule from "../db/modules/maintenanceWindowModule.js"; +import MonitorModule from "../db/modules/monitorModule.js"; +import NotificationModule from "../db/modules/notificationModule.js"; +import RecoveryModule from "../db/modules/recoveryModule.js"; +import SettingsModule from "../db/modules/settingsModule.js"; +import IncidentModule from "../db/modules/incidentModule.js"; -export const initializeServices = async ({ logger, envSettings, settingsService }) => { +// repositories +import { MongoMonitorsRepository, MongoChecksRepository, MongoMonitorStatsRepository } from "@/repositories/index.js"; + +export const initializeServices = async ({ logger, envSettings, settingsService }: { logger: any; envSettings: any; settingsService: any }) => { const serviceRegistry = new ServiceRegistry({ logger }); - ServiceRegistry.instance = serviceRegistry; + (ServiceRegistry as any).instance = serviceRegistry; const translationService = new TranslationService(logger); await translationService.initialize(); @@ -81,7 +82,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService const stringService = new StringService(translationService); // Create DB - const checkModule = new CheckModule({ logger, Check, Monitor, User }); + const checkModule = new CheckModule({ logger, Monitor, User }); const inviteModule = new InviteModule({ InviteToken, crypto, stringService }); const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService }); const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean, stringService }); @@ -89,7 +90,6 @@ export const initializeServices = async ({ logger, envSettings, settingsService const monitorModule = new MonitorModule({ Monitor, MonitorStats, - Check, stringService, fs, path, @@ -120,6 +120,11 @@ export const initializeServices = async ({ logger, envSettings, settingsService await db.connect(); + // Repositories + const monitorsRepository = new MongoMonitorsRepository(); + const checksRepository = new MongoChecksRepository(); + const monitorStatsRepository = new MongoMonitorStatsRepository(); + const networkService = new NetworkService({ axios, got, @@ -146,7 +151,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService const bufferService = new BufferService({ db, logger, envSettings, incidentService }); - const statusService = new StatusService({ db, logger, buffer: bufferService, incidentService }); + const statusService = new StatusService({ db, logger, buffer: bufferService, incidentService, monitorsRepository }); const notificationUtils = new NotificationUtils({ stringService, @@ -176,6 +181,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService db, logger, helper: superSimpleQueueHelper, + monitorsRepository, }); // Business services @@ -212,7 +218,6 @@ export const initializeServices = async ({ logger, envSettings, settingsService }); const monitorService = new MonitorService({ db, - settingsService, jobQueue: superSimpleQueue, stringService, emailService, @@ -220,6 +225,9 @@ export const initializeServices = async ({ logger, envSettings, settingsService logger, errorService, games, + monitorsRepository, + checksRepository, + monitorStatsRepository, }); const services = { diff --git a/server/src/controllers/authController.ts b/server/src/controllers/authController.ts new file mode 100755 index 000000000..d033aa39d --- /dev/null +++ b/server/src/controllers/authController.ts @@ -0,0 +1,233 @@ +import { Request, Response, NextFunction } from "express"; +import { AppError } from "@/utils/AppError.js"; + +import { + registrationBodyValidation, + loginValidation, + editUserBodyValidation, + recoveryValidation, + recoveryTokenBodyValidation, + newPasswordValidation, + getUserByIdParamValidation, + editUserByIdParamValidation, + editUserByIdBodyValidation, + editSuperadminUserByIdBodyValidation, + editUserPasswordByIdBodyValidation, +} from "@/validation/joi.js"; + +const SERVICE_NAME = "authController"; + +class AuthController { + static SERVICE_NAME = SERVICE_NAME; + + private userService: any; + + constructor(userService: any) { + this.userService = userService; + } + + get serviceName() { + return AuthController.SERVICE_NAME; + } + + registerUser = async (req: Request, res: Response, next: NextFunction) => { + try { + if (req.body?.email) { + req.body.email = req.body.email?.toLowerCase(); + } + await registrationBodyValidation.validateAsync(req.body); + const { user, token } = await this.userService.registerUser(req.body, req.file); + res.status(200).json({ + success: true, + msg: "User registered successfully", + data: { user, token }, + }); + } catch (error) { + next(error); + } + }; + + loginUser = async (req: Request, res: Response, next: NextFunction) => { + try { + if (req.body?.email) { + req.body.email = req.body.email?.toLowerCase(); + } + await loginValidation.validateAsync(req.body); + const { user, token } = await this.userService.loginUser(req.body.email, req.body.password); + + return res.status(200).json({ + success: true, + msg: "User logged in successfully", + data: { + user, + token, + }, + }); + } catch (error) { + next(error); + } + }; + + editUser = async (req: Request, res: Response, next: NextFunction) => { + try { + await editUserBodyValidation.validateAsync(req.body); + + const updatedUser = await this.userService.editUser(req.body, req.file, req.user); + + res.status(200).json({ + success: true, + msg: "User updated successfully", + data: updatedUser, + }); + } catch (error) { + next(error); + } + }; + + checkSuperadminExists = async (req: Request, res: Response, next: NextFunction) => { + try { + const superAdminExists = await this.userService.checkSuperadminExists(); + return res.status(200).json({ + success: true, + msg: "Superadmin existence checked successfully", + data: superAdminExists, + }); + } catch (error) { + next(error); + } + }; + + requestRecovery = async (req: Request, res: Response, next: NextFunction) => { + try { + await recoveryValidation.validateAsync(req.body); + const email = req?.body?.email; + const msgId = await this.userService.requestRecovery(email); + return res.status(200).json({ + success: true, + msg: "Password recovery email sent successfully", + data: msgId, + }); + } catch (error) { + next(error); + } + }; + + validateRecovery = async (req: Request, res: Response, next: NextFunction) => { + try { + await recoveryTokenBodyValidation.validateAsync(req.body); + await this.userService.validateRecovery(req.body.recoveryToken); + return res.status(200).json({ + success: true, + msg: "Recovery token is valid", + }); + } catch (error) { + next(error); + } + }; + + resetPassword = async (req: Request, res: Response, next: NextFunction) => { + try { + await newPasswordValidation.validateAsync(req.body); + const { user, token } = await this.userService.resetPassword(req.body.password, req.body.recoveryToken); + return res.status(200).json({ + success: true, + msg: "Password has been reset successfully", + data: { user, token }, + }); + } catch (error) { + next(error); + } + }; + + deleteUser = async (req: Request, res: Response, next: NextFunction) => { + try { + await this.userService.deleteUser(req.user); + return res.status(200).json({ + success: true, + msg: "User deleted successfully", + }); + } catch (error) { + next(error); + } + }; + + getAllUsers = async (req: Request, res: Response, next: NextFunction) => { + try { + const allUsers = await this.userService.getAllUsers(); + return res.status(200).json({ + success: true, + msg: "Users retrieved successfully", + data: allUsers, + }); + } catch (error) { + next(error); + } + }; + + getUserById = async (req: Request, res: Response, next: NextFunction) => { + try { + await getUserByIdParamValidation.validateAsync(req.params); + const userId = req?.params?.userId; + const roles = req?.user?.role; + + if (!userId) { + throw new Error("No user ID in request"); + } + + if (!roles || roles.length === 0) { + throw new Error("No roles in request"); + } + + const user = await this.userService.getUserById(roles, userId); + + return res.status(200).json({ success: true, msg: "ok", data: user }); + } catch (error) { + next(error); + } + }; + + editUserById = async (req: Request, res: Response, next: NextFunction) => { + try { + const roles = req?.user?.role; + if (!roles.includes("superadmin")) { + throw new AppError({ message: "Unauthorized", status: 403 }); + } + + const userId = req.params.userId; + const user = { ...req.body }; + + await editUserByIdParamValidation.validateAsync(req.params); + // If this is superadmin self edit, allow "superadmin" role + if (userId === req.user._id) { + await editSuperadminUserByIdBodyValidation.validateAsync(req.body); + } else { + await editUserByIdBodyValidation.validateAsync(req.body); + } + + await this.userService.editUserById(userId, user); + return res.status(200).json({ success: true, msg: "ok" }); + } catch (error) { + next(error); + } + }; + + editUserPasswordById = async (req: Request, res: Response, next: NextFunction) => { + try { + const roles = req?.user?.role; + if (!roles.includes("superadmin")) { + throw new AppError({ message: "Unauthorized", status: 403 }); + } + + const userId = req.params.userId; + await editUserByIdParamValidation.validateAsync(req.params); + await editUserPasswordByIdBodyValidation.validateAsync(req.body); + const updatedPassword = req.body.password; + await this.userService.setPasswordByUserId(userId, updatedPassword); + return res.status(200).json({ success: true, msg: "Password reset successfully" }); + } catch (error) { + next(error); + } + }; +} + +export default AuthController; diff --git a/server/src/controllers/checkController.ts b/server/src/controllers/checkController.ts new file mode 100755 index 000000000..8ae6fd785 --- /dev/null +++ b/server/src/controllers/checkController.ts @@ -0,0 +1,181 @@ +import { Request, Response, NextFunction } from "express"; + +import { + getChecksParamValidation, + getChecksQueryValidation, + getTeamChecksQueryValidation, + deleteChecksParamValidation, + deleteChecksByTeamIdParamValidation, + updateChecksTTLBodyValidation, + ackCheckBodyValidation, + ackAllChecksParamValidation, + ackAllChecksBodyValidation, +} from "@/validation/joi.js"; + +const SERVICE_NAME = "checkController"; + +class CheckController { + static SERVICE_NAME = SERVICE_NAME; + + private checkService: any; + constructor(checkService: any) { + this.checkService = checkService; + } + + get serviceName() { + return CheckController.SERVICE_NAME; + } + + getChecksByMonitor = async (req: Request, res: Response, next: NextFunction) => { + try { + await getChecksParamValidation.validateAsync(req.params); + await getChecksQueryValidation.validateAsync(req.query); + + const result = await this.checkService.getChecksByMonitor({ + monitorId: req?.params?.monitorId, + query: req?.query, + teamId: req?.user?.teamId, + }); + + return res.status(200).json({ + success: true, + msg: "Checks retrieved successfully", + data: result, + }); + } catch (error) { + next(error); + } + }; + + getChecksByTeam = async (req: Request, res: Response, next: NextFunction) => { + try { + await getTeamChecksQueryValidation.validateAsync(req.query); + const checkData = await this.checkService.getChecksByTeam({ + teamId: req?.user?.teamId, + query: req?.query, + }); + return res.status(200).json({ + success: true, + msg: "Team checks retrieved successfully", + data: checkData, + }); + } catch (error) { + next(error); + } + }; + + getChecksSummaryByTeamId = async (req: Request, res: Response, next: NextFunction) => { + try { + const summary = await this.checkService.getChecksSummaryByTeamId({ teamId: req?.user?.teamId }); + return res.status(200).json({ + success: true, + msg: "Checks summary retrieved successfully", + data: summary, + }); + } catch (error) { + next(error); + } + }; + + ackCheck = async (req: Request, res: Response, next: NextFunction) => { + try { + await ackCheckBodyValidation.validateAsync(req.body); + + const updatedCheck = await this.checkService.ackCheck({ + checkId: req?.params?.checkId, + teamId: req?.user?.teamId, + ack: req?.body?.ack, + }); + + return res.status(200).json({ + success: true, + msg: "Check acknowledged successfully", + data: updatedCheck, + }); + } catch (error) { + next(error); + } + }; + + ackAllChecks = async (req: Request, res: Response, next: NextFunction) => { + try { + await ackAllChecksParamValidation.validateAsync(req.params); + await ackAllChecksBodyValidation.validateAsync(req.body); + + const teamId = req?.user?.teamId; + if (!teamId) { + throw new Error("No team ID in request"); + } + + const updatedChecks = await this.checkService.ackAllChecks({ + monitorId: req?.params?.monitorId, + path: req?.params?.path, + teamId: req?.user?.teamId, + ack: req?.body?.ack, + }); + + return res.status(200).json({ + success: true, + msg: "All checks acknowledged successfully", + data: updatedChecks, + }); + } catch (error) { + next(error); + } + }; + + deleteChecks = async (req: Request, res: Response, next: NextFunction) => { + try { + await deleteChecksParamValidation.validateAsync(req.params); + + const deletedCount = await this.checkService.deleteChecks({ + monitorId: req.params.monitorId, + teamId: req?.user?.teamId, + }); + + return res.status(200).json({ + success: true, + msg: "Checks deleted successfully", + data: { deletedCount }, + }); + } catch (error) { + next(error); + } + }; + + deleteChecksByTeamId = async (req: Request, res: Response, next: NextFunction) => { + try { + await deleteChecksByTeamIdParamValidation.validateAsync(req.params); + + const deletedCount = await this.checkService.deleteChecksByTeamId({ teamId: req?.user?.teamId }); + + return res.status(200).json({ + success: true, + msg: "Checks deleted successfully", + data: { deletedCount }, + }); + } catch (error) { + next(error); + } + }; + + updateChecksTTL = async (req: Request, res: Response, next: NextFunction) => { + try { + await updateChecksTTLBodyValidation.validateAsync(req.body); + + await this.checkService.updateChecksTTL({ + teamId: req?.user?.teamId, + ttl: req?.body?.ttl, + }); + + return res.status(200).json({ + success: true, + msg: "Checks TTL updated successfully", + }); + } catch (error) { + next(error); + } + }; +} + +export default CheckController; diff --git a/server/src/controllers/controllerUtils.ts b/server/src/controllers/controllerUtils.ts new file mode 100755 index 000000000..af3eddd44 --- /dev/null +++ b/server/src/controllers/controllerUtils.ts @@ -0,0 +1,111 @@ +import { AppError } from "@/utils/AppError.js"; +import { type MonitorType, MonitorTypes } from "@/types/index.js"; + +const fetchMonitorCertificate = async (sslChecker: any, monitor: any): Promise => { + const monitorUrl = new URL(monitor.url); + const hostname = monitorUrl.hostname; + const cert = await sslChecker(hostname); + // Throw an error if no cert or if cert.validTo is not present + if (cert?.validTo === null || cert?.validTo === undefined) { + throw new Error("Certificate not found"); + } + return cert; +}; +const requireString = (value: unknown, fieldName: string): string => { + if (typeof value === "string" && value.trim().length > 0) { + return value; + } + throw new AppError({ message: `${fieldName} is required`, status: 400 }); +}; + +const optionalString = (value: unknown, fieldName: string): string | undefined => { + if (value === undefined) { + return undefined; + } + if (typeof value === "string") { + return value; + } + throw new AppError({ message: `${fieldName} must be a string`, status: 400 }); +}; + +const optionalNumber = (value: unknown, fieldName: string): number | undefined => { + if (value === undefined) { + return undefined; + } + if (typeof value === "number" && Number.isFinite(value)) { + return value; + } + if (typeof value === "string" && value.trim() !== "") { + const parsed = Number(value); + if (!Number.isNaN(parsed)) { + return parsed; + } + } + throw new AppError({ message: `${fieldName} must be a number`, status: 400 }); +}; + +const optionalBoolean = (value: unknown, fieldName: string): boolean | undefined => { + if (value === undefined) { + return undefined; + } + if (typeof value === "boolean") { + return value; + } + if (typeof value === "string") { + if (value === "true") { + return true; + } + if (value === "false") { + return false; + } + } + throw new AppError({ message: `${fieldName} must be a boolean`, status: 400 }); +}; + +const parseMonitorTypeFilter = (value: unknown): MonitorType | MonitorType[] | undefined => { + const parseSingle = (input: unknown): MonitorType => { + if (typeof input !== "string") { + throw new AppError({ message: "Monitor type must be a string", status: 400 }); + } + if (!MonitorTypes.includes(input as MonitorType)) { + throw new AppError({ message: `Invalid monitor type: ${input}`, status: 400 }); + } + return input as MonitorType; + }; + + if (value === undefined) { + return undefined; + } + if (Array.isArray(value)) { + return value.map((entry) => parseSingle(entry)); + } + return parseSingle(value); +}; + +const parseSortOrder = (value: unknown): "asc" | "desc" | undefined => { + if (value === undefined) { + return undefined; + } + if (value === "asc" || value === "desc") { + return value; + } + throw new AppError({ message: "order must be either 'asc' or 'desc'", status: 400 }); +}; + +const requireTeamId = (teamId?: string): string => { + if (!teamId) { + throw new AppError({ message: "Team ID is required", status: 400 }); + } + return teamId; +}; + +export { + fetchMonitorCertificate, + requireString, + optionalString, + optionalNumber, + optionalBoolean, + parseMonitorTypeFilter, + parseSortOrder, + requireTeamId, +}; diff --git a/server/src/controllers/diagnosticController.ts b/server/src/controllers/diagnosticController.ts new file mode 100755 index 000000000..b4d59ceaf --- /dev/null +++ b/server/src/controllers/diagnosticController.ts @@ -0,0 +1,31 @@ +import { Request, Response, NextFunction } from "express"; + +const SERVICE_NAME = "diagnosticController"; +class DiagnosticController { + static SERVICE_NAME = SERVICE_NAME; + + private diagnosticService: any; + + constructor(diagnosticService: any) { + this.diagnosticService = diagnosticService; + } + + get serviceName() { + return DiagnosticController.SERVICE_NAME; + } + + getSystemStats = async (req: Request, res: Response, next: NextFunction) => { + try { + const diagnostics = await this.diagnosticService.getSystemStats(); + return res.status(200).json({ + success: true, + msg: "OK", + data: diagnostics, + }); + } catch (error) { + next(error); + } + }; +} + +export default DiagnosticController; diff --git a/server/src/controllers/incidentController.ts b/server/src/controllers/incidentController.ts new file mode 100644 index 000000000..8a56b5ef8 --- /dev/null +++ b/server/src/controllers/incidentController.ts @@ -0,0 +1,88 @@ +import { Request, Response, NextFunction } from "express"; + +const SERVICE_NAME = "incidentController"; + +class IncidentController { + static SERVICE_NAME = SERVICE_NAME; + + private incidentService: any; + constructor(incidentService: any) { + this.incidentService = incidentService; + } + + get serviceName() { + return IncidentController.SERVICE_NAME; + } + + getIncidentsByTeam = async (req: Request, res: Response, next: NextFunction) => { + try { + const result = await this.incidentService.getIncidentsByTeam({ + teamId: req?.user?.teamId, + query: req?.query, + }); + + return res.status(200).json({ + success: true, + msg: "Incidents retrieved successfully", + data: result, + }); + } catch (error) { + next(error); + } + }; + + getIncidentSummary = async (req: Request, res: Response, next: NextFunction) => { + try { + const summary = await this.incidentService.getIncidentSummary({ + teamId: req?.user?.teamId, + query: req?.query, + }); + + return res.status(200).json({ + success: true, + msg: "Incident summary retrieved successfully", + data: summary, + }); + } catch (error) { + next(error); + } + }; + + getIncidentById = async (req: Request, res: Response, next: NextFunction) => { + try { + const incident = await this.incidentService.getIncidentById({ + incidentId: req?.params?.incidentId, + teamId: req?.user?.teamId, + }); + + return res.status(200).json({ + success: true, + msg: "Incident retrieved successfully", + data: incident, + }); + } catch (error) { + next(error); + } + }; + + resolveIncidentManually = async (req: Request, res: Response, next: NextFunction) => { + try { + const resolvedIncident = await this.incidentService.resolveIncidentManually({ + incidentId: req?.params?.incidentId, + userId: req?.user?._id, + teamId: req?.user?.teamId, + comment: req?.body?.comment, + }); + + return res.status(200).json({ + success: true, + msg: "Incident resolved successfully", + data: resolvedIncident, + }); + } catch (error) { + next(error); + } + }; +} + +export default IncidentController; diff --git a/server/src/controllers/inviteController.ts b/server/src/controllers/inviteController.ts new file mode 100755 index 000000000..c3d63aa80 --- /dev/null +++ b/server/src/controllers/inviteController.ts @@ -0,0 +1,68 @@ +import { Request, Response, NextFunction } from "express"; +import { inviteBodyValidation, inviteVerificationBodyValidation } from "@/validation/joi.js"; +const SERVICE_NAME = "inviteController"; + +class InviteController { + static SERVICE_NAME = SERVICE_NAME; + private inviteService: any; + constructor(inviteService: any) { + this.inviteService = inviteService; + } + + get serviceName() { + return InviteController.SERVICE_NAME; + } + + getInviteToken = async (req: Request, res: Response, next: NextFunction) => { + try { + const invite = req.body; + const teamId = req?.user?.teamId; + invite.teamId = teamId; + await inviteBodyValidation.validateAsync(invite); + const inviteToken = await this.inviteService.getInviteToken({ invite, teamId }); + return res.status(200).json({ + success: true, + msg: "Invite token generated successfully", + data: inviteToken, + }); + } catch (error) { + next(error); + } + }; + + sendInviteEmail = async (req: Request, res: Response, next: NextFunction) => { + try { + const inviteRequest = req.body; + inviteRequest.teamId = req?.user?.teamId; + await inviteBodyValidation.validateAsync(inviteRequest); + + const inviteToken = await this.inviteService.sendInviteEmail({ + inviteRequest, + firstName: req?.user?.firstName, + }); + return res.status(200).json({ + success: true, + msg: "Invite issued successfully", + data: inviteToken, + }); + } catch (error) { + next(error); + } + }; + + verifyInviteToken = async (req: Request, res: Response, next: NextFunction) => { + try { + await inviteVerificationBodyValidation.validateAsync(req.body); + const invite = await this.inviteService.verifyInviteToken({ inviteToken: req?.body?.token }); + return res.status(200).json({ + success: true, + msg: "Invite verified successfully", + data: invite, + }); + } catch (error) { + next(error); + } + }; +} + +export default InviteController; diff --git a/server/src/controllers/logController.ts b/server/src/controllers/logController.ts new file mode 100644 index 000000000..d99de1e9c --- /dev/null +++ b/server/src/controllers/logController.ts @@ -0,0 +1,28 @@ +import { Request, Response, NextFunction } from "express"; + +const SERVICE_NAME = "LogController"; + +class LogController { + static SERVICE_NAME = SERVICE_NAME; + private logger: any; + constructor(logger: any) { + this.logger = logger; + } + get serviceName() { + return LogController.SERVICE_NAME; + } + + getLogs = async (req: Request, res: Response, next: NextFunction) => { + try { + const logs = await this.logger.getLogs(); + res.status(200).json({ + success: true, + msg: "Logs fetched successfully", + data: logs, + }); + } catch (error) { + next(error); + } + }; +} +export default LogController; diff --git a/server/src/controllers/v1/maintenanceWindowController.js b/server/src/controllers/maintenanceWindowController.ts similarity index 52% rename from server/src/controllers/v1/maintenanceWindowController.js rename to server/src/controllers/maintenanceWindowController.ts index be373ace5..afeba9219 100644 --- a/server/src/controllers/v1/maintenanceWindowController.js +++ b/server/src/controllers/maintenanceWindowController.ts @@ -1,3 +1,5 @@ +import { Request, Response, NextFunction } from "express"; +import { AppError } from "@/utils/AppError.js"; import { createMaintenanceWindowBodyValidation, editMaintenanceWindowByIdParamValidation, @@ -6,16 +8,14 @@ import { getMaintenanceWindowsByMonitorIdParamValidation, getMaintenanceWindowsByTeamIdQueryValidation, deleteMaintenanceWindowByIdParamValidation, -} from "../../validation/joi.js"; -import BaseController from "./baseController.js"; +} from "@/validation/joi.js"; const SERVICE_NAME = "maintenanceWindowController"; -class MaintenanceWindowController extends BaseController { +class MaintenanceWindowController { static SERVICE_NAME = SERVICE_NAME; - constructor(commonDependencies, { settingsService, maintenanceWindowService }) { - super(commonDependencies); - this.settingsService = settingsService; + private maintenanceWindowService: any; + constructor(maintenanceWindowService: any) { this.maintenanceWindowService = maintenanceWindowService; } @@ -23,124 +23,128 @@ class MaintenanceWindowController extends BaseController { return MaintenanceWindowController.SERVICE_NAME; } - createMaintenanceWindows = this.asyncHandler( - async (req, res) => { + createMaintenanceWindows = async (req: Request, res: Response, next: NextFunction) => { + try { await createMaintenanceWindowBodyValidation.validateAsync(req.body); const teamId = req?.user?.teamId; if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); + throw new AppError({ message: "Team ID is required", status: 400 }); } await this.maintenanceWindowService.createMaintenanceWindow({ teamId, body: req.body }); - return res.success({ - msg: this.stringService.maintenanceWindowCreate, + return res.status(200).json({ + success: true, + msg: "Maintenance window created successfully", }); - }, - SERVICE_NAME, - "createMaintenanceWindows" - ); - - getMaintenanceWindowById = this.asyncHandler( - async (req, res) => { + } catch (error) { + next(error); + } + }; + getMaintenanceWindowById = async (req: Request, res: Response, next: NextFunction) => { + try { await getMaintenanceWindowByIdParamValidation.validateAsync(req.params); const teamId = req.user.teamId; if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); + throw new AppError({ message: "Team ID is required", status: 400 }); } const maintenanceWindow = await this.maintenanceWindowService.getMaintenanceWindowById({ id: req.params.id, teamId }); - return res.success({ - msg: this.stringService.maintenanceWindowGetById, + return res.status(200).json({ + success: true, + msg: "Maintenance window fetched successfully", data: maintenanceWindow, }); - }, - SERVICE_NAME, - "getMaintenanceWindowById" - ); + } catch (error) { + next(error); + } + }; - getMaintenanceWindowsByTeamId = this.asyncHandler( - async (req, res) => { + getMaintenanceWindowsByTeamId = async (req: Request, res: Response, next: NextFunction) => { + try { await getMaintenanceWindowsByTeamIdQueryValidation.validateAsync(req.query); const teamId = req?.user?.teamId; if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); + throw new AppError({ message: "Team ID is required", status: 400 }); } const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByTeamId({ teamId, query: req.query }); - return res.success({ - msg: this.stringService.maintenanceWindowGetByTeam, + return res.status(200).json({ + success: true, + msg: "Maintenance windows fetched successfully", data: maintenanceWindows, }); - }, - SERVICE_NAME, - "getMaintenanceWindowsByTeamId" - ); + } catch (error) { + next(error); + } + }; - getMaintenanceWindowsByMonitorId = this.asyncHandler( - async (req, res) => { + getMaintenanceWindowsByMonitorId = async (req: Request, res: Response, next: NextFunction) => { + try { await getMaintenanceWindowsByMonitorIdParamValidation.validateAsync(req.params); const teamId = req?.user?.teamId; if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); + throw new AppError({ message: "Team ID is required", status: 400 }); } const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByMonitorId({ monitorId: req.params.monitorId, teamId }); - return res.success({ - msg: this.stringService.maintenanceWindowGetByUser, + return res.status(200).json({ + success: true, + msg: "Maintenance windows fetched successfully", data: maintenanceWindows, }); - }, - SERVICE_NAME, - "getMaintenanceWindowsByMonitorId" - ); - - deleteMaintenanceWindow = this.asyncHandler( - async (req, res) => { + } catch (error) { + next(error); + } + }; + deleteMaintenanceWindow = async (req: Request, res: Response, next: NextFunction) => { + try { await deleteMaintenanceWindowByIdParamValidation.validateAsync(req.params); const teamId = req?.user?.teamId; if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); + throw new AppError({ message: "Team ID is required", status: 400 }); } await this.maintenanceWindowService.deleteMaintenanceWindow({ id: req.params.id, teamId }); - return res.success({ - msg: this.stringService.maintenanceWindowDelete, + return res.status(200).json({ + success: true, + msg: "Maintenance window deleted successfully", }); - }, - SERVICE_NAME, - "deleteMaintenanceWindow" - ); + } catch (error) { + next(error); + } + }; - editMaintenanceWindow = this.asyncHandler( - async (req, res) => { + editMaintenanceWindow = async (req: Request, res: Response, next: NextFunction) => { + try { await editMaintenanceWindowByIdParamValidation.validateAsync(req.params); await editMaintenanceByIdWindowBodyValidation.validateAsync(req.body); const teamId = req.user.teamId; if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); + throw new AppError({ message: "Team ID is required", status: 400 }); } const editedMaintenanceWindow = await this.maintenanceWindowService.editMaintenanceWindow({ id: req.params.id, body: req.body, teamId }); - return res.success({ - msg: this.stringService.maintenanceWindowEdit, + return res.status(200).json({ + success: true, + msg: "Maintenance window edited successfully", data: editedMaintenanceWindow, }); - }, - SERVICE_NAME, - "editMaintenanceWindow" - ); + } catch (error) { + next(error); + } + }; } export default MaintenanceWindowController; diff --git a/server/src/controllers/monitorController.ts b/server/src/controllers/monitorController.ts new file mode 100644 index 000000000..90ab5e7a5 --- /dev/null +++ b/server/src/controllers/monitorController.ts @@ -0,0 +1,464 @@ +import { Request, Response, NextFunction } from "express"; + +import { + getMonitorByIdParamValidation, + getMonitorByIdQueryValidation, + getMonitorsByTeamIdParamValidation, + getMonitorsByTeamIdQueryValidation, + getMonitorsWithChecksQueryValidation, + createMonitorBodyValidation, + editMonitorBodyValidation, + pauseMonitorParamValidation, + getCertificateParamValidation, + getHardwareDetailsByIdParamValidation, + getHardwareDetailsByIdQueryValidation, +} from "@/validation/joi.js"; +import sslChecker from "ssl-checker"; +import { + fetchMonitorCertificate, + requireString, + optionalString, + optionalNumber, + optionalBoolean, + parseMonitorTypeFilter, + parseSortOrder, + requireTeamId, +} from "./controllerUtils.js"; +import { AppError } from "@/utils/AppError.js"; +import { IMonitorService } from "@/service/index.js"; + +const SERVICE_NAME = "monitorController"; +class MonitorController { + static SERVICE_NAME = SERVICE_NAME; + + private monitorService: IMonitorService; + + constructor(monitorService: IMonitorService) { + this.monitorService = monitorService; + } + + get serviceName() { + return MonitorController.SERVICE_NAME; + } + + async verifyTeamAccess(teamId: string, monitorId: string) { + const monitor = await this.monitorService.getMonitorById({ teamId, monitorId }); + if (monitor.teamId !== teamId) { + throw new AppError({ message: "Access denied", status: 403 }); + } + } + + getMonitorCertificate = async (req: Request, res: Response, next: NextFunction) => { + try { + await getCertificateParamValidation.validateAsync(req.params); + const teamId = requireTeamId(req?.user?.teamId); + const monitorId = requireString(req.params?.monitorId, "Monitor ID"); + const monitor = await this.monitorService.getMonitorById({ teamId, monitorId }); + const certificate = await fetchMonitorCertificate(sslChecker, monitor); + + return res.status(200).json({ + success: true, + msg: "SSL certificate retrieved successfully", + data: { + certificateDate: new Date(certificate.validTo), + }, + }); + } catch (error) { + next(error); + } + }; + + getUptimeDetailsById = async (req: Request, res: Response, next: NextFunction) => { + try { + const monitorId = requireString(req?.params?.monitorId, "Monitor ID"); + const dateRange = requireString(req?.query?.dateRange, "dateRange"); + const normalize = optionalBoolean(req?.query?.normalize, "normalize"); + const teamId = requireTeamId(req?.user?.teamId); + + const data = await this.monitorService.getUptimeDetailsById({ + teamId, + monitorId, + dateRange, + normalize, + }); + return res.status(200).json({ + success: true, + msg: "Uptime details retrieved successfully", + data: data, + }); + } catch (error) { + next(error); + } + }; + + getHardwareDetailsById = async (req: Request, res: Response, next: NextFunction) => { + try { + await getHardwareDetailsByIdParamValidation.validateAsync(req.params); + await getHardwareDetailsByIdQueryValidation.validateAsync(req.query); + + const monitorId = requireString(req?.params?.monitorId, "Monitor ID"); + const dateRange = requireString(req?.query?.dateRange, "dateRange"); + const teamId = requireTeamId(req?.user?.teamId); + + const monitor = await this.monitorService.getHardwareDetailsById({ + teamId, + monitorId, + dateRange, + }); + + return res.status(200).json({ + success: true, + msg: "Hardware details retrieved successfully", + data: monitor, + }); + } catch (error) { + next(error); + } + }; + getPageSpeedDetailsById = async (req: Request, res: Response, next: NextFunction) => { + try { + await getHardwareDetailsByIdParamValidation.validateAsync(req.params); + await getHardwareDetailsByIdQueryValidation.validateAsync(req.query); + + const monitorId = requireString(req?.params?.monitorId, "Monitor ID"); + const dateRange = requireString(req?.query?.dateRange, "dateRange"); + const teamId = requireTeamId(req?.user?.teamId); + + const monitor = await this.monitorService.getPageSpeedDetailsById({ + teamId, + monitorId, + dateRange, + }); + + return res.status(200).json({ + success: true, + msg: "Page speed details retrieved successfully", + data: monitor, + }); + } catch (error) { + next(error); + } + }; + + getMonitorById = async (req: Request, res: Response, next: NextFunction) => { + try { + await getMonitorByIdParamValidation.validateAsync(req.params); + await getMonitorByIdQueryValidation.validateAsync(req.query); + + const teamId = requireTeamId(req?.user?.teamId); + const monitorId = requireString(req?.params?.monitorId, "Monitor ID"); + + const monitor = await this.monitorService.getMonitorById({ teamId, monitorId }); + + return res.status(200).json({ + success: true, + msg: "Monitor retrieved successfully", + data: monitor, + }); + } catch (error) { + next(error); + } + }; + + createMonitor = async (req: Request, res: Response, next: NextFunction) => { + try { + await createMonitorBodyValidation.validateAsync(req.body); + + const userId = requireString(req?.user?._id, "User ID"); + const teamId = requireTeamId(req?.user?.teamId); + + const monitor = await this.monitorService.createMonitor(teamId, userId, req.body); + + return res.status(200).json({ + success: true, + msg: "Monitor created successfully", + data: monitor, + }); + } catch (error) { + next(error); + } + }; + + createBulkMonitors = async (req: Request, res: Response, next: NextFunction) => { + try { + if (!req.file) { + throw new AppError({ message: "No file uploaded", status: 400 }); + } + + if (!req.file.mimetype.includes("csv")) { + throw new AppError({ message: "File is not a CSV", status: 400 }); + } + + if (req.file.size === 0) { + throw new AppError({ message: "File is empty", status: 400 }); + } + + const userId = requireString(req?.user?._id, "User ID"); + const teamId = requireTeamId(req?.user?.teamId); + + const fileData = req?.file?.buffer?.toString("utf-8"); + if (!fileData) { + throw new AppError({ message: "Cannot get file from buffer", status: 400 }); + } + + const monitors = await this.monitorService.createBulkMonitors(fileData, userId, teamId); + + return res.status(200).json({ + success: true, + msg: "Bulk monitors created successfully", + data: monitors, + }); + } catch (error) { + next(error); + } + }; + + deleteMonitor = async (req: Request, res: Response, next: NextFunction) => { + try { + await getMonitorByIdParamValidation.validateAsync(req.params); + const monitorId = requireString(req?.params?.monitorId, "Monitor ID"); + const teamId = requireTeamId(req?.user?.teamId); + + const deletedMonitor = await this.monitorService.deleteMonitor({ teamId, monitorId }); + + return res.status(200).json({ + success: true, + msg: "Monitor deleted successfully", + data: deletedMonitor, + }); + } catch (error) { + next(error); + } + }; + + deleteAllMonitors = async (req: Request, res: Response, next: NextFunction) => { + try { + const teamId = requireTeamId(req?.user?.teamId); + + const deletedCount = await this.monitorService.deleteAllMonitors({ teamId }); + + return res.status(200).json({ + success: true, + msg: `Deleted ${deletedCount} monitors`, + }); + } catch (error) { + next(error); + } + }; + + editMonitor = async (req: Request, res: Response, next: NextFunction) => { + try { + await getMonitorByIdParamValidation.validateAsync(req.params); + await editMonitorBodyValidation.validateAsync(req.body); + const monitorId = requireString(req?.params?.monitorId, "Monitor ID"); + const teamId = requireTeamId(req?.user?.teamId); + + const editedMonitor = await this.monitorService.editMonitor({ teamId, monitorId, body: req.body }); + + return res.status(200).json({ + success: true, + msg: "Monitor edited successfully", + data: editedMonitor, + }); + } catch (error) { + next(error); + } + }; + + pauseMonitor = async (req: Request, res: Response, next: NextFunction) => { + try { + await pauseMonitorParamValidation.validateAsync(req.params); + + const monitorId = requireString(req?.params?.monitorId, "Monitor ID"); + const teamId = requireTeamId(req?.user?.teamId); + + const monitor = await this.monitorService.pauseMonitor({ teamId, monitorId }); + + return res.status(200).json({ + success: true, + msg: monitor.isActive ? "Monitor resumed successfully" : "Monitor paused successfully", + data: monitor, + }); + } catch (error) { + next(error); + } + }; + + addDemoMonitors = async (req: Request, res: Response, next: NextFunction) => { + try { + const _id = requireString(req?.user?._id, "User ID"); + const teamId = requireTeamId(req?.user?.teamId); + const demoMonitors = await this.monitorService.addDemoMonitors({ userId: _id, teamId }); + + return res.status(200).json({ + success: true, + msg: "Demo monitors added successfully", + data: demoMonitors?.length ?? 0, + }); + } catch (error) { + next(error); + } + }; + + sendTestEmail = async (req: Request, res: Response, next: NextFunction) => { + try { + const { to } = req.body; + if (!to || typeof to !== "string") { + throw new AppError({ message: "Invalid 'to' email address", status: 400 }); + } + + const messageId = await this.monitorService.sendTestEmail({ to }); + return res.status(200).json({ + success: true, + msg: "Test email sent successfully", + data: { messageId }, + }); + } catch (error) { + next(error); + } + }; + + getMonitorsByTeamId = async (req: Request, res: Response, next: NextFunction) => { + try { + await getMonitorsByTeamIdParamValidation.validateAsync(req.params); + await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); + + const teamId = requireTeamId(req?.user?.teamId); + const type = parseMonitorTypeFilter(req.query?.type); + const filter = optionalString(req.query?.filter, "filter"); + + const monitors = await this.monitorService.getMonitorsByTeamId({ teamId, type, filter }); + + return res.status(200).json({ + success: true, + msg: "Monitors retrieved successfully", + data: monitors, + }); + } catch (error) { + next(error); + } + }; + + getMonitorsAndSummaryByTeamId = async (req: Request, res: Response, next: NextFunction) => { + try { + await getMonitorsByTeamIdParamValidation.validateAsync(req.params); + await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); + + const explain = optionalBoolean(req?.query?.explain, "explain"); + const type = parseMonitorTypeFilter(req?.query?.type); + const teamId = requireTeamId(req?.user?.teamId); + + const result = await this.monitorService.getMonitorsAndSummaryByTeamId({ teamId, type, explain }); + + return res.status(200).json({ + msg: "Monitors and summary retrieved successfully", + data: result, + }); + } catch (error) { + next(error); + } + }; + + getMonitorsWithChecksByTeamId = async (req: Request, res: Response, next: NextFunction) => { + try { + await getMonitorsByTeamIdParamValidation.validateAsync(req.params); + await getMonitorsWithChecksQueryValidation.validateAsync(req.query); + + const explain = optionalBoolean(req?.query?.explain, "explain"); + const limit = optionalNumber(req?.query?.limit, "limit"); + const page = optionalNumber(req?.query?.page, "page"); + const rowsPerPage = optionalNumber(req?.query?.rowsPerPage, "rowsPerPage"); + const filter = optionalString(req?.query?.filter, "filter"); + const field = optionalString(req?.query?.field, "field"); + const order = parseSortOrder(req?.query?.order); + const type = parseMonitorTypeFilter(req?.query?.type); + const teamId = requireTeamId(req?.user?.teamId); + + const monitors = await this.monitorService.getMonitorsWithChecksByTeamId({ + teamId, + limit, + type, + page, + rowsPerPage, + filter, + field, + order, + explain, + }); + + return res.status(200).json({ + msg: "Monitors retrieved successfully", + data: monitors, + }); + } catch (error) { + next(error); + } + }; + exportMonitorsToCSV = async (req: Request, res: Response, next: NextFunction) => { + try { + const teamId = req?.user?.teamId; + if (!teamId) { + throw new AppError({ message: "Team ID is required", status: 400 }); + } + + const csv = await this.monitorService.exportMonitorsToCSV({ teamId }); + res.setHeader("Content-Type", "text/csv"); + res.setHeader("Content-Disposition", "attachment; filename=monitors.csv"); + return res.send(csv); + } catch (error) { + next(error); + } + }; + + exportMonitorsToJSON = async (req: Request, res: Response, next: NextFunction) => { + try { + const teamId = req?.user?.teamId; + if (!teamId) { + throw new AppError({ message: "Team ID is required", status: 400 }); + } + + const json = await this.monitorService.exportMonitorsToJSON({ teamId }); + + return res.status(200).json({ + success: true, + msg: "Monitors exported successfully", + data: json, + }); + } catch (error) { + next(error); + } + }; + + getAllGames = async (req: Request, res: Response, next: NextFunction) => { + try { + const games = this.monitorService.getAllGames(); + return res.status(200).json({ + success: true, + msg: "Supported games retrieved successfully", + data: games, + }); + } catch (error) { + next(error); + } + }; + + getGroupsByTeamId = async (req: Request, res: Response, next: NextFunction) => { + try { + const teamId = req?.user?.teamId; + if (!teamId) { + throw new AppError({ message: "Team ID is required", status: 400 }); + } + + const groups = await this.monitorService.getGroupsByTeamId({ teamId }); + + return res.status(200).json({ + msg: "Groups retrieved successfully", + data: groups, + }); + } catch (error) { + next(error); + } + }; +} + +export default MonitorController; diff --git a/server/src/controllers/notificationController.ts b/server/src/controllers/notificationController.ts new file mode 100644 index 000000000..74849c6aa --- /dev/null +++ b/server/src/controllers/notificationController.ts @@ -0,0 +1,197 @@ +import { Request, Response, NextFunction } from "express"; + +import { createNotificationBodyValidation } from "@/validation/joi.js"; +import { AppError } from "@/utils/AppError.js"; + +const SERVICE_NAME = "NotificationController"; + +class NotificationController { + static SERVICE_NAME = SERVICE_NAME; + private db: any; + private notificationService: any; + constructor(notificationService: any, db: any) { + this.notificationService = notificationService; + this.db = db; + } + + get serviceName() { + return NotificationController.SERVICE_NAME; + } + + testNotification = async (req: Request, res: Response, next: NextFunction) => { + try { + const notification = req.body; + + const success = await this.notificationService.sendTestNotification(notification); + + if (!success) { + throw new AppError({ message: "Sending notification failed", status: 500 }); + } + + return res.status(200).json({ + success: true, + msg: "Notification sent successfully", + }); + } catch (error) { + next(error); + } + }; + + createNotification = async (req: Request, res: Response, next: NextFunction) => { + try { + await createNotificationBodyValidation.validateAsync(req.body, { + abortEarly: false, + }); + + const body = req.body; + + const teamId = req?.user?.teamId; + if (!teamId) { + throw new AppError({ message: "Team ID is required", status: 400 }); + } + + const userId = req?.user?._id; + if (!userId) { + throw new AppError({ message: "User ID is required", status: 400 }); + } + body.userId = userId; + body.teamId = teamId; + + const notification = await this.db.notificationModule.createNotification(body); + return res.status(200).json({ + success: true, + msg: "Notification created successfully", + data: notification, + }); + } catch (error) { + next(error); + } + }; + + getNotificationsByTeamId = async (req: Request, res: Response, next: NextFunction) => { + try { + const teamId = req?.user?.teamId; + if (!teamId) { + throw new AppError({ message: "Team ID is required", status: 400 }); + } + + const notifications = await this.db.notificationModule.getNotificationsByTeamId(teamId); + + return res.status(200).json({ + success: true, + msg: "Notifications fetched successfully", + data: notifications, + }); + } catch (error) { + next(error); + } + }; + + deleteNotification = async (req: Request, res: Response, next: NextFunction) => { + try { + const teamId = req?.user?.teamId; + if (!teamId) { + throw new AppError({ message: "Team ID is required", status: 400 }); + } + + const notification = await this.db.notificationModule.getNotificationById(req.params.id); + if (!notification.teamId.equals(teamId)) { + throw new AppError({ message: "Unauthorized", status: 403 }); + } + + await this.db.notificationModule.deleteNotificationById(req.params.id); + return res.status(200).json({ + success: true, + msg: "Notification deleted successfully", + }); + } catch (error) { + next(error); + } + }; + + getNotificationById = async (req: Request, res: Response, next: NextFunction) => { + try { + const notification = await this.db.notificationModule.getNotificationById(req.params.id); + + const teamId = req?.user?.teamId; + if (!teamId) { + throw new AppError({ message: "Team ID is required", status: 400 }); + } + + if (!notification.teamId.equals(teamId)) { + throw new AppError({ message: "Unauthorized", status: 403 }); + } + return res.status(200).json({ + success: true, + msg: "Notification fetched successfully", + data: notification, + }); + } catch (error) { + next(error); + } + }; + + editNotification = async (req: Request, res: Response, next: NextFunction) => { + try { + await createNotificationBodyValidation.validateAsync(req.body, { + abortEarly: false, + }); + + const teamId = req?.user?.teamId; + if (!teamId) { + throw new AppError({ message: "Team ID is required", status: 400 }); + } + + const notification = await this.db.notificationModule.getNotificationById(req.params.id); + + if (!notification.teamId.equals(teamId)) { + throw new AppError({ message: "Unauthorized", status: 403 }); + } + + const editedNotification = await this.db.notificationModule.editNotification(req.params.id, req.body); + return res.status(200).json({ + success: true, + msg: "Notification updated successfully", + data: editedNotification, + }); + } catch (error) { + next(error); + } + }; + + testAllNotifications = async (req: Request, res: Response, next: NextFunction) => { + try { + const monitorId = req.body.monitorId; + const teamId = req?.user?.teamId; + if (!teamId) { + throw new AppError({ message: "Team ID is required", status: 400 }); + } + + const monitor = await this.db.monitorModule.getMonitorById(monitorId); + + if (!monitor.teamId.equals(teamId)) { + throw new AppError({ message: "Unauthorized", status: 403 }); + } + + const notifications = monitor.notifications; + if (notifications.length === 0) { + throw new AppError({ message: "No notifications", status: 400 }); + } + + const result = await this.notificationService.testAllNotifications(notifications); + + if (!result) { + throw new AppError({ message: "Failed to send all notifications", status: 500 }); + } + + return res.status(200).json({ + success: true, + msg: "All notifications sent successfully", + }); + } catch (error) { + next(error); + } + }; +} + +export default NotificationController; diff --git a/server/src/controllers/queueController.ts b/server/src/controllers/queueController.ts new file mode 100755 index 000000000..9885bab87 --- /dev/null +++ b/server/src/controllers/queueController.ts @@ -0,0 +1,94 @@ +import { Request, Response, NextFunction } from "express"; + +const SERVICE_NAME = "JobQueueController"; + +class JobQueueController { + static SERVICE_NAME = SERVICE_NAME; + private jobQueue: any; + constructor(jobQueue: any) { + this.jobQueue = jobQueue; + } + + get serviceName() { + return JobQueueController.SERVICE_NAME; + } + + getMetrics = async (req: Request, res: Response, next: NextFunction) => { + try { + const metrics = await this.jobQueue.getMetrics(); + res.status(200).json({ + success: true, + msg: "Queue metrics fetched successfully", + data: metrics, + }); + } catch (error) { + next(error); + } + }; + + getJobs = async (req: Request, res: Response, next: NextFunction) => { + try { + const jobs = await this.jobQueue.getJobs(); + return res.status(200).json({ + success: true, + msg: "Queue jobs fetched successfully", + data: jobs, + }); + } catch (error) { + next(error); + } + }; + + getAllMetrics = async (req: Request, res: Response, next: NextFunction) => { + try { + const jobs = await this.jobQueue.getJobs(); + const metrics = await this.jobQueue.getMetrics(); + return res.status(200).json({ + success: true, + msg: "Queue metrics fetched successfully", + data: { jobs, metrics }, + }); + } catch (error) { + next(error); + } + }; + + addJob = async (req: Request, res: Response, next: NextFunction) => { + try { + await this.jobQueue.addJob(Math.random().toString(36).substring(7)); + return res.status(200).json({ + success: true, + msg: "Job added to queue successfully", + }); + } catch (error) { + next(error); + } + }; + + flushQueue = async (req: Request, res: Response, next: NextFunction) => { + try { + const result = await this.jobQueue.flushQueues(); + return res.status(200).json({ + success: true, + msg: "Queue flushed successfully", + data: result, + }); + } catch (error) { + next(error); + } + }; + + checkQueueHealth = async (req: Request, res: Response, next: NextFunction) => { + try { + const stuckQueues = await this.jobQueue.checkQueueHealth(); + return res.status(200).json({ + success: true, + msg: "Queue health checked successfully", + data: stuckQueues, + }); + } catch (error) { + next(error); + } + }; +} +export default JobQueueController; diff --git a/server/src/controllers/v1/settingsController.js b/server/src/controllers/settingsController.ts similarity index 57% rename from server/src/controllers/v1/settingsController.js rename to server/src/controllers/settingsController.ts index 3db73a05a..5d3f6ed0e 100644 --- a/server/src/controllers/v1/settingsController.js +++ b/server/src/controllers/settingsController.ts @@ -1,28 +1,32 @@ -import { updateAppSettingsBodyValidation } from "../../validation/joi.js"; -import { sendTestEmailBodyValidation } from "../../validation/joi.js"; -import BaseController from "./baseController.js"; +import { Request, Response, NextFunction } from "express"; +import { sendTestEmailBodyValidation, updateAppSettingsBodyValidation } from "@/validation/joi.js"; +import { AppError } from "@/utils/AppError.js"; const SERVICE_NAME = "SettingsController"; -class SettingsController extends BaseController { +class SettingsController { static SERVICE_NAME = SERVICE_NAME; - constructor(commonDependencies, { settingsService, emailService }) { - super(commonDependencies); + private settingsService: any; + private emailService: any; + private db: any; + constructor(settingsService: any, emailService: any, db: any) { this.settingsService = settingsService; this.emailService = emailService; + this.db = db; } get serviceName() { return SettingsController.SERVICE_NAME; } - buildAppSettings = (dbSettings) => { + buildAppSettings = (dbSettings: any) => { const sanitizedSettings = { ...dbSettings }; delete sanitizedSettings.version; const returnSettings = { pagespeedKeySet: false, emailPasswordSet: false, + settings: null, }; if (typeof sanitizedSettings.pagespeedApiKey !== "undefined") { @@ -37,37 +41,35 @@ class SettingsController extends BaseController { return returnSettings; }; - getAppSettings = this.asyncHandler( - async (req, res) => { + getAppSettings = async (req: Request, res: Response, next: NextFunction) => { + try { const dbSettings = await this.settingsService.getDBSettings(); const returnSettings = this.buildAppSettings(dbSettings); - return res.success({ - msg: this.stringService.getAppSettings, + return res.status(200).json({ + success: true, + msg: "App settings fetched successfully", data: returnSettings, }); - }, - SERVICE_NAME, - "getAppSettings" - ); + } catch (error) { + next(error); + } + }; - updateAppSettings = this.asyncHandler( - async (req, res) => { - await updateAppSettingsBodyValidation.validateAsync(req.body); + updateAppSettings = async (req: Request, res: Response, next: NextFunction) => { + await updateAppSettingsBodyValidation.validateAsync(req.body); - const updatedSettings = await this.db.settingsModule.updateAppSettings(req.body); - const returnSettings = this.buildAppSettings(updatedSettings); - return res.success({ - msg: this.stringService.updateAppSettings, - data: returnSettings, - }); - }, - SERVICE_NAME, - "updateAppSettings" - ); + const updatedSettings = await this.db.settingsModule.updateAppSettings(req.body); + const returnSettings = this.buildAppSettings(updatedSettings); + return res.status(200).json({ + success: true, + msg: "App settings updated successfully", + data: returnSettings, + }); + }; - sendTestEmail = this.asyncHandler( - async (req, res) => { + sendTestEmail = async (req: Request, res: Response, next: NextFunction) => { + try { await sendTestEmailBodyValidation.validateAsync(req.body); const { @@ -86,7 +88,7 @@ class SettingsController extends BaseController { systemEmailTLSServername, } = req.body; - const subject = this.stringService.testEmailSubject; + const subject = "This is a test email from Checkmate"; const context = { testName: "Monitoring System" }; const html = await this.emailService.buildEmail("testEmailTemplate", context); @@ -106,17 +108,18 @@ class SettingsController extends BaseController { }); if (!messageId) { - throw this.errorService.createServerError("Failed to send test email."); + throw new AppError({ message: "Failed to send test email.", status: 500 }); } - return res.success({ - msg: this.stringService.sendTestEmail, + return res.status(200).json({ + success: true, + msg: "Test email sent successfully", data: { messageId }, }); - }, - SERVICE_NAME, - "sendTestEmail" - ); + } catch (error) { + next(error); + } + }; } export default SettingsController; diff --git a/server/src/controllers/statusPageController.ts b/server/src/controllers/statusPageController.ts new file mode 100644 index 000000000..105beb230 --- /dev/null +++ b/server/src/controllers/statusPageController.ts @@ -0,0 +1,115 @@ +import { Request, Response, NextFunction } from "express"; + +import { createStatusPageBodyValidation, getStatusPageParamValidation, getStatusPageQueryValidation, imageValidation } from "@/validation/joi.js"; +import { AppError } from "@/utils/AppError.js"; + +const SERVICE_NAME = "statusPageController"; + +class StatusPageController { + static SERVICE_NAME = SERVICE_NAME; + private db: any; + constructor(db: any) { + this.db = db; + } + + get serviceName() { + return StatusPageController.SERVICE_NAME; + } + + createStatusPage = async (req: Request, res: Response, next: NextFunction) => { + try { + await createStatusPageBodyValidation.validateAsync(req.body); + await imageValidation.validateAsync(req.file); + + const { _id, teamId } = req.user; + const statusPage = await this.db.statusPageModule.createStatusPage({ + statusPageData: req.body, + image: req.file, + userId: _id, + teamId, + }); + return res.status(200).json({ + success: true, + msg: "Status page created successfully", + data: statusPage, + }); + } catch (error) { + next(error); + } + }; + + updateStatusPage = async (req: Request, res: Response, next: NextFunction) => { + try { + await createStatusPageBodyValidation.validateAsync(req.body); + await imageValidation.validateAsync(req.file); + + const statusPage = await this.db.statusPageModule.updateStatusPage(req.body, req.file); + if (statusPage === null) { + throw new AppError({ message: "Status page not found", status: 404 }); + } + return res.status(200).json({ + success: true, + msg: "Status page updated successfully", + data: statusPage, + }); + } catch (error) { + next(error); + } + }; + + getStatusPage = async (req: Request, res: Response, next: NextFunction) => { + try { + const statusPage = await this.db.statusPageModule.getStatusPage(); + return res.status(200).json({ + success: true, + msg: "Status page retrieved successfully", + data: statusPage, + }); + } catch (error) { + next(error); + } + }; + + getStatusPageByUrl = async (req: Request, res: Response, next: NextFunction) => { + try { + await getStatusPageParamValidation.validateAsync(req.params); + await getStatusPageQueryValidation.validateAsync(req.query); + + const statusPage = await this.db.statusPageModule.getStatusPageByUrl(req.params.url); + return res.status(200).json({ + success: true, + msg: "Status page retrieved successfully", + data: statusPage, + }); + } catch (error) { + next(error); + } + }; + getStatusPagesByTeamId = async (req: Request, res: Response, next: NextFunction) => { + try { + const teamId = req.user.teamId; + const statusPages = await this.db.statusPageModule.getStatusPagesByTeamId(teamId); + + return res.status(200).json({ + success: true, + msg: "Status pages retrieved successfully", + data: statusPages, + }); + } catch (error) { + next(error); + } + }; + deleteStatusPage = async (req: Request, res: Response, next: NextFunction) => { + try { + await this.db.statusPageModule.deleteStatusPage(req.params.url); + return res.status(200).json({ + success: true, + msg: "Status page deleted successfully", + }); + } catch (error) { + next(error); + } + }; +} + +export default StatusPageController; diff --git a/server/src/controllers/v1/announcementsController.js b/server/src/controllers/v1/announcementsController.js deleted file mode 100755 index 0425b44aa..000000000 --- a/server/src/controllers/v1/announcementsController.js +++ /dev/null @@ -1,77 +0,0 @@ -import { createAnnouncementValidation } from "../../validation/joi.js"; -import BaseController from "./baseController.js"; - -const SERVICE_NAME = "announcementController"; - -/** - * Controller for managing announcements in the system. - * This class handles the creation of new announcements. - * - * @class AnnouncementController - */ - -class AnnouncementController extends BaseController { - static SERVICE_NAME = SERVICE_NAME; - constructor(commonDependencies) { - super(commonDependencies); - this.createAnnouncement = this.createAnnouncement.bind(this); - this.getAnnouncement = this.getAnnouncement.bind(this); - } - - get serviceName() { - return AnnouncementController.SERVICE_NAME; - } - - /** - * Handles the creation of a new announcement. - * - * @async - * @param {Object} req - The request object, containing the announcement data in the body. - * @param {Object} res - The response object used to send the result back to the client. - * @param {Function} next - The next middleware function in the stack for error handling. - * - * @returns {Promise} A promise that resolves once the response is sent. - */ - createAnnouncement = asyncHandler( - async (req, res, next) => { - await createAnnouncementValidation.validateAsync(req.body); - const { title, message } = req.body; - const announcementData = { - title: title.trim(), - message: message.trim(), - userId: req.user._id, - }; - - const newAnnouncement = await this.db.createAnnouncement(announcementData); - return res.success({ - msg: this.stringService.createAnnouncement, - data: newAnnouncement, - }); - }, - SERVICE_NAME, - "createAnnouncement" - ); - - /** - * Handles retrieving announcements with pagination. - * - * @async - * @param {Object} res - The response object used to send the result back to the client. - * - `data`: The list of announcements to be sent back to the client. - * - `msg`: A message about the success of the request. - * @param {Function} next - The next middleware function in the stack for error handling. - */ - getAnnouncement = asyncHandler( - async (req, res, next) => { - const allAnnouncements = await this.db.getAnnouncements(); - return res.success({ - msg: this.stringService.getAnnouncement, - data: allAnnouncements, - }); - }, - SERVICE_NAME, - "getAnnouncement" - ); -} - -export default AnnouncementController; diff --git a/server/src/controllers/v1/authController.js b/server/src/controllers/v1/authController.js deleted file mode 100755 index e52de3849..000000000 --- a/server/src/controllers/v1/authController.js +++ /dev/null @@ -1,478 +0,0 @@ -import BaseController from "./baseController.js"; -import { - registrationBodyValidation, - loginValidation, - editUserBodyValidation, - recoveryValidation, - recoveryTokenBodyValidation, - newPasswordValidation, - getUserByIdParamValidation, - editUserByIdParamValidation, - editUserByIdBodyValidation, - editSuperadminUserByIdBodyValidation, - editUserPasswordByIdBodyValidation, -} from "../../validation/joi.js"; - -const SERVICE_NAME = "authController"; -/** - * Authentication Controller - * - * Handles all authentication-related HTTP requests including user registration, - * login, password recovery, and user management operations. - * - * @class AuthController - * @description Manages user authentication and authorization operations - */ -class AuthController extends BaseController { - static SERVICE_NAME = SERVICE_NAME; - /** - * Creates an instance of AuthController. - * - * @param {Object} commonDependencies - Common dependencies injected into the controller - * @param {Object} dependencies - The dependencies required by the controller - * @param {Object} dependencies.settingsService - Service for application settings - * @param {Object} dependencies.emailService - Service for email operations - * @param {Object} dependencies.jobQueue - Service for job queue operations - * @param {Object} dependencies.userService - User business logic service - */ - constructor(commonDependencies, { settingsService, emailService, jobQueue, userService }) { - super(commonDependencies); - this.settingsService = settingsService; - this.emailService = emailService; - this.jobQueue = jobQueue; - this.userService = userService; - } - - get serviceName() { - return AuthController.SERVICE_NAME; - } - - /** - * Registers a new user in the system. - * - * @async - * @function registerUser - * @param {Object} req - Express request object - * @param {Object} req.body - Request body containing user registration data - * @param {string} req.body.firstName - User's first name - * @param {string} req.body.lastName - User's last name - * @param {string} req.body.email - User's email address (will be converted to lowercase) - * @param {string} req.body.password - User's password - * @param {string} [req.body.inviteToken] - Invite token for registration (required if superadmin exists) - * @param {string} [req.body.teamId] - Team ID (auto-assigned if superadmin) - * @param {Array} [req.body.role] - User roles (auto-assigned if superadmin) - * @param {Object} [req.file] - Profile image file uploaded via multer - * @param {Object} res - Express response object - * @returns {Promise} Success response with user data and JWT token - * @throws {Error} 422 - Validation error if request body is invalid - * @throws {Error} 409 - Conflict if user already exists - * @example - * // Register first user (becomes superadmin) - * POST /auth/register - * { - * "firstName": "John", - * "lastName": "Doe", - * "email": "john@example.com", - * "password": "SecurePass123!" - * } - * - * // Register subsequent user (requires invite token) - * POST /auth/register - * { - * "firstName": "Jane", - * "lastName": "Smith", - * "email": "jane@example.com", - * "password": "SecurePass123!", - * "inviteToken": "abc123..." - * } - */ - registerUser = this.asyncHandler( - async (req, res) => { - if (req.body?.email) { - req.body.email = req.body.email?.toLowerCase(); - } - await registrationBodyValidation.validateAsync(req.body); - const { user, token } = await this.userService.registerUser(req.body, req.file); - res.success({ - msg: this.stringService.authCreateUser, - data: { user, token }, - }); - }, - SERVICE_NAME, - "registerUser" - ); - - /** - * Authenticates a user and returns a JWT token. - * - * @async - * @function loginUser - * @param {Object} req - Express request object - * @param {Object} req.body - Request body containing login credentials - * @param {string} req.body.email - User's email address (will be converted to lowercase) - * @param {string} req.body.password - User's password - * @param {Object} res - Express response object - * @returns {Promise} Success response with user data and JWT token - * @throws {Error} 422 - Validation error if request body is invalid - * @throws {Error} 401 - Unauthorized if credentials are incorrect - * @example - * POST /auth/login - * { - * "email": "john@example.com", - * "password": "SecurePass123!" - * } - */ - loginUser = this.asyncHandler( - async (req, res) => { - if (req.body?.email) { - req.body.email = req.body.email?.toLowerCase(); - } - await loginValidation.validateAsync(req.body); - const { user, token } = await this.userService.loginUser(req.body.email, req.body.password); - - return res.success({ - msg: this.stringService.authLoginUser, - data: { - user, - token, - }, - }); - }, - SERVICE_NAME, - "loginUser" - ); - - /** - * Updates the current user's profile information. - * - * @async - * @function editUser - * @param {Object} req - Express request object - * @param {Object} req.body - Request body containing user update data - * @param {string} [req.body.firstName] - Updated first name - * @param {string} [req.body.lastName] - Updated last name - * @param {string} [req.body.password] - Current password (required for password change) - * @param {string} [req.body.newPassword] - New password (required for password change) - * @param {boolean} [req.body.deleteProfileImage] - Flag to delete profile image - * @param {Object} [req.file] - New profile image file - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {Object} res - Express response object - * @returns {Promise} Success response with updated user data - * @throws {Error} 422 - Validation error if request body is invalid - * @throws {Error} 403 - Forbidden if current password is incorrect - * @example - * PUT /auth/user - * { - * "firstName": "John Updated", - * "lastName": "Doe Updated" - * } - * - * // Change password - * PUT /auth/user - * { - * "password": "OldPass123!", - * "newPassword": "NewPass123!" - * } - */ - editUser = this.asyncHandler( - async (req, res) => { - await editUserBodyValidation.validateAsync(req.body); - - const updatedUser = await this.userService.editUser(req.body, req.file, req.user); - - res.success({ - msg: this.stringService.authUpdateUser, - data: updatedUser, - }); - }, - SERVICE_NAME, - "editUser" - ); - - /** - * Checks if a superadmin account exists in the system. - * - * @async - * @function checkSuperadminExists - * @param {Object} req - Express request object - * @param {Object} res - Express response object - * @returns {Promise} Success response with boolean indicating superadmin existence - * @example - * GET /auth/users/superadmin - * // Response: { "data": true } or { "data": false } - */ - checkSuperadminExists = this.asyncHandler( - async (req, res) => { - const superAdminExists = await this.userService.checkSuperadminExists(); - return res.success({ - msg: this.stringService.authAdminExists, - data: superAdminExists, - }); - }, - SERVICE_NAME, - "checkSuperadminExists" - ); - - /** - * Initiates password recovery process by sending a recovery email. - * - * @async - * @function requestRecovery - * @param {Object} req - Express request object - * @param {Object} req.body - Request body containing email - * @param {string} req.body.email - Email address for password recovery - * @param {Object} res - Express response object - * @returns {Promise} Success response with message ID - * @throws {Error} 422 - Validation error if email is invalid - * @throws {Error} 404 - Not found if user doesn't exist - * @example - * POST /auth/recovery/request - * { - * "email": "john@example.com" - * } - */ - requestRecovery = this.asyncHandler( - async (req, res) => { - await recoveryValidation.validateAsync(req.body); - const email = req?.body?.email; - const msgId = await this.userService.requestRecovery(email); - return res.success({ - msg: this.stringService.authCreateRecoveryToken, - data: msgId, - }); - }, - SERVICE_NAME, - "requestRecovery" - ); - - /** - * Validates a password recovery token. - * - * @async - * @function validateRecovery - * @param {Object} req - Express request object - * @param {Object} req.body - Request body containing recovery token - * @param {string} req.body.recoveryToken - Recovery token to validate - * @param {Object} res - Express response object - * @returns {Promise} Success response if token is valid - * @throws {Error} 422 - Validation error if token format is invalid - * @throws {Error} 400 - Bad request if token is invalid or expired - * @example - * POST /auth/recovery/validate - * { - * "recoveryToken": "abc123..." - * } - */ - validateRecovery = this.asyncHandler( - async (req, res) => { - await recoveryTokenBodyValidation.validateAsync(req.body); - await this.userService.validateRecovery(req.body.recoveryToken); - return res.success({ - msg: this.stringService.authVerifyRecoveryToken, - }); - }, - SERVICE_NAME, - "validateRecovery" - ); - - /** - * Resets user password using a valid recovery token. - * - * @async - * @function resetPassword - * @param {Object} req - Express request object - * @param {Object} req.body - Request body containing new password and recovery token - * @param {string} req.body.password - New password - * @param {string} req.body.recoveryToken - Valid recovery token - * @param {Object} res - Express response object - * @returns {Promise} Success response with user data and JWT token - * @throws {Error} 422 - Validation error if password format is invalid - * @throws {Error} 400 - Bad request if token is invalid or expired - * @example - * POST /auth/recovery/reset - * { - * "password": "NewSecurePass123!", - * "recoveryToken": "abc123..." - * } - */ - resetPassword = this.asyncHandler( - async (req, res) => { - await newPasswordValidation.validateAsync(req.body); - const { user, token } = await this.userService.resetPassword(req.body.password, req.body.recoveryToken); - return res.success({ - msg: this.stringService.authResetPassword, - data: { user, token }, - }); - }, - SERVICE_NAME, - "resetPassword" - ); - - /** - * Deletes the current user's account and associated data. - * - * @async - * @function deleteUser - * @param {Object} req - Express request object - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {string} req.user._id - User ID - * @param {string} req.user.email - User email - * @param {string} req.user.teamId - User's team ID - * @param {Array} req.user.role - User roles - * @param {Object} res - Express response object - * @returns {Promise} Success response confirming user deletion - * @throws {Error} 400 - Bad request if user is demo user - * @throws {Error} 404 - Not found if user doesn't exist - * @example - * DELETE /auth/user - * // Requires JWT authentication - */ - deleteUser = this.asyncHandler( - async (req, res) => { - await this.userService.deleteUser(req.user); - return res.success({ - msg: this.stringService.authDeleteUser, - }); - }, - SERVICE_NAME, - "deleteUser" - ); - - /** - * Retrieves all users in the system (admin/superadmin only). - * - * @async - * @function getAllUsers - * @param {Object} req - Express request object - * @param {Object} res - Express response object - * @returns {Promise} Success response with array of users - * @throws {Error} 403 - Forbidden if user doesn't have admin/superadmin role - * @example - * GET /auth/users - * // Requires JWT authentication with admin/superadmin role - */ - getAllUsers = this.asyncHandler( - async (req, res) => { - const allUsers = await this.userService.getAllUsers(); - return res.success({ - msg: this.stringService.authGetAllUsers, - data: allUsers, - }); - }, - SERVICE_NAME, - "getAllUsers" - ); - - /** - * Retrieves a specific user by ID (superadmin only). - * - * @async - * @function getUserById - * @param {Object} req - Express request object - * @param {Object} req.params - URL parameters - * @param {string} req.params.userId - ID of the user to retrieve - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {Array} req.user.role - Current user's roles - * @param {Object} res - Express response object - * @returns {Promise} Success response with user data - * @throws {Error} 422 - Validation error if userId is invalid - * @throws {Error} 403 - Forbidden if user doesn't have superadmin role - * @throws {Error} 404 - Not found if user doesn't exist - * @example - * GET /auth/users/507f1f77bcf86cd799439011 - * // Requires JWT authentication with superadmin role - */ - getUserById = this.asyncHandler( - async (req, res) => { - await getUserByIdParamValidation.validateAsync(req.params); - const userId = req?.params?.userId; - const roles = req?.user?.role; - - if (!userId) { - throw new Error("No user ID in request"); - } - - if (!roles || roles.length === 0) { - throw new Error("No roles in request"); - } - - const user = await this.userService.getUserById(roles, userId); - - return res.success({ msg: "ok", data: user }); - }, - SERVICE_NAME, - "getUserById" - ); - - /** - * Updates a specific user by ID (superadmin only). - * - * @async - * @function editUserById - * @param {Object} req - Express request object - * @param {Object} req.params - URL parameters - * @param {string} req.params.userId - ID of the user to update - * @param {Object} req.body - Request body containing user update data - * @param {string} [req.body.firstName] - Updated first name - * @param {string} [req.body.lastName] - Updated last name - * @param {Array} [req.body.role] - Updated user roles - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {string} req.user._id - Current user's ID - * @param {Array} req.user.role - Current user's roles - * @param {Object} res - Express response object - * @returns {Promise} Success response confirming user update - * @throws {Error} 422 - Validation error if parameters or body are invalid - * @throws {Error} 403 - Forbidden if user doesn't have superadmin role - * @throws {Error} 404 - Not found if user doesn't exist - * @example - * PUT /auth/users/507f1f77bcf86cd799439011 - * { - * "firstName": "Updated Name", - * "role": ["admin"] - * } - * // Requires JWT authentication with superadmin role - */ - editUserById = this.asyncHandler( - async (req, res) => { - const roles = req?.user?.role; - if (!roles.includes("superadmin")) { - throw this.errorService.createError("Unauthorized", 403); - } - - const userId = req.params.userId; - const user = { ...req.body }; - - await editUserByIdParamValidation.validateAsync(req.params); - // If this is superadmin self edit, allow "superadmin" role - if (userId === req.user._id) { - await editSuperadminUserByIdBodyValidation.validateAsync(req.body); - } else { - await editUserByIdBodyValidation.validateAsync(req.body); - } - - await this.userService.editUserById(userId, user); - return res.success({ msg: "ok" }); - }, - SERVICE_NAME, - "editUserById" - ); - editUserPasswordById = this.asyncHandler( - async (req, res) => { - const roles = req?.user?.role; - if (!roles.includes("superadmin")) { - throw this.errorService.createError("Unauthorized", 403); - } - - const userId = req.params.userId; - await editUserByIdParamValidation.validateAsync(req.params); - await editUserPasswordByIdBodyValidation.validateAsync(req.body); - const updatedPassword = req.body.password; - await this.userService.setPasswordByUserId(userId, updatedPassword); - return res.success({ msg: "Password reset successfully" }); - }, - SERVICE_NAME, - "editUserPasswordById" - ); -} - -export default AuthController; diff --git a/server/src/controllers/v1/baseController.js b/server/src/controllers/v1/baseController.js deleted file mode 100644 index e8612cec5..000000000 --- a/server/src/controllers/v1/baseController.js +++ /dev/null @@ -1,83 +0,0 @@ -import { AppError } from "../../service/v1/infrastructure/errorService.js"; - -export const createCommonDependencies = (db, errorService, logger, stringService) => { - return { - db, - errorService, - logger, - stringService, - }; -}; - -class BaseController { - constructor({ db, logger, errorService, ...additionalDependencies }) { - this.db = db; - this.logger = logger; - this.errorService = errorService; - Object.assign(this, additionalDependencies); - - this.asyncHandler = (fn, serviceName, methodName) => { - return async (req, res, next) => { - try { - await fn(req, res, next); - } catch (error) { - // Handle validation errors - if (error.isJoi) { - const validationError = this.errorService.createValidationError(error.message, error.details, serviceName, methodName); - return next(validationError); - } - - if (error.name === "ValidationError") { - const validationError = this.errorService.createValidationError("Database validation failed", error.errors, serviceName, methodName); - return next(validationError); - } - - if (error.name === "CastError") { - const notFoundError = this.errorService.createNotFoundError( - "Invalid resource identifier", - { field: error.path, value: error.value }, - serviceName, - methodName - ); - return next(notFoundError); - } - - if (error.code === "11000") { - const conflictError = this.errorService.createConflictError("Resource already exists", { - originalError: error.message, - code: error.code, - }); - conflictError.service = serviceName; - conflictError.method = methodName; - return next(conflictError); - } - - if (error instanceof AppError) { - error.service = error.service || serviceName; - error.method = error.method || methodName; - return next(error); - } - - if (error.status) { - const appError = this.errorService.createError(error.message, error.status, serviceName, methodName, { - originalError: error.message, - stack: error.stack, - }); - return next(appError); - } - - // For unknown errors, create a server error - const appError = this.errorService.createServerError(error.message || "An unexpected error occurred", { - originalError: error.message, - stack: error.stack, - }); - appError.service = serviceName; - appError.method = methodName; - appError.stack = error.stack; // Preserve original stack - return next(appError); - } - }; - }; - } -} -export default BaseController; diff --git a/server/src/controllers/v1/checkController.js b/server/src/controllers/v1/checkController.js deleted file mode 100755 index ea293d74d..000000000 --- a/server/src/controllers/v1/checkController.js +++ /dev/null @@ -1,360 +0,0 @@ -import BaseController from "./baseController.js"; -import { - getChecksParamValidation, - getChecksQueryValidation, - getTeamChecksQueryValidation, - deleteChecksParamValidation, - deleteChecksByTeamIdParamValidation, - updateChecksTTLBodyValidation, - ackCheckBodyValidation, - ackAllChecksParamValidation, - ackAllChecksBodyValidation, -} from "../../validation/joi.js"; - -const SERVICE_NAME = "checkController"; - -/** - * Check Controller - * - * Handles all check-related HTTP requests including retrieving checks, - * acknowledging checks, deleting checks, and managing check TTL settings. - * - * @class CheckController - * @description Manages check operations and monitoring data - */ -class CheckController extends BaseController { - static SERVICE_NAME = SERVICE_NAME; - /** - * Creates an instance of CheckController. - * - * @param {Object} commonDependencies - Common dependencies injected into the controller - * @param {Object} dependencies - The dependencies required by the controller - * @param {Object} dependencies.settingsService - Service for application settings - * @param {Object} dependencies.checkService - Check business logic service - */ - constructor(commonDependencies, { settingsService, checkService }) { - super(commonDependencies); - this.settingsService = settingsService; - this.checkService = checkService; - } - - get serviceName() { - return CheckController.SERVICE_NAME; - } - - /** - * Retrieves checks for a specific monitor with filtering and pagination. - * - * @async - * @function getChecksByMonitor - * @param {Object} req - Express request object - * @param {Object} req.params - URL parameters - * @param {string} req.params.monitorId - ID of the monitor to get checks for - * @param {Object} req.query - Query parameters for filtering and pagination - * @param {string} [req.query.type] - Type of checks to filter by - * @param {string} [req.query.sortOrder] - Sort order (asc/desc) - * @param {string} [req.query.dateRange] - Date range filter - * @param {string} [req.query.filter] - General filter string - * @param {boolean} [req.query.ack] - Filter by acknowledgment status - * @param {number} [req.query.page] - Page number for pagination - * @param {number} [req.query.rowsPerPage] - Number of rows per page - * @param {string} [req.query.status] - Filter by check status - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {string} req.user.teamId - User's team ID - * @param {Object} res - Express response object - * @returns {Promise} Success response with checks data - * @throws {Error} 422 - Validation error if parameters are invalid - * @throws {Error} 404 - Not found if monitor doesn't exist - * @throws {Error} 403 - Forbidden if user doesn't have access to monitor - * @example - * GET /checks/monitor/507f1f77bcf86cd799439011?page=1&rowsPerPage=10&status=down - * // Requires JWT authentication - */ - getChecksByMonitor = this.asyncHandler( - async (req, res) => { - await getChecksParamValidation.validateAsync(req.params); - await getChecksQueryValidation.validateAsync(req.query); - - const result = await this.checkService.getChecksByMonitor({ - monitorId: req?.params?.monitorId, - query: req?.query, - teamId: req?.user?.teamId, - }); - - return res.success({ - msg: this.stringService.checkGet, - data: result, - }); - }, - SERVICE_NAME, - "getChecksByMonitor" - ); - - /** - * Retrieves all checks for the current user's team with filtering and pagination. - * - * @async - * @function getChecksByTeam - * @param {Object} req - Express request object - * @param {Object} req.query - Query parameters for filtering and pagination - * @param {string} [req.query.sortOrder] - Sort order (asc/desc) - * @param {string} [req.query.dateRange] - Date range filter - * @param {string} [req.query.filter] - General filter string - * @param {boolean} [req.query.ack] - Filter by acknowledgment status - * @param {number} [req.query.page] - Page number for pagination - * @param {number} [req.query.rowsPerPage] - Number of rows per page - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {string} req.user.teamId - User's team ID - * @param {Object} res - Express response object - * @returns {Promise} Success response with checks data - * @throws {Error} 422 - Validation error if query parameters are invalid - * @example - * GET /checks/team?page=1&rowsPerPage=20&status=down&ack=false - * // Requires JWT authentication - */ - getChecksByTeam = this.asyncHandler( - async (req, res) => { - await getTeamChecksQueryValidation.validateAsync(req.query); - const checkData = await this.checkService.getChecksByTeam({ - teamId: req?.user?.teamId, - query: req?.query, - }); - return res.success({ - msg: this.stringService.checkGet, - data: checkData, - }); - }, - SERVICE_NAME, - "getChecksByTeam" - ); - - /** - * Retrieves a summary of checks for the current user's team. - * - * @async - * @function getChecksSummaryByTeamId - * @param {Object} req - Express request object - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {string} req.user.teamId - User's team ID - * @param {Object} res - Express response object - * @returns {Promise} Success response with checks summary - * @example - * GET /checks/summary - * // Requires JWT authentication - * // Response includes counts by status, time ranges, etc. - */ - getChecksSummaryByTeamId = this.asyncHandler( - async (req, res) => { - const summary = await this.checkService.getChecksSummaryByTeamId({ teamId: req?.user?.teamId }); - return res.success({ - msg: this.stringService.checkGetSummary, - data: summary, - }); - }, - SERVICE_NAME, - "getChecksSummaryByTeamId" - ); - - /** - * Acknowledges a specific check by ID. - * - * @async - * @function ackCheck - * @param {Object} req - Express request object - * @param {Object} req.params - URL parameters - * @param {string} req.params.checkId - ID of the check to acknowledge - * @param {Object} req.body - Request body containing acknowledgment data - * @param {boolean} req.body.ack - Acknowledgment status (true/false) - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {string} req.user.teamId - User's team ID - * @param {Object} res - Express response object - * @returns {Promise} Success response with updated check data - * @throws {Error} 422 - Validation error if request body is invalid - * @throws {Error} 404 - Not found if check doesn't exist - * @throws {Error} 403 - Forbidden if user doesn't have access to check - * @example - * PUT /checks/507f1f77bcf86cd799439011/ack - * { - * "ack": true - * } - * // Requires JWT authentication - */ - ackCheck = this.asyncHandler( - async (req, res) => { - await ackCheckBodyValidation.validateAsync(req.body); - - const updatedCheck = await this.checkService.ackCheck({ - checkId: req?.params?.checkId, - teamId: req?.user?.teamId, - ack: req?.body?.ack, - }); - - return res.success({ - msg: this.stringService.checkUpdateStatus, - data: updatedCheck, - }); - }, - SERVICE_NAME, - "ackCheck" - ); - - /** - * Acknowledges all checks for a specific monitor or path. - * - * @async - * @function ackAllChecks - * @param {Object} req - Express request object - * @param {Object} req.params - URL parameters - * @param {string} req.params.monitorId - ID of the monitor - * @param {string} req.params.path - Path for acknowledgment (e.g., "monitor") - * @param {Object} req.body - Request body containing acknowledgment data - * @param {boolean} req.body.ack - Acknowledgment status (true/false) - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {string} req.user.teamId - User's team ID - * @param {Object} res - Express response object - * @returns {Promise} Success response with updated checks data - * @throws {Error} 422 - Validation error if parameters or body are invalid - * @throws {Error} 404 - Not found if monitor doesn't exist - * @throws {Error} 403 - Forbidden if user doesn't have access to monitor - * @example - * PUT /checks/monitor/507f1f77bcf86cd799439011/ack - * { - * "ack": true - * } - * // Requires JWT authentication - */ - ackAllChecks = this.asyncHandler( - async (req, res) => { - await ackAllChecksParamValidation.validateAsync(req.params); - await ackAllChecksBodyValidation.validateAsync(req.body); - - const teamId = req?.user?.teamId; - if (!teamId) { - throw new Error("No team ID in request"); - } - - const updatedChecks = await this.checkService.ackAllChecks({ - monitorId: req?.params?.monitorId, - path: req?.params?.path, - teamId: req?.user?.teamId, - ack: req?.body?.ack, - }); - - return res.success({ - msg: this.stringService.checkUpdateStatus, - data: updatedChecks, - }); - }, - SERVICE_NAME, - "ackAllChecks" - ); - - /** - * Deletes all checks for a specific monitor. - * - * @async - * @function deleteChecks - * @param {Object} req - Express request object - * @param {Object} req.params - URL parameters - * @param {string} req.params.monitorId - ID of the monitor whose checks to delete - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {string} req.user.teamId - User's team ID - * @param {Object} res - Express response object - * @returns {Promise} Success response with deletion count - * @throws {Error} 422 - Validation error if monitorId is invalid - * @throws {Error} 404 - Not found if monitor doesn't exist - * @throws {Error} 403 - Forbidden if user doesn't have access to monitor - * @example - * DELETE /checks/monitor/507f1f77bcf86cd799439011 - * // Requires JWT authentication - * // Response: { "data": { "deletedCount": 150 } } - */ - deleteChecks = this.asyncHandler( - async (req, res) => { - await deleteChecksParamValidation.validateAsync(req.params); - - const deletedCount = await this.checkService.deleteChecks({ - monitorId: req.params.monitorId, - teamId: req?.user?.teamId, - }); - - return res.success({ - msg: this.stringService.checkDelete, - data: { deletedCount }, - }); - }, - SERVICE_NAME, - "deleteChecks" - ); - - /** - * Deletes all checks for the current user's team. - * - * @async - * @function deleteChecksByTeamId - * @param {Object} req - Express request object - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {string} req.user.teamId - User's team ID - * @param {Object} res - Express response object - * @returns {Promise} Success response with deletion count - * @throws {Error} 422 - Validation error if parameters are invalid - * @example - * DELETE /checks/team - * // Requires JWT authentication - * // Response: { "data": { "deletedCount": 1250 } } - */ - deleteChecksByTeamId = this.asyncHandler( - async (req, res) => { - await deleteChecksByTeamIdParamValidation.validateAsync(req.params); - - const deletedCount = await this.checkService.deleteChecksByTeamId({ teamId: req?.user?.teamId }); - - return res.success({ - msg: this.stringService.checkDelete, - data: { deletedCount }, - }); - }, - SERVICE_NAME, - "deleteChecksByTeamId" - ); - - /** - * Updates the TTL (Time To Live) setting for checks in the current user's team. - * - * @async - * @function updateChecksTTL - * @param {Object} req - Express request object - * @param {Object} req.body - Request body containing TTL data - * @param {number} req.body.ttl - TTL value in days - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {string} req.user.teamId - User's team ID - * @param {Object} res - Express response object - * @returns {Promise} Success response confirming TTL update - * @throws {Error} 422 - Validation error if TTL value is invalid - * @example - * PUT /checks/ttl - * { - * "ttl": 30 - * } - * // Requires JWT authentication - * // Sets check TTL to 30 days - */ - updateChecksTTL = this.asyncHandler( - async (req, res) => { - await updateChecksTTLBodyValidation.validateAsync(req.body); - - await this.checkService.updateChecksTTL({ - teamId: req?.user?.teamId, - ttl: req?.body?.ttl, - }); - - return res.success({ - msg: this.stringService.checkUpdateTTL, - }); - }, - SERVICE_NAME, - "updateChecksTtl" - ); -} - -export default CheckController; diff --git a/server/src/controllers/v1/controllerUtils.js b/server/src/controllers/v1/controllerUtils.js deleted file mode 100755 index f21ba154b..000000000 --- a/server/src/controllers/v1/controllerUtils.js +++ /dev/null @@ -1,12 +0,0 @@ -const fetchMonitorCertificate = async (sslChecker, monitor) => { - const monitorUrl = new URL(monitor.url); - const hostname = monitorUrl.hostname; - const cert = await sslChecker(hostname); - // Throw an error if no cert or if cert.validTo is not present - if (cert?.validTo === null || cert?.validTo === undefined) { - throw new Error("Certificate not found"); - } - return cert; -}; - -export { fetchMonitorCertificate }; diff --git a/server/src/controllers/v1/diagnosticController.js b/server/src/controllers/v1/diagnosticController.js deleted file mode 100755 index 0723a9f6c..000000000 --- a/server/src/controllers/v1/diagnosticController.js +++ /dev/null @@ -1,61 +0,0 @@ -const SERVICE_NAME = "diagnosticController"; -import BaseController from "./baseController.js"; -/** - * Diagnostic Controller - * - * Handles system diagnostic and monitoring requests including system statistics, - * performance metrics, and health checks. - * - * @class DiagnosticController - * @description Manages system diagnostics and performance monitoring - */ -class DiagnosticController extends BaseController { - static SERVICE_NAME = SERVICE_NAME; - /** - * Creates an instance of DiagnosticController. - * @param {Object} commonDependencies - Common dependencies injected into the controller - * @param {Object} dependencies - The dependencies required by the controller - * @param {Object} dependencies.diagnosticService - Service for system diagnostics and monitoring - */ - constructor(commonDependencies, { diagnosticService }) { - super(commonDependencies); - this.diagnosticService = diagnosticService; - } - - get serviceName() { - return DiagnosticController.SERVICE_NAME; - } - /** - * Retrieves comprehensive system statistics and performance metrics. - * - * @async - * @function getSystemStats - * @param {Object} req - Express request object - * @param {Object} res - Express response object - * @returns {Promise} Success response with system diagnostics data - * @description Returns system performance metrics, memory usage, CPU statistics, - * and other diagnostic information useful for monitoring system health. - * @example - * GET /diagnostics/stats - * // Response includes: - * // - Memory usage (heap, external, arrayBuffers) - * // - CPU usage statistics - * // - System uptime - * // - Performance metrics - * // - Database connection status - * // - Active processes/connections - */ - getSystemStats = this.asyncHandler( - async (req, res) => { - const diagnostics = await this.diagnosticService.getSystemStats(); - return res.success({ - msg: "OK", - data: diagnostics, - }); - }, - SERVICE_NAME, - "getSystemStats" - ); -} - -export default DiagnosticController; diff --git a/server/src/controllers/v1/incidentController.js b/server/src/controllers/v1/incidentController.js deleted file mode 100644 index d20e3cdb8..000000000 --- a/server/src/controllers/v1/incidentController.js +++ /dev/null @@ -1,191 +0,0 @@ -import BaseController from "./baseController.js"; - -const SERVICE_NAME = "incidentController"; - -/** - * Incident Controller - * - * Handles all incident-related HTTP requests including retrieving incidents, - * resolving incidents manually, and getting incident summaries. - * - * @class IncidentController - * @description Manages incident operations and tracking - */ -class IncidentController extends BaseController { - static SERVICE_NAME = SERVICE_NAME; - - /** - * Creates an instance of IncidentController. - * - * @param {Object} commonDependencies - Common dependencies injected into the controller - * @param {Object} dependencies - The dependencies required by the controller - * @param {Object} dependencies.incidentService - Incident business logic service - */ - constructor(commonDependencies, { incidentService }) { - super(commonDependencies); - this.incidentService = incidentService; - } - - get serviceName() { - return IncidentController.SERVICE_NAME; - } - - /** - * Retrieves all incidents for the current user's team with filtering and pagination. - * - * @async - * @function getIncidentsByTeam - * @param {Object} req - Express request object - * @param {Object} req.query - Query parameters for filtering and pagination - * @param {string} [req.query.sortOrder] - Sort order (asc/desc) - * @param {string} [req.query.dateRange] - Date range filter - * @param {string} [req.query.filter] - General filter string - * @param {number} [req.query.page] - Page number for pagination - * @param {number} [req.query.rowsPerPage] - Number of rows per page - * @param {boolean} [req.query.status] - Filter by incident status (true=active, false=resolved) - * @param {string} [req.query.monitorId] - Filter by monitor ID - * @param {string} [req.query.resolutionType] - Filter by resolution type (automatic/manual) - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {string} req.user.teamId - User's team ID - * @param {Object} res - Express response object - * @returns {Promise} Success response with incidents data - * @throws {Error} 422 - Validation error if query parameters are invalid - * @example - * GET /incidents/team?page=1&rowsPerPage=20&status=true&filter=active - * // Requires JWT authentication - */ - getIncidentsByTeam = this.asyncHandler( - async (req, res) => { - const result = await this.incidentService.getIncidentsByTeam({ - teamId: req?.user?.teamId, - query: req?.query, - }); - - return res.success({ - msg: "Incidents retrieved successfully", - data: result, - }); - }, - SERVICE_NAME, - "getIncidentsByTeam" - ); - - /** - * Retrieves a summary of incidents for the current user's team. - * - * @async - * @function getIncidentSummary - * @param {Object} req - Express request object - * @param {Object} req.query - Query parameters - * @param {number} [req.query.limit=10] - Number of latest incidents to return - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {string} req.user.teamId - User's team ID - * @param {Object} res - Express response object - * @returns {Promise} Success response with incidents summary containing: - * - totalActive: Number of active incidents - * - avgResolutionTimeHours: Average time to resolve incidents in hours - * - topMonitor: Monitor with most incidents (monitorId, monitorName, incidentCount) - * - total: Total number of incidents - * - totalManualResolutions: Total incidents resolved manually - * - totalAutomaticResolutions: Total incidents resolved automatically - * - latestIncidents: Array of latest incidents created - * @example - * GET /incidents/team/summary?limit=5 - * // Requires JWT authentication - */ - getIncidentSummary = this.asyncHandler( - async (req, res) => { - const summary = await this.incidentService.getIncidentSummary({ - teamId: req?.user?.teamId, - query: req?.query, - }); - - return res.success({ - msg: "Incident summary retrieved successfully", - data: summary, - }); - }, - SERVICE_NAME, - "getIncidentSummary" - ); - - /** - * Retrieves a specific incident by ID. - * - * @async - * @function getIncidentById - * @param {Object} req - Express request object - * @param {Object} req.params - URL parameters - * @param {string} req.params.incidentId - ID of the incident to retrieve - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {string} req.user.teamId - User's team ID - * @param {Object} res - Express response object - * @returns {Promise} Success response with incident data - * @throws {Error} 404 - Not found if incident doesn't exist - * @throws {Error} 403 - Forbidden if user doesn't have access to incident - * @example - * GET /incidents/507f1f77bcf86cd799439011 - * // Requires JWT authentication - */ - getIncidentById = this.asyncHandler( - async (req, res) => { - const incident = await this.incidentService.getIncidentById({ - incidentId: req?.params?.incidentId, - teamId: req?.user?.teamId, - }); - - return res.success({ - msg: "Incident retrieved successfully", - data: incident, - }); - }, - SERVICE_NAME, - "getIncidentById" - ); - - /** - * Manually resolves a specific incident by ID. - * - * @async - * @function resolveIncidentManually - * @param {Object} req - Express request object - * @param {Object} req.params - URL parameters - * @param {string} req.params.incidentId - ID of the incident to resolve - * @param {Object} req.body - Request body containing resolution data - * @param {string} [req.body.comment] - Optional comment about the resolution - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {string} req.user.teamId - User's team ID - * @param {string} req.user._id - User's ID - * @param {Object} res - Express response object - * @returns {Promise} Success response with updated incident data - * @throws {Error} 422 - Validation error if request body is invalid - * @throws {Error} 404 - Not found if incident doesn't exist - * @throws {Error} 403 - Forbidden if user doesn't have access to incident - * @throws {Error} 400 - Bad request if incident is already resolved - * @example - * PUT /incidents/507f1f77bcf86cd799439011/resolve - * { - * "comment": "Issue resolved by restarting the service" - * } - * // Requires JWT authentication - */ - resolveIncidentManually = this.asyncHandler( - async (req, res) => { - const resolvedIncident = await this.incidentService.resolveIncidentManually({ - incidentId: req?.params?.incidentId, - userId: req?.user?._id, - teamId: req?.user?.teamId, - comment: req?.body?.comment, - }); - - return res.success({ - msg: "Incident resolved successfully", - data: resolvedIncident, - }); - }, - SERVICE_NAME, - "resolveIncidentManually" - ); -} - -export default IncidentController; diff --git a/server/src/controllers/v1/inviteController.js b/server/src/controllers/v1/inviteController.js deleted file mode 100755 index 1ec586f31..000000000 --- a/server/src/controllers/v1/inviteController.js +++ /dev/null @@ -1,101 +0,0 @@ -import { inviteBodyValidation, inviteVerificationBodyValidation } from "../../validation/joi.js"; -import BaseController from "./baseController.js"; -const SERVICE_NAME = "inviteController"; - -/** - * Controller for handling user invitation operations - * Manages invite token generation, email sending, and token verification - */ -class InviteController extends BaseController { - static SERVICE_NAME = SERVICE_NAME; - /** - * Creates a new InviteController instance - * @param {Object} commonDependencies - Common dependencies injected into the controller - * @param {Object} dependencies.inviteService - Service for invite-related operations - */ - constructor(commonDependencies, { inviteService }) { - super(commonDependencies); - this.inviteService = inviteService; - } - - get serviceName() { - return InviteController.SERVICE_NAME; - } - - /** - * Generates an invite token for a user invitation - * @param {Object} req - Express request object - * @param {Object} req.body - Request body containing invite details - * @param {Object} req.user - Authenticated user object - * @param {string} req.user.teamId - Team ID of the authenticated user - * @param {Object} res - Express response object - * @returns {Promise} Response with invite token data - */ - getInviteToken = this.asyncHandler( - async (req, res) => { - const invite = req.body; - const teamId = req?.user?.teamId; - invite.teamId = teamId; - await inviteBodyValidation.validateAsync(invite); - const inviteToken = await this.inviteService.getInviteToken({ invite, teamId }); - return res.success({ - msg: this.stringService.inviteIssued, - data: inviteToken, - }); - }, - SERVICE_NAME, - "getInviteToken" - ); - - /** - * Sends an invitation email to a user - * @param {Object} req - Express request object - * @param {Object} req.body - Request body containing invite details - * @param {Object} req.user - Authenticated user object - * @param {string} req.user.teamId - Team ID of the authenticated user - * @param {string} req.user.firstName - First name of the authenticated user - * @param {Object} res - Express response object - * @returns {Promise} Response with invite token data - */ - sendInviteEmail = this.asyncHandler( - async (req, res) => { - const inviteRequest = req.body; - inviteRequest.teamId = req?.user?.teamId; - await inviteBodyValidation.validateAsync(inviteRequest); - - const inviteToken = await this.inviteService.sendInviteEmail({ - inviteRequest, - firstName: req?.user?.firstName, - }); - return res.success({ - msg: this.stringService.inviteIssued, - data: inviteToken, - }); - }, - SERVICE_NAME, - "sendInviteEmail" - ); - - /** - * Verifies an invite token and returns invite details - * @param {Object} req - Express request object - * @param {Object} req.body - Request body containing the invite token - * @param {string} req.body.token - The invite token to verify - * @param {Object} res - Express response object - * @returns {Promise} Response with verified invite data - */ - verifyInviteToken = this.asyncHandler( - async (req, res) => { - await inviteVerificationBodyValidation.validateAsync(req.body); - const invite = await this.inviteService.verifyInviteToken({ inviteToken: req?.body?.token }); - return res.success({ - msg: this.stringService.inviteVerified, - data: invite, - }); - }, - SERVICE_NAME, - "verifyInviteToken" - ); -} - -export default InviteController; diff --git a/server/src/controllers/v1/logController.js b/server/src/controllers/v1/logController.js deleted file mode 100644 index 3537246bb..000000000 --- a/server/src/controllers/v1/logController.js +++ /dev/null @@ -1,26 +0,0 @@ -import BaseController from "./baseController.js"; -const SERVICE_NAME = "LogController"; - -class LogController extends BaseController { - static SERVICE_NAME = SERVICE_NAME; - constructor(commonDependencies) { - super(commonDependencies); - } - - get serviceName() { - return LogController.SERVICE_NAME; - } - - getLogs = this.asyncHandler( - async (req, res) => { - const logs = await this.logger.getLogs(); - res.success({ - msg: "Logs fetched successfully", - data: logs, - }); - }, - SERVICE_NAME, - "getLogs" - ); -} -export default LogController; diff --git a/server/src/controllers/v1/monitorController.js b/server/src/controllers/v1/monitorController.js deleted file mode 100644 index 85cfea3d1..000000000 --- a/server/src/controllers/v1/monitorController.js +++ /dev/null @@ -1,493 +0,0 @@ -import { - getMonitorByIdParamValidation, - getMonitorByIdQueryValidation, - getMonitorsByTeamIdParamValidation, - getMonitorsByTeamIdQueryValidation, - createMonitorBodyValidation, - editMonitorBodyValidation, - pauseMonitorParamValidation, - getMonitorStatsByIdParamValidation, - getMonitorStatsByIdQueryValidation, - getCertificateParamValidation, - getHardwareDetailsByIdParamValidation, - getHardwareDetailsByIdQueryValidation, -} from "../../validation/joi.js"; -import sslChecker from "ssl-checker"; -import { fetchMonitorCertificate } from "./controllerUtils.js"; -import BaseController from "./baseController.js"; - -const SERVICE_NAME = "monitorController"; -class MonitorController extends BaseController { - static SERVICE_NAME = SERVICE_NAME; - constructor(commonDependencies, { settingsService, jobQueue, emailService, monitorService }) { - super(commonDependencies); - this.settingsService = settingsService; - this.jobQueue = jobQueue; - this.emailService = emailService; - this.monitorService = monitorService; - } - - get serviceName() { - return MonitorController.SERVICE_NAME; - } - - async verifyTeamAccess(teamId, monitorId) { - const monitor = await this.db.monitorModule.getMonitorById(monitorId); - if (!monitor.teamId.equals(teamId)) { - throw this.errorService.createAuthorizationError(); - } - } - - getAllMonitors = this.asyncHandler( - async (req, res) => { - const monitors = await this.monitorService.getAllMonitors(); - return res.success({ - msg: this.stringService.monitorGetAll, - data: monitors, - }); - }, - SERVICE_NAME, - "getAllMonitors" - ); - - getUptimeDetailsById = this.asyncHandler( - async (req, res) => { - const monitorId = req?.params?.monitorId; - const dateRange = req?.query?.dateRange; - const normalize = req?.query?.normalize; - - const teamId = req?.user?.teamId; - - if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); - } - - const data = await this.monitorService.getUptimeDetailsById({ - teamId, - monitorId, - dateRange, - normalize, - }); - return res.success({ - msg: this.stringService.monitorGetByIdSuccess, - data: data, - }); - }, - SERVICE_NAME, - "getUptimeDetailsById" - ); - - getMonitorStatsById = this.asyncHandler( - async (req, res) => { - await getMonitorStatsByIdParamValidation.validateAsync(req.params); - await getMonitorStatsByIdQueryValidation.validateAsync(req.query); - - let { limit, sortOrder, dateRange, numToDisplay, normalize } = req.query; - const monitorId = req?.params?.monitorId; - - const teamId = req?.user?.teamId; - if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); - } - - const monitorStats = await this.monitorService.getMonitorStatsById({ - teamId, - monitorId, - limit, - sortOrder, - dateRange, - numToDisplay, - normalize, - }); - - return res.success({ - msg: this.stringService.monitorStatsById, - data: monitorStats, - }); - }, - SERVICE_NAME, - "getMonitorStatsById" - ); - - /** - * Get hardware details for a specific monitor by ID - * @async - * @param {Express.Request} req - Express request object containing monitorId in params - * @param {Express.Response} res - Express response object - * @param {Express.NextFunction} next - Express next middleware function - * @returns {Promise} - * @throws {Error} - Throws error if monitor not found or other database errors - */ - getHardwareDetailsById = this.asyncHandler( - async (req, res) => { - await getHardwareDetailsByIdParamValidation.validateAsync(req.params); - await getHardwareDetailsByIdQueryValidation.validateAsync(req.query); - - const monitorId = req?.params?.monitorId; - const dateRange = req?.query?.dateRange; - const teamId = req?.user?.teamId; - if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); - } - - const monitor = await this.monitorService.getHardwareDetailsById({ - teamId, - monitorId, - dateRange, - }); - - return res.success({ - msg: this.stringService.monitorGetByIdSuccess, - data: monitor, - }); - }, - SERVICE_NAME, - "getHardwareDetailsById" - ); - - getMonitorCertificate = this.asyncHandler( - async (req, res) => { - await getCertificateParamValidation.validateAsync(req.params); - - const { monitorId } = req.params; - const monitor = await this.db.monitorModule.getMonitorById(monitorId); - const certificate = await fetchMonitorCertificate(sslChecker, monitor); - - return res.success({ - msg: this.stringService.monitorCertificate, - data: { - certificateDate: new Date(certificate.validTo), - }, - }); - }, - SERVICE_NAME, - "getMonitorCertificate" - ); - - getMonitorById = this.asyncHandler( - async (req, res) => { - await getMonitorByIdParamValidation.validateAsync(req.params); - await getMonitorByIdQueryValidation.validateAsync(req.query); - - const teamId = req?.user?.teamId; - if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); - } - - const monitor = await this.monitorService.getMonitorById({ teamId, monitorId: req?.params?.monitorId }); - - return res.success({ - msg: this.stringService.monitorGetByIdSuccess, - data: monitor, - }); - }, - SERVICE_NAME, - "getMonitorById" - ); - - createMonitor = this.asyncHandler( - async (req, res) => { - await createMonitorBodyValidation.validateAsync(req.body); - - const userId = req?.user?._id; - const teamId = req?.user?.teamId; - - const monitor = await this.monitorService.createMonitor({ teamId, userId, body: req.body }); - - return res.success({ - msg: this.stringService.monitorCreate, - data: monitor, - }); - }, - SERVICE_NAME, - "createMonitor" - ); - - createBulkMonitors = this.asyncHandler( - async (req, res) => { - if (!req.file) { - throw this.errorService.createBadRequestError("No file uploaded"); - } - - if (!req.file.mimetype.includes("csv")) { - throw this.errorService.createBadRequestError("File is not a CSV"); - } - - if (req.file.size === 0) { - throw this.errorService.createBadRequestError("File is empty"); - } - - const userId = req?.user?._id; - const teamId = req?.user?.teamId; - - if (!userId || !teamId) { - throw this.errorService.createBadRequestError("Missing userId or teamId"); - } - - const fileData = req?.file?.buffer?.toString("utf-8"); - if (!fileData) { - throw this.errorService.createBadRequestError("Cannot get file from buffer"); - } - - const monitors = await this.monitorService.createBulkMonitors({ fileData, userId, teamId }); - - return res.success({ - msg: this.stringService.bulkMonitorsCreate, - data: monitors, - }); - }, - SERVICE_NAME, - "createBulkMonitors" - ); - - deleteMonitor = this.asyncHandler( - async (req, res) => { - await getMonitorByIdParamValidation.validateAsync(req.params); - const monitorId = req.params.monitorId; - const teamId = req?.user?.teamId; - if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); - } - - const deletedMonitor = await this.monitorService.deleteMonitor({ teamId, monitorId }); - - return res.success({ msg: this.stringService.monitorDelete, data: deletedMonitor }); - }, - SERVICE_NAME, - "deleteMonitor" - ); - - deleteAllMonitors = this.asyncHandler( - async (req, res) => { - const teamId = req?.user?.teamId; - if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); - } - - const deletedCount = await this.monitorService.deleteAllMonitors({ teamId }); - - return res.success({ msg: `Deleted ${deletedCount} monitors` }); - }, - SERVICE_NAME, - "deleteAllMonitors" - ); - - editMonitor = this.asyncHandler( - async (req, res) => { - await getMonitorByIdParamValidation.validateAsync(req.params); - await editMonitorBodyValidation.validateAsync(req.body); - const monitorId = req?.params?.monitorId; - - const teamId = req?.user?.teamId; - if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); - } - - const editedMonitor = await this.monitorService.editMonitor({ teamId, monitorId, body: req.body }); - - return res.success({ - msg: this.stringService.monitorEdit, - data: editedMonitor, - }); - }, - SERVICE_NAME, - "editMonitor" - ); - - pauseMonitor = this.asyncHandler( - async (req, res) => { - await pauseMonitorParamValidation.validateAsync(req.params); - - const monitorId = req.params.monitorId; - const teamId = req?.user?.teamId; - if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); - } - - const monitor = await this.monitorService.pauseMonitor({ teamId, monitorId }); - - return res.success({ - msg: monitor.isActive ? this.stringService.monitorResume : this.stringService.monitorPause, - data: monitor, - }); - }, - SERVICE_NAME, - "pauseMonitor" - ); - - addDemoMonitors = this.asyncHandler( - async (req, res) => { - const { _id, teamId } = req.user; - const demoMonitors = await this.monitorService.addDemoMonitors({ userId: _id, teamId }); - - return res.success({ - msg: this.stringService.monitorDemoAdded, - data: demoMonitors?.length ?? 0, - }); - }, - SERVICE_NAME, - "addDemoMonitors" - ); - - sendTestEmail = this.asyncHandler( - async (req, res) => { - const { to } = req.body; - if (!to || typeof to !== "string") { - throw this.errorService.createBadRequestError(this.stringService.errorForValidEmailAddress); - } - - const messageId = await this.monitorService.sendTestEmail({ to }); - return res.success({ - msg: this.stringService.sendTestEmail, - data: { messageId }, - }); - }, - SERVICE_NAME, - "sendTestEmail" - ); - - getMonitorsByTeamId = this.asyncHandler( - async (req, res) => { - await getMonitorsByTeamIdParamValidation.validateAsync(req.params); - await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); - - let { limit, type, page, rowsPerPage, filter, field, order } = req.query; - const teamId = req?.user?.teamId; - - const monitors = await this.monitorService.getMonitorsByTeamId({ teamId, limit, type, page, rowsPerPage, filter, field, order }); - - return res.success({ - msg: this.stringService.monitorGetByTeamId, - data: monitors, - }); - }, - SERVICE_NAME, - "getMonitorsByTeamId" - ); - - getMonitorsAndSummaryByTeamId = this.asyncHandler( - async (req, res) => { - await getMonitorsByTeamIdParamValidation.validateAsync(req.params); - await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); - - const explain = req?.query?.explain; - const type = req?.query?.type; - const teamId = req?.user?.teamId; - if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); - } - - const result = await this.monitorService.getMonitorsAndSummaryByTeamId({ teamId, type, explain }); - - return res.success({ - msg: this.stringService.monitorSummaryByTeamId, - data: result, - }); - }, - SERVICE_NAME, - "getMonitorsAndSummaryByTeamId" - ); - - getMonitorsWithChecksByTeamId = this.asyncHandler( - async (req, res) => { - await getMonitorsByTeamIdParamValidation.validateAsync(req.params); - await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); - - const explain = req?.query?.explain; - let { limit, type, page, rowsPerPage, filter, field, order } = req.query; - const teamId = req?.user?.teamId; - if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); - } - - const monitors = await this.monitorService.getMonitorsWithChecksByTeamId({ - teamId, - limit, - type, - page, - rowsPerPage, - filter, - field, - order, - explain, - }); - - return res.success({ - msg: this.stringService.monitorGetByTeamId, - data: monitors, - }); - }, - SERVICE_NAME, - "getMonitorsWithChecksByTeamId" - ); - - exportMonitorsToCSV = this.asyncHandler( - async (req, res) => { - const teamId = req?.user?.teamId; - if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); - } - - const csv = await this.monitorService.exportMonitorsToCSV({ teamId }); - - return res.file({ - data: csv, - headers: { - "Content-Type": "text/csv", - "Content-Disposition": "attachment; filename=monitors.csv", - }, - }); - }, - SERVICE_NAME, - "exportMonitorsToCSV" - ); - - exportMonitorsToJSON = this.asyncHandler( - async (req, res) => { - const teamId = req?.user?.teamId; - if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); - } - - const json = await this.monitorService.exportMonitorsToJSON({ teamId }); - - return res.success({ - msg: this.stringService.monitorExportSuccess, - data: json, - }); - }, - SERVICE_NAME, - "exportMonitorsToJSON" - ); - - getAllGames = this.asyncHandler( - async (req, res) => { - return res.success({ - msg: this.stringService.gameListSuccess, - data: this.monitorService.getAllGames(), - }); - }, - 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: this.stringService.groupsByTeamId, - data: groups, - }); - }, - SERVICE_NAME, - "getGroupsByTeamId" - ); -} - -export default MonitorController; diff --git a/server/src/controllers/v1/notificationController.js b/server/src/controllers/v1/notificationController.js deleted file mode 100644 index 37c2b4e60..000000000 --- a/server/src/controllers/v1/notificationController.js +++ /dev/null @@ -1,180 +0,0 @@ -import { createNotificationBodyValidation } from "../../validation/joi.js"; -import BaseController from "./baseController.js"; - -const SERVICE_NAME = "NotificationController"; - -class NotificationController extends BaseController { - static SERVICE_NAME = SERVICE_NAME; - constructor(commonDependencies, { notificationService, statusService }) { - super(commonDependencies); - this.notificationService = notificationService; - this.statusService = statusService; - } - - get serviceName() { - return NotificationController.SERVICE_NAME; - } - - testNotification = this.asyncHandler( - async (req, res) => { - const notification = req.body; - - const success = await this.notificationService.sendTestNotification(notification); - - if (!success) { - throw this.errorService.createServerError("Sending notification failed"); - } - - return res.success({ - msg: "Notification sent successfully", - }); - }, - SERVICE_NAME, - "testNotification" - ); - - createNotification = this.asyncHandler( - async (req, res) => { - await createNotificationBodyValidation.validateAsync(req.body, { - abortEarly: false, - }); - - const body = req.body; - - const teamId = req?.user?.teamId; - if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); - } - - const userId = req?.user?._id; - if (!userId) { - throw this.errorService.createBadRequestError("User ID is required"); - } - body.userId = userId; - body.teamId = teamId; - - const notification = await this.db.notificationModule.createNotification(body); - return res.success({ - msg: "Notification created successfully", - data: notification, - }); - }, - SERVICE_NAME, - "createNotification" - ); - - getNotificationsByTeamId = this.asyncHandler( - async (req, res) => { - const teamId = req?.user?.teamId; - if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); - } - - const notifications = await this.db.notificationModule.getNotificationsByTeamId(teamId); - - return res.success({ - msg: "Notifications fetched successfully", - data: notifications, - }); - }, - SERVICE_NAME, - "getNotificationsByTeamId" - ); - - deleteNotification = this.asyncHandler( - async (req, res) => { - const teamId = req?.user?.teamId; - if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); - } - - const notification = await this.db.notificationModule.getNotificationById(req.params.id); - if (!notification.teamId.equals(teamId)) { - throw this.errorService.createAuthorizationError(); - } - - await this.db.notificationModule.deleteNotificationById(req.params.id); - return res.success({ - msg: "Notification deleted successfully", - }); - }, - SERVICE_NAME, - "deleteNotification" - ); - - getNotificationById = this.asyncHandler( - async (req, res) => { - const notification = await this.db.notificationModule.getNotificationById(req.params.id); - - const teamId = req?.user?.teamId; - if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); - } - - if (!notification.teamId.equals(teamId)) { - throw this.errorService.createAuthorizationError(); - } - return res.success({ - msg: "Notification fetched successfully", - data: notification, - }); - }, - SERVICE_NAME, - "getNotificationById" - ); - - editNotification = this.asyncHandler( - async (req, res) => { - await createNotificationBodyValidation.validateAsync(req.body, { - abortEarly: false, - }); - - const teamId = req?.user?.teamId; - if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); - } - - const notification = await this.db.notificationModule.getNotificationById(req.params.id); - - if (!notification.teamId.equals(teamId)) { - throw this.errorService.createAuthorizationError(); - } - - const editedNotification = await this.db.notificationModule.editNotification(req.params.id, req.body); - return res.success({ - msg: "Notification updated successfully", - data: editedNotification, - }); - }, - SERVICE_NAME, - "editNotification" - ); - - testAllNotifications = this.asyncHandler( - async (req, res) => { - const monitorId = req.body.monitorId; - const teamId = req?.user?.teamId; - if (!teamId) { - throw this.errorService.createBadRequestError("Team ID is required"); - } - - const monitor = await this.db.monitorModule.getMonitorById(monitorId); - - if (!monitor.teamId.equals(teamId)) { - throw this.errorService.createAuthorizationError(); - } - - const notifications = monitor.notifications; - if (notifications.length === 0) throw this.errorService.createBadRequestError("No notifications"); - const result = await this.notificationService.testAllNotifications(notifications); - if (!result) throw this.errorService.createServerError("Failed to send all notifications"); - return res.success({ - msg: "All notifications sent successfully", - }); - }, - SERVICE_NAME, - "testAllNotifications" - ); -} - -export default NotificationController; diff --git a/server/src/controllers/v1/queueController.js b/server/src/controllers/v1/queueController.js deleted file mode 100755 index d2c3fd06f..000000000 --- a/server/src/controllers/v1/queueController.js +++ /dev/null @@ -1,87 +0,0 @@ -import BaseController from "./baseController.js"; -const SERVICE_NAME = "JobQueueController"; - -class JobQueueController extends BaseController { - static SERVICE_NAME = SERVICE_NAME; - constructor(commonDependencies, { jobQueue }) { - super(commonDependencies); - this.jobQueue = jobQueue; - } - - get serviceName() { - return JobQueueController.SERVICE_NAME; - } - - getMetrics = this.asyncHandler( - async (req, res) => { - const metrics = await this.jobQueue.getMetrics(); - res.success({ - msg: this.stringService.queueGetMetrics, - data: metrics, - }); - }, - SERVICE_NAME, - "getMetrics" - ); - - getJobs = this.asyncHandler( - async (req, res) => { - const jobs = await this.jobQueue.getJobs(); - return res.success({ - msg: this.stringService.queueGetJobs, - data: jobs, - }); - }, - SERVICE_NAME, - "getJobs" - ); - - getAllMetrics = this.asyncHandler( - async (req, res) => { - const jobs = await this.jobQueue.getJobs(); - const metrics = await this.jobQueue.getMetrics(); - return res.success({ - msg: this.stringService.queueGetAllMetrics, - data: { jobs, metrics }, - }); - }, - SERVICE_NAME, - "getAllMetrics" - ); - - addJob = this.asyncHandler( - async (req, res) => { - await this.jobQueue.addJob(Math.random().toString(36).substring(7)); - return res.success({ - msg: this.stringService.queueAddJob, - }); - }, - SERVICE_NAME, - "addJob" - ); - - flushQueue = this.asyncHandler( - async (req, res) => { - const result = await this.jobQueue.flushQueues(); - return res.success({ - msg: this.stringService.jobQueueFlush, - data: result, - }); - }, - SERVICE_NAME, - "flushQueue" - ); - - checkQueueHealth = this.asyncHandler( - async (req, res) => { - const stuckQueues = await this.jobQueue.checkQueueHealth(); - return res.success({ - msg: this.stringService.queueHealthCheck, - data: stuckQueues, - }); - }, - SERVICE_NAME, - "checkQueueHealth" - ); -} -export default JobQueueController; diff --git a/server/src/controllers/v1/statusPageController.js b/server/src/controllers/v1/statusPageController.js deleted file mode 100644 index de6a1ca7c..000000000 --- a/server/src/controllers/v1/statusPageController.js +++ /dev/null @@ -1,108 +0,0 @@ -import { createStatusPageBodyValidation, getStatusPageParamValidation, getStatusPageQueryValidation, imageValidation } from "../../validation/joi.js"; -import BaseController from "./baseController.js"; - -const SERVICE_NAME = "statusPageController"; - -class StatusPageController extends BaseController { - static SERVICE_NAME = SERVICE_NAME; - constructor(commonDependencies) { - super(commonDependencies); - } - - get serviceName() { - return StatusPageController.SERVICE_NAME; - } - - createStatusPage = this.asyncHandler( - async (req, res) => { - await createStatusPageBodyValidation.validateAsync(req.body); - await imageValidation.validateAsync(req.file); - - const { _id, teamId } = req.user; - const statusPage = await this.db.statusPageModule.createStatusPage({ - statusPageData: req.body, - image: req.file, - userId: _id, - teamId, - }); - return res.success({ - msg: this.stringService.statusPageCreate, - data: statusPage, - }); - }, - SERVICE_NAME, - "createStatusPage" - ); - - updateStatusPage = this.asyncHandler( - async (req, res) => { - await createStatusPageBodyValidation.validateAsync(req.body); - await imageValidation.validateAsync(req.file); - - const statusPage = await this.db.statusPageModule.updateStatusPage(req.body, req.file); - if (statusPage === null) { - throw this.errorService.createNotFoundError(this.stringService.statusPageNotFound); - } - return res.success({ - msg: this.stringService.statusPageUpdate, - data: statusPage, - }); - }, - SERVICE_NAME, - "updateStatusPage" - ); - - getStatusPage = this.asyncHandler( - async (req, res) => { - const statusPage = await this.db.statusPageModule.getStatusPage(); - return res.success({ - msg: this.stringService.statusPageByUrl, - data: statusPage, - }); - }, - SERVICE_NAME, - "getStatusPage" - ); - - getStatusPageByUrl = this.asyncHandler( - async (req, res) => { - await getStatusPageParamValidation.validateAsync(req.params); - await getStatusPageQueryValidation.validateAsync(req.query); - - const statusPage = await this.db.statusPageModule.getStatusPageByUrl(req.params.url); - return res.success({ - msg: this.stringService.statusPageByUrl, - data: statusPage, - }); - }, - SERVICE_NAME, - "getStatusPageByUrl" - ); - - getStatusPagesByTeamId = this.asyncHandler( - async (req, res) => { - const teamId = req.user.teamId; - const statusPages = await this.db.statusPageModule.getStatusPagesByTeamId(teamId); - - return res.success({ - msg: this.stringService.statusPageByTeamId, - data: statusPages, - }); - }, - SERVICE_NAME, - "getStatusPagesByTeamId" - ); - - deleteStatusPage = this.asyncHandler( - async (req, res) => { - await this.db.statusPageModule.deleteStatusPage(req.params.url); - return res.success({ - msg: this.stringService.statusPageDelete, - }); - }, - SERVICE_NAME, - "deleteStatusPage" - ); -} - -export default StatusPageController; diff --git a/server/src/db/v1/MongoDB.js b/server/src/db/MongoDB.js similarity index 91% rename from server/src/db/v1/MongoDB.js rename to server/src/db/MongoDB.js index 115c7e9ee..3db17c78f 100755 --- a/server/src/db/v1/MongoDB.js +++ b/server/src/db/MongoDB.js @@ -11,12 +11,9 @@ class MongoDB { inviteModule, statusPageModule, userModule, - hardwareCheckModule, maintenanceWindowModule, monitorModule, - networkCheckModule, notificationModule, - pageSpeedCheckModule, recoveryModule, settingsModule, incidentModule, @@ -26,15 +23,12 @@ class MongoDB { this.userModule = userModule; this.inviteModule = inviteModule; this.recoveryModule = recoveryModule; - this.pageSpeedCheckModule = pageSpeedCheckModule; - this.hardwareCheckModule = hardwareCheckModule; this.checkModule = checkModule; this.maintenanceWindowModule = maintenanceWindowModule; this.monitorModule = monitorModule; this.notificationModule = notificationModule; this.settingsModule = settingsModule; this.statusPageModule = statusPageModule; - this.networkCheckModule = networkCheckModule; this.incidentModule = incidentModule; } diff --git a/server/src/db/migration/0001_migrateStatusWindowThreshold.ts b/server/src/db/migration/0001_migrateStatusWindowThreshold.ts new file mode 100644 index 000000000..d9dd12aa1 --- /dev/null +++ b/server/src/db/migration/0001_migrateStatusWindowThreshold.ts @@ -0,0 +1,10 @@ +import Monitor from "@/db/models/Monitor.js"; +async function migrateStatusWindowThreshold() { + const monitors = await Monitor.find({ statusWindowThreshold: { $lt: 1 } }); + for (const monitor of monitors) { + monitor.statusWindowThreshold = monitor.statusWindowThreshold * 100; + await monitor.save(); + } +} + +export { migrateStatusWindowThreshold }; diff --git a/server/src/db/migration/0002_convertChecksToTimeSeries.ts b/server/src/db/migration/0002_convertChecksToTimeSeries.ts new file mode 100644 index 000000000..a6c503804 --- /dev/null +++ b/server/src/db/migration/0002_convertChecksToTimeSeries.ts @@ -0,0 +1,95 @@ +import mongoose from "mongoose"; + +const CHECKS_COLLECTION = "checks"; +const BACKUP_COLLECTION = "checks_backup"; +const BATCH_SIZE = 1000; + +const getDb = () => { + const db = mongoose.connection.db; + if (!db) { + throw new Error("Database connection is not initialized"); + } + return db; +}; + +const backupAndDropExistingCollection = async () => { + const db = getDb(); + const collections = await db.listCollections({ name: CHECKS_COLLECTION }).toArray(); + if (collections.length === 0) { + return false; + } + + await db + .collection(CHECKS_COLLECTION) + .aggregate([{ $match: {} }, { $out: BACKUP_COLLECTION }]) + .toArray(); + await db.collection(CHECKS_COLLECTION).drop(); + return true; +}; + +const createTimeSeriesCollection = async () => { + const db = getDb(); + const existing = await db.listCollections({ name: CHECKS_COLLECTION }).toArray(); + if (existing.length === 0) { + await db.createCollection(CHECKS_COLLECTION, { + timeseries: { + timeField: "createdAt", + metaField: "metadata", + granularity: "seconds", + }, + }); + } + + await db + .collection(CHECKS_COLLECTION) + .createIndexes([ + { key: { updatedAt: 1 } }, + { key: { "metadata.monitorId": 1, updatedAt: 1 } }, + { key: { "metadata.monitorId": 1, updatedAt: -1 } }, + { key: { "metadata.teamId": 1, updatedAt: -1 } }, + ]); +}; + +const migrateBackupData = async () => { + const db = getDb(); + const backupExists = await db.listCollections({ name: BACKUP_COLLECTION }).toArray(); + if (backupExists.length === 0) { + return; + } + + const source = db.collection(BACKUP_COLLECTION); + const target = db.collection(CHECKS_COLLECTION); + const cursor = source.find(); + const operations: any[] = []; + + while (await cursor.hasNext()) { + const doc = await cursor.next(); + if (!doc) { + continue; + } + const metadata = { + monitorId: doc.monitorId, + teamId: doc.teamId, + type: doc.type, + }; + + operations.push({ insertOne: { document: { ...doc, metadata } } }); + + if (operations.length >= BATCH_SIZE) { + await target.bulkWrite(operations); + operations.length = 0; + } + } + + if (operations.length) { + await target.bulkWrite(operations); + } +}; + +export const convertChecksToTimeSeries = async () => { + const backedUp = await backupAndDropExistingCollection(); + await createTimeSeriesCollection(); + if (backedUp) { + await migrateBackupData(); + } +}; diff --git a/server/src/db/migration/index.ts b/server/src/db/migration/index.ts new file mode 100644 index 000000000..c8e9e7c75 --- /dev/null +++ b/server/src/db/migration/index.ts @@ -0,0 +1,47 @@ +import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshold.js"; +import { convertChecksToTimeSeries } from "./0002_convertChecksToTimeSeries.js"; +import MigrationModel from "../models/Migration.js"; + +type MigrationEntry = { + name: string; + execute: () => Promise; +}; + +const migrations: MigrationEntry[] = [ + { name: "0001_migrateStatusWindowThreshold", execute: migrateStatusWindowThreshold }, + { name: "0002_convertChecksToTimeSeries", execute: convertChecksToTimeSeries }, +]; + +const runMigrations = async (logger?: { info: Function; error: Function }) => { + try { + logger?.info({ message: "Running migrations", service: "Migrations" }); + for (const migration of migrations) { + const exists = await MigrationModel.findOne({ name: migration.name, status: "completed" }); + if (exists) { + logger?.info({ message: `Skipping ${migration.name}`, service: "Migrations" }); + continue; + } + + try { + await migration.execute(); + await MigrationModel.findOneAndUpdate( + { name: migration.name }, + { status: "completed", completedAt: new Date(), error: undefined }, + { upsert: true } + ); + logger?.info({ message: `Completed ${migration.name}`, service: "Migrations" }); + } catch (error) { + const err = error as Error; + await MigrationModel.findOneAndUpdate({ name: migration.name }, { status: "failed", error: err?.message }, { upsert: true }); + throw error; + } + } + logger?.info({ message: "Migrations completed", service: "Migrations" }); + } catch (error) { + const err = error as Error; + logger?.error({ message: "Migration failed", service: "Migrations", details: err?.message, stack: err?.stack }); + throw error; + } +}; + +export { runMigrations }; diff --git a/server/src/db/v1/models/AppSettings.js b/server/src/db/models/AppSettings.js similarity index 100% rename from server/src/db/v1/models/AppSettings.js rename to server/src/db/models/AppSettings.js diff --git a/server/src/db/models/Check.ts b/server/src/db/models/Check.ts new file mode 100644 index 000000000..c176aa3bb --- /dev/null +++ b/server/src/db/models/Check.ts @@ -0,0 +1,299 @@ +import { Schema, model, Types } from "mongoose"; +import { MonitorTypes, type MonitorType } from "@/types/monitor.js"; +import type { + Check, + CheckAudits, + CheckCaptureInfo, + CheckCpuInfo, + CheckDiskInfo, + CheckErrorInfo, + CheckHostInfo, + CheckMemoryInfo, + CheckMetadata, + CheckNetworkInterfaceInfo, + CheckTimings, + CheckTimingPhases, + ILighthouseAudit, +} from "@/types/check.js"; + +type CheckMetadataDocument = Omit & { + monitorId: Types.ObjectId; + teamId: Types.ObjectId; + type: MonitorType; +}; + +type CheckDocumentBase = Omit & { + metadata: CheckMetadataDocument; + expiry: Date; + ackAt?: Date | null; + createdAt: Date; + updatedAt: Date; + __v: number; +}; + +interface CheckDocument extends CheckDocumentBase { + _id: Types.ObjectId; +} + +const timingPhasesSchema = new Schema( + { + wait: { type: Number, default: 0 }, + dns: { type: Number, default: 0 }, + tcp: { type: Number, default: 0 }, + tls: { type: Number, default: 0 }, + request: { type: Number, default: 0 }, + firstByte: { type: Number, default: 0 }, + download: { type: Number, default: 0 }, + total: { type: Number, default: 0 }, + }, + { _id: false } +); + +const timingsSchema = new Schema( + { + start: { type: Number, default: 0 }, + socket: { type: Number, default: 0 }, + lookup: { type: Number, default: 0 }, + connect: { type: Number, default: 0 }, + secureConnect: { type: Number, default: 0 }, + upload: { type: Number, default: 0 }, + response: { type: Number, default: 0 }, + end: { type: Number, default: 0 }, + phases: { + type: timingPhasesSchema, + default: () => ({}), + }, + }, + { _id: false } +); + +const cpuSchema = new Schema( + { + physical_core: { type: Number, default: 0 }, + logical_core: { type: Number, default: 0 }, + frequency: { type: Number, default: 0 }, + temperature: { type: [Number], default: [] }, + free_percent: { type: Number, default: 0 }, + usage_percent: { type: Number, default: 0 }, + }, + { _id: false } +); + +const memorySchema = new Schema( + { + total_bytes: { type: Number, default: 0 }, + available_bytes: { type: Number, default: 0 }, + used_bytes: { type: Number, default: 0 }, + usage_percent: { type: Number, default: 0 }, + }, + { _id: false } +); + +const diskSchema = new Schema( + { + device: { type: String, default: "" }, + mountpoint: { type: String, default: "" }, + read_speed_bytes: { type: Number, default: 0 }, + write_speed_bytes: { type: Number, default: 0 }, + total_bytes: { type: Number, default: 0 }, + free_bytes: { type: Number, default: 0 }, + usage_percent: { type: Number, default: 0 }, + }, + { _id: false } +); + +const hostSchema = new Schema( + { + os: { type: String, default: "" }, + platform: { type: String, default: "" }, + kernel_version: { type: String, default: "" }, + }, + { _id: false } +); + +const errorSchema = new Schema( + { + metric: { type: [String], default: [] }, + err: { type: String, default: "" }, + }, + { _id: false } +); + +const captureSchema = new Schema( + { + version: { type: String, default: "" }, + mode: { type: String, default: "" }, + }, + { _id: false } +); + +const networkInterfaceSchema = new Schema( + { + name: { type: String, default: "" }, + bytes_sent: { type: Number, default: 0 }, + bytes_recv: { type: Number, default: 0 }, + packets_sent: { type: Number, default: 0 }, + packets_recv: { type: Number, default: 0 }, + err_in: { type: Number, default: 0 }, + err_out: { type: Number, default: 0 }, + drop_in: { type: Number, default: 0 }, + drop_out: { type: Number, default: 0 }, + fifo_in: { type: Number, default: 0 }, + fifo_out: { type: Number, default: 0 }, + }, + { _id: false } +); + +const lighthouseAuditSchema = new Schema( + { + id: { type: String }, + title: { type: String }, + score: { type: Number }, + displayValue: { type: String }, + numericValue: { type: Number }, + numericUnit: { type: String }, + }, + { _id: false } +); + +const auditsSchema = new Schema( + { + cls: { type: lighthouseAuditSchema, default: undefined }, + si: { type: lighthouseAuditSchema, default: undefined }, + fcp: { type: lighthouseAuditSchema, default: undefined }, + lcp: { type: lighthouseAuditSchema, default: undefined }, + tbt: { type: lighthouseAuditSchema, default: undefined }, + }, + { _id: false } +); + +const metadataSchema = new Schema( + { + monitorId: { + type: Schema.Types.ObjectId, + ref: "Monitor", + required: true, + immutable: true, + index: true, + }, + teamId: { + type: Schema.Types.ObjectId, + ref: "Team", + required: true, + immutable: true, + index: true, + }, + type: { + type: String, + enum: MonitorTypes, + required: true, + index: true, + }, + }, + { _id: false } +); + +const CheckSchema = new Schema( + { + metadata: { + type: metadataSchema, + required: true, + }, + status: { + type: Boolean, + index: true, + }, + responseTime: { + type: Number, + }, + timings: { + type: timingsSchema, + default: undefined, + }, + statusCode: { + type: Number, + index: true, + }, + message: { + type: String, + }, + expiry: { + type: Date, + default: Date.now, + }, + ack: { + type: Boolean, + default: false, + }, + ackAt: { + type: Date, + default: undefined, + }, + cpu: { + type: cpuSchema, + default: () => ({}), + }, + memory: { + type: memorySchema, + default: () => ({}), + }, + disk: { + type: [diskSchema], + default: () => [], + }, + host: { + type: hostSchema, + default: () => ({}), + }, + errors: { + type: [errorSchema], + default: () => [], + }, + capture: { + type: captureSchema, + default: () => ({}), + }, + net: { + type: [networkInterfaceSchema], + default: () => [], + }, + accessibility: { + type: Number, + }, + bestPractices: { + type: Number, + }, + seo: { + type: Number, + }, + performance: { + type: Number, + }, + audits: { + type: auditsSchema, + default: undefined, + }, + }, + { + timestamps: true, + strict: false, + timeseries: { + timeField: "createdAt", + metaField: "metadata", + granularity: "seconds", + }, + } +); + +CheckSchema.index({ updatedAt: 1 }); +CheckSchema.index({ "metadata.monitorId": 1, updatedAt: 1 }); +CheckSchema.index({ "metadata.monitorId": 1, updatedAt: -1 }); +CheckSchema.index({ "metadata.monitorId": 1, createdAt: -1 }); +CheckSchema.index({ "metadata.teamId": 1, updatedAt: -1 }); +CheckSchema.index({ "metadata.teamId": 1, createdAt: -1 }); + +const CheckModel = model("Check", CheckSchema); + +export type { CheckDocument, CheckMetadataDocument }; +export { CheckModel }; +export default CheckModel; diff --git a/server/src/db/v1/models/Incident.js b/server/src/db/models/Incident.js similarity index 100% rename from server/src/db/v1/models/Incident.js rename to server/src/db/models/Incident.js diff --git a/server/src/db/v1/models/InviteToken.js b/server/src/db/models/InviteToken.js similarity index 100% rename from server/src/db/v1/models/InviteToken.js rename to server/src/db/models/InviteToken.js diff --git a/server/src/db/v1/models/MaintenanceWindow.js b/server/src/db/models/MaintenanceWindow.js similarity index 100% rename from server/src/db/v1/models/MaintenanceWindow.js rename to server/src/db/models/MaintenanceWindow.js diff --git a/server/src/db/models/Migration.ts b/server/src/db/models/Migration.ts new file mode 100644 index 000000000..ba928d910 --- /dev/null +++ b/server/src/db/models/Migration.ts @@ -0,0 +1,13 @@ +import mongoose from "mongoose"; + +const MigrationSchema = new mongoose.Schema( + { + name: { type: String, required: true, unique: true }, + status: { type: String, enum: ["completed", "failed"], default: "completed" }, + completedAt: { type: Date }, + error: { type: String }, + }, + { timestamps: true } +); + +export default mongoose.model("Migration", MigrationSchema); diff --git a/server/src/db/v1/models/Monitor.js b/server/src/db/models/Monitor.ts old mode 100755 new mode 100644 similarity index 70% rename from server/src/db/v1/models/Monitor.js rename to server/src/db/models/Monitor.ts index 901dc39d1..f6f9d4371 --- a/server/src/db/v1/models/Monitor.js +++ b/server/src/db/models/Monitor.ts @@ -1,18 +1,49 @@ -import mongoose from "mongoose"; +import { Schema, model, Types, type UpdateQuery } from "mongoose"; +import type { Monitor, MonitorMatchMethod, MonitorThresholds } from "@/types/monitor.js"; +import { MonitorTypes } from "@/types/monitor.js"; import Check from "./Check.js"; import MonitorStats from "./MonitorStats.js"; import StatusPage from "./StatusPage.js"; -const MonitorSchema = mongoose.Schema( +type MonitorDocumentBase = Omit< + Monitor, + "id" | "userId" | "teamId" | "notifications" | "selectedDisks" | "statusWindow" | "createdAt" | "updatedAt" +> & { + statusWindow: boolean[]; + notifications: Types.ObjectId[]; + selectedDisks: string[]; + matchMethod?: MonitorMatchMethod; + thresholds?: MonitorThresholds; +}; + +interface MonitorDocument extends MonitorDocumentBase { + _id: Types.ObjectId; + userId: Types.ObjectId; + teamId: Types.ObjectId; + createdAt: Date; + updatedAt: Date; +} + +const thresholdsSchema = new Schema( + { + usage_cpu: { type: Number }, + usage_memory: { type: Number }, + usage_disk: { type: Number }, + usage_temperature: { type: Number }, + }, + { _id: false } +); + +const MonitorSchema = new Schema( { userId: { - type: mongoose.Schema.Types.ObjectId, + type: Schema.Types.ObjectId, ref: "User", immutable: true, required: true, }, teamId: { - type: mongoose.Schema.Types.ObjectId, + type: Schema.Types.ObjectId, ref: "Team", immutable: true, required: true, @@ -43,7 +74,7 @@ const MonitorSchema = mongoose.Schema( type: { type: String, required: true, - enum: ["http", "ping", "pagespeed", "hardware", "docker", "port", "game"], + enum: MonitorTypes, }, ignoreTlsErrors: { type: Boolean, @@ -71,7 +102,6 @@ const MonitorSchema = mongoose.Schema( default: true, }, interval: { - // in milliseconds type: Number, default: 60000, }, @@ -81,7 +111,7 @@ const MonitorSchema = mongoose.Schema( }, notifications: [ { - type: mongoose.Schema.Types.ObjectId, + type: Schema.Types.ObjectId, ref: "Notification", }, ], @@ -89,13 +119,7 @@ const MonitorSchema = mongoose.Schema( type: String, }, thresholds: { - type: { - usage_cpu: { type: Number }, - usage_memory: { type: Number }, - usage_disk: { type: Number }, - usage_temperature: { type: Number }, - }, - _id: false, + type: thresholdsSchema, }, alertThreshold: { type: Number, @@ -137,7 +161,7 @@ const MonitorSchema = mongoose.Schema( trim: true, maxLength: 50, default: null, - set: function (value) { + set(value: string | null) { return value && value.trim() ? value.trim() : null; }, }, @@ -148,7 +172,6 @@ const MonitorSchema = mongoose.Schema( ); MonitorSchema.pre("findOneAndDelete", async function (next) { - // Delete checks and stats try { const doc = await this.model.findOne(this.getFilter()); @@ -157,20 +180,17 @@ MonitorSchema.pre("findOneAndDelete", async function (next) { } await Check.deleteMany({ monitorId: doc._id }); - - // Deal with status pages await StatusPage.updateMany({ monitors: doc?._id }, { $pull: { monitors: doc?._id } }); - await MonitorStats.deleteMany({ monitorId: doc?._id.toString() }); next(); } catch (error) { - next(error); + next(error as Error); } }); MonitorSchema.pre("deleteMany", async function (next) { const filter = this.getFilter(); - const monitors = await this.model.find(filter).select(["_id", "type"]).lean(); + const monitors = (await this.model.find(filter).select(["_id", "type"]).lean()) as { _id: Types.ObjectId }[]; for (const monitor of monitors) { await Check.deleteMany({ monitorId: monitor._id }); @@ -197,8 +217,8 @@ MonitorSchema.pre("save", function (next) { }); MonitorSchema.pre("findOneAndUpdate", function (next) { - const update = this.getUpdate(); - if (update.alertThreshold) { + const update = this.getUpdate() as UpdateQuery | null; + if (update && !Array.isArray(update) && update.alertThreshold !== undefined) { update.cpuAlertThreshold = update.alertThreshold; update.memoryAlertThreshold = update.alertThreshold; update.diskAlertThreshold = update.alertThreshold; @@ -209,4 +229,8 @@ MonitorSchema.pre("findOneAndUpdate", function (next) { MonitorSchema.index({ teamId: 1, type: 1 }); -export default mongoose.model("Monitor", MonitorSchema); +const MonitorModel = model("Monitor", MonitorSchema); + +export type { MonitorDocument }; +export { MonitorModel }; +export default MonitorModel; diff --git a/server/src/db/models/MonitorStats.ts b/server/src/db/models/MonitorStats.ts new file mode 100755 index 000000000..d1d49ca9f --- /dev/null +++ b/server/src/db/models/MonitorStats.ts @@ -0,0 +1,63 @@ +import { Schema, model, type Types } from "mongoose"; +import type { MonitorStats as MonitorStatsEntity } from "@/types/monitorStats.js"; + +type MonitorStatsDocumentBase = Omit & { + monitorId: Types.ObjectId; +}; + +interface MonitorStatsDocument extends MonitorStatsDocumentBase { + _id: Types.ObjectId; + createdAt: Date; + updatedAt: Date; +} + +const MonitorStatsSchema = new Schema( + { + monitorId: { + type: Schema.Types.ObjectId, + ref: "Monitor", + immutable: true, + index: true, + required: true, + }, + avgResponseTime: { + type: Number, + default: 0, + }, + totalChecks: { + type: Number, + default: 0, + }, + totalUpChecks: { + type: Number, + default: 0, + }, + totalDownChecks: { + type: Number, + default: 0, + }, + uptimePercentage: { + type: Number, + default: 0, + }, + lastCheckTimestamp: { + type: Number, + default: 0, + }, + lastResponseTime: { + type: Number, + default: 0, + }, + timeOfLastFailure: { + type: Number, + default: undefined, + }, + }, + { timestamps: true } +); + +const MonitorStatsModel = model("MonitorStats", MonitorStatsSchema); + +export type { MonitorStatsDocument }; +export { MonitorStatsModel }; +export default MonitorStatsModel; diff --git a/server/src/db/v1/models/Notification.js b/server/src/db/models/Notification.js similarity index 100% rename from server/src/db/v1/models/Notification.js rename to server/src/db/models/Notification.js diff --git a/server/src/db/v1/models/RecoveryToken.js b/server/src/db/models/RecoveryToken.js similarity index 100% rename from server/src/db/v1/models/RecoveryToken.js rename to server/src/db/models/RecoveryToken.js diff --git a/server/src/db/v1/models/StatusPage.js b/server/src/db/models/StatusPage.js similarity index 100% rename from server/src/db/v1/models/StatusPage.js rename to server/src/db/models/StatusPage.js diff --git a/server/src/db/v1/models/Team.js b/server/src/db/models/Team.js similarity index 100% rename from server/src/db/v1/models/Team.js rename to server/src/db/models/Team.js diff --git a/server/src/db/v1/models/User.js b/server/src/db/models/User.js similarity index 100% rename from server/src/db/v1/models/User.js rename to server/src/db/models/User.js diff --git a/server/src/db/models/index.ts b/server/src/db/models/index.ts new file mode 100644 index 000000000..3ac99a390 --- /dev/null +++ b/server/src/db/models/index.ts @@ -0,0 +1,8 @@ +export * from "@/db/models/Monitor.js"; +export { default as MonitorModel } from "@/db/models/Monitor.js"; + +export * from "@/db/models/Check.js"; +export { default as CheckModel } from "@/db/models/Check.js"; + +export * from "@/db/models/MonitorStats.js"; +export { default as MonitorStatsModel } from "@/db/models/MonitorStats.js"; diff --git a/server/src/db/v1/modules/checkModule.js b/server/src/db/modules/checkModule.js similarity index 81% rename from server/src/db/v1/modules/checkModule.js rename to server/src/db/modules/checkModule.js index dfca02bed..e420d5ce7 100755 --- a/server/src/db/v1/modules/checkModule.js +++ b/server/src/db/modules/checkModule.js @@ -1,4 +1,6 @@ import { ObjectId } from "mongodb"; +import mongoose from "mongoose"; +import { CheckModel } from "@/db/models/index.js"; import { buildChecksSummaryByTeamIdPipeline } from "./checkModuleQueries.js"; const SERVICE_NAME = "checkModule"; @@ -12,18 +14,15 @@ const dateRangeLookup = { }; class CheckModule { - constructor({ logger, Check, HardwareCheck, PageSpeedCheck, Monitor, User }) { + constructor({ logger, Monitor, User }) { this.logger = logger; - this.Check = Check; - this.HardwareCheck = HardwareCheck; - this.PageSpeedCheck = PageSpeedCheck; this.Monitor = Monitor; this.User = User; } createChecks = async (checks) => { try { - await this.Check.insertMany(checks, { ordered: false }); + await CheckModel.insertMany(checks, { ordered: false, runValidators: true }); } catch (error) { error.service = SERVICE_NAME; error.method = "createCheck"; @@ -41,7 +40,7 @@ class CheckModule { // Match const matchStage = { - monitorId: new ObjectId(monitorId), + "metadata.monitorId": new ObjectId(monitorId), ...(typeof status !== "undefined" && { status }), ...(typeof ack !== "undefined" && ackStage), ...(dateRangeLookup[dateRange] && { @@ -79,7 +78,7 @@ class CheckModule { skip = page * rowsPerPage; } - const checks = await this.Check.aggregate([ + const checks = await CheckModel.aggregate([ { $match: matchStage }, { $sort: { createdAt: sortOrder } }, { @@ -115,7 +114,7 @@ class CheckModule { const ackStage = ack === "true" ? { ack: true } : { $or: [{ ack: false }, { ack: { $exists: false } }] }; const matchStage = { - teamId: new ObjectId(teamId), + "metadata.teamId": new ObjectId(teamId), status: false, ...(typeof ack !== "undefined" && ackStage), ...(dateRangeLookup[dateRange] && { @@ -170,7 +169,7 @@ class CheckModule { }, ]; - const checks = await this.Check.aggregate(aggregatePipeline); + const checks = await CheckModel.aggregate(aggregatePipeline); return checks[0]; } catch (error) { error.service = SERVICE_NAME; @@ -181,7 +180,11 @@ class CheckModule { ackCheck = async (checkId, teamId, ack) => { try { - const updatedCheck = await this.Check.findOneAndUpdate({ _id: checkId, teamId: teamId }, { $set: { ack, ackAt: new Date() } }, { new: true }); + const updatedCheck = await CheckModel.findOneAndUpdate( + { _id: checkId, "metadata.teamId": teamId }, + { $set: { ack, ackAt: new Date() } }, + { new: true } + ); if (!updatedCheck) { throw new Error("Check not found"); @@ -197,7 +200,11 @@ class CheckModule { ackAllChecks = async (monitorId, teamId, ack, path) => { try { - const updatedChecks = await this.Check.updateMany(path === "monitor" ? { monitorId } : { teamId }, { $set: { ack, ackAt: new Date() } }); + const filter = + path === "monitor" + ? { "metadata.monitorId": new mongoose.Types.ObjectId(monitorId) } + : { "metadata.teamId": new mongoose.Types.ObjectId(teamId) }; + const updatedChecks = await CheckModel.updateMany(filter, { $set: { ack, ackAt: new Date() } }); return updatedChecks.modifiedCount; } catch (error) { error.service = SERVICE_NAME; @@ -209,9 +216,9 @@ class CheckModule { getChecksSummaryByTeamId = async ({ teamId }) => { try { const matchStage = { - teamId: new ObjectId(teamId), + "metadata.teamId": new ObjectId(teamId), }; - const checks = await this.Check.aggregate(buildChecksSummaryByTeamIdPipeline({ matchStage })); + const checks = await CheckModel.aggregate(buildChecksSummaryByTeamIdPipeline({ matchStage })); return checks[0].summary; } catch (error) { error.service = SERVICE_NAME; @@ -221,7 +228,7 @@ class CheckModule { }; deleteChecks = async (monitorId) => { try { - const result = await this.Check.deleteMany({ monitorId }); + const result = await CheckModel.deleteMany({ "metadata.monitorId": monitorId }); return result.deletedCount; } catch (error) { error.service = SERVICE_NAME; @@ -237,7 +244,7 @@ class CheckModule { const monitorIds = teamMonitors.map((monitor) => monitor._id); // Delete all checks for these monitors in one operation - const deleteResult = await this.Check.deleteMany({ monitorId: { $in: monitorIds } }); + const deleteResult = await CheckModel.deleteMany({ "metadata.monitorId": { $in: monitorIds } }); return deleteResult.deletedCount; } catch (error) { @@ -249,7 +256,7 @@ class CheckModule { updateChecksTTL = async (teamId, ttl) => { try { - await this.Check.collection.dropIndex("expiry_1"); + await CheckModel.collection.dropIndex("expiry_1"); } catch (error) { this.logger.error({ message: error.message, @@ -260,9 +267,9 @@ class CheckModule { } try { - await this.Check.collection.createIndex( + await CheckModel.collection.createIndex( { expiry: 1 }, - { expireAfterSeconds: ttl } // TTL in seconds, adjust as necessary + { expireAfterSeconds: ttl, partialFilterExpression: { "metadata.mode": { $exists: true } } } ); } catch (error) { error.service = SERVICE_NAME; diff --git a/server/src/db/v1/modules/checkModuleQueries.js b/server/src/db/modules/checkModuleQueries.js similarity index 100% rename from server/src/db/v1/modules/checkModuleQueries.js rename to server/src/db/modules/checkModuleQueries.js diff --git a/server/src/db/v1/modules/incidentModule.js b/server/src/db/modules/incidentModule.js similarity index 100% rename from server/src/db/v1/modules/incidentModule.js rename to server/src/db/modules/incidentModule.js diff --git a/server/src/db/v1/modules/inviteModule.js b/server/src/db/modules/inviteModule.js similarity index 100% rename from server/src/db/v1/modules/inviteModule.js rename to server/src/db/modules/inviteModule.js diff --git a/server/src/db/v1/modules/maintenanceWindowModule.js b/server/src/db/modules/maintenanceWindowModule.js similarity index 100% rename from server/src/db/v1/modules/maintenanceWindowModule.js rename to server/src/db/modules/maintenanceWindowModule.js diff --git a/server/src/db/v1/modules/monitorModule.js b/server/src/db/modules/monitorModule.js similarity index 78% rename from server/src/db/v1/modules/monitorModule.js rename to server/src/db/modules/monitorModule.js index 39168f0b2..84ff86996 100755 --- a/server/src/db/v1/modules/monitorModule.js +++ b/server/src/db/modules/monitorModule.js @@ -1,20 +1,23 @@ import { buildUptimeDetailsPipeline, - buildHardwareDetailsPipeline, buildMonitorSummaryByTeamIdPipeline, buildMonitorsByTeamIdPipeline, buildMonitorsAndSummaryByTeamIdPipeline, buildMonitorsWithChecksByTeamIdPipeline, buildFilteredMonitorsByTeamIdPipeline, + getHardwareStats, + getUpChecks, + getAggregateData, } from "./monitorModuleQueries.js"; +import { CheckModel } from "@/db/models/index.js"; + const SERVICE_NAME = "monitorModule"; class MonitorModule { - constructor({ Monitor, MonitorStats, Check, stringService, fs, path, fileURLToPath, ObjectId, NormalizeData, NormalizeDataUptimeDetails }) { + constructor({ Monitor, MonitorStats, stringService, fs, path, fileURLToPath, ObjectId, NormalizeData, NormalizeDataUptimeDetails }) { this.Monitor = Monitor; this.MonitorStats = MonitorStats; - this.Check = Check; this.stringService = stringService; this.fs = fs; this.path = path; @@ -123,15 +126,18 @@ class MonitorModule { //Helper getMonitorChecks = async (monitorId, dateRange, sortOrder) => { + const objectId = new this.ObjectId(monitorId); const indexSpec = { - monitorId: 1, - updatedAt: sortOrder, // This will be 1 or -1 + "metadata.monitorId": 1, + updatedAt: sortOrder, }; + const matchBase = { "metadata.monitorId": objectId }; + const [checksAll, checksForDateRange] = await Promise.all([ - this.Check.find({ monitorId }).sort({ createdAt: sortOrder }).hint(indexSpec).lean(), - this.Check.find({ - monitorId, + CheckModel.find(matchBase).sort({ createdAt: sortOrder }).hint(indexSpec).lean(), + CheckModel.find({ + ...matchBase, createdAt: { $gte: dateRange.start, $lte: dateRange.end }, }) .hint(indexSpec) @@ -188,17 +194,6 @@ class MonitorModule { }; }; - getAllMonitors = async () => { - try { - const monitors = await this.Monitor.find(); - return monitors; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getAllMonitors"; - throw error; - } - }; - getMonitorById = async (monitorId) => { try { const monitor = await this.Monitor.findById(monitorId); @@ -238,7 +233,7 @@ class MonitorModule { const dateString = formatLookup[dateRange]; - const results = await this.Check.aggregate(buildUptimeDetailsPipeline(monitorId, dates, dateString)); + const results = await CheckModel.aggregate(buildUptimeDetailsPipeline(monitorId, dates, dateString)); const monitorData = results[0]; @@ -258,48 +253,6 @@ class MonitorModule { } }; - getMonitorStatsById = async ({ monitorId, sortOrder, dateRange, numToDisplay, normalize }) => { - try { - // Get monitor, if we can't find it, abort with error - const monitor = await this.Monitor.findById(monitorId); - if (monitor === null || monitor === undefined) { - throw new Error(this.stringService.getDbFindMonitorById(monitorId)); - } - - // Get query params - const sort = sortOrder === "asc" ? 1 : -1; - - // Get Checks for monitor in date range requested - const dates = this.getDateRange(dateRange); - const { checksAll, checksForDateRange } = await this.getMonitorChecks(monitorId, dates, sort); - - // Build monitor stats - const monitorStats = { - ...monitor.toObject(), - uptimeDuration: this.calculateUptimeDuration(checksAll), - lastChecked: this.getLastChecked(checksAll), - latestResponseTime: this.getLatestResponseTime(checksAll), - periodIncidents: this.getIncidents(checksForDateRange), - periodTotalChecks: checksForDateRange.length, - checks: this.processChecksForDisplay(this.NormalizeData, checksForDateRange, numToDisplay, normalize), - }; - - if (monitor.type === "http" || monitor.type === "ping" || monitor.type === "docker" || monitor.type === "port" || monitor.type === "game") { - // HTTP/PING Specific stats - monitorStats.periodAvgResponseTime = this.getAverageResponseTime(checksForDateRange); - monitorStats.periodUptime = this.getUptimePercentage(checksForDateRange); - const groupedChecks = this.groupChecksByTime(checksForDateRange, dateRange); - monitorStats.aggregateData = Object.values(groupedChecks).map(this.calculateGroupStats); - } - - return monitorStats; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorStatsById"; - throw error; - } - }; - getHardwareDetailsById = async ({ monitorId, dateRange }) => { try { const monitor = await this.Monitor.findById(monitorId); @@ -313,9 +266,17 @@ class MonitorModule { }; const dateString = formatLookup[dateRange]; - const hardwareStats = await this.Check.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString)); + const [aggregateData, upChecksCount, metrics] = await Promise.all([ + getAggregateData(monitorId, dates), + getUpChecks(monitorId, dates), + getHardwareStats(monitorId, dates, dateString), + ]); - const stats = hardwareStats[0]; + const stats = { + aggregateData: aggregateData, + upChecks: upChecksCount, + checks: metrics, + }; return { ...monitor.toObject(), @@ -328,47 +289,33 @@ class MonitorModule { } }; - getMonitorsByTeamId = async ({ limit, type, page, rowsPerPage, filter, field, order, teamId }) => { - limit = parseInt(limit); - page = parseInt(page); - rowsPerPage = parseInt(rowsPerPage); - if (field === undefined) { - field = "name"; - order = "asc"; - } - // Build match stage - const matchStage = { teamId: new this.ObjectId(teamId) }; - if (type !== undefined) { - matchStage.type = Array.isArray(type) ? { $in: type } : type; - } - - const summaryResult = await this.Monitor.aggregate(buildMonitorSummaryByTeamIdPipeline({ matchStage })); - const summary = summaryResult[0]; - - const monitors = await this.Monitor.aggregate(buildMonitorsByTeamIdPipeline({ matchStage, field, order })); - - const filteredMonitors = await this.Monitor.aggregate( - buildFilteredMonitorsByTeamIdPipeline({ - matchStage, - filter, - page, - rowsPerPage, - field, - order, - limit, - type, - }) - ); - - const normalizedFilteredMonitors = filteredMonitors.map((monitor) => { - if (!monitor.checks) { - return monitor; + getMonitorsByTeamId = async ({ teamId, type, filter }) => { + try { + const matchStage = { teamId: new this.ObjectId(teamId) }; + if (type !== undefined) { + matchStage.type = Array.isArray(type) ? { $in: type } : type; } - monitor.checks = this.NormalizeData(monitor.checks, 10, 100); - return monitor; - }); - - return { summary, monitors, filteredMonitors: normalizedFilteredMonitors }; + if (filter !== undefined && filter !== null && filter !== "") { + matchStage.$or = [{ name: { $regex: filter, $options: "i" } }, { url: { $regex: filter, $options: "i" } }]; + } + const monitors = await this.Monitor.find(matchStage) + .sort({ name: 1 }) + .select({ + _id: 1, + name: 1, + type: 1, + url: 1, + status: 1, + isActive: 1, + teamId: 1, + }) + .lean(); + return monitors; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorsByTeamId"; + throw error; + } }; getMonitorsAndSummaryByTeamId = async ({ type, explain, teamId }) => { diff --git a/server/src/db/modules/monitorModuleQueries.js b/server/src/db/modules/monitorModuleQueries.js new file mode 100755 index 000000000..596f11791 --- /dev/null +++ b/server/src/db/modules/monitorModuleQueries.js @@ -0,0 +1,740 @@ +import { ObjectId } from "mongodb"; +import { CheckModel } from "@/db/models/index.js"; + +const buildUptimeDetailsPipeline = (monitorId, dates, dateString) => { + return [ + { + $match: { + "metadata.monitorId": new ObjectId(monitorId), + updatedAt: { $gte: dates.start, $lte: dates.end }, + }, + }, + { + $sort: { + updatedAt: 1, + }, + }, + { + $facet: { + // For the response time chart, should return checks for date window + // Grouped by: {day: hour}, {week: day}, {month: day} + uptimePercentage: [ + { + $group: { + _id: null, + upChecks: { + $sum: { $cond: [{ $eq: ["$status", true] }, 1, 0] }, + }, + totalChecks: { $sum: 1 }, + }, + }, + { + $project: { + _id: 0, + percentage: { + $cond: [{ $eq: ["$totalChecks", 0] }, 0, { $divide: ["$upChecks", "$totalChecks"] }], + }, + }, + }, + ], + groupedAvgResponseTime: [ + { + $group: { + _id: null, + avgResponseTime: { + $avg: "$responseTime", + }, + }, + }, + ], + groupedChecks: [ + { + $group: { + _id: { + $dateToString: { + format: dateString, + date: "$createdAt", + }, + }, + avgResponseTime: { + $avg: "$responseTime", + }, + totalChecks: { + $sum: 1, + }, + }, + }, + { + $sort: { + _id: 1, + }, + }, + ], + // Up checks grouped by: {day: hour}, {week: day}, {month: day} + groupedUpChecks: [ + { + $match: { + status: true, + }, + }, + { + $group: { + _id: { + $dateToString: { + format: dateString, + date: "$createdAt", + }, + }, + totalChecks: { + $sum: 1, + }, + avgResponseTime: { + $avg: "$responseTime", + }, + }, + }, + { + $sort: { _id: 1 }, + }, + ], + // Down checks grouped by: {day: hour}, {week: day}, {month: day} for the date window + groupedDownChecks: [ + { + $match: { + status: false, + }, + }, + { + $group: { + _id: { + $dateToString: { + format: dateString, + date: "$createdAt", + }, + }, + totalChecks: { + $sum: 1, + }, + avgResponseTime: { + $avg: "$responseTime", + }, + }, + }, + { + $sort: { _id: 1 }, + }, + ], + }, + }, + { + $lookup: { + from: "monitors", + let: { monitor_id: { $toObjectId: monitorId } }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$_id", "$$monitor_id"] }, + }, + }, + { + $project: { + _id: 1, + teamId: 1, + name: 1, + status: 1, + interval: 1, + type: 1, + url: 1, + isActive: 1, + notifications: 1, + }, + }, + ], + as: "monitor", + }, + }, + { + $project: { + groupedAvgResponseTime: { + $arrayElemAt: ["$groupedAvgResponseTime.avgResponseTime", 0], + }, + + groupedChecks: "$groupedChecks", + groupedUpChecks: "$groupedUpChecks", + groupedDownChecks: "$groupedDownChecks", + groupedUptimePercentage: { $arrayElemAt: ["$uptimePercentage.percentage", 0] }, + monitor: { $arrayElemAt: ["$monitor", 0] }, + }, + }, + ]; +}; + +const buildMonitorStatsPipeline = (monitor) => { + return [ + { + $match: { + monitorId: monitor._id, + }, + }, + { + $project: { + avgResponseTime: 1, + uptimePercentage: 1, + totalChecks: 1, + timeSinceLastCheck: { + $subtract: [Date.now(), "$lastCheckTimestamp"], + }, + lastCheckTimestamp: 1, + uptBurnt: { $toString: "$uptBurnt" }, + }, + }, + ]; +}; + +const buildMonitorSummaryByTeamIdPipeline = ({ matchStage }) => { + return [ + { $match: matchStage }, + { + $group: { + _id: null, + totalMonitors: { $sum: 1 }, + upMonitors: { + $sum: { + $cond: [{ $eq: ["$status", true] }, 1, 0], + }, + }, + downMonitors: { + $sum: { + $cond: [{ $eq: ["$status", false] }, 1, 0], + }, + }, + pausedMonitors: { + $sum: { + $cond: [{ $eq: ["$isActive", false] }, 1, 0], + }, + }, + }, + }, + { + $project: { + _id: 0, + }, + }, + ]; +}; + +const buildMonitorsByTeamIdPipeline = ({ matchStage, field, order }) => { + const sort = { [field]: order === "asc" ? 1 : -1 }; + + return [ + { $match: matchStage }, + { $sort: sort }, + { + $project: { + _id: 1, + name: 1, + type: 1, + port: 1, + }, + }, + ]; +}; + +const buildMonitorsAndSummaryByTeamIdPipeline = ({ matchStage }) => { + return [ + { $match: matchStage }, + { + $facet: { + summary: [ + { + $group: { + _id: null, + totalMonitors: { $sum: 1 }, + upMonitors: { + $sum: { + $cond: [{ $eq: ["$status", true] }, 1, 0], + }, + }, + downMonitors: { + $sum: { + $cond: [{ $eq: ["$status", false] }, 1, 0], + }, + }, + pausedMonitors: { + $sum: { + $cond: [{ $eq: ["$isActive", false] }, 1, 0], + }, + }, + }, + }, + { + $project: { + _id: 0, + }, + }, + ], + monitors: [ + { $sort: { name: 1 } }, + { + $project: { + _id: 1, + name: 1, + type: 1, + }, + }, + ], + }, + }, + { + $project: { + summary: { $arrayElemAt: ["$summary", 0] }, + monitors: 1, + }, + }, + ]; +}; + +const buildMonitorsWithChecksByTeamIdPipeline = ({ matchStage, filter, page, rowsPerPage, field, order, limit, type }) => { + const skip = page && rowsPerPage ? page * rowsPerPage : 0; + const sort = { [field]: order === "asc" ? 1 : -1 }; + const limitStage = rowsPerPage ? [{ $limit: rowsPerPage }] : []; + + // Match name + if (typeof filter !== "undefined" && field === "name") { + matchStage.$or = [{ name: { $regex: filter, $options: "i" } }, { url: { $regex: filter, $options: "i" } }]; + } + + // Match isActive + if (typeof filter !== "undefined" && field === "isActive") { + matchStage.isActive = filter === "true" ? true : false; + } + + if (typeof filter !== "undefined" && field === "status") { + matchStage.status = filter === "true" ? true : false; + } + + // Match type + if (typeof filter !== "undefined" && field === "type") { + matchStage.type = filter; + } + + const monitorsPipeline = [ + { $sort: sort }, + { $skip: skip }, + ...limitStage, + { + $project: { + _id: 1, + name: 1, + description: 1, + type: 1, + url: 1, + isActive: 1, + createdAt: 1, + updatedAt: 1, + uptimePercentage: 1, + status: 1, + }, + }, + ]; + + // Add checks + if (limit) { + const checksCollection = "checks"; + monitorsPipeline.push({ + $lookup: { + from: checksCollection, + let: { monitorId: "$_id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + }, + }, + { $sort: { updatedAt: -1 } }, + { $limit: limit }, + { + $project: { + _id: 1, + status: 1, + responseTime: 1, + statusCode: 1, + createdAt: 1, + updatedAt: 1, + originalResponseTime: 1, + }, + }, + ], + as: "checks", + }, + }); + } + + const pipeline = [ + { $match: matchStage }, + { + $facet: { + count: [{ $count: "monitorsCount" }], + monitors: monitorsPipeline, + }, + }, + { + $project: { + count: { $arrayElemAt: ["$count", 0] }, + monitors: 1, + }, + }, + ]; + return pipeline; +}; + +const buildFilteredMonitorsByTeamIdPipeline = ({ matchStage, filter, page, rowsPerPage, field, order, limit, type }) => { + const skip = page && rowsPerPage ? page * rowsPerPage : 0; + const sort = { [field]: order === "asc" ? 1 : -1 }; + const limitStage = rowsPerPage ? [{ $limit: rowsPerPage }] : []; + + if (typeof filter !== "undefined" && field === "name") { + matchStage.$or = [{ name: { $regex: filter, $options: "i" } }, { url: { $regex: filter, $options: "i" } }]; + } + + if (typeof filter !== "undefined" && field === "status") { + matchStage.status = filter === "true"; + } + + const pipeline = [{ $match: matchStage }, { $sort: sort }, { $skip: skip }, ...limitStage]; + + // Add checks + if (limit) { + const checksCollection = "checks"; + + pipeline.push({ + $lookup: { + from: checksCollection, + let: { monitorId: "$_id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + }, + }, + { $sort: { createdAt: -1 } }, + { $limit: limit }, + ], + as: "checks", + }, + }); + } + + return pipeline; +}; + +const buildGetMonitorsByTeamIdPipeline = (req) => { + let { limit, type, page, rowsPerPage, filter, field, order } = req.query; + + limit = parseInt(limit); + page = parseInt(page); + rowsPerPage = parseInt(rowsPerPage); + if (field === undefined) { + field = "name"; + order = "asc"; + } + // Build the match stage + const matchStage = { teamId: new ObjectId(req.params.teamId) }; + if (type !== undefined) { + matchStage.type = Array.isArray(type) ? { $in: type } : type; + } + + const skip = page && rowsPerPage ? page * rowsPerPage : 0; + const sort = { [field]: order === "asc" ? 1 : -1 }; + return [ + { $match: matchStage }, + { + $facet: { + summary: [ + { + $group: { + _id: null, + totalMonitors: { $sum: 1 }, + upMonitors: { + $sum: { + $cond: [{ $eq: ["$status", true] }, 1, 0], + }, + }, + downMonitors: { + $sum: { + $cond: [{ $eq: ["$status", false] }, 1, 0], + }, + }, + pausedMonitors: { + $sum: { + $cond: [{ $eq: ["$isActive", false] }, 1, 0], + }, + }, + }, + }, + { + $project: { + _id: 0, + }, + }, + ], + monitors: [ + { $sort: sort }, + { + $project: { + _id: 1, + name: 1, + }, + }, + ], + filteredMonitors: [ + ...(filter !== undefined + ? [ + { + $match: { + $or: [{ name: { $regex: filter, $options: "i" } }, { url: { $regex: filter, $options: "i" } }], + }, + }, + ] + : []), + { $sort: sort }, + { $skip: skip }, + ...(rowsPerPage ? [{ $limit: rowsPerPage }] : []), + ...(limit + ? [ + { + $lookup: { + from: "checks", + let: { monitorId: "$_id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + }, + }, + { $sort: { createdAt: -1 } }, + ...(limit ? [{ $limit: limit }] : []), + ], + as: "standardchecks", + }, + }, + ] + : []), + + { + $addFields: { + checks: { + $switch: { + branches: [ + { + case: { $in: ["$type", ["http", "ping", "docker", "port", "game"]] }, + then: "$standardchecks", + }, + ], + default: [], + }, + }, + }, + }, + { + $project: { + standardchecks: 0, + }, + }, + ], + }, + }, + { + $project: { + summary: { $arrayElemAt: ["$summary", 0] }, + filteredMonitors: 1, + monitors: 1, + }, + }, + ]; +}; + +export { + buildUptimeDetailsPipeline, + buildMonitorStatsPipeline, + buildGetMonitorsByTeamIdPipeline, + buildMonitorSummaryByTeamIdPipeline, + buildMonitorsByTeamIdPipeline, + buildMonitorsAndSummaryByTeamIdPipeline, + buildMonitorsWithChecksByTeamIdPipeline, + buildFilteredMonitorsByTeamIdPipeline, +}; + +export const getAggregateData = async (monitorId, dates) => { + const result = await CheckModel.aggregate([ + { + $match: { + "metadata.monitorId": new ObjectId(monitorId), + "metadata.type": "hardware", + createdAt: { $gte: dates.start, $lte: dates.end }, + }, + }, + { $sort: { createdAt: -1 } }, + { + $group: { + _id: null, + latestCheck: { $first: "$$ROOT" }, + totalChecks: { $sum: 1 }, + }, + }, + ]); + return result[0] || { totalChecks: 0, latestCheck: null }; +}; + +export const getUpChecks = async (monitorId, dates) => { + const count = await CheckModel.countDocuments({ + "metadata.monitorId": new ObjectId(monitorId), + "metadata.type": "hardware", + createdAt: { $gte: dates.start, $lte: dates.end }, + status: true, + }); + return { totalChecks: count }; +}; + +export const getHardwareStats = async (monitorId, dates, dateString) => { + return await CheckModel.aggregate([ + { + $match: { + "metadata.monitorId": new ObjectId(monitorId), + "metadata.type": "hardware", + createdAt: { $gte: dates.start, $lte: dates.end }, + }, + }, + { $sort: { createdAt: 1 } }, + { + $group: { + _id: { $dateToString: { format: dateString, date: "$createdAt" } }, + avgCpuUsage: { $avg: "$cpu.usage_percent" }, + avgMemoryUsage: { $avg: "$memory.usage_percent" }, + avgTemperatures: { $push: { $ifNull: ["$cpu.temperature", [0]] } }, + disks: { $push: "$disk" }, + net: { $push: "$net" }, + updatedAts: { $push: "$updatedAt" }, + sampleDoc: { $first: "$$ROOT" }, + }, + }, + { + $project: { + _id: 1, + avgCpuUsage: 1, + avgMemoryUsage: 1, + avgTemperature: { + $map: { + input: { $range: [0, { $size: { $ifNull: [{ $arrayElemAt: ["$avgTemperatures", 0] }, [0]] } }] }, + as: "idx", + in: { $avg: { $map: { input: "$avgTemperatures", as: "t", in: { $arrayElemAt: ["$$t", "$$idx"] } } } }, + }, + }, + disks: { + $map: { + input: { $range: [0, { $size: { $ifNull: ["$sampleDoc.disk", []] } }] }, + as: "dIdx", + in: { + name: { $concat: ["disk", { $toString: "$$dIdx" }] }, + readSpeed: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.read_speed_bytes", "$$dIdx"] } } } }, + writeSpeed: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.write_speed_bytes", "$$dIdx"] } } } }, + totalBytes: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.total_bytes", "$$dIdx"] } } } }, + freeBytes: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.free_bytes", "$$dIdx"] } } } }, + usagePercent: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.usage_percent", "$$dIdx"] } } } }, + }, + }, + }, + net: { + $map: { + input: { $range: [0, { $size: { $ifNull: ["$sampleDoc.net", []] } }] }, + as: "nIdx", + in: { + name: { $arrayElemAt: ["$sampleDoc.net.name", "$$nIdx"] }, + bytesSentPerSecond: { + $let: { + vars: { + tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] }, + f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.bytes_sent" } }, "$$nIdx"] }, + l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.bytes_sent" } }, "$$nIdx"] }, + }, + in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] }, + }, + }, + deltaBytesRecv: { + $let: { + vars: { + tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] }, + f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.bytes_recv" } }, "$$nIdx"] }, + l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.bytes_recv" } }, "$$nIdx"] }, + }, + in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] }, + }, + }, + deltaPacketsSent: { + $let: { + vars: { + tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] }, + f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.packets_sent" } }, "$$nIdx"] }, + l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.packets_sent" } }, "$$nIdx"] }, + }, + in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] }, + }, + }, + deltaPacketsRecv: { + $let: { + vars: { + tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] }, + f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.packets_recv" } }, "$$nIdx"] }, + l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.packets_recv" } }, "$$nIdx"] }, + }, + in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] }, + }, + }, + deltaErrIn: { + $let: { + vars: { + tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] }, + f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.err_in" } }, "$$nIdx"] }, + l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.err_in" } }, "$$nIdx"] }, + }, + in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] }, + }, + }, + deltaErrOut: { + $let: { + vars: { + tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] }, + f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.err_out" } }, "$$nIdx"] }, + l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.err_out" } }, "$$nIdx"] }, + }, + in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] }, + }, + }, + deltaDropIn: { + $let: { + vars: { + tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] }, + f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.drop_in" } }, "$$nIdx"] }, + l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.drop_in" } }, "$$nIdx"] }, + }, + in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] }, + }, + }, + deltaDropOut: { + $let: { + vars: { + tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] }, + f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.drop_out" } }, "$$nIdx"] }, + l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.drop_out" } }, "$$nIdx"] }, + }, + in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] }, + }, + }, + }, + }, + }, + }, + }, + { $sort: { _id: 1 } }, + ]); +}; diff --git a/server/src/db/v1/modules/notificationModule.js b/server/src/db/modules/notificationModule.js similarity index 100% rename from server/src/db/v1/modules/notificationModule.js rename to server/src/db/modules/notificationModule.js diff --git a/server/src/db/v1/modules/recoveryModule.js b/server/src/db/modules/recoveryModule.js similarity index 100% rename from server/src/db/v1/modules/recoveryModule.js rename to server/src/db/modules/recoveryModule.js diff --git a/server/src/db/v1/modules/settingsModule.js b/server/src/db/modules/settingsModule.js similarity index 100% rename from server/src/db/v1/modules/settingsModule.js rename to server/src/db/modules/settingsModule.js diff --git a/server/src/db/v1/modules/statusPageModule.js b/server/src/db/modules/statusPageModule.js similarity index 99% rename from server/src/db/v1/modules/statusPageModule.js rename to server/src/db/modules/statusPageModule.js index aadde0c81..999e0bdc9 100755 --- a/server/src/db/v1/modules/statusPageModule.js +++ b/server/src/db/modules/statusPageModule.js @@ -153,7 +153,7 @@ class StatusPageModule { pipeline: [ { $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, + $expr: { $eq: ["$metadata.monitorId", "$$monitorId"] }, }, }, { $sort: { createdAt: -1 } }, diff --git a/server/src/db/v1/modules/userModule.js b/server/src/db/modules/userModule.js similarity index 100% rename from server/src/db/v1/modules/userModule.js rename to server/src/db/modules/userModule.js diff --git a/server/src/db/v1/utils/seedDb.js b/server/src/db/utils/seedDb.js similarity index 100% rename from server/src/db/v1/utils/seedDb.js rename to server/src/db/utils/seedDb.js diff --git a/server/src/db/v1/migration/0001_migrateStatusWindowThreshold.ts b/server/src/db/v1/migration/0001_migrateStatusWindowThreshold.ts deleted file mode 100644 index 857d36381..000000000 --- a/server/src/db/v1/migration/0001_migrateStatusWindowThreshold.ts +++ /dev/null @@ -1,18 +0,0 @@ -import Monitor from "../models/Monitor.js"; -async function migrateStatusWindowThreshold() { - try { - const monitors = await Monitor.find({ statusWindowThreshold: { $lt: 1 } }); - for (const monitor of monitors) { - monitor.statusWindowThreshold = monitor.statusWindowThreshold * 100; - await monitor.save(); - console.log(`Migrated monitor ${monitor._id}: statusWindowThreshold set to ${monitor.statusWindowThreshold}`); - } - console.log("StatusWindowThreshold migration complete."); - return true; - } catch (err) { - console.error("Migration error:", err); - return false; - } -} - -export { migrateStatusWindowThreshold }; diff --git a/server/src/db/v1/migration/index.ts b/server/src/db/v1/migration/index.ts deleted file mode 100644 index 4bf56895a..000000000 --- a/server/src/db/v1/migration/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshold.js"; - -const runMigrations = async () => { - await migrateStatusWindowThreshold(); -}; - -export { runMigrations }; diff --git a/server/src/db/v1/models/Check.js b/server/src/db/v1/models/Check.js deleted file mode 100755 index 7a5117979..000000000 --- a/server/src/db/v1/models/Check.js +++ /dev/null @@ -1,178 +0,0 @@ -import mongoose from "mongoose"; - -const cpuSchema = mongoose.Schema({ - physical_core: { type: Number, default: 0 }, - logical_core: { type: Number, default: 0 }, - frequency: { type: Number, default: 0 }, - temperature: { type: [Number], default: [] }, - free_percent: { type: Number, default: 0 }, - usage_percent: { type: Number, default: 0 }, -}); - -const memorySchema = mongoose.Schema({ - total_bytes: { type: Number, default: 0 }, - available_bytes: { type: Number, default: 0 }, - used_bytes: { type: Number, default: 0 }, - usage_percent: { type: Number, default: 0 }, -}); - -const diskSchema = mongoose.Schema({ - device: { type: String, default: "" }, - mountpoint: { type: String, default: "" }, - read_speed_bytes: { type: Number, default: 0 }, - write_speed_bytes: { type: Number, default: 0 }, - total_bytes: { type: Number, default: 0 }, - free_bytes: { type: Number, default: 0 }, - usage_percent: { type: Number, default: 0 }, -}); - -const hostSchema = mongoose.Schema({ - os: { type: String, default: "" }, - platform: { type: String, default: "" }, - kernel_version: { type: String, default: "" }, -}); - -const errorSchema = mongoose.Schema({ - metric: { type: [String], default: [] }, - err: { type: String, default: "" }, -}); - -const captureSchema = mongoose.Schema({ - version: { type: String, default: "" }, - mode: { type: String, default: "" }, -}); - -const networkInterfaceSchema = mongoose.Schema({ - name: { type: String }, - bytes_sent: { type: Number, default: 0 }, - bytes_recv: { type: Number, default: 0 }, - packets_sent: { type: Number, default: 0 }, - packets_recv: { type: Number, default: 0 }, - err_in: { type: Number, default: 0 }, - err_out: { type: Number, default: 0 }, - drop_in: { type: Number, default: 0 }, - drop_out: { type: Number, default: 0 }, - fifo_in: { type: Number, default: 0 }, - fifo_out: { type: Number, default: 0 }, -}); - -const CheckSchema = new mongoose.Schema( - { - // Common fields - monitorId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Monitor", - immutable: true, - index: true, - }, - - teamId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Team", - immutable: true, - index: true, - }, - type: { - type: String, - enum: ["http", "ping", "pagespeed", "hardware", "docker", "port", "game"], - required: true, - index: true, - }, - - status: { - type: Boolean, - index: true, - }, - - responseTime: { - type: Number, - }, - - timings: { - type: Object, - default: {}, - }, - - statusCode: { - type: Number, - index: true, - }, - - message: { - type: String, - }, - - expiry: { - type: Date, - default: Date.now, - expires: 60 * 60 * 24 * 30, // 30 days - }, - - ack: { - type: Boolean, - default: false, - }, - - ackAt: { - type: Date, - }, - - // Hardware fields - cpu: { - type: cpuSchema, - default: () => ({}), - }, - memory: { - type: memorySchema, - default: () => ({}), - }, - disk: { - type: [diskSchema], - default: () => [], - }, - host: { - type: hostSchema, - default: () => ({}), - }, - - errors: { - type: [errorSchema], - default: () => [], - }, - - capture: { - type: captureSchema, - default: () => ({}), - }, - - net: { - type: [networkInterfaceSchema], - default: () => [], - }, - - // PageSpeed fields - accessibility: { - type: Number, - }, - bestPractices: { - type: Number, - }, - seo: { - type: Number, - }, - performance: { - type: Number, - }, - audits: { - type: Object, - }, - }, - { timestamps: true } -); - -CheckSchema.index({ updatedAt: 1 }); -CheckSchema.index({ monitorId: 1, updatedAt: 1 }); -CheckSchema.index({ monitorId: 1, updatedAt: -1 }); -CheckSchema.index({ teamId: 1, updatedAt: -1 }); - -export default mongoose.model("Check", CheckSchema); diff --git a/server/src/db/v1/models/MonitorStats.js b/server/src/db/v1/models/MonitorStats.js deleted file mode 100755 index c081c8a4f..000000000 --- a/server/src/db/v1/models/MonitorStats.js +++ /dev/null @@ -1,49 +0,0 @@ -import mongoose from "mongoose"; - -const MonitorStatsSchema = new mongoose.Schema( - { - monitorId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Monitor", - immutable: true, - index: true, - }, - avgResponseTime: { - type: Number, - default: 0, - }, - totalChecks: { - type: Number, - default: 0, - }, - totalUpChecks: { - type: Number, - default: 0, - }, - totalDownChecks: { - type: Number, - default: 0, - }, - uptimePercentage: { - type: Number, - default: 0, - }, - lastCheckTimestamp: { - type: Number, - default: 0, - }, - lastResponseTime: { - type: Number, - default: 0, - }, - timeOfLastFailure: { - type: Number, - default: 0, - }, - }, - { timestamps: true } -); - -const MonitorStats = mongoose.model("MonitorStats", MonitorStatsSchema); - -export default MonitorStats; diff --git a/server/src/db/v1/modules/monitorModuleQueries.js b/server/src/db/v1/modules/monitorModuleQueries.js deleted file mode 100755 index 32aed1529..000000000 --- a/server/src/db/v1/modules/monitorModuleQueries.js +++ /dev/null @@ -1,1214 +0,0 @@ -import { ObjectId } from "mongodb"; - -const buildUptimeDetailsPipeline = (monitorId, dates, dateString) => { - return [ - { - $match: { - monitorId: new ObjectId(monitorId), - updatedAt: { $gte: dates.start, $lte: dates.end }, - }, - }, - { - $sort: { - updatedAt: 1, - }, - }, - { - $facet: { - // For the response time chart, should return checks for date window - // Grouped by: {day: hour}, {week: day}, {month: day} - uptimePercentage: [ - { - $group: { - _id: null, - upChecks: { - $sum: { $cond: [{ $eq: ["$status", true] }, 1, 0] }, - }, - totalChecks: { $sum: 1 }, - }, - }, - { - $project: { - _id: 0, - percentage: { - $cond: [{ $eq: ["$totalChecks", 0] }, 0, { $divide: ["$upChecks", "$totalChecks"] }], - }, - }, - }, - ], - groupedAvgResponseTime: [ - { - $group: { - _id: null, - avgResponseTime: { - $avg: "$responseTime", - }, - }, - }, - ], - groupedChecks: [ - { - $group: { - _id: { - $dateToString: { - format: dateString, - date: "$createdAt", - }, - }, - avgResponseTime: { - $avg: "$responseTime", - }, - totalChecks: { - $sum: 1, - }, - }, - }, - { - $sort: { - _id: 1, - }, - }, - ], - // Up checks grouped by: {day: hour}, {week: day}, {month: day} - groupedUpChecks: [ - { - $match: { - status: true, - }, - }, - { - $group: { - _id: { - $dateToString: { - format: dateString, - date: "$createdAt", - }, - }, - totalChecks: { - $sum: 1, - }, - avgResponseTime: { - $avg: "$responseTime", - }, - }, - }, - { - $sort: { _id: 1 }, - }, - ], - // Down checks grouped by: {day: hour}, {week: day}, {month: day} for the date window - groupedDownChecks: [ - { - $match: { - status: false, - }, - }, - { - $group: { - _id: { - $dateToString: { - format: dateString, - date: "$createdAt", - }, - }, - totalChecks: { - $sum: 1, - }, - avgResponseTime: { - $avg: "$responseTime", - }, - }, - }, - { - $sort: { _id: 1 }, - }, - ], - }, - }, - { - $lookup: { - from: "monitors", - let: { monitor_id: { $toObjectId: monitorId } }, - pipeline: [ - { - $match: { - $expr: { $eq: ["$_id", "$$monitor_id"] }, - }, - }, - { - $project: { - _id: 1, - teamId: 1, - name: 1, - status: 1, - interval: 1, - type: 1, - url: 1, - isActive: 1, - notifications: 1, - }, - }, - ], - as: "monitor", - }, - }, - { - $project: { - groupedAvgResponseTime: { - $arrayElemAt: ["$groupedAvgResponseTime.avgResponseTime", 0], - }, - - groupedChecks: "$groupedChecks", - groupedUpChecks: "$groupedUpChecks", - groupedDownChecks: "$groupedDownChecks", - groupedUptimePercentage: { $arrayElemAt: ["$uptimePercentage.percentage", 0] }, - monitor: { $arrayElemAt: ["$monitor", 0] }, - }, - }, - ]; -}; - -const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { - return [ - { - $match: { - monitorId: monitor._id, - type: "hardware", - createdAt: { $gte: dates.start, $lte: dates.end }, - }, - }, - { - $sort: { - createdAt: 1, - }, - }, - { - $facet: { - aggregateData: [ - { - $group: { - _id: null, - latestCheck: { - $last: "$$ROOT", - }, - totalChecks: { - $sum: 1, - }, - }, - }, - ], - upChecks: [ - { - $match: { - status: true, - }, - }, - { - $group: { - _id: null, - totalChecks: { - $sum: 1, - }, - }, - }, - ], - checks: [ - { - $limit: 1, - }, - { - $project: { - diskCount: { - $size: { $ifNull: ["$disk", []] }, - }, - netCount: { $size: { $ifNull: ["$net", []] } }, - }, - }, - { - $lookup: { - from: "checks", - let: { - diskCount: "$diskCount", - netCount: "$netCount", - }, - pipeline: [ - { - $match: { - $expr: { - $and: [{ $eq: ["$monitorId", monitor._id] }, { $gte: ["$createdAt", dates.start] }, { $lte: ["$createdAt", dates.end] }], - }, - }, - }, - { - $group: { - _id: { - $dateToString: { - format: dateString, - date: "$createdAt", - }, - }, - avgCpuUsage: { - $avg: "$cpu.usage_percent", - }, - avgMemoryUsage: { - $avg: "$memory.usage_percent", - }, - avgTemperatures: { - $push: { - $ifNull: ["$cpu.temperature", [0]], - }, - }, - disks: { - $push: "$disk", - }, - net: { - $push: "$net", - }, - updatedAts: { - $push: "$updatedAt", - }, - }, - }, - { - $project: { - _id: 1, - avgCpuUsage: 1, - avgMemoryUsage: 1, - avgTemperature: { - $map: { - input: { - $range: [ - 0, - { - $size: { - // Handle null temperatures array - $ifNull: [ - { $arrayElemAt: ["$avgTemperatures", 0] }, - [0], // Default to single-element array if null - ], - }, - }, - ], - }, - as: "index", - in: { - $avg: { - $map: { - input: "$avgTemperatures", - as: "tempArray", - in: { - $ifNull: [ - { $arrayElemAt: ["$$tempArray", "$$index"] }, - 0, // Default to 0 if element is null - ], - }, - }, - }, - }, - }, - }, - disks: { - $map: { - input: { - $range: [0, "$$diskCount"], - }, - as: "diskIndex", - in: { - name: { - $concat: [ - "disk", - { - $toString: "$$diskIndex", - }, - ], - }, - readSpeed: { - $avg: { - $map: { - input: "$disks", - as: "diskArray", - in: { - $arrayElemAt: ["$$diskArray.read_speed_bytes", "$$diskIndex"], - }, - }, - }, - }, - writeSpeed: { - $avg: { - $map: { - input: "$disks", - as: "diskArray", - in: { - $arrayElemAt: ["$$diskArray.write_speed_bytes", "$$diskIndex"], - }, - }, - }, - }, - totalBytes: { - $avg: { - $map: { - input: "$disks", - as: "diskArray", - in: { - $arrayElemAt: ["$$diskArray.total_bytes", "$$diskIndex"], - }, - }, - }, - }, - freeBytes: { - $avg: { - $map: { - input: "$disks", - as: "diskArray", - in: { - $arrayElemAt: ["$$diskArray.free_bytes", "$$diskIndex"], - }, - }, - }, - }, - usagePercent: { - $avg: { - $map: { - input: "$disks", - as: "diskArray", - in: { - $arrayElemAt: ["$$diskArray.usage_percent", "$$diskIndex"], - }, - }, - }, - }, - }, - }, - }, - net: { - $map: { - input: { $range: [0, { $size: { $ifNull: [{ $arrayElemAt: ["$net", 0] }, []] } }] }, - as: "netIndex", - in: { - name: { - $arrayElemAt: [ - { - $map: { - input: { $arrayElemAt: ["$net", 0] }, - as: "iface", - in: "$$iface.name", - }, - }, - "$$netIndex", - ], - }, - bytesSentPerSecond: { - $let: { - vars: { - first: { - $arrayElemAt: [ - { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.bytes_sent" } }, - "$$netIndex", - ], - }, - last: { - $arrayElemAt: [ - { - $map: { - input: { - $arrayElemAt: [{ $ifNull: ["$net", []] }, { $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1] }], - }, - as: "iface", - in: "$$iface.bytes_sent", - }, - }, - "$$netIndex", - ], - }, - tFirst: { $arrayElemAt: ["$updatedAts", 0] }, - tLast: { - $arrayElemAt: [{ $ifNull: ["$updatedAts", []] }, { $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1] }], - }, - }, - in: { - $cond: [ - { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, - { - $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], - }, - 0, - ], - }, - }, - }, - deltaBytesRecv: { - $let: { - vars: { - first: { - $arrayElemAt: [ - { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.bytes_recv" } }, - "$$netIndex", - ], - }, - last: { - $arrayElemAt: [ - { - $map: { - input: { - $arrayElemAt: [{ $ifNull: ["$net", []] }, { $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1] }], - }, - as: "iface", - in: "$$iface.bytes_recv", - }, - }, - "$$netIndex", - ], - }, - tFirst: { $arrayElemAt: ["$updatedAts", 0] }, - tLast: { - $arrayElemAt: [{ $ifNull: ["$updatedAts", []] }, { $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1] }], - }, - }, - in: { - $cond: [ - { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, - { - $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], - }, - 0, - ], - }, - }, - }, - deltaPacketsSent: { - $let: { - vars: { - first: { - $arrayElemAt: [ - { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.packets_sent" } }, - "$$netIndex", - ], - }, - last: { - $arrayElemAt: [ - { - $map: { - input: { - $arrayElemAt: [{ $ifNull: ["$net", []] }, { $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1] }], - }, - as: "iface", - in: "$$iface.packets_sent", - }, - }, - "$$netIndex", - ], - }, - tFirst: { $arrayElemAt: ["$updatedAts", 0] }, - tLast: { - $arrayElemAt: [{ $ifNull: ["$updatedAts", []] }, { $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1] }], - }, - }, - in: { - $cond: [ - { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, - { - $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], - }, - 0, - ], - }, - }, - }, - deltaPacketsRecv: { - $let: { - vars: { - first: { - $arrayElemAt: [ - { - $map: { - input: { $arrayElemAt: ["$net", 0] }, - as: "iface", - in: "$$iface.packets_recv", - }, - }, - "$$netIndex", - ], - }, - last: { - $arrayElemAt: [ - { - $map: { - input: { - $arrayElemAt: [ - { $ifNull: ["$net", []] }, - { - $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1], - }, - ], - }, - as: "iface", - in: "$$iface.packets_recv", - }, - }, - "$$netIndex", - ], - }, - tFirst: { $arrayElemAt: ["$updatedAts", 0] }, - tLast: { - $arrayElemAt: [ - { $ifNull: ["$updatedAts", []] }, - { - $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1], - }, - ], - }, - }, - in: { - $cond: [ - { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, - { - $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], - }, - 0, - ], - }, - }, - }, - deltaErrIn: { - $let: { - vars: { - first: { - $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.err_in" } }, "$$netIndex"], - }, - last: { - $arrayElemAt: [ - { - $map: { - input: { - $arrayElemAt: [{ $ifNull: ["$net", []] }, { $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1] }], - }, - as: "iface", - in: "$$iface.err_in", - }, - }, - "$$netIndex", - ], - }, - tFirst: { $arrayElemAt: ["$updatedAts", 0] }, - tLast: { - $arrayElemAt: [{ $ifNull: ["$updatedAts", []] }, { $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1] }], - }, - }, - in: { - $cond: [ - { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, - { - $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], - }, - 0, - ], - }, - }, - }, - deltaErrOut: { - $let: { - vars: { - first: { - $arrayElemAt: [ - { - $map: { - input: { $arrayElemAt: ["$net", 0] }, - as: "iface", - in: "$$iface.err_out", - }, - }, - "$$netIndex", - ], - }, - last: { - $arrayElemAt: [ - { - $map: { - input: { - $arrayElemAt: [ - { $ifNull: ["$net", []] }, - { - $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1], - }, - ], - }, - as: "iface", - in: "$$iface.err_out", - }, - }, - "$$netIndex", - ], - }, - tFirst: { $arrayElemAt: ["$updatedAts", 0] }, - tLast: { - $arrayElemAt: [ - { $ifNull: ["$updatedAts", []] }, - { - $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1], - }, - ], - }, - }, - in: { - $cond: [ - { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, - { - $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], - }, - 0, - ], - }, - }, - }, - deltaDropIn: { - $let: { - vars: { - first: { - $arrayElemAt: [ - { - $map: { - input: { $arrayElemAt: ["$net", 0] }, - as: "iface", - in: "$$iface.drop_in", - }, - }, - "$$netIndex", - ], - }, - last: { - $arrayElemAt: [ - { - $map: { - input: { - $arrayElemAt: [ - { $ifNull: ["$net", []] }, - { - $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1], - }, - ], - }, - as: "iface", - in: "$$iface.drop_in", - }, - }, - "$$netIndex", - ], - }, - tFirst: { $arrayElemAt: ["$updatedAts", 0] }, - tLast: { - $arrayElemAt: [ - { $ifNull: ["$updatedAts", []] }, - { - $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1], - }, - ], - }, - }, - in: { - $cond: [ - { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, - { - $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], - }, - 0, - ], - }, - }, - }, - deltaDropOut: { - $let: { - vars: { - first: { - $arrayElemAt: [ - { - $map: { - input: { $arrayElemAt: ["$net", 0] }, - as: "iface", - in: "$$iface.drop_out", - }, - }, - "$$netIndex", - ], - }, - last: { - $arrayElemAt: [ - { - $map: { - input: { - $arrayElemAt: [ - { $ifNull: ["$net", []] }, - { - $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1], - }, - ], - }, - as: "iface", - in: "$$iface.drop_out", - }, - }, - "$$netIndex", - ], - }, - tFirst: { $arrayElemAt: ["$updatedAts", 0] }, - tLast: { - $arrayElemAt: [ - { $ifNull: ["$updatedAts", []] }, - { - $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1], - }, - ], - }, - }, - in: { - $cond: [ - { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, - { - $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], - }, - 0, - ], - }, - }, - }, - }, - }, - }, - firstUpdatedAt: 1, - lastUpdatedAt: 1, - }, - }, - ], - as: "hourlyStats", - }, - }, - { - $unwind: "$hourlyStats", - }, - { - $replaceRoot: { - newRoot: "$hourlyStats", - }, - }, - ], - }, - }, - { $unwind: "$checks" }, - { $sort: { "checks._id": 1 } }, - { - $group: { - _id: "$_id", - checks: { $push: "$checks" }, - aggregateData: { $first: "$aggregateData" }, - upChecks: { $first: "$upChecks" }, - }, - }, - { - $project: { - aggregateData: { - $arrayElemAt: ["$aggregateData", 0], - }, - upChecks: { - $arrayElemAt: ["$upChecks", 0], - }, - checks: 1, - }, - }, - ]; -}; - -const buildMonitorStatsPipeline = (monitor) => { - return [ - { - $match: { - monitorId: monitor._id, - }, - }, - { - $project: { - avgResponseTime: 1, - uptimePercentage: 1, - totalChecks: 1, - timeSinceLastCheck: { - $subtract: [Date.now(), "$lastCheckTimestamp"], - }, - lastCheckTimestamp: 1, - uptBurnt: { $toString: "$uptBurnt" }, - }, - }, - ]; -}; - -const buildMonitorSummaryByTeamIdPipeline = ({ matchStage }) => { - return [ - { $match: matchStage }, - { - $group: { - _id: null, - totalMonitors: { $sum: 1 }, - upMonitors: { - $sum: { - $cond: [{ $eq: ["$status", true] }, 1, 0], - }, - }, - downMonitors: { - $sum: { - $cond: [{ $eq: ["$status", false] }, 1, 0], - }, - }, - pausedMonitors: { - $sum: { - $cond: [{ $eq: ["$isActive", false] }, 1, 0], - }, - }, - }, - }, - { - $project: { - _id: 0, - }, - }, - ]; -}; - -const buildMonitorsByTeamIdPipeline = ({ matchStage, field, order }) => { - const sort = { [field]: order === "asc" ? 1 : -1 }; - - return [ - { $match: matchStage }, - { $sort: sort }, - { - $project: { - _id: 1, - name: 1, - type: 1, - port: 1, - }, - }, - ]; -}; - -const buildMonitorsAndSummaryByTeamIdPipeline = ({ matchStage }) => { - return [ - { $match: matchStage }, - { - $facet: { - summary: [ - { - $group: { - _id: null, - totalMonitors: { $sum: 1 }, - upMonitors: { - $sum: { - $cond: [{ $eq: ["$status", true] }, 1, 0], - }, - }, - downMonitors: { - $sum: { - $cond: [{ $eq: ["$status", false] }, 1, 0], - }, - }, - pausedMonitors: { - $sum: { - $cond: [{ $eq: ["$isActive", false] }, 1, 0], - }, - }, - }, - }, - { - $project: { - _id: 0, - }, - }, - ], - monitors: [ - { $sort: { name: 1 } }, - { - $project: { - _id: 1, - name: 1, - type: 1, - }, - }, - ], - }, - }, - { - $project: { - summary: { $arrayElemAt: ["$summary", 0] }, - monitors: 1, - }, - }, - ]; -}; - -const buildMonitorsWithChecksByTeamIdPipeline = ({ matchStage, filter, page, rowsPerPage, field, order, limit, type }) => { - const skip = page && rowsPerPage ? page * rowsPerPage : 0; - const sort = { [field]: order === "asc" ? 1 : -1 }; - const limitStage = rowsPerPage ? [{ $limit: rowsPerPage }] : []; - - // Match name - if (typeof filter !== "undefined" && field === "name") { - matchStage.$or = [{ name: { $regex: filter, $options: "i" } }, { url: { $regex: filter, $options: "i" } }]; - } - - // Match isActive - if (typeof filter !== "undefined" && field === "isActive") { - matchStage.isActive = filter === "true" ? true : false; - } - - if (typeof filter !== "undefined" && field === "status") { - matchStage.status = filter === "true" ? true : false; - } - - // Match type - if (typeof filter !== "undefined" && field === "type") { - matchStage.type = filter; - } - - const monitorsPipeline = [ - { $sort: sort }, - { $skip: skip }, - ...limitStage, - { - $project: { - _id: 1, - name: 1, - description: 1, - type: 1, - url: 1, - isActive: 1, - createdAt: 1, - updatedAt: 1, - uptimePercentage: 1, - status: 1, - }, - }, - ]; - - // Add checks - if (limit) { - const checksCollection = "checks"; - monitorsPipeline.push({ - $lookup: { - from: checksCollection, - let: { monitorId: "$_id" }, - pipeline: [ - { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, - }, - }, - { $sort: { updatedAt: -1 } }, - { $limit: limit }, - { - $project: { - _id: 1, - status: 1, - responseTime: 1, - statusCode: 1, - createdAt: 1, - updatedAt: 1, - originalResponseTime: 1, - }, - }, - ], - as: "checks", - }, - }); - } - - const pipeline = [ - { $match: matchStage }, - { - $facet: { - count: [{ $count: "monitorsCount" }], - monitors: monitorsPipeline, - }, - }, - { - $project: { - count: { $arrayElemAt: ["$count", 0] }, - monitors: 1, - }, - }, - ]; - return pipeline; -}; - -const buildFilteredMonitorsByTeamIdPipeline = ({ matchStage, filter, page, rowsPerPage, field, order, limit, type }) => { - const skip = page && rowsPerPage ? page * rowsPerPage : 0; - const sort = { [field]: order === "asc" ? 1 : -1 }; - const limitStage = rowsPerPage ? [{ $limit: rowsPerPage }] : []; - - if (typeof filter !== "undefined" && field === "name") { - matchStage.$or = [{ name: { $regex: filter, $options: "i" } }, { url: { $regex: filter, $options: "i" } }]; - } - - if (typeof filter !== "undefined" && field === "status") { - matchStage.status = filter === "true"; - } - - const pipeline = [{ $match: matchStage }, { $sort: sort }, { $skip: skip }, ...limitStage]; - - // Add checks - if (limit) { - const checksCollection = "checks"; - - pipeline.push({ - $lookup: { - from: checksCollection, - let: { monitorId: "$_id" }, - pipeline: [ - { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, - }, - }, - { $sort: { createdAt: -1 } }, - { $limit: limit }, - ], - as: "checks", - }, - }); - } - - return pipeline; -}; - -const buildGetMonitorsByTeamIdPipeline = (req) => { - let { limit, type, page, rowsPerPage, filter, field, order } = req.query; - - limit = parseInt(limit); - page = parseInt(page); - rowsPerPage = parseInt(rowsPerPage); - if (field === undefined) { - field = "name"; - order = "asc"; - } - // Build the match stage - const matchStage = { teamId: new ObjectId(req.params.teamId) }; - if (type !== undefined) { - matchStage.type = Array.isArray(type) ? { $in: type } : type; - } - - const skip = page && rowsPerPage ? page * rowsPerPage : 0; - const sort = { [field]: order === "asc" ? 1 : -1 }; - return [ - { $match: matchStage }, - { - $facet: { - summary: [ - { - $group: { - _id: null, - totalMonitors: { $sum: 1 }, - upMonitors: { - $sum: { - $cond: [{ $eq: ["$status", true] }, 1, 0], - }, - }, - downMonitors: { - $sum: { - $cond: [{ $eq: ["$status", false] }, 1, 0], - }, - }, - pausedMonitors: { - $sum: { - $cond: [{ $eq: ["$isActive", false] }, 1, 0], - }, - }, - }, - }, - { - $project: { - _id: 0, - }, - }, - ], - monitors: [ - { $sort: sort }, - { - $project: { - _id: 1, - name: 1, - }, - }, - ], - filteredMonitors: [ - ...(filter !== undefined - ? [ - { - $match: { - $or: [{ name: { $regex: filter, $options: "i" } }, { url: { $regex: filter, $options: "i" } }], - }, - }, - ] - : []), - { $sort: sort }, - { $skip: skip }, - ...(rowsPerPage ? [{ $limit: rowsPerPage }] : []), - ...(limit - ? [ - { - $lookup: { - from: "checks", - let: { monitorId: "$_id" }, - pipeline: [ - { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, - }, - }, - { $sort: { createdAt: -1 } }, - ...(limit ? [{ $limit: limit }] : []), - ], - as: "standardchecks", - }, - }, - ] - : []), - - { - $addFields: { - checks: { - $switch: { - branches: [ - { - case: { $in: ["$type", ["http", "ping", "docker", "port", "game"]] }, - then: "$standardchecks", - }, - ], - default: [], - }, - }, - }, - }, - { - $project: { - standardchecks: 0, - }, - }, - ], - }, - }, - { - $project: { - summary: { $arrayElemAt: ["$summary", 0] }, - filteredMonitors: 1, - monitors: 1, - }, - }, - ]; -}; - -export { - buildUptimeDetailsPipeline, - buildHardwareDetailsPipeline, - buildMonitorStatsPipeline, - buildGetMonitorsByTeamIdPipeline, - buildMonitorSummaryByTeamIdPipeline, - buildMonitorsByTeamIdPipeline, - buildMonitorsAndSummaryByTeamIdPipeline, - buildMonitorsWithChecksByTeamIdPipeline, - buildFilteredMonitorsByTeamIdPipeline, -}; diff --git a/server/src/index.js b/server/src/index.js index e8b756d0e..28dc5e8fd 100755 --- a/server/src/index.js +++ b/server/src/index.js @@ -5,10 +5,11 @@ import { initShutdownListener } from "./shutdown.js"; import { fileURLToPath } from "url"; import path from "path"; import fs from "fs"; +import { runMigrations } from "./db/migration/index.js"; import Logger from "./utils/logger.js"; -import SettingsService from "./service/v1/system/settingsService.js"; -import AppSettings from "./db/v1/models/AppSettings.js"; +import SettingsService from "./service/system/settingsService.js"; +import AppSettings from "./db/models/AppSettings.js"; const SERVICE_NAME = "Server"; let logger; @@ -30,6 +31,8 @@ const startApp = async () => { // Initialize services const services = await initializeServices({ logger, envSettings, settingsService }); + await runMigrations(logger); + // Initialize controllers const controllers = initializeControllers(services); diff --git a/server/src/middleware/v1/handleErrors.js b/server/src/middleware/v1/handleErrors.js index 43671fbe2..0a30b0420 100755 --- a/server/src/middleware/v1/handleErrors.js +++ b/server/src/middleware/v1/handleErrors.js @@ -1,6 +1,6 @@ import { logger } from "../../utils/logger.js"; -import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; -import StringService from "../../service/v1/system/stringService.js"; +import ServiceRegistry from "../../service/system/serviceRegistry.js"; +import StringService from "../../service/system/stringService.js"; const handleErrors = (error, req, res, next) => { const status = error.status || 500; diff --git a/server/src/middleware/v1/isAllowed.js b/server/src/middleware/v1/isAllowed.js index cc240f531..1d0de42b4 100755 --- a/server/src/middleware/v1/isAllowed.js +++ b/server/src/middleware/v1/isAllowed.js @@ -1,9 +1,9 @@ import jwt from "jsonwebtoken"; const TOKEN_PREFIX = "Bearer "; const SERVICE_NAME = "allowedRoles"; -import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; -import StringService from "../../service/v1/system/stringService.js"; -import SettingsService from "../../service/v1/system/settingsService.js"; +import ServiceRegistry from "../../service/system/serviceRegistry.js"; +import StringService from "../../service/system/stringService.js"; +import SettingsService from "../../service/system/settingsService.js"; const isAllowed = (allowedRoles) => { return (req, res, next) => { diff --git a/server/src/middleware/v1/verifyJWT.js b/server/src/middleware/v1/verifyJWT.js index b57391dfd..cf983a6c8 100755 --- a/server/src/middleware/v1/verifyJWT.js +++ b/server/src/middleware/v1/verifyJWT.js @@ -1,7 +1,7 @@ import jwt from "jsonwebtoken"; -import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; -import SettingsService from "../../service/v1/system/settingsService.js"; -import StringService from "../../service/v1/system/stringService.js"; +import ServiceRegistry from "../../service/system/serviceRegistry.js"; +import SettingsService from "../../service/system/settingsService.js"; +import StringService from "../../service/system/stringService.js"; const SERVICE_NAME = "verifyJWT"; const TOKEN_PREFIX = "Bearer "; diff --git a/server/src/middleware/v1/verifyOwnership.js b/server/src/middleware/v1/verifyOwnership.js index 741fb6436..6fcc84f60 100755 --- a/server/src/middleware/v1/verifyOwnership.js +++ b/server/src/middleware/v1/verifyOwnership.js @@ -1,6 +1,6 @@ import { logger } from "../../utils/logger.js"; -import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; -import StringService from "../../service/v1/system/stringService.js"; +import ServiceRegistry from "../../service/system/serviceRegistry.js"; +import StringService from "../../service/system/stringService.js"; import { ObjectId } from "mongodb"; const SERVICE_NAME = "verifyOwnership"; diff --git a/server/src/repositories/checks/IChecksRepository.ts b/server/src/repositories/checks/IChecksRepository.ts new file mode 100644 index 000000000..cedea8d86 --- /dev/null +++ b/server/src/repositories/checks/IChecksRepository.ts @@ -0,0 +1,65 @@ +import type { Check, CheckAudits, MonitorType } from "@/types/index.js"; +import type { LatestChecksMap } from "@/repositories/checks/MongoChecksRepistory.js"; + +export interface PageSpeedChecksResult { + monitorType: "pagespeed"; + checks: Check[]; +} + +export interface HardwareChecksResult { + monitorType: "hardware"; + aggregateData: { + latestCheck: Check | null; + totalChecks: number; + }; + upChecks: { + totalChecks: number; + }; + checks: Array<{ + _id: string; + avgCpuUsage: number; + avgMemoryUsage: number; + avgTemperature: number[]; + disks: Array<{ + name: string; + readSpeed: number; + writeSpeed: number; + totalBytes: number; + freeBytes: number; + usagePercent: number; + }>; + net: Array<{ + name: string; + bytesSentPerSecond: number; + deltaBytesRecv: number; + deltaPacketsSent: number; + deltaPacketsRecv: number; + deltaErrIn: number; + deltaErrOut: number; + deltaDropIn: number; + deltaDropOut: number; + deltaFifoIn: number; + deltaFifoOut: number; + }>; + }>; +} + +export interface UptimeChecksResult { + monitorType: Exclude; + groupedChecks: Array<{ _id: string; avgResponseTime: number; totalChecks: number }>; + groupedUpChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>; + groupedDownChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>; + uptimePercentage: number; + avgResponseTime: number; +} + +export interface IChecksRepository { + findLatestChecksByMonitorIds(monitorIds: string[], options?: { limitPerMonitor?: number }): Promise; + findDateRangeChecksByMonitor( + monitorId: string, + startDate: Date, + endDate: Date, + dateString: string, + options?: { type?: MonitorType } + ): Promise; +} diff --git a/server/src/repositories/checks/MongoChecksRepistory.ts b/server/src/repositories/checks/MongoChecksRepistory.ts new file mode 100644 index 000000000..7ede5587e --- /dev/null +++ b/server/src/repositories/checks/MongoChecksRepistory.ts @@ -0,0 +1,395 @@ +import { IChecksRepository } from "@/repositories/index.js"; +import type { + Check, + CheckAudits, + CheckCaptureInfo, + CheckCpuInfo, + CheckDiskInfo, + CheckErrorInfo, + CheckHostInfo, + CheckMemoryInfo, + CheckMetadata, + CheckNetworkInterfaceInfo, + CheckTimings, + MonitorType, +} from "@/types/index.js"; +import { CheckModel, type CheckDocument } from "@/db/models/index.js"; +import mongoose from "mongoose"; +import { + getAggregateData as getHardwareAggregateData, + getHardwareStats, + getUpChecks as getHardwareUpChecks, +} from "@/db/modules/monitorModuleQueries.js"; + +export type LatestChecksMap = Record; + +class MongoChecksRepistory implements IChecksRepository { + private toEntity = (doc: CheckDocument): Check => { + const toStringId = (value: mongoose.Types.ObjectId | string | undefined | null): string => { + if (!value) { + return ""; + } + return value instanceof mongoose.Types.ObjectId ? value.toString() : String(value); + }; + + const toDateString = (value?: Date | string | null): string => { + if (!value) { + return new Date(0).toISOString(); + } + return value instanceof Date ? value.toISOString() : new Date(value).toISOString(); + }; + + const toOptionalDateString = (value?: Date | string | null): string | undefined => { + if (!value) { + return undefined; + } + return toDateString(value); + }; + + const mapTimings = (timings?: CheckTimings): CheckTimings => { + const phases = timings?.phases ?? { + wait: 0, + dns: 0, + tcp: 0, + tls: 0, + request: 0, + firstByte: 0, + download: 0, + total: 0, + }; + + return { + start: timings?.start ?? 0, + socket: timings?.socket ?? 0, + lookup: timings?.lookup ?? 0, + connect: timings?.connect ?? 0, + secureConnect: timings?.secureConnect ?? 0, + upload: timings?.upload ?? 0, + response: timings?.response ?? 0, + end: timings?.end ?? 0, + phases, + }; + }; + + const mapCpu = (cpu?: CheckCpuInfo): CheckCpuInfo => ({ + physical_core: cpu?.physical_core ?? 0, + logical_core: cpu?.logical_core ?? 0, + frequency: cpu?.frequency ?? 0, + temperature: cpu?.temperature ?? [], + free_percent: cpu?.free_percent ?? 0, + usage_percent: cpu?.usage_percent ?? 0, + }); + + const mapMemory = (memory?: CheckMemoryInfo): CheckMemoryInfo => ({ + total_bytes: memory?.total_bytes ?? 0, + available_bytes: memory?.available_bytes ?? 0, + used_bytes: memory?.used_bytes ?? 0, + usage_percent: memory?.usage_percent ?? 0, + }); + + const mapHost = (host?: CheckHostInfo): CheckHostInfo => ({ + os: host?.os ?? "", + platform: host?.platform ?? "", + kernel_version: host?.kernel_version ?? "", + }); + + const mapCapture = (capture?: CheckCaptureInfo): CheckCaptureInfo => ({ + version: capture?.version ?? "", + mode: capture?.mode ?? "", + }); + + const mapDisks = (disks?: CheckDiskInfo[]): CheckDiskInfo[] => + (disks ?? []).map((disk) => ({ + device: disk?.device ?? "", + mountpoint: disk?.mountpoint ?? "", + read_speed_bytes: disk?.read_speed_bytes ?? 0, + write_speed_bytes: disk?.write_speed_bytes ?? 0, + total_bytes: disk?.total_bytes ?? 0, + free_bytes: disk?.free_bytes ?? 0, + usage_percent: disk?.usage_percent ?? 0, + })); + + const mapErrors = (errors?: CheckErrorInfo[]): CheckErrorInfo[] => + (errors ?? []).map((error) => ({ + metric: error?.metric ?? [], + err: error?.err ?? "", + })); + + const mapNet = (net?: CheckNetworkInterfaceInfo[]): CheckNetworkInterfaceInfo[] => + (net ?? []).map((iface) => ({ + name: iface?.name ?? "", + bytes_sent: iface?.bytes_sent ?? 0, + bytes_recv: iface?.bytes_recv ?? 0, + packets_sent: iface?.packets_sent ?? 0, + packets_recv: iface?.packets_recv ?? 0, + err_in: iface?.err_in ?? 0, + err_out: iface?.err_out ?? 0, + drop_in: iface?.drop_in ?? 0, + drop_out: iface?.drop_out ?? 0, + fifo_in: iface?.fifo_in ?? 0, + fifo_out: iface?.fifo_out ?? 0, + })); + + const mapAudits = (audits?: CheckAudits): CheckAudits | undefined => { + if (!audits) { + return undefined; + } + return { + cls: audits.cls ?? 0, + si: audits.si ?? 0, + fcp: audits.fcp ?? 0, + lcp: audits.lcp ?? 0, + tbt: audits.tbt ?? 0, + }; + }; + + const mapMetadata = (metadata: CheckDocument["metadata"]): CheckMetadata => ({ + monitorId: toStringId(metadata.monitorId), + teamId: toStringId(metadata.teamId), + type: metadata.type, + }); + + return { + id: toStringId(doc._id), + metadata: mapMetadata(doc.metadata), + status: doc.status ?? false, + responseTime: doc.responseTime ?? 0, + timings: mapTimings(doc.timings), + statusCode: doc.statusCode ?? 0, + message: doc.message ?? "", + ack: doc.ack ?? false, + ackAt: toOptionalDateString(doc.ackAt), + expiry: toDateString(doc.expiry), + cpu: mapCpu(doc.cpu), + memory: mapMemory(doc.memory), + disk: mapDisks(doc.disk), + host: mapHost(doc.host), + errors: mapErrors(doc.errors), + capture: mapCapture(doc.capture), + net: mapNet(doc.net), + accessibility: doc.accessibility, + bestPractices: doc.bestPractices, + seo: doc.seo, + performance: doc.performance, + audits: mapAudits(doc.audits), + __v: doc.__v ?? 0, + createdAt: toDateString(doc.createdAt), + updatedAt: toDateString(doc.updatedAt), + }; + }; + + findLatestChecksByMonitorIds = async (monitorIds: string[], options?: { limitPerMonitor?: number }): Promise => { + if (monitorIds.length === 0) { + return {}; + } + const mongoIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id)); + const limitPerMonitor = options?.limitPerMonitor ?? 25; + const maxIntervalMs = Number(10 * 60 * 1000); + const bufferMs = Number(maxIntervalMs); + const lookbackMs = limitPerMonitor * maxIntervalMs + bufferMs; + + const cutoffDate = new Date(Date.now() - lookbackMs); + const checkGroups = await CheckModel.aggregate([ + { + $match: { + "metadata.monitorId": { $in: mongoIds }, + createdAt: { $gte: cutoffDate }, + }, + }, + { + $group: { + _id: "$metadata.monitorId", + latestChecks: { + $topN: { + n: limitPerMonitor, + sortBy: { createdAt: -1 }, + output: "$$ROOT", + }, + }, + }, + }, + ]); + + return checkGroups.reduce((acc, group) => { + const monitorId = group._id.toString(); + acc[monitorId] = (group.latestChecks ?? []).map((doc: CheckDocument) => this.toEntity(doc)); + return acc; + }, {}); + }; + + findDateRangeChecksByMonitor = async (monitorId: string, startDate: Date, endDate: Date, dateString: string, options?: { type?: MonitorType }) => { + const monitorObjectId = new mongoose.Types.ObjectId(monitorId); + if (options?.type === "hardware") { + return this.findHardwareDateRangeChecks(monitorObjectId, startDate, endDate, dateString); + } + if (options?.type === "pagespeed") { + return this.findPageSpeedDateRangeChecks(monitorObjectId, startDate, endDate); + } + return this.findUptimeDateRangeChecks(options?.type ?? "http", monitorObjectId, startDate, endDate, dateString); + }; + + private findUptimeDateRangeChecks = async ( + monitorType: Exclude, + monitorObjectId: mongoose.Types.ObjectId, + startDate: Date, + endDate: Date, + dateString: string + ) => { + const matchStage = { + "metadata.monitorId": monitorObjectId, + updatedAt: { $gte: startDate, $lte: endDate }, + }; + const [result] = await CheckModel.aggregate([ + { $match: matchStage }, + { $sort: { updatedAt: 1 } }, + { + $facet: { + uptimePercentage: [ + { + $group: { + _id: null, + upChecks: { $sum: { $cond: [{ $eq: ["$status", true] }, 1, 0] } }, + totalChecks: { $sum: 1 }, + }, + }, + { + $project: { + _id: 0, + percentage: { + $cond: [{ $eq: ["$totalChecks", 0] }, 0, { $divide: ["$upChecks", "$totalChecks"] }], + }, + }, + }, + ], + groupedAvgResponseTime: [ + { + $group: { + _id: null, + avgResponseTime: { $avg: "$responseTime" }, + }, + }, + ], + groupedChecks: [ + { + $group: { + _id: { + $dateToString: { format: dateString, date: "$createdAt" }, + }, + avgResponseTime: { $avg: "$responseTime" }, + totalChecks: { $sum: 1 }, + }, + }, + { $sort: { _id: 1 } }, + ], + groupedUpChecks: [ + { $match: { status: true } }, + { + $group: { + _id: { + $dateToString: { format: dateString, date: "$createdAt" }, + }, + totalChecks: { $sum: 1 }, + avgResponseTime: { $avg: "$responseTime" }, + }, + }, + { $sort: { _id: 1 } }, + ], + groupedDownChecks: [ + { $match: { status: false } }, + { + $group: { + _id: { + $dateToString: { format: dateString, date: "$createdAt" }, + }, + totalChecks: { $sum: 1 }, + avgResponseTime: { $avg: "$responseTime" }, + }, + }, + { $sort: { _id: 1 } }, + ], + }, + }, + ]); + + const uptimePercentage = result?.uptimePercentage?.[0]?.percentage ?? 0; + const avgResponseTime = result?.groupedAvgResponseTime?.[0]?.avgResponseTime ?? 0; + + return { + monitorType, + groupedChecks: result?.groupedChecks ?? [], + groupedUpChecks: result?.groupedUpChecks ?? [], + groupedDownChecks: result?.groupedDownChecks ?? [], + uptimePercentage, + avgResponseTime, + }; + }; + + private findHardwareDateRangeChecks = async (monitorObjectId: mongoose.Types.ObjectId, startDate: Date, endDate: Date, dateString: string) => { + const monitorId = monitorObjectId.toHexString(); + const dates = { start: startDate, end: endDate }; + const [aggregateDataDoc, upChecksDoc, hardwareMetrics] = await Promise.all([ + getHardwareAggregateData(monitorId, dates), + getHardwareUpChecks(monitorId, dates), + getHardwareStats(monitorId, dates, dateString), + ]); + + const aggregateData = { + latestCheck: aggregateDataDoc?.latestCheck ? this.toEntity(aggregateDataDoc.latestCheck as CheckDocument) : null, + totalChecks: aggregateDataDoc?.totalChecks ?? 0, + }; + + const upChecks = { + totalChecks: upChecksDoc?.totalChecks ?? 0, + }; + + const checks = (hardwareMetrics ?? []).map((metric) => ({ + _id: metric._id, + avgCpuUsage: metric.avgCpuUsage ?? 0, + avgMemoryUsage: metric.avgMemoryUsage ?? 0, + avgTemperature: metric.avgTemperature ?? [], + disks: (metric.disks ?? []).map((disk: { [key: string]: number | string | undefined }) => ({ + name: disk?.name ?? "", + readSpeed: disk?.readSpeed ?? 0, + writeSpeed: disk?.writeSpeed ?? 0, + totalBytes: disk?.totalBytes ?? 0, + freeBytes: disk?.freeBytes ?? 0, + usagePercent: disk?.usagePercent ?? 0, + })), + net: (metric.net ?? []).map((iface: { [key: string]: number | string | undefined }) => ({ + name: iface?.name ?? "", + bytesSentPerSecond: iface?.bytesSentPerSecond ?? 0, + deltaBytesRecv: iface?.deltaBytesRecv ?? 0, + deltaPacketsSent: iface?.deltaPacketsSent ?? 0, + deltaPacketsRecv: iface?.deltaPacketsRecv ?? 0, + deltaErrIn: iface?.deltaErrIn ?? 0, + deltaErrOut: iface?.deltaErrOut ?? 0, + deltaDropIn: iface?.deltaDropIn ?? 0, + deltaDropOut: iface?.deltaDropOut ?? 0, + deltaFifoIn: iface?.deltaFifoIn ?? 0, + deltaFifoOut: iface?.deltaFifoOut ?? 0, + })), + })); + + return { + monitorType: "hardware" as const, + aggregateData, + upChecks, + checks, + }; + }; + + private findPageSpeedDateRangeChecks = async (monitorObjectId: mongoose.Types.ObjectId, startDate: Date, endDate: Date) => { + const matchStage = { + "metadata.monitorId": monitorObjectId, + createdAt: { $gte: startDate, $lte: endDate }, + }; + + const checks = await CheckModel.find(matchStage).sort({ createdAt: -1 }).limit(25).lean(); + return { + monitorType: "pagespeed" as const, + checks: checks.map((doc) => this.toEntity(doc)), + }; + }; +} + +export default MongoChecksRepistory; diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts new file mode 100644 index 000000000..44d64f5bb --- /dev/null +++ b/server/src/repositories/index.ts @@ -0,0 +1,8 @@ +export * from "@/repositories/monitors/IMonitorsRepository.js"; +export { default as MongoMonitorsRepository } from "@/repositories/monitors/MongoMonitorsRepository.js"; + +export * from "@/repositories/checks/IChecksRepository.js"; +export { default as MongoChecksRepository } from "@/repositories/checks/MongoChecksRepistory.js"; + +export * from "@/repositories/monitor-stats/IMonitorStatsRepository.js"; +export { default as MongoMonitorStatsRepository } from "@/repositories/monitor-stats/MongoMonitorStatsRepository.js"; diff --git a/server/src/repositories/monitor-stats/IMonitorStatsRepository.ts b/server/src/repositories/monitor-stats/IMonitorStatsRepository.ts new file mode 100644 index 000000000..a0720619f --- /dev/null +++ b/server/src/repositories/monitor-stats/IMonitorStatsRepository.ts @@ -0,0 +1,9 @@ +import type { MonitorStats } from "@/types/index.js"; +export interface IMonitorStatsRepository { + // create + // single fetch + findByMonitorId(monitorId: string): Promise; + // update + // delete + // other +} diff --git a/server/src/repositories/monitor-stats/MongoMonitorStatsRepository.ts b/server/src/repositories/monitor-stats/MongoMonitorStatsRepository.ts new file mode 100644 index 000000000..d1d6a4825 --- /dev/null +++ b/server/src/repositories/monitor-stats/MongoMonitorStatsRepository.ts @@ -0,0 +1,44 @@ +import { type MonitorStatsDocument, MonitorStatsModel } from "@/db/models/index.js"; +import type { MonitorStats } from "@/types/index.js"; +import { IMonitorStatsRepository } from "@/repositories/index.js"; +import mongoose from "mongoose"; +import { AppError } from "@/utils/AppError.js"; +class MongoMonitorStatsRepository implements IMonitorStatsRepository { + private toEntity = (doc: MonitorStatsDocument): MonitorStats => { + const toStringId = (value: unknown): string => { + if (value instanceof mongoose.Types.ObjectId) { + return value.toString(); + } + return value?.toString() ?? ""; + }; + + const toDateString = (value: Date | string): string => { + return value instanceof Date ? value.toISOString() : value; + }; + + return { + id: toStringId(doc._id), + monitorId: toStringId(doc.monitorId), + avgResponseTime: doc.avgResponseTime, + totalChecks: doc.totalChecks, + totalUpChecks: doc.totalUpChecks, + totalDownChecks: doc.totalDownChecks, + uptimePercentage: doc.uptimePercentage, + lastCheckTimestamp: doc.lastCheckTimestamp, + lastResponseTime: doc.lastResponseTime, + timeOfLastFailure: doc.timeOfLastFailure, + createdAt: toDateString(doc.createdAt), + updatedAt: toDateString(doc.updatedAt), + }; + }; + + findByMonitorId = async (monitorId: string): Promise => { + const monitorStats = await MonitorStatsModel.findOne({ monitorId: new mongoose.Types.ObjectId(monitorId) }); + if (!monitorStats) { + throw new AppError({ message: "Monitor stats not found", status: 404 }); + } + return this.toEntity(monitorStats); + }; +} + +export default MongoMonitorStatsRepository; diff --git a/server/src/repositories/monitors/IMonitorsRepository.ts b/server/src/repositories/monitors/IMonitorsRepository.ts new file mode 100644 index 000000000..0da9c7279 --- /dev/null +++ b/server/src/repositories/monitors/IMonitorsRepository.ts @@ -0,0 +1,30 @@ +import { type MonitorType, type Monitor } from "@/types/index.js"; + +export interface TeamQueryConfig { + limit?: number; + type?: MonitorType | MonitorType[]; + page?: number; + rowsPerPage?: number; + filter?: string; + field?: string; + order?: "asc" | "desc"; +} + +export interface IMonitorsRepository { + // create + create(monitor: Monitor, teamId: string, userId: string): Promise; + createBulkMonitors(monitors: Monitor[]): Promise; + // single fetch + findById(monitorId: string, teamId?: string): Promise; + + // collection fetch + findAll(): Promise; + findByTeamId(teamId: string, config: TeamQueryConfig): Promise; + // update + update(monitorId: string, updates: Partial): Promise; + // delete + deleteByTeamId(teamId: string): Promise<{ monitors: Monitor[]; deletedCount: number }>; + + // counts + findMonitorCountByTeamIdAndType(teamId: string, config: TeamQueryConfig): Promise; +} diff --git a/server/src/repositories/monitors/MongoMonitorsRepository.ts b/server/src/repositories/monitors/MongoMonitorsRepository.ts new file mode 100644 index 000000000..c9af6668d --- /dev/null +++ b/server/src/repositories/monitors/MongoMonitorsRepository.ts @@ -0,0 +1,181 @@ +import { MonitorModel } from "@/db/models/index.js"; +import type { MonitorDocument } from "@/db/models/Monitor.js"; +import type { Monitor, MonitorType } from "@/types/monitor.js"; +import mongoose, { type FilterQuery } from "mongoose"; +import type { IMonitorsRepository, TeamQueryConfig } from "./IMonitorsRepository.js"; +import { AppError } from "@/utils/AppError.js"; + +class MongoMonitorsRepository implements IMonitorsRepository { + create = async (monitor: Monitor, teamId: string, userId: string) => { + const monitorModel = new MonitorModel({ ...monitor, teamId, userId }); + const saved = await monitorModel.save(); + return this.toEntity(saved); + }; + + createBulkMonitors = async (monitors: Monitor[]): Promise => { + if (!monitors.length) { + return []; + } + const payload = monitors.map((monitor) => ({ ...monitor, notifications: undefined })); + const inserted = await MonitorModel.insertMany(payload, { ordered: false }); + return this.mapDocuments(inserted); + }; + + findById = async (monitorId: string, teamId?: string): Promise => { + const match: { _id: string; teamId?: string } = { _id: monitorId }; + if (teamId) { + match.teamId = teamId; + } + const monitor = await MonitorModel.findOne(match); + if (!monitor) { + if (monitor === null || monitor === undefined) { + throw new AppError({ + message: `Monitor with ID ${monitorId} not found.`, + status: 404, + }); + } + } + return this.toEntity(monitor); + }; + + findAll = async (): Promise => { + const monitors = await MonitorModel.find(); + return this.mapDocuments(monitors); + }; + + findByTeamId = async (teamId: string, config: TeamQueryConfig): Promise => { + const { page = 0, rowsPerPage = 25, filter, field = "createdAt", order = "desc", type, limit } = config ?? {}; + + const query: Record = { + teamId: new mongoose.Types.ObjectId(teamId), + }; + + if (type !== undefined) { + query.type = Array.isArray(type) ? { $in: type } : type; + } + + if (filter !== undefined) { + switch (field) { + case "name": + query.$or = [{ name: { $regex: filter, $options: "i" } }, { url: { $regex: filter, $options: "i" } }]; + break; + case "isActive": + query.isActive = filter === "true"; + break; + case "status": + query.status = filter === "true"; + break; + case "type": + query.type = filter; + break; + default: + break; + } + } + + const sort = { [field]: order === "asc" ? 1 : -1 } as const; + const skip = Math.max(page, 0) * rowsPerPage; + const limitValue = limit ?? rowsPerPage; + + const documents = await MonitorModel.find(query).sort(sort).skip(skip).limit(limitValue).exec(); + + return this.mapDocuments(documents); + }; + + findMonitorCountByTeamIdAndType = async (teamId: string, config?: TeamQueryConfig): Promise => { + const { type } = config ?? {}; + + const query: FilterQuery = { + teamId: new mongoose.Types.ObjectId(teamId), + }; + + if (type !== undefined) { + query.type = Array.isArray(type) ? { $in: type } : type; + } + + const count = await MonitorModel.countDocuments(query); + return count; + }; + + update = async (monitorId: string, patch: Partial) => { + const updatedMonitor = await MonitorModel.findOneAndUpdate( + { _id: monitorId }, + { + $set: { + ...patch, + }, + }, + { new: true, runValidators: true } + ); + if (!updatedMonitor) { + throw new AppError({ message: `Failed to update monitor with id ${monitorId}`, status: 500 }); + } + return this.toEntity(updatedMonitor); + }; + + deleteByTeamId = async (teamId: string) => { + const monitors = await MonitorModel.find({ teamId }); + const { deletedCount } = await MonitorModel.deleteMany({ teamId }); + + return { monitors: this.mapDocuments(monitors), deletedCount }; + }; + + private mapDocuments = (documents: MonitorDocument[]): Monitor[] => { + if (!documents?.length) { + return []; + } + return documents.map((doc) => this.toEntity(doc)); + }; + + private toEntity = (doc: MonitorDocument): Monitor => { + const toStringId = (value: unknown): string => { + if (value instanceof mongoose.Types.ObjectId) { + return value.toString(); + } + return value?.toString() ?? ""; + }; + + const toDateString = (value: Date | string): string => { + return value instanceof Date ? value.toISOString() : value; + }; + + const notificationIds = (doc.notifications ?? []).map((notification) => toStringId(notification)); + + return { + id: toStringId(doc._id), + userId: toStringId(doc.userId), + teamId: toStringId(doc.teamId), + name: doc.name, + description: doc.description ?? undefined, + status: doc.status ?? undefined, + statusWindow: doc.statusWindow ?? [], + statusWindowSize: doc.statusWindowSize, + statusWindowThreshold: doc.statusWindowThreshold, + type: doc.type, + ignoreTlsErrors: doc.ignoreTlsErrors, + jsonPath: doc.jsonPath ?? undefined, + expectedValue: doc.expectedValue ?? undefined, + matchMethod: doc.matchMethod ?? undefined, + url: doc.url, + port: doc.port ?? undefined, + isActive: doc.isActive, + interval: doc.interval, + uptimePercentage: doc.uptimePercentage ?? undefined, + notifications: notificationIds, + secret: doc.secret ?? undefined, + thresholds: doc.thresholds ?? undefined, + alertThreshold: doc.alertThreshold, + cpuAlertThreshold: doc.cpuAlertThreshold, + memoryAlertThreshold: doc.memoryAlertThreshold, + diskAlertThreshold: doc.diskAlertThreshold, + tempAlertThreshold: doc.tempAlertThreshold, + selectedDisks: doc.selectedDisks ?? [], + gameId: doc.gameId ?? undefined, + group: doc.group ?? null, + createdAt: toDateString(doc.createdAt), + updatedAt: toDateString(doc.updatedAt), + }; + }; +} + +export default MongoMonitorsRepository; diff --git a/server/src/routes/v1/announcementsRoute.js b/server/src/routes/v1/announcementsRoute.js deleted file mode 100755 index 011dfac4a..000000000 --- a/server/src/routes/v1/announcementsRoute.js +++ /dev/null @@ -1,33 +0,0 @@ -import { Router } from "express"; -import { verifyJWT } from "../middleware/verifyJWT.js"; -import { isAllowed } from "../middleware/isAllowed.js"; - -class AnnouncementRoutes { - constructor(controller) { - this.router = Router(); - this.announcementController = controller; - this.initRoutes(); - } - - initRoutes() { - /** - * @route POST / - * @desc Create a new announcement - * @access Private (Requires JWT verification) - */ - this.router.post("/", verifyJWT, isAllowed(["admin", "superadmin"]), this.announcementController.createAnnouncement); - - /** - * @route GET / - * @desc Get announcements - * @access Public - */ - this.router.get("/", this.announcementController.getAnnouncement); - } - - getRouter() { - return this.router; - } -} - -export default AnnouncementRoutes; diff --git a/server/src/routes/v1/monitorRoute.js b/server/src/routes/v1/monitorRoute.ts similarity index 89% rename from server/src/routes/v1/monitorRoute.js rename to server/src/routes/v1/monitorRoute.ts index fe430d46f..24bc64ec8 100755 --- a/server/src/routes/v1/monitorRoute.js +++ b/server/src/routes/v1/monitorRoute.ts @@ -1,13 +1,15 @@ import { Router } from "express"; import { isAllowed } from "../../middleware/v1/isAllowed.js"; import multer from "multer"; -import { fetchMonitorCertificate } from "../../controllers/v1/controllerUtils.js"; +import { fetchMonitorCertificate } from "../../controllers/controllerUtils.js"; const upload = multer({ storage: multer.memoryStorage(), // Store file in memory as Buffer }); class MonitorRoutes { - constructor(monitorController) { + private router: Router; + private monitorController: any; + constructor(monitorController: any) { this.router = Router(); this.monitorController = monitorController; this.initRoutes(); @@ -25,10 +27,11 @@ class MonitorRoutes { // Hardware routes this.router.get("/hardware/details/:monitorId", this.monitorController.getHardwareDetailsById); + // PageSpeed routes + this.router.get("/pagespeed/details/:monitorId", this.monitorController.getPageSpeedDetailsById); // General monitor routes this.router.post("/pause/:monitorId", isAllowed(["admin", "superadmin"]), this.monitorController.pauseMonitor); - this.router.get("/stats/:monitorId", this.monitorController.getMonitorStatsById); // Util routes this.router.get("/certificate/:monitorId", (req, res, next) => { @@ -36,7 +39,6 @@ class MonitorRoutes { }); // General monitor CRUD routes - this.router.get("/", this.monitorController.getAllMonitors); this.router.post("/", isAllowed(["admin", "superadmin"]), this.monitorController.createMonitor); this.router.delete("/", isAllowed(["superadmin"]), this.monitorController.deleteAllMonitors); diff --git a/server/src/service/v1/business/checkService.js b/server/src/service/business/checkService.js similarity index 100% rename from server/src/service/v1/business/checkService.js rename to server/src/service/business/checkService.js diff --git a/server/src/service/v1/business/diagnosticService.js b/server/src/service/business/diagnosticService.js similarity index 100% rename from server/src/service/v1/business/diagnosticService.js rename to server/src/service/business/diagnosticService.js diff --git a/server/src/service/v1/business/incidentService.js b/server/src/service/business/incidentService.js similarity index 100% rename from server/src/service/v1/business/incidentService.js rename to server/src/service/business/incidentService.js diff --git a/server/src/service/v1/business/inviteService.js b/server/src/service/business/inviteService.js similarity index 100% rename from server/src/service/v1/business/inviteService.js rename to server/src/service/business/inviteService.js diff --git a/server/src/service/v1/business/maintenanceWindowService.js b/server/src/service/business/maintenanceWindowService.js similarity index 100% rename from server/src/service/v1/business/maintenanceWindowService.js rename to server/src/service/business/maintenanceWindowService.js diff --git a/server/src/service/business/monitorService.ts b/server/src/service/business/monitorService.ts new file mode 100644 index 000000000..3793ac35e --- /dev/null +++ b/server/src/service/business/monitorService.ts @@ -0,0 +1,566 @@ +import { createMonitorsBodyValidation } from "@/validation/joi.js"; +import { NormalizeData, NormalizeDataUptimeDetails } from "@/utils/dataUtils.js"; +import { type Monitor } from "@/types/index.js"; +import type { MonitorType } from "@/types/monitor.js"; +import type { IChecksRepository, IMonitorsRepository, IMonitorStatsRepository } from "@/repositories/index.js"; +import fs from "fs"; +import { fileURLToPath } from "url"; +import path from "path"; + +import { AppError } from "../infrastructure/errorService.js"; + +const SERVICE_NAME = "MonitorService"; +type DateRangeKey = "recent" | "day" | "week" | "month" | "all"; + +export interface IMonitorService { + readonly serviceName: string; + verifyTeamAccess(args: { teamId: string; monitorId: string }): Promise; + + // create + createMonitor(teamId: string, userId: string, body: Monitor): Promise; + createBulkMonitors(fileData: string, userId: string, teamId: string): Promise; + addDemoMonitors(args: { userId: string; teamId: string }): Promise; + + // read + getUptimeDetailsById(args: { teamId: string; monitorId: string; dateRange: string; normalize?: boolean }): Promise; + getHardwareDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise; + getPageSpeedDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise; + getMonitorById(args: { teamId: string; monitorId: string }): Promise; + getMonitorsByTeamId(args: { + teamId: string; + limit?: number; + type?: MonitorType | MonitorType[]; + page?: number; + rowsPerPage?: number; + filter?: string; + field?: string; + order?: "asc" | "desc"; + }): Promise; + getMonitorsAndSummaryByTeamId(args: { teamId: string; type?: string | string[]; explain?: boolean }): Promise; + getMonitorsWithChecksByTeamId(args: { + teamId: string; + limit?: number; + type?: MonitorType | MonitorType[]; + page?: number; + rowsPerPage?: number; + filter?: string; + field?: string; + order?: "asc" | "desc"; + explain?: boolean; + }): Promise<{ count: number; monitors: any[] }>; + getAllGames(): any; + getGroupsByTeamId(args: { teamId: string }): Promise; + + // update + editMonitor(args: { teamId: string; monitorId: string; body: any }): Promise; + pauseMonitor(args: { teamId: string; monitorId: string }): Promise; + + // delete + deleteMonitor(args: { teamId: string; monitorId: string }): Promise; + deleteAllMonitors(args: { teamId: string }): Promise; + + // other + sendTestEmail(args: { to: string }): Promise; + exportMonitorsToCSV(args: { teamId: string }): Promise; + exportMonitorsToJSON(args: { teamId: string }): Promise; +} + +export class MonitorService implements IMonitorService { + static SERVICE_NAME = SERVICE_NAME; + + private db: any; + private jobQueue: any; + private stringService: any; + private emailService: any; + private papaparse: any; + private logger: any; + private errorService: any; + private games: any; + private monitorsRepository: IMonitorsRepository; + private checksRepository: IChecksRepository; + private monitorStatsRepository: IMonitorStatsRepository; + + constructor({ + db, + jobQueue, + stringService, + emailService, + papaparse, + logger, + errorService, + games, + monitorsRepository, + checksRepository, + monitorStatsRepository, + }: { + db: any; + jobQueue: any; + stringService: any; + emailService: any; + papaparse: any; + logger: any; + errorService: any; + games: any; + monitorsRepository: IMonitorsRepository; + checksRepository: IChecksRepository; + monitorStatsRepository: IMonitorStatsRepository; + }) { + this.db = db; + this.jobQueue = jobQueue; + this.stringService = stringService; + this.emailService = emailService; + this.papaparse = papaparse; + this.logger = logger; + this.errorService = errorService; + this.games = games; + this.monitorsRepository = monitorsRepository; + this.checksRepository = checksRepository; + this.monitorStatsRepository = monitorStatsRepository; + } + + get serviceName(): string { + return MonitorService.SERVICE_NAME; + } + + private getDateRange = (dateRange: DateRangeKey) => { + const startDates = { + recent: new Date(new Date().setHours(new Date().getHours() - 2)), + day: new Date(new Date().setDate(new Date().getDate() - 1)), + week: new Date(new Date().setDate(new Date().getDate() - 7)), + month: new Date(new Date().setMonth(new Date().getMonth() - 1)), + all: new Date(0), + }; + return { + start: startDates[dateRange], + end: new Date(), + }; + }; + + private getDateFormat = (dateRange: DateRangeKey): string => { + const formatLookup = { + recent: "%Y-%m-%dT%H:%M:00Z", + day: "%Y-%m-%dT%H:00:00Z", + week: "%Y-%m-%dT00:00:00Z", + month: "%Y-%m-%dT00:00:00Z", + all: "%Y-%m-%dT00:00:00Z", + }; + return formatLookup[dateRange]; + }; + + verifyTeamAccess = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise => { + const monitor = await this.db.monitorModule.getMonitorById(monitorId); + if (!monitor?.teamId?.equals(teamId)) { + throw this.errorService.createAuthorizationError(); + } + }; + + createMonitor = async (teamId: string, userId: string, body: Monitor): Promise => { + const monitor = await this.monitorsRepository.create(body, teamId, userId); + if (!monitor) { + throw new AppError("Failed to create monitor", 500); + } + + this.jobQueue.addJob(monitor.id, monitor); + }; + + createBulkMonitors = async (fileData: string, userId: string, teamId: string): Promise => { + const { parse } = this.papaparse; + + return new Promise((resolve, reject) => { + parse(fileData, { + header: true, + skipEmptyLines: true, + transform: (value: string, header: string): string | number | undefined => { + if (value === "") return undefined; // Empty fields become undefined + + // Handle 'port' and 'interval' fields, check if they're valid numbers + if (["port", "interval"].includes(header)) { + const num = parseInt(value, 10); + if (isNaN(num)) { + throw this.errorService.createBadRequestError(`${header} should be a valid number, got: ${value}`); + } + return num; + } + + return value; + }, + complete: async ({ data, errors }: { data: any[]; errors: Error[] }): Promise => { + try { + if (errors.length > 0) { + throw this.errorService.createServerError("Error parsing CSV"); + } + + if (!data || data.length === 0) { + throw this.errorService.createServerError("CSV file contains no data rows"); + } + + const enrichedData = data.map((monitor: Monitor) => ({ + ...monitor, + userId, + teamId, + description: monitor.description || monitor.name || monitor.url, + name: monitor.name || monitor.url, + type: monitor.type || "http", + })); + + await createMonitorsBodyValidation.validateAsync(enrichedData); + + const monitors = await this.monitorsRepository.createBulkMonitors(enrichedData); + + await Promise.all( + monitors.map(async (monitor: Monitor) => { + this.jobQueue.addJob(monitor.id, monitor); + }) + ); + + resolve(monitors); + } catch (error) { + reject(error); + } + }, + }); + }); + }; + + addDemoMonitors = async ({ userId, teamId }: { userId: string; teamId: string }): Promise => { + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); + const demoMonitorsPath = path.resolve(__dirname, "../../utils/demoMonitors.json"); + + const demoData = JSON.parse(fs.readFileSync(demoMonitorsPath, "utf8")); + const monitors: Monitor[] = demoData.map((monitor: Monitor) => { + return { + userId, + teamId, + name: monitor.name, + description: monitor.name, + type: "http", + url: monitor.url, + interval: 60000, + }; + }); + const demoMonitors = await this.monitorsRepository.createBulkMonitors(monitors); + + await Promise.all(demoMonitors.map((monitor) => this.jobQueue.addJob(monitor.id, monitor))); + return demoMonitors; + }; + + getUptimeDetailsById = async ({ + teamId, + monitorId, + dateRange, + normalize, + }: { + teamId: string; + monitorId: string; + dateRange: string; + normalize?: boolean; + }): Promise => { + await this.verifyTeamAccess({ teamId, monitorId }); + + const monitor = await this.monitorsRepository.findById(monitorId, teamId); + if (!monitor) { + throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 }); + } + const rangeKey = (dateRange as DateRangeKey) ?? "recent"; + const { start, end } = this.getDateRange(rangeKey); + const checksData = await this.checksRepository.findDateRangeChecksByMonitor(monitor.id, start, end, this.getDateFormat(rangeKey), { + type: monitor.type, + }); + const monitorStats = await this.monitorStatsRepository.findByMonitorId(monitor.id); + + if ( + checksData.monitorType !== "http" && + checksData.monitorType !== "ping" && + checksData.monitorType !== "docker" && + checksData.monitorType !== "port" && + checksData.monitorType !== "game" + ) { + throw new AppError({ message: `${monitor.type} monitors are not supported for uptime details`, status: 400 }); + } + + return { + monitorData: { + monitor, + groupedChecks: NormalizeDataUptimeDetails(checksData.groupedChecks, 10, 100), + groupedUpChecks: NormalizeDataUptimeDetails(checksData.groupedUpChecks, 10, 100), + groupedDownChecks: NormalizeDataUptimeDetails(checksData.groupedDownChecks, 10, 100), + groupedAvgResponseTime: checksData.avgResponseTime, + groupedUptimePercentage: checksData.uptimePercentage, + }, + monitorStats, + }; + }; + + getHardwareDetailsById = async ({ teamId, monitorId, dateRange }: { teamId: string; monitorId: string; dateRange: string }): Promise => { + await this.verifyTeamAccess({ teamId, monitorId }); + const monitor = await this.monitorsRepository.findById(monitorId, teamId); + if (!monitor) { + throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 }); + } + if (monitor.type !== "hardware") { + throw new AppError({ message: `${monitor.type} monitors are not supported for hardware details`, status: 400 }); + } + + const rangeKey = (dateRange as DateRangeKey) ?? "recent"; + const { start, end } = this.getDateRange(rangeKey); + const checksData = await this.checksRepository.findDateRangeChecksByMonitor(monitor.id, start, end, this.getDateFormat(rangeKey), { + type: monitor.type, + }); + + if (checksData.monitorType !== "hardware") { + throw new AppError({ message: "Unable to load hardware stats for this monitor", status: 500 }); + } + + const stats = { + aggregateData: checksData.aggregateData, + upChecks: checksData.upChecks, + checks: checksData.checks, + }; + + return { + ...monitor, + stats, + }; + }; + + getPageSpeedDetailsById = async ({ teamId, monitorId, dateRange }: { teamId: string; monitorId: string; dateRange: string }): Promise => { + await this.verifyTeamAccess({ teamId, monitorId }); + const monitor = await this.monitorsRepository.findById(monitorId, teamId); + if (!monitor) { + throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 }); + } + if (monitor.type !== "pagespeed") { + throw new AppError({ message: `${monitor.type} monitors are not supported for pagespeed details`, status: 400 }); + } + + const rangeKey = (dateRange as DateRangeKey) ?? "recent"; + const { start, end } = this.getDateRange(rangeKey); + const checksData = await this.checksRepository.findDateRangeChecksByMonitor(monitor.id, start, end, this.getDateFormat(rangeKey), { + type: monitor.type, + }); + + if (checksData.monitorType !== "pagespeed") { + throw new AppError({ message: "Unable to load pagespeed stats for this monitor", status: 500 }); + } + return { + ...monitor, + checks: checksData.checks, + }; + }; + getMonitorById = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise => { + await this.verifyTeamAccess({ teamId, monitorId }); + const monitor = await this.monitorsRepository.findById(monitorId, teamId); + return monitor; + }; + + getMonitorsByTeamId = async ({ teamId, type, filter }: { teamId: string; type?: MonitorType | MonitorType[]; filter?: string }): Promise => { + const monitors = await this.monitorsRepository.findByTeamId(teamId, { + type, + filter, + }); + return monitors; + }; + + getMonitorsAndSummaryByTeamId = async ({ + teamId, + type, + explain, + }: { + teamId: string; + type?: string | string[]; + explain?: boolean; + }): Promise => { + const result = await this.db.monitorModule.getMonitorsAndSummaryByTeamId({ + type, + explain, + teamId, + }); + return result; + }; + + getMonitorsWithChecksByTeamId = async ({ + teamId, + limit, + type, + page, + rowsPerPage, + filter, + field, + order, + explain, + }: { + teamId: string; + limit?: number; + type?: MonitorType | MonitorType[]; + page?: number; + rowsPerPage?: number; + filter?: string; + field?: string; + order?: "asc" | "desc"; + explain?: boolean; + }): Promise<{ count: number; monitors: any[] }> => { + const count = await this.monitorsRepository.findMonitorCountByTeamIdAndType(teamId, { type, filter }); + const monitors = await this.monitorsRepository.findByTeamId(teamId, { + limit, + type, + page, + rowsPerPage, + filter, + field, + order, + }); + + const monitorsList = (monitors ?? []) as Monitor[]; + const snapshotTypes: MonitorType[] = ["hardware", "pagespeed"]; + const requestedTypes = Array.isArray(type) ? type : type ? [type] : []; + const snapshotOnlyRequest = + requestedTypes.length > 0 && requestedTypes.every((requestedType) => snapshotTypes.includes(requestedType as MonitorType)); + + const limitPerMonitor = snapshotOnlyRequest ? 1 : 25; + const checksMap = await this.checksRepository.findLatestChecksByMonitorIds( + monitorsList.map((monitor) => monitor.id), + { limitPerMonitor } + ); + + const monitorsWithChecks = monitorsList.map((monitor: Monitor) => { + const rawChecks = checksMap[monitor.id] ?? []; + const isSnapshotType = snapshotOnlyRequest || snapshotTypes.includes(monitor.type); + const checks = isSnapshotType ? rawChecks.slice(0, 1) : NormalizeData(rawChecks, 10, 100); + return { + ...monitor, + checks, + }; + }); + + return { count, monitors: monitorsWithChecks }; + }; + + getAllGames = (): any => { + return this.games; + }; + + getGroupsByTeamId = async ({ teamId }: { teamId: string }): Promise => { + const groups = await this.db.monitorModule.getGroupsByTeamId({ teamId }); + return groups; + }; + + editMonitor = async ({ teamId, monitorId, body }: { teamId: string; monitorId: string; body: any }): Promise => { + await this.verifyTeamAccess({ teamId, monitorId }); + const editedMonitor = await this.db.monitorModule.editMonitor({ monitorId, body }); + await this.jobQueue.updateJob(editedMonitor); + }; + + pauseMonitor = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise => { + await this.verifyTeamAccess({ teamId, monitorId }); + const monitor = await this.db.monitorModule.pauseMonitor({ monitorId }); + monitor.isActive === true ? await this.jobQueue.resumeJob(monitor._id, monitor) : await this.jobQueue.pauseJob(monitor); + return monitor; + }; + + deleteMonitor = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise => { + await this.verifyTeamAccess({ teamId, monitorId }); + const monitor = await this.db.monitorModule.deleteMonitor({ teamId, monitorId }); + await this.jobQueue.deleteJob(monitor); + await this.db.statusPageModule.deleteStatusPagesByMonitorId(monitor._id); + return monitor; + }; + + deleteAllMonitors = async ({ teamId }: { teamId: string }): Promise => { + const { monitors, deletedCount } = await this.monitorsRepository.deleteByTeamId(teamId); + await Promise.all( + monitors.map(async (monitor) => { + try { + await this.jobQueue.deleteJob(monitor); + await this.db.checkModule.deleteChecks(monitor.id); + await this.db.pageSpeedCheckModule.deletePageSpeedChecksByMonitorId(monitor.id); + await this.db.notificationsModule.deleteNotificationsByMonitorId(monitor.id); + } catch (error: any) { + this.logger.warn({ + message: `Error deleting associated records for monitor ${monitor.id} with name ${monitor.name}`, + service: SERVICE_NAME, + method: "deleteAllMonitors", + stack: error.stack, + }); + } + }) + ); + return deletedCount; + }; + + sendTestEmail = async ({ to }: { to: string }): Promise => { + const subject = this.stringService.testEmailSubject; + const context = { testName: "Monitoring System" }; + + const html = await this.emailService.buildEmail("testEmailTemplate", context); + const messageId = await this.emailService.sendEmail(to, subject, html); + + if (!messageId) { + throw this.errorService.createServerError("Failed to send test email."); + } + + return messageId; + }; + + exportMonitorsToCSV = async ({ teamId }: { teamId: string }): Promise => { + const monitors = await this.db.monitorModule.getMonitorsByTeamId({ teamId }); + + if (!monitors || monitors.length === 0) { + throw this.errorService.createNotFoundError("No monitors to export"); + } + + const csvData = monitors?.filteredMonitors?.map((monitor: any) => ({ + name: monitor.name, + description: monitor.description, + type: monitor.type, + url: monitor.url, + interval: monitor.interval, + port: monitor.port, + ignoreTlsErrors: monitor.ignoreTlsErrors, + isActive: monitor.isActive, + })); + + const csv = this.papaparse.unparse(csvData); + return csv; + }; + exportMonitorsToJSON = async ({ teamId }: { teamId: string }): Promise => { + const monitors = await this.db.monitorModule.getMonitorsByTeamId({ teamId }); + + if (!monitors || monitors.length === 0) { + throw this.errorService.createNotFoundError("No monitors to export"); + } + + const json = monitors?.filteredMonitors + ?.map((monitor: any) => { + const initialType = monitor.type; + let parsedType; + + if (initialType === "hardware") { + parsedType = "infrastructure"; + } else if (initialType === "http") { + if (monitor.url.startsWith("https://")) { + parsedType = "https"; + } else { + parsedType = "http"; + } + } else if (initialType === "pagespeed") { + parsedType = initialType; + } else { + // Skip unsupported types + return; + } + + return { + name: monitor.name, + url: monitor.url, + type: parsedType, + interval: monitor.interval, + n: monitor.statusWindowSize, + secret: monitor.secret, + }; + }) + .filter(Boolean); + + return json; + }; +} 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/index.ts b/server/src/service/index.ts new file mode 100644 index 000000000..4793f055b --- /dev/null +++ b/server/src/service/index.ts @@ -0,0 +1 @@ +export * from "@/service/business/monitorService.js"; diff --git a/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js similarity index 83% rename from server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js rename to server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js index a3cdea8e7..a6637d4cc 100644 --- a/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js +++ b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js @@ -4,19 +4,20 @@ const SERVICE_NAME = "JobQueue"; class SuperSimpleQueue { static SERVICE_NAME = SERVICE_NAME; - constructor({ envSettings, db, logger, helper }) { + constructor({ envSettings, db, logger, helper, monitorsRepository }) { this.envSettings = envSettings; this.db = db; this.logger = logger; this.helper = helper; + this.monitorsRepository = monitorsRepository; } get serviceName() { return SuperSimpleQueue.SERVICE_NAME; } - static async create({ envSettings, db, logger, helper }) { - const instance = new SuperSimpleQueue({ envSettings, db, logger, helper }); + static async create({ envSettings, db, logger, helper, monitorsRepository }) { + const instance = new SuperSimpleQueue({ envSettings, db, logger, helper, monitorsRepository }); await instance.init(); return instance; } @@ -33,11 +34,11 @@ class SuperSimpleQueue { this.scheduler.start(); this.scheduler.addTemplate("monitor-job", this.helper.getMonitorJob()); - const monitors = await this.db.monitorModule.getAllMonitors(); + const monitors = await this.monitorsRepository.findAll(); for (const monitor of monitors) { const randomOffset = Math.floor(Math.random() * 100); setTimeout(() => { - this.addJob(monitor._id, monitor); + this.addJob(monitor.id, monitor); }, randomOffset); } @@ -55,44 +56,44 @@ class SuperSimpleQueue { addJob = async (monitorId, monitor) => { this.scheduler.addJob({ - id: monitorId.toString(), + id: monitorId, template: "monitor-job", repeat: monitor.interval, active: monitor.isActive, - data: monitor.toObject(), + data: monitor, }); }; deleteJob = async (monitor) => { - this.scheduler.removeJob(monitor._id.toString()); + this.scheduler.removeJob(monitor.id); }; pauseJob = async (monitor) => { - const result = this.scheduler.pauseJob(monitor._id.toString()); + const result = this.scheduler.pauseJob(monitor.id); if (result === false) { throw new Error("Failed to resume monitor"); } this.logger.debug({ - message: `Paused monitor ${monitor._id}`, + message: `Paused monitor ${monitor.id}`, service: SERVICE_NAME, method: "pauseJob", }); }; resumeJob = async (monitor) => { - const result = this.scheduler.resumeJob(monitor._id.toString()); + const result = this.scheduler.resumeJob(monitor.id); if (result === false) { throw new Error("Failed to resume monitor"); } this.logger.debug({ - message: `Resumed monitor ${monitor._id}`, + message: `Resumed monitor ${monitor.id}`, service: SERVICE_NAME, method: "resumeJob", }); }; updateJob = async (monitor) => { - this.scheduler.updateJob(monitor._id.toString(), { repeat: monitor.interval, data: monitor.toObject() }); + this.scheduler.updateJob(monitor.id, { repeat: monitor.interval, data: monitor }); }; shutdown = async () => { diff --git a/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js similarity index 96% rename from server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js rename to server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js index 2833a59f5..3cd6b37a8 100644 --- a/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js +++ b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js @@ -27,7 +27,7 @@ class SuperSimpleQueueHelper { getMonitorJob = () => { return async (monitor) => { try { - const monitorId = monitor._id; + const monitorId = monitor.id; const teamId = monitor.teamId; if (!monitorId) { throw new Error("No monitor id"); @@ -62,7 +62,7 @@ class SuperSimpleQueueHelper { message: error.message, service: SERVICE_NAME, method: "getMonitorJob", - details: `Error sending notifications for job ${monitor._id}: ${error.message}`, + details: `Error sending notifications for job ${monitor.id}: ${error.message}`, stack: error.stack, }); }); diff --git a/server/src/service/v1/infrastructure/bufferService.js b/server/src/service/infrastructure/bufferService.js similarity index 95% rename from server/src/service/v1/infrastructure/bufferService.js rename to server/src/service/infrastructure/bufferService.js index 9b05d05c8..a218f56a0 100755 --- a/server/src/service/v1/infrastructure/bufferService.js +++ b/server/src/service/infrastructure/bufferService.js @@ -1,9 +1,12 @@ +import { config } from "@/config/index.js"; + const SERVICE_NAME = "BufferService"; class BufferService { static SERVICE_NAME = SERVICE_NAME; constructor({ db, logger, envSettings, incidentService }) { - this.BUFFER_TIMEOUT = envSettings.nodeEnv === "development" ? 1000 : 1000 * 60 * 1; // 1 minute + console.log(envSettings); + this.BUFFER_TIMEOUT = config.NODE_ENV === "development" ? 10 : 1000 * 60 * 1; // 1 minute this.db = db; this.logger = logger; this.incidentService = incidentService; 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 98% rename from server/src/service/v1/infrastructure/networkService.js rename to server/src/service/infrastructure/networkService.js index d364f373e..2375e7233 100644 --- a/server/src/service/v1/infrastructure/networkService.js +++ b/server/src/service/infrastructure/networkService.js @@ -95,7 +95,7 @@ class NetworkService { } const pingResponse = { - monitorId: monitor._id, + monitorId: monitor.id, type: "ping", status: response.alive, code: 200, @@ -120,9 +120,9 @@ class NetworkService { } async requestHttp(monitor) { - const { url, secret, _id, teamId, type, ignoreTlsErrors, jsonPath, matchMethod, expectedValue } = monitor; + const { url, secret, id, teamId, type, ignoreTlsErrors, jsonPath, matchMethod, expectedValue } = monitor; const httpResponse = { - monitorId: _id, + monitorId: id, teamId: teamId, type, }; @@ -298,7 +298,7 @@ class NetworkService { }); const dockerResponse = { - monitorId: monitor._id, + monitorId: monitor.id, type: monitor.type, }; @@ -416,7 +416,7 @@ class NetworkService { code: 200, status: response.success, message: this.stringService.portSuccess, - monitorId: monitor._id, + monitorId: monitor.id, type: monitor.type, responseTime: responseTime, }; @@ -444,7 +444,7 @@ class NetworkService { code: 200, status: true, message: "Success", - monitorId: monitor._id, + monitorId: monitor.id, type: "game", }; diff --git a/server/src/service/infrastructure/notes b/server/src/service/infrastructure/notes deleted file mode 100644 index ce5a77ddb..000000000 --- a/server/src/service/infrastructure/notes +++ /dev/null @@ -1,83 +0,0 @@ -import { Typography, Select } from "@mui/material"; -import MenuItem from "@mui/material/MenuItem"; -import type { SelectProps } from "@mui/material/Select"; -import { useTheme } from "@mui/material/styles"; -import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; - -export const SelectInput: React.FC = ({ ...props }) => { - const theme = useTheme(); - return ( -