Merge pull request #892 from bluewave-labs/develop

Develop -> Master
This commit is contained in:
Alexander Holliday
2024-09-29 21:27:43 -07:00
committed by GitHub
50 changed files with 1302 additions and 366 deletions
+87 -83
View File
@@ -1759,9 +1759,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.1.tgz",
"integrity": "sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz",
"integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==",
"cpu": [
"arm"
],
@@ -1772,9 +1772,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.1.tgz",
"integrity": "sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz",
"integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==",
"cpu": [
"arm64"
],
@@ -1785,9 +1785,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.1.tgz",
"integrity": "sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz",
"integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==",
"cpu": [
"arm64"
],
@@ -1798,9 +1798,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.1.tgz",
"integrity": "sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz",
"integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==",
"cpu": [
"x64"
],
@@ -1811,9 +1811,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.1.tgz",
"integrity": "sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz",
"integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==",
"cpu": [
"arm"
],
@@ -1824,9 +1824,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.1.tgz",
"integrity": "sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz",
"integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==",
"cpu": [
"arm"
],
@@ -1837,9 +1837,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.1.tgz",
"integrity": "sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz",
"integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==",
"cpu": [
"arm64"
],
@@ -1850,9 +1850,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.1.tgz",
"integrity": "sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz",
"integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==",
"cpu": [
"arm64"
],
@@ -1863,9 +1863,9 @@
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.1.tgz",
"integrity": "sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz",
"integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==",
"cpu": [
"ppc64"
],
@@ -1876,9 +1876,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.1.tgz",
"integrity": "sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz",
"integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==",
"cpu": [
"riscv64"
],
@@ -1889,9 +1889,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.1.tgz",
"integrity": "sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz",
"integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==",
"cpu": [
"s390x"
],
@@ -1902,9 +1902,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.1.tgz",
"integrity": "sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz",
"integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==",
"cpu": [
"x64"
],
@@ -1915,9 +1915,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.1.tgz",
"integrity": "sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz",
"integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==",
"cpu": [
"x64"
],
@@ -1928,9 +1928,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.1.tgz",
"integrity": "sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz",
"integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==",
"cpu": [
"arm64"
],
@@ -1941,9 +1941,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.1.tgz",
"integrity": "sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz",
"integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==",
"cpu": [
"ia32"
],
@@ -1954,9 +1954,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.1.tgz",
"integrity": "sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz",
"integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==",
"cpu": [
"x64"
],
@@ -5249,9 +5249,9 @@
}
},
"node_modules/picocolors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
"license": "ISC"
},
"node_modules/picomatch": {
@@ -5277,9 +5277,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.39",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz",
"integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==",
"version": "8.4.47",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
"funding": [
{
"type": "opencollective",
@@ -5297,8 +5297,8 @@
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.1",
"source-map-js": "^1.2.0"
"picocolors": "^1.1.0",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -5678,9 +5678,9 @@
"license": "Unlicense"
},
"node_modules/rollup": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.1.tgz",
"integrity": "sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz",
"integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==",
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.5"
@@ -5693,22 +5693,22 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.18.1",
"@rollup/rollup-android-arm64": "4.18.1",
"@rollup/rollup-darwin-arm64": "4.18.1",
"@rollup/rollup-darwin-x64": "4.18.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.18.1",
"@rollup/rollup-linux-arm-musleabihf": "4.18.1",
"@rollup/rollup-linux-arm64-gnu": "4.18.1",
"@rollup/rollup-linux-arm64-musl": "4.18.1",
"@rollup/rollup-linux-powerpc64le-gnu": "4.18.1",
"@rollup/rollup-linux-riscv64-gnu": "4.18.1",
"@rollup/rollup-linux-s390x-gnu": "4.18.1",
"@rollup/rollup-linux-x64-gnu": "4.18.1",
"@rollup/rollup-linux-x64-musl": "4.18.1",
"@rollup/rollup-win32-arm64-msvc": "4.18.1",
"@rollup/rollup-win32-ia32-msvc": "4.18.1",
"@rollup/rollup-win32-x64-msvc": "4.18.1",
"@rollup/rollup-android-arm-eabi": "4.22.4",
"@rollup/rollup-android-arm64": "4.22.4",
"@rollup/rollup-darwin-arm64": "4.22.4",
"@rollup/rollup-darwin-x64": "4.22.4",
"@rollup/rollup-linux-arm-gnueabihf": "4.22.4",
"@rollup/rollup-linux-arm-musleabihf": "4.22.4",
"@rollup/rollup-linux-arm64-gnu": "4.22.4",
"@rollup/rollup-linux-arm64-musl": "4.22.4",
"@rollup/rollup-linux-powerpc64le-gnu": "4.22.4",
"@rollup/rollup-linux-riscv64-gnu": "4.22.4",
"@rollup/rollup-linux-s390x-gnu": "4.22.4",
"@rollup/rollup-linux-x64-gnu": "4.22.4",
"@rollup/rollup-linux-x64-musl": "4.22.4",
"@rollup/rollup-win32-arm64-msvc": "4.22.4",
"@rollup/rollup-win32-ia32-msvc": "4.22.4",
"@rollup/rollup-win32-x64-msvc": "4.22.4",
"fsevents": "~2.3.2"
}
},
@@ -5887,9 +5887,9 @@
}
},
"node_modules/source-map-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@@ -6266,14 +6266,14 @@
}
},
"node_modules/vite": {
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.3.4.tgz",
"integrity": "sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==",
"version": "5.4.8",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz",
"integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==",
"license": "MIT",
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.39",
"rollup": "^4.13.0"
"postcss": "^8.4.43",
"rollup": "^4.20.0"
},
"bin": {
"vite": "bin/vite.js"
@@ -6292,6 +6292,7 @@
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
@@ -6309,6 +6310,9 @@
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"stylus": {
"optional": true
},
+29 -2
View File
@@ -19,6 +19,7 @@ import SetNewPassword from "./Pages/Auth/SetNewPassword";
import NewPasswordConfirmed from "./Pages/Auth/NewPasswordConfirmed";
import ProtectedRoute from "./Components/ProtectedRoute";
import Details from "./Pages/Monitors/Details";
import AdvancedSettings from "./Pages/AdvancedSettings";
// import Maintenance from "./Pages/Maintenance";
import withAdminCheck from "./HOC/withAdminCheck";
import withAdminProp from "./HOC/withAdminProp";
@@ -33,7 +34,11 @@ import lightTheme from "./Utils/Theme/lightTheme";
import darkTheme from "./Utils/Theme/darkTheme";
import { useSelector } from "react-redux";
import { CssBaseline } from "@mui/material";
import { useEffect } from "react";
import { useDispatch } from "react-redux";
import { getAppSettings } from "./Features/Settings/settingsSlice";
import { logger } from "./Utils/Logger"; // Import the logger
import { networkService } from "./main";
function App() {
const AdminCheckedRegister = withAdminCheck(Register);
const MonitorsWithAdminProp = withAdminProp(Monitors);
@@ -42,8 +47,24 @@ function App() {
const PageSpeedDetailsWithAdminProp = withAdminProp(PageSpeedDetails);
// const MaintenanceWithAdminProp = withAdminProp(Maintenance);
const SettingsWithAdminProp = withAdminProp(Settings);
const AdvancedSettingsWithAdminProp = withAdminProp(AdvancedSettings);
const mode = useSelector((state) => state.ui.mode);
const { authToken } = useSelector((state) => state.auth);
const dispatch = useDispatch();
useEffect(() => {
if (authToken) {
dispatch(getAppSettings({ authToken }));
}
}, [dispatch, authToken]);
// Cleanup
useEffect(() => {
return () => {
logger.cleanup();
networkService.cleanup();
};
}, []);
return (
<ThemeProvider theme={mode === "light" ? lightTheme : darkTheme}>
@@ -96,6 +117,12 @@ function App() {
path="settings"
element={<ProtectedRoute Component={SettingsWithAdminProp} />}
/>
<Route
path="advanced-settings"
element={
<ProtectedRoute Component={AdvancedSettingsWithAdminProp} />
}
/>
<Route
path="account/profile"
element={<ProtectedRoute Component={Account} open="profile" />}
+10 -1
View File
@@ -16,6 +16,7 @@ import "./index.css";
* @param {Object} props
* @param {string} [props.type] - Type of input field (e.g., 'text', 'password').
* @param {string} props.id - ID of the input field.
* @param {string} props.name - Name of the input field.
* @param {string} [props.label] - Label for the input field.
* @param {boolean} [props.https] - Indicates if it should display http or https.
* @param {boolean} [props.isRequired] - Indicates if the field is required, will display a red asterisk.
@@ -27,6 +28,7 @@ import "./index.css";
* @param {function} props.onChange - Function called on input change.
* @param {string} [props.error] - Error message to display for the input field.
* @param {boolean} [props.disabled] - Indicates if the input field is disabled.
* @param {boolean} [props.hidden] - Indicates if the input field is hidden.
* @param {React.Ref} [ref] - Ref forwarded to the underlying `TextField` component. Allows for direct interactions such as focusing.
*/
@@ -35,6 +37,7 @@ const Field = forwardRef(
{
type = "text",
id,
name,
label,
https,
isRequired,
@@ -47,6 +50,7 @@ const Field = forwardRef(
onInput,
error,
disabled,
hidden,
},
ref
) => {
@@ -107,6 +111,7 @@ const Field = forwardRef(
)}
<TextField
type={type === "password" ? (isVisible ? "text" : type) : type}
name={name}
id={id}
autoComplete={autoComplete}
placeholder={placeholder}
@@ -136,7 +141,9 @@ const Field = forwardRef(
borderBottomLeftRadius: theme.shape.borderRadius,
},
}
: {}
: {
display: hidden ? "none" : "",
}
}
InputProps={{
startAdornment: type === "url" && (
@@ -213,6 +220,7 @@ Field.propTypes = {
"number",
]),
id: PropTypes.string.isRequired,
name: PropTypes.string,
label: PropTypes.string,
https: PropTypes.bool,
isRequired: PropTypes.bool,
@@ -225,6 +233,7 @@ Field.propTypes = {
onInput: PropTypes.func,
error: PropTypes.string,
disabled: PropTypes.bool,
hidden: PropTypes.bool,
};
export default Field;
@@ -1,5 +1,5 @@
import TabPanel from "@mui/lab/TabPanel";
import React, { useState } from "react";
import { useState } from "react";
import { useTheme } from "@emotion/react";
import { Box, Stack, Typography } from "@mui/material";
import LoadingButton from "@mui/lab/LoadingButton";
@@ -116,6 +116,14 @@ const PasswordPanel = () => {
<Box flex={0.9}>
<Typography component="h1">Current password</Typography>
</Box>
<Field
type="text"
id="hidden-username"
name="username"
autoComplete="username"
hidden={true}
value=""
/>
<Field
type="password"
id="edit-current-password"
@@ -0,0 +1,117 @@
import { networkService } from "../../main";
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
const initialState = {
isLoading: false,
apiBaseUrl: "http://localhost:5000/api/v1",
logLevel: "debug",
};
export const getAppSettings = createAsyncThunk(
"settings/getSettings",
async (data, thunkApi) => {
try {
const res = await networkService.getAppSettings({
authToken: data.authToken,
});
return res.data;
} catch (error) {
if (error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const updateAppSettings = createAsyncThunk(
"settings/updateSettings",
async ({ settings, authToken }, thunkApi) => {
networkService.setBaseUrl(settings.apiBaseUrl);
try {
const parsedSettings = {
apiBaseUrl: settings.apiBaseUrl,
logLevel: settings.logLevel,
clientHost: settings.clientHost,
jwtSecret: settings.jwtSecret,
dbType: settings.dbType,
dbConnectionString: settings.dbConnectionString,
redisHost: settings.redisHost,
redisPort: settings.redisPort,
jwtTTL: settings.jwtTTL,
pagespeedApiKey: settings.pagespeedApiKey,
systemEmailHost: settings.systemEmailHost,
systemEmailPort: settings.systemEmailPort,
systemEmailAddress: settings.systemEmailAddress,
systemEmailPassword: settings.systemEmailPassword,
};
const res = await networkService.updateAppSettings({
settings: parsedSettings,
authToken,
});
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
const handleGetSettingsFulfilled = (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
state.apiBaseUrl = action.payload.data.apiBaseUrl;
state.logLevel = action.payload.data.logLevel;
};
const handleGetSettingsRejected = (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload ? action.payload.msg : "Failed to get settings.";
};
const handleUpdateSettingsFulfilled = (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
state.apiBaseUrl = action.payload.data.apiBaseUrl;
state.logLevel = action.payload.data.logLevel;
};
const handleUpdateSettingsRejected = (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to update settings.";
};
const settingsSlice = createSlice({
name: "settings",
initialState,
extraReducers: (builder) => {
builder
.addCase(getAppSettings.pending, (state) => {
state.isLoading = true;
})
.addCase(getAppSettings.fulfilled, handleGetSettingsFulfilled)
.addCase(getAppSettings.rejected, handleGetSettingsRejected);
builder
.addCase(updateAppSettings.pending, (state) => {
state.isLoading = true;
})
.addCase(updateAppSettings.fulfilled, handleUpdateSettingsFulfilled)
.addCase(updateAppSettings.rejected, handleUpdateSettingsRejected);
},
});
export default settingsSlice.reducer;
+266
View File
@@ -0,0 +1,266 @@
import { useTheme } from "@emotion/react";
import { Box, Stack, Typography } from "@mui/material";
import Field from "../../Components/Inputs/Field";
import Link from "../../Components/Link";
import "./index.css";
import { useDispatch, useSelector } from "react-redux";
import { createToast } from "../../Utils/toastUtils";
import PropTypes from "prop-types";
import LoadingButton from "@mui/lab/LoadingButton";
import { ConfigBox } from "../Settings/styled";
import { useNavigate } from "react-router";
import {
getAppSettings,
updateAppSettings,
} from "../../Features/Settings/settingsSlice";
import { useState, useEffect } from "react";
import Select from "../../Components/Inputs/Select";
const AdvancedSettings = ({ isAdmin }) => {
const navigate = useNavigate();
useEffect(() => {
if (!isAdmin) {
navigate("/");
}
}, [navigate, isAdmin]);
const theme = useTheme();
const { authToken } = useSelector((state) => state.auth);
const dispatch = useDispatch();
const settings = useSelector((state) => state.settings);
const [localSettings, setLocalSettings] = useState({
apiBaseUrl: "",
logLevel: "debug",
systemEmailHost: "",
systemEmailPort: "",
systemEmailAddress: "",
systemEmailPassword: "",
jwtTTL: "",
dbType: "",
redisHost: "",
redisPort: "",
pagespeedApiKey: "",
});
useEffect(() => {
const getSettings = async () => {
const action = await dispatch(getAppSettings({ authToken }));
if (action.payload.success) {
console.log(action.payload.data);
setLocalSettings(action.payload.data);
} else {
createToast({ body: "Failed to get settings" });
}
};
getSettings();
}, [authToken, dispatch]);
const logItems = [
{ _id: 1, name: "none" },
{ _id: 2, name: "debug" },
{ _id: 3, name: "error" },
{ _id: 4, name: "warn" },
];
const logItemLookup = {
none: 1,
debug: 2,
error: 3,
warn: 4,
};
const handleLogLevel = (e) => {
const id = e.target.value;
const newLogLevel = logItems.find((item) => item._id === id).name;
setLocalSettings({ ...localSettings, logLevel: newLogLevel });
};
const handleChange = (event) => {
const { value, id } = event.target;
setLocalSettings({ ...localSettings, [id]: value });
};
const handleSave = async () => {
const action = await dispatch(
updateAppSettings({ settings: localSettings, authToken })
);
let body = "";
if (action.payload.success) {
console.log(action.payload.data);
setLocalSettings(action.payload.data);
body = "Settings saved successfully";
} else {
body = "Failed to save settings";
}
createToast({ body });
};
return (
<Box
className="settings"
style={{
paddingBottom: 0,
}}
>
<Stack
component="form"
gap={theme.spacing(12)}
noValidate
spellCheck="false"
>
<ConfigBox>
<Box>
<Typography component="h1">Client Settings</Typography>
<Typography sx={{ mt: theme.spacing(2) }}>
Modify client settings here
</Typography>
</Box>
<Stack gap={theme.spacing(20)}>
<Field
id="apiBaseUrl"
label="API URL Host"
value={localSettings.apiBaseUrl}
onChange={handleChange}
/>
<Select
id="logLevel"
label="logLevel"
name="logLevel"
items={logItems}
value={logItemLookup[localSettings.logLevel]}
onChange={handleLogLevel}
/>
</Stack>
</ConfigBox>
<ConfigBox>
<Box>
<Typography component="h1">Email Settings</Typography>
<Typography sx={{ mt: theme.spacing(2) }}>
Set your host email settings here. These settings are used for
sending system emails
</Typography>
</Box>
<Stack gap={theme.spacing(20)}>
<Field
type="text"
id="systemEmailHost"
label="Email Host"
name="systemEmailHost"
value={localSettings.systemEmailHost}
onChange={handleChange}
/>
<Field
type="number"
id="systemEmailPort"
label="System Email Address"
name="systemEmailPort"
value={localSettings.systemEmailPort.toString()}
onChange={handleChange}
/>
<Field
type="email"
id="systemEmailAddress"
label="System Email Address"
name="systemEmailAddress"
value={localSettings.systemEmailAddress}
onChange={handleChange}
/>
<Field
type="text"
id="systemEmailPassword"
label="System Email Password"
name="systemEmailPassword"
value={localSettings.systemEmailPassword}
onChange={handleChange}
/>
</Stack>
</ConfigBox>
<ConfigBox>
<Box>
<Typography component="h1">Server Settings</Typography>
<Typography sx={{ mt: theme.spacing(2) }}>
Modify server settings here
</Typography>
</Box>
<Stack gap={theme.spacing(20)}>
<Field
type="text"
id="jwtTTL"
label="JWT Time To Live"
name="jwtTTL"
value={localSettings.jwtTTL}
onChange={handleChange}
/>
<Field
type="text"
id="dbType"
label="Database Type"
name="dbType"
value={localSettings.dbType}
onChange={handleChange}
/>
<Field
type="text"
id="redisHost"
label="Redis Host"
name="redisHost"
value={localSettings.redisHost}
onChange={handleChange}
/>
<Field
type="number"
id="redisPort"
label="Redis Port"
name="redisPort"
value={localSettings.redisPort.toString()}
onChange={handleChange}
/>
<Field
type="text"
id="pagespeedApiKey"
label="PageSpeed API Key"
name="pagespeedApiKey"
value={localSettings.pagespeedApiKey}
onChange={handleChange}
/>
</Stack>
</ConfigBox>
<ConfigBox>
<Box>
<Typography component="h1">About</Typography>
</Box>
<Box>
<Typography component="h2">BlueWave Uptime v1.0.0</Typography>
<Typography
sx={{ mt: theme.spacing(2), mb: theme.spacing(6), opacity: 0.6 }}
>
Developed by Bluewave Labs.
</Typography>
<Link
level="secondary"
url="https://github.com/bluewave-labs"
label="https://github.com/bluewave-labs"
/>
</Box>
</ConfigBox>
<Stack direction="row" justifyContent="flex-end">
<LoadingButton
loading={settings.isLoading || settings.authIsLoading}
variant="contained"
color="primary"
sx={{ px: theme.spacing(12), mt: theme.spacing(20) }}
onClick={handleSave}
>
Save
</LoadingButton>
</Stack>
</Stack>
</Box>
);
};
AdvancedSettings.propTypes = {
isAdmin: PropTypes.bool,
};
export default AdvancedSettings;
+1 -1
View File
@@ -134,7 +134,7 @@ const Monitors = ({ isAdmin }) => {
</Box>
<Box width="25%" minWidth={150} ml="auto">
<Search
options={monitorState?.monitorsSummary.monitors}
options={monitorState?.monitorsSummary?.monitors ?? []}
filteredBy="name"
value={search}
handleInputChange={handleSearch}
+1 -1
View File
@@ -54,7 +54,7 @@ const PageSpeedDetails = ({ isAdmin }) => {
normalize: null,
});
setMonitor(res?.data?.data ?? {});
setAudits(res?.data?.data?.checks?.[0]?.audits ?? []);
setAudits(res?.data?.data?.checks?.[0]?.audits ?? {});
} catch (error) {
logger.error(logger);
navigate("/not-found", { replace: true });
+26 -2
View File
@@ -1,5 +1,5 @@
import { useTheme } from "@emotion/react";
import { Box, Stack, Typography } from "@mui/material";
import { Box, Stack, Typography, Button } from "@mui/material";
import Field from "../../Components/Inputs/Field";
import Link from "../../Components/Link";
import Select from "../../Components/Inputs/Select";
@@ -21,6 +21,7 @@ import { useState } from "react";
import { ConfigBox } from "./styled";
import { networkService } from "../../main";
import { settingsValidation } from "../../Validation/validation";
import { useNavigate } from "react-router";
const SECONDS_PER_DAY = 86400;
@@ -33,10 +34,11 @@ const Settings = ({ isAdmin }) => {
const { timezone } = useSelector((state) => state.ui);
const [checksIsLoading, setChecksIsLoading] = useState(false);
const [form, setForm] = useState({
ttl: (checkTTL / SECONDS_PER_DAY).toString(),
ttl: checkTTL ? (checkTTL / SECONDS_PER_DAY).toString() : 0,
});
const [errors, setErrors] = useState({});
const dispatch = useDispatch();
const navigate = useNavigate();
const handleChange = (event) => {
const { value, id } = event.target;
@@ -249,6 +251,28 @@ const Settings = ({ isAdmin }) => {
</Stack>
</ConfigBox>
)}
{isAdmin && (
<ConfigBox>
<Box>
<Typography component="h1">Advanced Settings</Typography>
<Typography sx={{ mt: theme.spacing(2) }}>
Click here to modify advanced settings
</Typography>
</Box>
<Stack gap={theme.spacing(20)}>
<Box>
<Button
variant="contained"
onClick={() => {
navigate("/advanced-settings");
}}
>
Advanced Settings
</Button>
</Box>
</Stack>
</ConfigBox>
)}
<ConfigBox>
<Box>
<Typography component="h1">About</Typography>
+19 -3
View File
@@ -1,6 +1,16 @@
const LOG_LEVEL = import.meta.env.VITE_APP_LOG_LEVEL;
import store from "../store";
const LOG_LEVEL = import.meta.env.VITE_APP_LOG_LEVEL || "debug";
class Logger {
constructor(logLevel) {
constructor() {
let logLevel = LOG_LEVEL;
this.unsubscribe = store.subscribe(() => {
const state = store.getState();
logLevel = state.settings.logLevel || "debug";
this.updateLogLevel(logLevel);
});
}
updateLogLevel(logLevel) {
const NO_OP = () => {};
if (logLevel === "none") {
@@ -25,6 +35,12 @@ class Logger {
}
this.log = console.log.bind(console);
}
cleanup() {
if (this.unsubscribe) {
this.unsubscribe();
}
}
}
export const logger = new Logger(LOG_LEVEL);
export const logger = new Logger();
+62 -2
View File
@@ -1,10 +1,18 @@
import axios from "axios";
const BASE_URL = import.meta.env.VITE_APP_API_BASE_URL;
const BASE_URL =
import.meta.env.VITE_APP_API_BASE_URL || "http://localhost:5000/api/v1";
import { logger } from "./Logger";
class NetworkService {
constructor(store) {
this.store = store;
this.axiosInstance = axios.create({ baseURL: BASE_URL });
let baseURL = BASE_URL;
this.axiosInstance = axios.create();
this.setBaseUrl(baseURL);
this.unsubscribe = store.subscribe(() => {
const state = store.getState();
baseURL = state.settings.apiBaseUrl || BASE_URL;
this.setBaseUrl(baseURL);
});
this.axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
@@ -17,6 +25,16 @@ class NetworkService {
);
}
setBaseUrl = (url) => {
this.axiosInstance.defaults.baseURL = url;
};
cleanup() {
if (this.unsubscribe) {
this.unsubscribe();
}
}
/**
*
* ************************************
@@ -619,6 +637,48 @@ class NetworkService {
}
);
}
/**
* ************************************
* Get app settings
* ************************************
*
* @async
* @param {Object} config - The configuration object.
* @param {string} config.authToken - The authorization token to be used in the request header.
* @returns {Promise<AxiosResponse>} The response from the axios GET request.
*
*/
async getAppSettings(config) {
return this.axiosInstance.get("/settings", {
headers: {
Authorization: `Bearer ${config.authToken}`,
"Content-Type": "application/json",
},
});
}
/**
*
* ************************************
* Create a new monitor
* ************************************
*
* @async
* @param {Object} config - The configuration object.
* @param {string} config.authToken - The authorization token to be used in the request header.
* @param {Object} config.settings - The monitor object to be sent in the request body.
* @returns {Promise<AxiosResponse>} The response from the axios POST request.
*/
async updateAppSettings(config) {
return this.axiosInstance.put(`/settings`, config.settings, {
headers: {
Authorization: `Bearer ${config.authToken}`,
"Content-Type": "application/json",
},
});
}
}
export default NetworkService;
+4 -1
View File
@@ -4,6 +4,7 @@ import uptimeMonitorsReducer from "./Features/UptimeMonitors/uptimeMonitorsSlice
import pageSpeedMonitorReducer from "./Features/PageSpeedMonitor/pageSpeedMonitorSlice";
import authReducer from "./Features/Auth/authSlice";
import uiReducer from "./Features/UI/uiSlice";
import settingsReducer from "./Features/Settings/settingsSlice";
import storage from "redux-persist/lib/storage";
import { persistReducer, persistStore, createTransform } from "redux-persist";
@@ -21,7 +22,7 @@ const authTransform = createTransform(
const persistConfig = {
key: "root",
storage,
whitielist: ["auth", "monitors", "pageSpeed", "ui"],
whitelist: ["auth", "monitors", "pageSpeed", "ui", "settings"],
transforms: [authTransform],
};
@@ -30,6 +31,7 @@ const rootReducer = combineReducers({
auth: authReducer,
pageSpeedMonitors: pageSpeedMonitorReducer,
ui: uiReducer,
settings: settingsReducer,
});
const persistedReducer = persistReducer(persistConfig, rootReducer);
@@ -49,3 +51,4 @@ export const store = configureStore({
});
export const persistor = persistStore(store);
export default store;
Vendored Executable
+24
View File
@@ -0,0 +1,24 @@
#!/bin/bash
# Change directory to root Server directory for correct Docker Context
cd ../..
#Client
client="./Docker/dist/client.Dockerfile"
# MongoDB
mongoDB="./Docker/dist/mongoDB.Dockerfile"
# Redis
redis="./Docker/dist/redis.Dockerfile"
# Server
server="./Docker/dist/server.Dockerfile"
docker build -f $client -t dist_uptime_client .
docker build -f $mongoDB -t dist_uptime_database_mongo .
docker build -f $redis -t dist_uptime_redis .
docker build -f $server -t dist_uptime_server .
echo "All images built"
+19
View File
@@ -0,0 +1,19 @@
FROM node:20-alpine as build
WORKDIR /app
COPY ./Client/package*.json ./
RUN npm install
COPY ./Client .
RUN npm run build
FROM nginx:1.27.1-alpine
COPY ./Docker/nginx/conf.d/dist_default.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist /usr/share/nginx/html
CMD ["nginx", "-g", "daemon off;"]
@@ -1,22 +1,21 @@
services:
client:
image: bluewaveuptime/uptime_client:latest
image: bluewaveuptime/uptime_client:test
ports:
- "80:80"
- "443:443"
depends_on:
- server
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d/:ro
server:
image: bluewaveuptime/uptime_server:latest
image: bluewaveuptime/uptime_server:test
ports:
- "5000:5000"
env_file:
- server.env
depends_on:
- redis
- mongodb
environment:
- DB_CONNECTION_STRING=mongodb://mongodb:27017/uptime_db
- REDIS_HOST=redis
redis:
image: bluewaveuptime/uptime_redis:latest
ports:
@@ -25,10 +24,8 @@ services:
- ./redis/data:/data
mongodb:
image: bluewaveuptime/uptime_database_mongo:latest
command: ["mongod", "--quiet", "--auth"]
ports:
- "27017:27017"
volumes:
- ./mongo/data:/data/db
env_file:
- mongo.env
command: ["mongod", "--quiet"]
ports:
- "27017:27017"
+3
View File
@@ -0,0 +1,3 @@
FROM mongo
EXPOSE 27017
CMD ["mongod"]
+2
View File
@@ -0,0 +1,2 @@
FROM redis
EXPOSE 6379
+13
View File
@@ -0,0 +1,13 @@
FROM node:20-alpine
WORKDIR /app
COPY ./Server/package*.json ./
RUN npm install
COPY ./Server/ ./
EXPOSE 5000
CMD ["node", "index.js"]
+1 -1
View File
@@ -40,4 +40,4 @@ services:
- ./mongo/data:/data/db
- ./mongo/init/create_users.js:/docker-entrypoint-initdb.d/create_users.js
env_file:
- mongo.env
- mongo.env
+35
View File
@@ -0,0 +1,35 @@
server {
listen 80;
listen [::]:80;
server_name uptime-demo.bluewavelabs.ca;
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://server:5000/api/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /api-docs/ {
proxy_pass http://server:5000/api-docs/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
-123
View File
@@ -1,123 +0,0 @@
default_server_base_url="http://localhost:5000/api/v1"
default_client_host="http://localhost"
default_jwt_secret="my_secret"
default_db_type="MongoDB"
default_redis_host="redis"
default_redis_port=6379
default_token_ttl="99d"
default_db_username="uptime_user"
default_db_password="uptime_password"
default_system_email_host="smtp.gmail.com"
default_system_email_port=465
echo "Welcome to the Uptime Monitor Setup Script! \n"
echo
echo "Configuring server"
printf '%*s\n' "${COLUMNS:-$(tput cols)}" '' | tr ' ' '*'
echo
db_type=$default_db_type
redis_host=$default_redis_host
redis_port=$default_redis_port
jwt_secret=$default_jwt_secret
read -p "Enter a username for your database [$default_db_username]: " db_username
db_username="${db_username:-$default_db_username}"
read -p "Enter a password for your database [$default_db_password]: " db_password
db_password="${db_password:-$default_db_password}"
read -p "Enter your system email host [$default_system_email_host]: " system_email_host
system_email_host="${system_email_host:-$default_system_email_host}"
echo "System email host: $system_email_host"
echo
read -p "Enter your DB connection string [$default_db_connection_string]: " db_connection_string
db_connection_string="mongodb://${db_username}:${db_password}@mongodb:27017/uptime_db"
echo "DB connection string: $db_connection_string"
echo
read -p "Enter your system email port [$default_system_email_port]: " system_email_port
system_email_port="${system_email_port:-$default_system_email_port}"
echo "System email port: $system_email_port"
echo
read -p "Enter your system email address: " system_email_address
echo "System email address: $system_email_address"
echo
read -p "Enter your system email password: " system_email_password
echo "System email password: $system_email_password"
echo
read -p "Enter your Token TTL [$default_token_ttl]: " token_ttl
token_ttl="${token_ttl:-$default_token_ttl}"
echo "Token TTL: $token_ttl"
echo
read -p "Enter your Pagespeed API key: " pagespeed_api_key
echo "Pagespeed API key: $pagespeed_api_key"
echo
echo "Writing to ./server.env"
echo
{
echo "CLIENT_HOST=\"$client_host\""
echo "JWT_SECRET=\"$jwt_secret\""
echo "DB_TYPE=\"$db_type\""
echo "DB_CONNECTION_STRING=\"$db_connection_string\""
echo "REDIS_HOST=\"$redis_host\""
echo "REDIS_PORT=$redis_port"
echo "SYSTEM_EMAIL_HOST=\"$system_email_host\""
echo "SYSTEM_EMAIL_PORT=$system_email_port"
echo "SYSTEM_EMAIL_ADDRESS=\"$system_email_address\""
echo "SYSTEM_EMAIL_PASSWORD=\"$system_email_password\""
echo "TOKEN_TTL=\"$token_ttl\""
echo "PAGESPEED_API_KEY=\"$pagespeed_api_key\""
} > ./server.env
echo
{
echo USERNAME_ENV_VAR=${db_username}
echo PASSWORD_ENV_VAR=${db_password}
} > ./mongo.env
mkdir -p ./nginx/conf.d/
cat <<EOL > ./nginx/conf.d/default.conf
server {
listen 80;
listen [::]:80;
server_name uptime-demo.bluewavelabs.ca;
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files \$uri \$uri/ /index.html;
}
location /api/ {
proxy_pass http://server:5000/api/;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
}
EOL
+6 -6
View File
@@ -72,7 +72,7 @@ Also check other developer and contributor-friendly projects of BlueWave:
- [BlueWave HRM](https://github.com/bluewave-labs/bluewave-hrm)
- [BlueWave Onboarding](https://github.com/bluewave-labs/bluewave-onboarding)
- [BlueWave DataRoom](https://github.com/bluewave-labs/bluewave-dataroom)
- [BlueWave ChatFabrica](https://github.com/bluewave-labs/bluewave-chatfabrica)
- [VerifyWise](https://github.com/bluewave-labs/verifywise)
## Getting Started
@@ -154,7 +154,7 @@ USERNAME_ENV_VAR=user
PASSWORD_ENV_VAR=password
```
3. In the `Docker` directory, create a `server.env` file with the [requried environtmental variables](#env-vars-server) for the server. Sample file:
3. In the `Docker` directory, create a `server.env` file with the [requried environmental variables](#env-vars-server) for the server. Sample file:
```
CLIENT_HOST="http://localhost:5173"
@@ -171,7 +171,7 @@ SYSTEM_EMAIL_ADDRESS=<system_email>
SYSTEM_EMAIL_PASSWORD=<system_email_password>
```
4. In the `Client` directory, create a `client.env` file with the [required environtmental variables](#env-vars-client) for the client. Sample file:
4. In the `Client` directory, create a `client.env` file with the [required environmental variables](#env-vars-client) for the client. Sample file:
```
VITE_APP_API_BASE_URL="http://localhost:5000/api/v1"
@@ -246,10 +246,10 @@ Configure the server with the following environmental variables:
##### Databases <a id="databases"></a>
This project requires a number of databases to run:
This project requires two databases:
1. Main database for the application. This project includes an implementation for a MongoDB database as well as a MongoDB Docker image.
2. A Redis database is required for the Queue implementation in the PingService. This project includes a Redis docker image.
1. **Main Application Database:** The project uses MongoDB for its primary database, with a MongoDB Docker image provided for easy setup.
2. **Redis for Queue Management:** A Redis database is used for the PingServices queue system, and a Redis Docker image is included for deployment.
You may use the included Dockerfiles to spin up databases quickly if you wish.
+1 -1
View File
@@ -1,4 +1,4 @@
const PORT = process.env.PORT || 5000;
const PORT = 5000;
const connectDbAndRunServer = async (app, db) => {
try {
+45 -26
View File
@@ -16,8 +16,9 @@ const logger = require("../utils/logger");
require("dotenv").config();
const { errorMessages, successMessages } = require("../utils/messages");
var jwt = require("jsonwebtoken");
const SERVICE_NAME = "auth";
const SERVICE_NAME = "AuthController";
const { getTokenFromHeaders } = require("../utils/utils");
const crypto = require("crypto");
/**
* Creates and returns JWT token with an arbitrary payload
@@ -25,10 +26,15 @@ const { getTokenFromHeaders } = require("../utils/utils");
* @param {Object} payload
* @returns {String}
*/
const issueToken = (payload) => {
//TODO Add proper expiration date
const tokenTTL = process.env.TOKEN_TTL ? process.env.TOKEN_TTL : "2h";
return jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: tokenTTL });
const issueToken = (payload, appSettings) => {
try {
const tokenTTL = appSettings.jwtTTL ? appSettings.jwtTTL : "2h";
return jwt.sign(payload, appSettings.jwtSecret, { expiresIn: tokenTTL });
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "issueToken") : null;
next(error);
}
};
/**
@@ -56,7 +62,12 @@ const registerController = async (req, res, next) => {
const superAdminExists = await req.db.checkSuperadmin(req, res);
if (superAdminExists) {
await req.db.getInviteTokenAndDelete(inviteToken);
} else {
// This is the first account, create JWT secret to use if one is not supplied by env
const jwtSecret = crypto.randomBytes(64).toString("hex");
await req.db.updateAppSettings({ jwtSecret });
}
const newUser = await req.db.insertUser({ ...req.body }, req.file);
logger.info(successMessages.AUTH_CREATE_USER, {
service: SERVICE_NAME,
@@ -67,8 +78,8 @@ const registerController = async (req, res, next) => {
delete userForToken.profileImage;
delete userForToken.avatarImage;
const token = issueToken(userForToken);
const appSettings = await req.settingsService.getSettings();
const token = issueToken(userForToken, appSettings);
req.emailService
.buildAndSendEmail(
"welcomeEmailTemplate",
@@ -130,8 +141,8 @@ const loginController = async (req, res, next) => {
delete userWithoutPassword.avatarImage;
// Happy path, return token
const token = issueToken(userWithoutPassword);
const appSettings = req.settingsService.getSettings();
const token = issueToken(userWithoutPassword, appSettings);
// reset avatar image
userWithoutPassword.avatarImage = user.avatarImage;
@@ -180,7 +191,8 @@ const userEditController = async (req, res, next) => {
// Get token from headers
const token = getTokenFromHeaders(req.headers);
// Get email from token
const { email } = jwt.verify(token, process.env.JWT_SECRET);
const { jwtSecret } = req.settingsService.getSettings();
const { email } = jwt.verify(token, jwtSecret);
// Add user email to body for DB operation
req.body.email = email;
// Get user
@@ -230,12 +242,13 @@ const inviteController = async (req, res, next) => {
}
const inviteToken = await req.db.requestInviteToken(req, res);
const { clientHost } = req.settingsService.getSettings();
req.emailService
.buildAndSendEmail(
"employeeActivationTemplate",
{
name: firstname,
link: `${process.env.CLIENT_HOST}/register/${inviteToken.token}`,
link: `${clientHost}/register/${inviteToken.token}`,
},
req.body.email,
"Welcome to Uptime Monitor"
@@ -339,7 +352,8 @@ const recoveryRequestController = async (req, res, next) => {
const recoveryToken = await req.db.requestRecoveryToken(req, res);
const name = user.firstName;
const email = req.body.email;
const url = `${process.env.CLIENT_HOST}/set-new-password/${recoveryToken.token}`;
const { clientHost } = req.settingsService.getSettings();
const url = `${clientHost}/set-new-password/${recoveryToken.token}`;
const msgId = await req.emailService.buildAndSendEmail(
"passwordResetTemplate",
@@ -425,7 +439,9 @@ const resetPasswordController = async (req, res, next) => {
}
try {
const user = await req.db.resetPassword(req, res);
const token = issueToken(user._doc);
const appSettings = await req.settingsService.getSettings();
const token = issueToken(user._doc, appSettings);
res.status(200).json({
success: true,
msg: successMessages.AUTH_RESET_PASSWORD,
@@ -466,23 +482,26 @@ const deleteUserController = async (req, res, next) => {
const result = await req.db.getMonitorsByTeamId({
params: { teamId: user.teamId },
});
if (user.role.includes("superadmin") && result?.monitors.length > 0) {
if (user.role.includes("superadmin")) {
//2. Remove all jobs, delete checks and alerts
await Promise.all(
monitors.map(async (monitor) => {
await req.jobQueue.deleteJob(monitor);
result?.monitors.length > 0 &&
(await Promise.all(
result.monitors.map(async (monitor) => {
await req.jobQueue.deleteJob(monitor);
await req.db.deleteChecks(monitor._id);
await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
await req.db.deleteNotificationsByMonitorId(monitor._id);
})
));
await req.db.deleteChecks(monitor._id);
await req.db.deleteAlertByMonitorId(monitor._id);
await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
await req.db.deleteNotificationsByMonitorId(monitor._id);
})
);
// 3. Delete each monitor
// 3. Delete team
await req.db.deleteTeam(user.teamId);
// 4. Delete all other team members
await req.db.deleteAllOtherUsers();
// 5. Delete each monitor
await req.db.deleteMonitorsByUserId(user._id);
}
// 4. Delete the user by id
// 6. Delete the user by id
await req.db.deleteUser(user._id);
return res.status(200).json({
+6 -5
View File
@@ -34,7 +34,7 @@ const createCheck = async (req, res, next) => {
.status(200)
.json({ success: true, msg: successMessages.CHECK_CREATE, data: check });
} catch (error) {
error.service === undefined ? (error.serivce = SERVICE_NAME) : null;
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "createCheck") : null;
next(error);
}
@@ -62,7 +62,7 @@ const getChecks = async (req, res, next) => {
data: { checksCount, checks },
});
} catch (error) {
error.service === undefined ? (error.serivce = SERVICE_NAME) : null;
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "getChecks") : null;
next(error);
}
@@ -88,7 +88,7 @@ const getTeamChecks = async (req, res, next) => {
data: checkData,
});
} catch (error) {
error.service === undefined ? (error.serivce = SERVICE_NAME) : null;
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "getTeamChecks") : null;
next(error);
}
@@ -114,7 +114,7 @@ const deleteChecks = async (req, res, next) => {
data: { deletedCount },
});
} catch (error) {
error.service === undefined ? (error.serivce = SERVICE_NAME) : null;
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "deleteChecks") : null;
next(error);
}
@@ -163,7 +163,8 @@ const updateChecksTTL = async (req, res, next) => {
try {
// Get user's teamId
const token = getTokenFromHeaders(req.headers);
const { teamId } = jwt.verify(token, process.env.JWT_SECRET);
const { jwtSecret } = req.settingsService.getSettings();
const { teamId } = jwt.verify(token, jwtSecret);
const ttl = parseInt(req.body.ttl, 10) * SECONDS_PER_DAY;
await req.db.updateChecksTTL(teamId, ttl);
return res.status(200).json({
+2 -1
View File
@@ -39,12 +39,13 @@ const inviteController = async (req, res, next) => {
}
const inviteToken = await req.db.requestInviteToken({ ...req.body });
const { clientHost } = req.settingsService.getSettings();
req.emailService
.buildAndSendEmail(
"employeeActivationTemplate",
{
name: firstname,
link: `${process.env.CLIENT_HOST}/register/${inviteToken.token}`,
link: `${clientHost}/register/${inviteToken.token}`,
},
req.body.email,
"Welcome to Uptime Monitor"
+21 -13
View File
@@ -20,6 +20,7 @@ const SERVICE_NAME = "monitorController";
const { errorMessages, successMessages } = require("../utils/messages");
const jwt = require("jsonwebtoken");
const { getTokenFromHeaders } = require("../utils/utils");
const logger = require("../utils/logger");
/**
* Returns all monitors
@@ -315,18 +316,22 @@ const deleteMonitor = async (req, res, next) => {
try {
const monitor = await req.db.deleteMonitor(req, res, next);
// Delete associated checks,alerts,and notifications
await req.jobQueue.deleteJob(monitor);
await req.db.deleteChecks(monitor._id);
await req.db.deleteAlertByMonitorId(monitor._id);
await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
await req.db.deleteNotificationsByMonitorId(monitor._id);
try {
await req.jobQueue.deleteJob(monitor);
await req.db.deleteChecks(monitor._id);
await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
await req.db.deleteNotificationsByMonitorId(monitor._id);
} catch (error) {
logger.error(
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`,
{
method: "deleteMonitor",
service: SERVICE_NAME,
error: error.message,
}
);
}
/**
* TODO
* We should remove all checks and alerts associated with this monitor
* when it is deleted so there is no orphaned data
* We also need to make sure to stop all running services for this monitor
*/
return res
.status(200)
.json({ success: true, msg: successMessages.MONITOR_DELETE });
@@ -340,7 +345,8 @@ const deleteMonitor = async (req, res, next) => {
const deleteAllMonitors = async (req, res) => {
try {
const token = getTokenFromHeaders(req.headers);
const { teamId } = jwt.verify(token, process.env.JWT_SECRET);
const { jwtSecret } = req.settingsService.getSettings();
const { teamId } = jwt.verify(token, jwtSecret);
const { monitors, deletedCount } = await req.db.deleteAllMonitors(teamId);
await monitors.forEach(async (monitor) => {
await req.jobQueue.deleteJob(monitor);
@@ -455,7 +461,9 @@ const pauseMonitor = async (req, res, next) => {
const addDemoMonitors = async (req, res, next) => {
try {
const token = getTokenFromHeaders(req.headers);
const { _id, teamId } = jwt.verify(token, process.env.JWT_SECRET);
const { jwtSecret } = req.settingsService.getSettings();
const { _id, teamId } = jwt.verify(token, jwtSecret);
const demoMonitors = await req.db.addDemoMonitors(_id, teamId);
await demoMonitors.forEach(async (monitor) => {
await req.jobQueue.addJob(monitor._id, monitor);
+52
View File
@@ -0,0 +1,52 @@
const { successMessages } = require("../utils/messages");
const SERVICE_NAME = "SettingsController";
const { updateAppSettingsBodyValidation } = require("../validation/joi");
const getAppSettings = async (req, res, next) => {
try {
const settings = { ...(await req.settingsService.getSettings()) };
delete settings.jwtSecret;
return res.status(200).json({
success: true,
msg: successMessages.GET_APP_SETTINGS,
data: settings,
});
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "getAppSettings") : null;
next(error);
}
};
const updateAppSettings = async (req, res, next) => {
try {
await updateAppSettingsBodyValidation.validateAsync(req.body);
} catch (error) {
error.status = 422;
error.service = SERVICE_NAME;
error.message =
error.details?.[0]?.message || error.message || "Validation Error";
next(error);
return;
}
try {
await req.db.updateAppSettings(req.body);
const updatedSettings = { ...(await req.settingsService.reloadSettings()) };
delete updatedSettings.jwtSecret;
return res.status(200).json({
success: true,
msg: successMessages.UPDATE_APP_SETTINGS,
data: updatedSettings,
});
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "updateAppSettings") : null;
next(error);
}
};
module.exports = {
getAppSettings,
updateAppSettings,
};
+1 -1
View File
@@ -21,7 +21,7 @@
// **************************
const Monitor = require("../models/Monitor");
const UserModel = require("../models/user");
const UserModel = require("../models/User");
const bcrypt = require("bcrypt");
let FAKE_MONITOR_DATA = [];
+29 -2
View File
@@ -1,5 +1,6 @@
const mongoose = require("mongoose");
const UserModel = require("../../models/user");
const UserModel = require("../../models/User");
const AppSettings = require("../../models/AppSettings");
//****************************************
// DB Connection
@@ -7,7 +8,16 @@ const UserModel = require("../../models/user");
const connect = async () => {
try {
await mongoose.connect(process.env.DB_CONNECTION_STRING);
const connectionString =
process.env.DB_CONNECTION_STRING || "mongodb://localhost:27017/uptime_db";
await mongoose.connect(connectionString);
// If there are no AppSettings, create one
let appSettings = await AppSettings.find();
if (appSettings.length === 0) {
appSettings = new AppSettings({});
await appSettings.save();
}
console.log("Connected to MongoDB");
} catch (error) {
console.error("Failed to connect to MongoDB");
@@ -36,6 +46,8 @@ const {
getUserByEmail,
updateUser,
deleteUser,
deleteTeam,
deleteAllOtherUsers,
getAllUsers,
logoutUser,
} = require("./modules/userModule");
@@ -113,18 +125,31 @@ const {
deleteMaintenanceWindowByUserId,
} = require("./modules/maintenaceWindowModule");
//****************************************
// Notifications
//****************************************
const {
createNotification,
getNotificationsByMonitorId,
deleteNotificationsByMonitorId,
} = require("./modules/notificationModule");
//****************************************
// AppSettings
//****************************************
const {
getAppSettings,
updateAppSettings,
} = require("./modules/settingsModule");
module.exports = {
connect,
insertUser,
getUserByEmail,
updateUser,
deleteUser,
deleteTeam,
deleteAllOtherUsers,
getAllUsers,
logoutUser,
requestInviteToken,
@@ -164,4 +189,6 @@ module.exports = {
createNotification,
getNotificationsByMonitorId,
deleteNotificationsByMonitorId,
getAppSettings,
updateAppSettings,
};
+1 -1
View File
@@ -1,6 +1,6 @@
const Check = require("../../../models/Check");
const Monitor = require("../../../models/Monitor");
const User = require("../../../models/user");
const User = require("../../../models/User");
const logger = require("../../../utils/logger");
const SERVICE_NAME = "checkModule";
const dateRangeLookup = {
+1 -1
View File
@@ -1,4 +1,4 @@
const UserModel = require("../../../models/user");
const UserModel = require("../../../models/User");
const RecoveryToken = require("../../../models/RecoveryToken");
const crypto = require("crypto");
const { errorMessages } = require("../../../utils/messages");
+33
View File
@@ -0,0 +1,33 @@
const AppSettings = require("../../../models/AppSettings");
const SERVICE_NAME = "SettingsModule";
const getAppSettings = async () => {
try {
const settings = AppSettings.findOne();
return settings;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getSettings";
throw error;
}
};
const updateAppSettings = async (newSettings) => {
try {
const settings = await AppSettings.findOneAndUpdate(
{},
{ $set: newSettings },
{ new: true }
);
return settings;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "updateAppSettings";
throw error;
}
};
module.exports = {
getAppSettings,
updateAppSettings,
};
+31 -1
View File
@@ -1,4 +1,4 @@
const UserModel = require("../../../models/user");
const UserModel = require("../../../models/User");
const TeamModel = require("../../../models/Team");
const { errorMessages } = require("../../../utils/messages");
const { GenerateAvatarImage } = require("../../../utils/imageProcessing");
@@ -35,6 +35,7 @@ const insertUser = async (userData, imageFile) => {
email: userData.email,
});
userData.teamId = team._id;
userData.checkTTL = 60 * 60 * 24 * 30;
await team.save();
}
@@ -156,6 +157,33 @@ const deleteUser = async (userId) => {
}
};
/**
* Delete a user by ID
* @async
* @param {string} teamId
* @returns {void}
* @throws {Error}
*/
const deleteTeam = async (teamId) => {
try {
await TeamModel.findByIdAndDelete(teamId);
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteTeam";
throw error;
}
};
const deleteAllOtherUsers = async () => {
try {
await UserModel.deleteMany({ role: { $ne: "superadmin" } });
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteAllOtherUsers";
throw error;
}
};
const getAllUsers = async (req, res) => {
try {
const users = await UserModel.find()
@@ -185,6 +213,8 @@ module.exports = {
getUserByEmail,
updateUser,
deleteUser,
deleteTeam,
deleteAllOtherUsers,
getAllUsers,
logoutUser,
};
+18 -7
View File
@@ -5,7 +5,6 @@ const swaggerUi = require("swagger-ui-express");
const express = require("express");
const helmet = require("helmet");
const cors = require("cors");
require("dotenv").config();
const logger = require("./utils/logger");
const { verifyJWT } = require("./middleware/verifyJWT");
const { handleErrors } = require("./middleware/handleErrors");
@@ -15,6 +14,7 @@ const inviteRouter = require("./routes/inviteRoute");
const monitorRouter = require("./routes/monitorRoute");
const checkRouter = require("./routes/checkRoute");
const maintenanceWindowRouter = require("./routes/maintenanceWindowRoute");
const settingsRouter = require("./routes/settingsRoute");
const { connectDbAndRunServer } = require("./configs/db");
const queueRouter = require("./routes/queueRoute");
@@ -22,6 +22,7 @@ const JobQueue = require("./service/jobQueue");
const NetworkService = require("./service/networkService");
const EmailService = require("./service/emailService");
const PageSpeedService = require("./service/pageSpeedService");
const SettingsService = require("./service/settingsService");
const SERVICE_NAME = "Server";
let cleaningUp = false;
@@ -47,9 +48,11 @@ const startApp = async () => {
FakedDB: () => require("./db/FakeDb"),
};
const db = DB_TYPE[process.env.DB_TYPE]
? DB_TYPE[process.env.DB_TYPE]()
: require("./db/FakeDb");
// const db = DB_TYPE[process.env.DB_TYPE]
// ? DB_TYPE[process.env.DB_TYPE]()
// : require("./db/FakeDb");
const db = DB_TYPE.MongoDB();
const app = express();
@@ -60,7 +63,6 @@ const startApp = async () => {
);
app.use(express.json());
app.use(helmet());
// **************************
// Make DB accessible anywhere we have a Request object
// By adding the DB to the request object, we can access it in any route
@@ -72,6 +74,7 @@ const startApp = async () => {
req.jobQueue = jobQueue;
req.emailService = emailService;
req.pageSpeedService = pageSpeedService;
req.settingsService = settingsService;
next();
});
@@ -80,6 +83,7 @@ const startApp = async () => {
//routes
app.use("/api/v1/auth", authRouter);
app.use("/api/v1/settings", verifyJWT, settingsRouter);
app.use("/api/v1/invite", inviteRouter);
app.use("/api/v1/monitors", verifyJWT, monitorRouter);
app.use("/api/v1/checks", verifyJWT, checkRouter);
@@ -122,9 +126,16 @@ const startApp = async () => {
// Create services
await connectDbAndRunServer(app, db);
const emailService = new EmailService();
const settingsService = new SettingsService();
await settingsService.loadSettings();
const emailService = new EmailService(settingsService);
const networkService = new NetworkService(db, emailService);
const jobQueue = await JobQueue.createJobQueue(db, networkService);
const jobQueue = await JobQueue.createJobQueue(
db,
networkService,
settingsService
);
const pageSpeedService = new PageSpeedService();
const cleanup = async () => {
+2 -1
View File
@@ -27,7 +27,8 @@ const isAllowed = (allowedRoles) => {
// Parse the token
try {
const parsedToken = token.slice(TOKEN_PREFIX.length, token.length);
var decoded = jwt.verify(parsedToken, process.env.JWT_SECRET);
const { jwtSecret } = req.settingsService.getSettings();
var decoded = jwt.verify(parsedToken, jwtSecret);
const userRoles = decoded.role;
// Check if the user has the required role
+3 -2
View File
@@ -4,7 +4,7 @@ const SERVICE_NAME = "verifyJWT";
const TOKEN_PREFIX = "Bearer ";
const { errorMessages } = require("../utils/messages");
const { parse } = require("path");
const User = require("../models/user");
const User = require("../models/User");
/**
* Verifies the JWT token
* @function
@@ -35,7 +35,8 @@ const verifyJWT = (req, res, next) => {
const parsedToken = token.slice(TOKEN_PREFIX.length, token.length);
// Verify the token's authenticity
jwt.verify(parsedToken, process.env.JWT_SECRET, (err, decoded) => {
const { jwtSecret } = req.settingsService.getSettings();
jwt.verify(parsedToken, jwtSecret, (err, decoded) => {
if (err) {
return res
.status(401)
+3 -1
View File
@@ -33,7 +33,9 @@ const verifySuperAdmin = (req, res, next) => {
const parsedToken = token.slice(TOKEN_PREFIX.length, token.length);
// verify admin role is present
jwt.verify(parsedToken, process.env.JWT_SECRET, (err, decoded) => {
const { jwtSecret } = req.settingsService.getSettings();
jwt.verify(parsedToken, jwtSecret, (err, decoded) => {
if (err) {
logger.error(errorMessages.INVALID_AUTH_TOKEN, {
service: SERVICE_NAME,
+81
View File
@@ -0,0 +1,81 @@
const mongoose = require("mongoose");
const AppSettingsSchema = mongoose.Schema(
{
apiBaseUrl: {
type: String,
required: true,
default: "http://localhost:5000/api/v1",
},
logLevel: {
type: String,
default: "debug",
enum: ["debug", "none", "error", "warn"],
},
clientHost: {
type: String,
required: true,
default: "http://localhost:5173",
},
jwtSecret: {
type: String,
required: true,
default: "my_secret",
},
dbType: {
type: String,
required: true,
default: "MongoDB",
},
dbConnectionString: {
type: String,
required: true,
default: "mongodb://localhost:27017/uptime_db",
},
redisHost: {
type: String,
required: true,
default: "127.0.0.1",
},
redisPort: {
type: Number,
default: "6379",
},
jwtTTL: {
type: String,
required: true,
default: "99d",
},
pagespeedApiKey: {
type: String,
default: "",
},
systemEmailHost: {
type: String,
default: "smtp.gmail.com",
},
systemEmailPort: {
type: Number,
default: 465,
},
systemEmailAddress: {
type: String,
default: "",
},
systemEmailPassword: {
type: String,
default: "",
},
singleton: {
type: Boolean,
required: true,
unique: true,
default: true,
},
},
{
timestamps: true,
}
);
module.exports = mongoose.model("AppSettings", AppSettingsSchema);
+90 -46
View File
@@ -1024,9 +1024,10 @@
}
},
"node_modules/body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"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==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.5",
@@ -1036,7 +1037,7 @@
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"qs": "6.13.0",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
@@ -1150,6 +1151,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
@@ -1453,6 +1455,7 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
@@ -1704,6 +1707,7 @@
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
@@ -1757,6 +1761,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
@@ -1765,6 +1770,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
"license": "MIT",
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
@@ -1861,7 +1867,8 @@
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.4.832",
@@ -1879,9 +1886,10 @@
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
},
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
@@ -1959,7 +1967,8 @@
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
"node_modules/escape-string-regexp": {
"version": "1.0.5",
@@ -1973,41 +1982,43 @@
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express": {
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
"integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.2",
"body-parser": "1.20.3",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.6.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "1.2.0",
"finalhandler": "1.3.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"merge-descriptors": "1.0.1",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"path-to-regexp": "0.1.10",
"proxy-addr": "~2.0.7",
"qs": "6.11.0",
"qs": "6.13.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "0.18.0",
"serve-static": "1.15.0",
"send": "0.19.0",
"serve-static": "1.16.2",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"type-is": "~1.6.18",
@@ -2040,12 +2051,13 @@
}
},
"node_modules/finalhandler": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
@@ -2131,6 +2143,7 @@
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
@@ -2443,6 +2456,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
"license": "MIT",
"dependencies": {
"depd": "2.0.0",
"inherits": "2.0.4",
@@ -2491,6 +2505,7 @@
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
@@ -2962,9 +2977,13 @@
"integrity": "sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g=="
},
"node_modules/merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/methods": {
"version": "1.1.2",
@@ -2978,6 +2997,7 @@
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"license": "MIT",
"bin": {
"mime": "cli.js"
},
@@ -3611,7 +3631,8 @@
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/msgpackr": {
"version": "1.10.2",
@@ -3896,6 +3917,7 @@
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
@@ -3979,6 +4001,7 @@
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
@@ -4015,9 +4038,10 @@
}
},
"node_modules/path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
"license": "MIT"
},
"node_modules/picocolors": {
"version": "1.0.1",
@@ -4634,11 +4658,12 @@
}
},
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.0.4"
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
@@ -4651,6 +4676,7 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
@@ -4659,6 +4685,7 @@
"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==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
@@ -4778,7 +4805,8 @@
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/semver": {
"version": "7.6.2",
@@ -4792,9 +4820,10 @@
}
},
"node_modules/send": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
@@ -4814,20 +4843,31 @@
"node": ">= 0.8.0"
}
},
"node_modules/send/node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/serve-static": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"license": "MIT",
"dependencies": {
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.18.0"
"send": "0.19.0"
},
"engines": {
"node": ">= 0.8.0"
@@ -4857,7 +4897,8 @@
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/sharp": {
"version": "0.33.4",
@@ -5051,6 +5092,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
@@ -5241,6 +5283,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
@@ -5323,6 +5366,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
+1 -1
View File
@@ -4,7 +4,7 @@ const { verifyOwnership } = require("../middleware/verifyOwnership");
const { isAllowed } = require("../middleware/isAllowed");
const multer = require("multer");
const upload = multer();
const User = require("../models/user");
const User = require("../models/User");
const {
registerController,
+13
View File
@@ -0,0 +1,13 @@
const router = require("express").Router();
const settingsController = require("../controllers/settingsController");
const { isAllowed } = require("../middleware/isAllowed");
const Monitor = require("../models/Monitor");
router.get("/", isAllowed(["superadmin"]), settingsController.getAppSettings);
router.put(
"/",
isAllowed(["superadmin"]),
settingsController.updateAppSettings
);
module.exports = router;
+19 -8
View File
@@ -13,7 +13,8 @@ class EmailService {
/**
* Constructs an instance of the EmailService, initializing template loaders and the email transporter.
*/
constructor() {
constructor(settingsService) {
this.settingsService = settingsService;
/**
* Loads an email template from the filesystem.
*
@@ -54,15 +55,25 @@ class EmailService {
* The email transporter used to send emails.
* @type {Object}
*/
this.transporter = nodemailer.createTransport({
host: process.env.SYSTEM_EMAIL_HOST,
port: process.env.SYSTEM_EMAIL_PORT,
secure: true, // Use `true` for port 465, `false` for all other ports
const {
systemEmailHost,
systemEmailPort,
systemEmailAddress,
systemEmailPassword,
} = this.settingsService.getSettings();
const emailConfig = {
host: systemEmailHost,
port: systemEmailPort,
secure: true,
auth: {
user: process.env.SYSTEM_EMAIL_ADDRESS,
pass: process.env.SYSTEM_EMAIL_PASSWORD,
user: systemEmailAddress,
pass: systemEmailPassword,
},
});
};
this.transporter = nodemailer.createTransport(emailConfig);
}
/**
+12 -8
View File
@@ -1,9 +1,6 @@
const { Queue, Worker, Job } = require("bullmq");
const QUEUE_NAME = "monitors";
const connection = {
host: process.env.REDIS_HOST || "127.0.0.1",
port: process.env.REDIS_PORT || 6379,
};
const JOBS_PER_WORKER = 5;
const logger = require("../utils/logger");
const { errorMessages, successMessages } = require("../utils/messages");
@@ -15,13 +12,20 @@ class JobQueue {
* @constructor
* @throws {Error}
*/
constructor(networkService) {
constructor(settingsService) {
const { redisHost, redisPort } = settingsService.getSettings();
const connection = {
host: redisHost || "127.0.0.1",
port: redisPort || 6379,
};
this.connection = connection;
this.queue = new Queue(QUEUE_NAME, {
connection,
});
this.workers = [];
this.db = null;
this.networkService = null;
this.settingsService = settingsService;
}
/**
@@ -31,8 +35,8 @@ class JobQueue {
* @returns {Promise<JobQueue>} - Returns a new JobQueue
*
*/
static async createJobQueue(db, networkService) {
const queue = new JobQueue();
static async createJobQueue(db, networkService, settingsService) {
const queue = new JobQueue(settingsService);
try {
queue.db = db;
queue.networkService = networkService;
@@ -99,7 +103,7 @@ class JobQueue {
}
},
{
connection,
connection: this.connection,
}
);
return worker;
+9 -2
View File
@@ -44,10 +44,17 @@ class NetworkService {
}
async handleStatusUpdate(job, isAlive) {
let monitor;
// Look up the monitor, if it doesn't exist, it's probably been removed, return
try {
const { _id } = job.data;
const monitor = await this.db.getMonitorById(_id);
monitor = await this.db.getMonitorById(_id);
} catch (error) {
return;
}
// Otherwise, try to update monitor status
try {
if (monitor === null || monitor === undefined) {
logger.error(`Null Monitor: ${_id}`, {
method: "handleStatusUpdate",
@@ -90,7 +97,7 @@ class NetworkService {
const endTime = Date.now();
error.responseTime = endTime - startTime;
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.mheotd === undefined
error.method === undefined
? (error.method = "measureResponseTime")
: null;
throw error;
+62
View File
@@ -0,0 +1,62 @@
const { env } = require("process");
const AppSettings = require("../models/AppSettings");
const SERVICE_NAME = "SettingsService";
const envConfig = {
logLevel: undefined,
apiBaseUrl: undefined,
clientHost: process.env.CLIENT_HOST,
jwtSecret: process.env.JWT_SECRET,
dbType: process.env.DB_TYPE,
dbConnectionString: process.env.DB_CONNECTION_STRING,
redisHost: process.env.REDIS_HOST,
redisPort: process.env.REDIS_PORT,
jwtTTL: process.env.TOKEN_TTL,
pagespeedApiKey: process.env.PAGESPEED_API_KEY,
systemEmailHost: process.env.SYSTEM_EMAIL_HOST,
systemEmailPort: process.env.SYSTEM_EMAIL_PORT,
systemEmailAddress: process.env.SYSTEM_EMAIL_ADDRESS,
systemEmailPassword: process.env.SYSTEM_EMAIL_PASSWORD,
};
class SettingsService {
constructor() {
this.settings = { ...envConfig };
}
async loadSettings() {
try {
const dbSettings = await AppSettings.findOne();
if (!this.settings) {
throw new Error("Settings not found");
}
// Try to load settings from env first, if not found, load from db
for (const key in envConfig) {
if (envConfig[key] === undefined && dbSettings[key] !== undefined) {
this.settings[key] = dbSettings[key];
}
}
if (!this.settings) {
throw new Error("Settings not found");
}
return this.settings;
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "loadSettings") : null;
throw error;
}
}
async reloadSettings() {
return this.loadSettings();
}
getSettings() {
if (!this.settings) {
throw new Error("Settings have not been loaded");
}
return this.settings;
}
}
module.exports = SettingsService;
+4
View File
@@ -98,6 +98,10 @@ const successMessages = {
//Ping Operations
PING_SUCCESS: "Success",
// App Settings
GET_APP_SETTINGS: "Got app settings successfully",
UPDATE_APP_SETTINGS: "Updated app settings successfully",
};
module.exports = {
+20
View File
@@ -382,6 +382,25 @@ const getMaintenanceWindowsByMonitorIdParamValidation = joi.object({
monitorId: joi.string().required(),
});
//****************************************
// SettingsValidation
//****************************************
const updateAppSettingsBodyValidation = joi.object({
apiBaseUrl: joi.string().allow(""),
logLevel: joi.string().valid("debug", "none", "error", "warn").allow(""),
clientHost: joi.string().allow(""),
dbType: joi.string().allow(""),
dbConnectionString: joi.string().allow(""),
redisHost: joi.string().allow(""),
redisPort: joi.number().allow(null, ""),
jwtTTL: joi.string().allow(""),
pagespeedApiKey: joi.string().allow(""),
systemEmailHost: joi.string().allow(""),
systemEmailPort: joi.number().allow(""),
systemEmailAddress: joi.string().allow(""),
systemEmailPassword: joi.string().allow(""),
});
module.exports = {
roleValidatior,
loginValidation,
@@ -432,4 +451,5 @@ module.exports = {
createMaintenanceWindowBodyValidation,
getMaintenanceWindowsByUserIdParamValidation,
getMaintenanceWindowsByMonitorIdParamValidation,
updateAppSettingsBodyValidation,
};