mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-01-28 04:39:38 -06:00
Merge branch 'develop' into 913-fe-advanced-settings-page-validation-and-error-handling
This commit is contained in:
329
Client/package-lock.json
generated
329
Client/package-lock.json
generated
@@ -59,12 +59,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
|
||||
"integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
|
||||
"version": "7.25.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz",
|
||||
"integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/highlight": "^7.24.7",
|
||||
"@babel/highlight": "^7.25.7",
|
||||
"picocolors": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -72,30 +72,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.24.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.9.tgz",
|
||||
"integrity": "sha512-e701mcfApCJqMMueQI0Fb68Amflj83+dvAvHawoBpAz+GDjCIyGHzNwnefjsWJ3xiYAqqiQFoWbspGYBdb2/ng==",
|
||||
"version": "7.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.8.tgz",
|
||||
"integrity": "sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/core": {
|
||||
"version": "7.24.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.9.tgz",
|
||||
"integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==",
|
||||
"version": "7.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.8.tgz",
|
||||
"integrity": "sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.24.7",
|
||||
"@babel/generator": "^7.24.9",
|
||||
"@babel/helper-compilation-targets": "^7.24.8",
|
||||
"@babel/helper-module-transforms": "^7.24.9",
|
||||
"@babel/helpers": "^7.24.8",
|
||||
"@babel/parser": "^7.24.8",
|
||||
"@babel/template": "^7.24.7",
|
||||
"@babel/traverse": "^7.24.8",
|
||||
"@babel/types": "^7.24.9",
|
||||
"@babel/code-frame": "^7.25.7",
|
||||
"@babel/generator": "^7.25.7",
|
||||
"@babel/helper-compilation-targets": "^7.25.7",
|
||||
"@babel/helper-module-transforms": "^7.25.7",
|
||||
"@babel/helpers": "^7.25.7",
|
||||
"@babel/parser": "^7.25.8",
|
||||
"@babel/template": "^7.25.7",
|
||||
"@babel/traverse": "^7.25.7",
|
||||
"@babel/types": "^7.25.8",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.2",
|
||||
@@ -117,29 +117,29 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.24.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.10.tgz",
|
||||
"integrity": "sha512-o9HBZL1G2129luEUlG1hB4N/nlYNWHnpwlND9eOMclRqqu1YDy2sSYVCFUZwl8I1Gxh+QSRrP2vD7EpUmFVXxg==",
|
||||
"version": "7.25.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz",
|
||||
"integrity": "sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.24.9",
|
||||
"@babel/types": "^7.25.7",
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"jsesc": "^2.5.1"
|
||||
"jsesc": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-compilation-targets": {
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.8.tgz",
|
||||
"integrity": "sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw==",
|
||||
"version": "7.25.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz",
|
||||
"integrity": "sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.24.8",
|
||||
"@babel/helper-validator-option": "^7.24.8",
|
||||
"browserslist": "^4.23.1",
|
||||
"@babel/compat-data": "^7.25.7",
|
||||
"@babel/helper-validator-option": "^7.25.7",
|
||||
"browserslist": "^4.24.0",
|
||||
"lru-cache": "^5.1.1",
|
||||
"semver": "^6.3.1"
|
||||
},
|
||||
@@ -147,67 +147,29 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-environment-visitor": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz",
|
||||
"integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.24.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-function-name": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz",
|
||||
"integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.24.7",
|
||||
"@babel/types": "^7.24.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-hoist-variables": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz",
|
||||
"integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.24.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-imports": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz",
|
||||
"integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==",
|
||||
"version": "7.25.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz",
|
||||
"integrity": "sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/traverse": "^7.24.7",
|
||||
"@babel/types": "^7.24.7"
|
||||
"@babel/traverse": "^7.25.7",
|
||||
"@babel/types": "^7.25.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-transforms": {
|
||||
"version": "7.24.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.9.tgz",
|
||||
"integrity": "sha512-oYbh+rtFKj/HwBQkFlUzvcybzklmVdVV3UU+mN7n2t/q3yGHbuVdNxyFvSBO1tfvjyArpHNcWMAzsSPdyI46hw==",
|
||||
"version": "7.25.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz",
|
||||
"integrity": "sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-environment-visitor": "^7.24.7",
|
||||
"@babel/helper-module-imports": "^7.24.7",
|
||||
"@babel/helper-simple-access": "^7.24.7",
|
||||
"@babel/helper-split-export-declaration": "^7.24.7",
|
||||
"@babel/helper-validator-identifier": "^7.24.7"
|
||||
"@babel/helper-module-imports": "^7.25.7",
|
||||
"@babel/helper-simple-access": "^7.25.7",
|
||||
"@babel/helper-validator-identifier": "^7.25.7",
|
||||
"@babel/traverse": "^7.25.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -227,77 +189,65 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-simple-access": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz",
|
||||
"integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==",
|
||||
"version": "7.25.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz",
|
||||
"integrity": "sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/traverse": "^7.24.7",
|
||||
"@babel/types": "^7.24.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-split-export-declaration": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz",
|
||||
"integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.24.7"
|
||||
"@babel/traverse": "^7.25.7",
|
||||
"@babel/types": "^7.25.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
|
||||
"integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
|
||||
"version": "7.25.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz",
|
||||
"integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
|
||||
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
|
||||
"version": "7.25.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz",
|
||||
"integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-option": {
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz",
|
||||
"integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==",
|
||||
"version": "7.25.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz",
|
||||
"integrity": "sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.8.tgz",
|
||||
"integrity": "sha512-gV2265Nkcz7weJJfvDoAEVzC1e2OTDpkGbEsebse8koXUJUXPsCMi7sRo/+SPMuMZ9MtUPnGwITTnQnU5YjyaQ==",
|
||||
"version": "7.25.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.7.tgz",
|
||||
"integrity": "sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.24.7",
|
||||
"@babel/types": "^7.24.8"
|
||||
"@babel/template": "^7.25.7",
|
||||
"@babel/types": "^7.25.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
|
||||
"integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
|
||||
"version": "7.25.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz",
|
||||
"integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"@babel/helper-validator-identifier": "^7.25.7",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.0.0"
|
||||
@@ -307,10 +257,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.8.tgz",
|
||||
"integrity": "sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==",
|
||||
"version": "7.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.8.tgz",
|
||||
"integrity": "sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.25.8"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
@@ -363,33 +316,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz",
|
||||
"integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==",
|
||||
"version": "7.25.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz",
|
||||
"integrity": "sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.24.7",
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@babel/types": "^7.24.7"
|
||||
"@babel/code-frame": "^7.25.7",
|
||||
"@babel/parser": "^7.25.7",
|
||||
"@babel/types": "^7.25.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.8.tgz",
|
||||
"integrity": "sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ==",
|
||||
"version": "7.25.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.7.tgz",
|
||||
"integrity": "sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.24.7",
|
||||
"@babel/generator": "^7.24.8",
|
||||
"@babel/helper-environment-visitor": "^7.24.7",
|
||||
"@babel/helper-function-name": "^7.24.7",
|
||||
"@babel/helper-hoist-variables": "^7.24.7",
|
||||
"@babel/helper-split-export-declaration": "^7.24.7",
|
||||
"@babel/parser": "^7.24.8",
|
||||
"@babel/types": "^7.24.8",
|
||||
"@babel/code-frame": "^7.25.7",
|
||||
"@babel/generator": "^7.25.7",
|
||||
"@babel/parser": "^7.25.7",
|
||||
"@babel/template": "^7.25.7",
|
||||
"@babel/types": "^7.25.7",
|
||||
"debug": "^4.3.1",
|
||||
"globals": "^11.1.0"
|
||||
},
|
||||
@@ -398,13 +348,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.24.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.9.tgz",
|
||||
"integrity": "sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==",
|
||||
"version": "7.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.8.tgz",
|
||||
"integrity": "sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.24.8",
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"@babel/helper-string-parser": "^7.25.7",
|
||||
"@babel/helper-validator-identifier": "^7.25.7",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -992,9 +942,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "8.57.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
|
||||
"integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
|
||||
"version": "8.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
|
||||
"integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1061,14 +1011,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.11.14",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
|
||||
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
|
||||
"integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
|
||||
"deprecated": "Use @eslint/config-array instead",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@humanwhocodes/object-schema": "^2.0.2",
|
||||
"@humanwhocodes/object-schema": "^2.0.3",
|
||||
"debug": "^4.3.1",
|
||||
"minimatch": "^3.0.5"
|
||||
},
|
||||
@@ -2396,15 +2346,15 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz",
|
||||
"integrity": "sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==",
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.3.tgz",
|
||||
"integrity": "sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.24.5",
|
||||
"@babel/plugin-transform-react-jsx-self": "^7.24.5",
|
||||
"@babel/plugin-transform-react-jsx-source": "^7.24.1",
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/plugin-transform-react-jsx-self": "^7.24.7",
|
||||
"@babel/plugin-transform-react-jsx-source": "^7.24.7",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"react-refresh": "^0.14.2"
|
||||
},
|
||||
@@ -2700,9 +2650,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.23.2",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz",
|
||||
"integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==",
|
||||
"version": "4.24.2",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz",
|
||||
"integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -2719,10 +2669,10 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001640",
|
||||
"electron-to-chromium": "^1.4.820",
|
||||
"node-releases": "^2.0.14",
|
||||
"update-browserslist-db": "^1.1.0"
|
||||
"caniuse-lite": "^1.0.30001669",
|
||||
"electron-to-chromium": "^1.5.41",
|
||||
"node-releases": "^2.0.18",
|
||||
"update-browserslist-db": "^1.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"browserslist": "cli.js"
|
||||
@@ -2773,9 +2723,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001642",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz",
|
||||
"integrity": "sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==",
|
||||
"version": "1.0.30001669",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz",
|
||||
"integrity": "sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -3224,9 +3174,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.829",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.829.tgz",
|
||||
"integrity": "sha512-5qp1N2POAfW0u1qGAxXEtz6P7bO1m6gpZr5hdf5ve6lxpLM7MpiM4jIPz7xcrNlClQMafbyUDDWjlIQZ1Mw0Rw==",
|
||||
"version": "1.5.41",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.41.tgz",
|
||||
"integrity": "sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/entities": {
|
||||
@@ -3455,9 +3405,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
|
||||
"integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -3476,17 +3426,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "8.57.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
|
||||
"integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
|
||||
"version": "8.57.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
|
||||
"integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
|
||||
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.6.1",
|
||||
"@eslint/eslintrc": "^2.1.4",
|
||||
"@eslint/js": "8.57.0",
|
||||
"@humanwhocodes/config-array": "^0.11.14",
|
||||
"@eslint/js": "8.57.1",
|
||||
"@humanwhocodes/config-array": "^0.13.0",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@nodelib/fs.walk": "^1.2.8",
|
||||
"@ungap/structured-clone": "^1.2.0",
|
||||
@@ -3579,9 +3530,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react-refresh": {
|
||||
"version": "0.4.8",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.8.tgz",
|
||||
"integrity": "sha512-MIKAclwaDFIiYtVBLzDdm16E+Ty4GwhB6wZlCAG1R3Ur+F9Qbo6PRxpA5DK7XtDgm+WlCoAY2WxAwqhmIDHg6Q==",
|
||||
"version": "0.4.13",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.13.tgz",
|
||||
"integrity": "sha512-f1EppwrpJRWmqDTyvAyomFVDYRtrS7iTEqv3nokETnMiMzs2SSTmKRTACce4O2p4jYyowiSMvpdwC/RLcMFhuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
@@ -4766,15 +4717,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
|
||||
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
|
||||
"integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jsesc": "bin/jsesc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/json-buffer": {
|
||||
@@ -5006,9 +4957,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.17",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.17.tgz",
|
||||
"integrity": "sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA==",
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
|
||||
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
@@ -6212,9 +6163,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
|
||||
"integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
|
||||
"integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -6231,8 +6182,8 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"escalade": "^3.1.2",
|
||||
"picocolors": "^1.0.1"
|
||||
"escalade": "^3.2.0",
|
||||
"picocolors": "^1.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"update-browserslist-db": "cli.js"
|
||||
|
||||
@@ -38,6 +38,7 @@ const SearchAdornment = () => {
|
||||
);
|
||||
};
|
||||
|
||||
//TODO keep search state inside of component
|
||||
const Search = ({
|
||||
id,
|
||||
options,
|
||||
|
||||
@@ -12,47 +12,45 @@ import { useNavigate } from "react-router";
|
||||
import { getAppSettings, updateAppSettings } from "../../Features/Settings/settingsSlice";
|
||||
import { useState, useEffect } from "react";
|
||||
import Select from "../../Components/Inputs/Select";
|
||||
import { advancedSettingsValidation } from "../../Validation/validation";
|
||||
import { buildErrors, hasValidationErrors } from "../../Validation/error";
|
||||
|
||||
const AdvancedSettings = ({ isAdmin }) => {
|
||||
const navigate = useNavigate();
|
||||
useEffect(() => {
|
||||
if (!isAdmin) {
|
||||
navigate("/");
|
||||
}
|
||||
}, [navigate, isAdmin]);
|
||||
const navigate = useNavigate();
|
||||
|
||||
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: "",
|
||||
});
|
||||
const [errors, setErrors] = useState({});
|
||||
useEffect(() => {
|
||||
if (!isAdmin) {
|
||||
navigate("/");
|
||||
}
|
||||
}, [navigate, isAdmin]);
|
||||
|
||||
useEffect(() => {
|
||||
const getSettings = async () => {
|
||||
const action = await dispatch(getAppSettings({ authToken }));
|
||||
if (action.payload?.success) {
|
||||
setLocalSettings(action.payload.data);
|
||||
} else {
|
||||
createToast({ body: "Failed to get settings" });
|
||||
}
|
||||
};
|
||||
getSettings();
|
||||
}, [authToken, dispatch]);
|
||||
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) {
|
||||
setLocalSettings(action.payload.data);
|
||||
} else {
|
||||
createToast({ body: "Failed to get settings" });
|
||||
}
|
||||
};
|
||||
getSettings();
|
||||
}, [authToken, dispatch]);
|
||||
|
||||
const logItems = [
|
||||
{ _id: 1, name: "none" },
|
||||
@@ -74,210 +72,189 @@ const AdvancedSettings = ({ isAdmin }) => {
|
||||
setLocalSettings({ ...localSettings, logLevel: newLogLevel });
|
||||
};
|
||||
|
||||
const handleChange = (event) => {
|
||||
const { value, id } = event.target;
|
||||
setLocalSettings({ ...localSettings, [id]: value });
|
||||
const { error } = advancedSettingsValidation.validate(
|
||||
{ [id]: value },
|
||||
{
|
||||
abortEarly: false,
|
||||
}
|
||||
);
|
||||
setErrors((prev) => {
|
||||
return buildErrors(prev, id, error);
|
||||
});
|
||||
};
|
||||
const handleChange = (event) => {
|
||||
const { value, id } = event.target;
|
||||
setLocalSettings({ ...localSettings, [id]: value });
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (hasValidationErrors(localSettings, advancedSettingsValidation, setErrors))
|
||||
return;
|
||||
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 });
|
||||
};
|
||||
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}
|
||||
error={errors.apiBaseUrl}
|
||||
/>
|
||||
<Select
|
||||
id="logLevel"
|
||||
label="Log level"
|
||||
name="logLevel"
|
||||
items={logItems}
|
||||
value={logItemLookup[localSettings.logLevel]}
|
||||
onChange={handleLogLevel}
|
||||
error={errors.logLevel}
|
||||
/>
|
||||
</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}
|
||||
error={errors.systemEmailHost}
|
||||
/>
|
||||
<Field
|
||||
type="number"
|
||||
id="systemEmailPort"
|
||||
label="System email address"
|
||||
name="systemEmailPort"
|
||||
value={localSettings.systemEmailPort.toString()}
|
||||
onChange={handleChange}
|
||||
error={errors.systemEmailPort}
|
||||
/>
|
||||
<Field
|
||||
type="email"
|
||||
id="systemEmailAddress"
|
||||
label="System email address"
|
||||
name="systemEmailAddress"
|
||||
value={localSettings.systemEmailAddress}
|
||||
onChange={handleChange}
|
||||
error={errors.systemEmailAddress}
|
||||
/>
|
||||
<Field
|
||||
type="text"
|
||||
id="systemEmailPassword"
|
||||
label="System email password"
|
||||
name="systemEmailPassword"
|
||||
value={localSettings.systemEmailPassword}
|
||||
onChange={handleChange}
|
||||
error={errors.systemEmailPassword}
|
||||
/>
|
||||
</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}
|
||||
error={errors.jwtTTL}
|
||||
/>
|
||||
<Field
|
||||
type="text"
|
||||
id="dbType"
|
||||
label="Database type"
|
||||
name="dbType"
|
||||
value={localSettings.dbType}
|
||||
onChange={handleChange}
|
||||
error={errors.dbType}
|
||||
/>
|
||||
<Field
|
||||
type="text"
|
||||
id="redisHost"
|
||||
label="Redis host"
|
||||
name="redisHost"
|
||||
value={localSettings.redisHost}
|
||||
onChange={handleChange}
|
||||
error={errors.redisHost}
|
||||
/>
|
||||
<Field
|
||||
type="number"
|
||||
id="redisPort"
|
||||
label="Redis port"
|
||||
name="redisPort"
|
||||
value={localSettings.redisPort.toString()}
|
||||
onChange={handleChange}
|
||||
error={errors.redisPort}
|
||||
/>
|
||||
<Field
|
||||
type="text"
|
||||
id="pagespeedApiKey"
|
||||
label="PageSpeed API key"
|
||||
name="pagespeedApiKey"
|
||||
value={localSettings.pagespeedApiKey}
|
||||
onChange={handleChange}
|
||||
error={errors.pagespeedApiKey}
|
||||
/>
|
||||
</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>
|
||||
);
|
||||
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="Log level"
|
||||
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 = {
|
||||
|
||||
32
Client/src/Pages/Incidents/IncidentTable/Empty/Empty.jsx
Normal file
32
Client/src/Pages/Incidents/IncidentTable/Empty/Empty.jsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import PlaceholderLight from "../../../../assets/Images/data_placeholder.svg?react";
|
||||
import PlaceholderDark from "../../../../assets/Images/data_placeholder_dark.svg?react";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const Empty = ({ styles, mode }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Box sx={{ ...styles }}>
|
||||
<Box
|
||||
textAlign="center"
|
||||
pb={theme.spacing(20)}
|
||||
>
|
||||
{mode === "light" ? <PlaceholderLight /> : <PlaceholderDark />}
|
||||
</Box>
|
||||
<Typography
|
||||
textAlign="center"
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
No incidents recorded yet.
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Empty.propTypes = {
|
||||
styles: PropTypes.object,
|
||||
mode: PropTypes.string,
|
||||
};
|
||||
|
||||
export { Empty };
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Skeleton /* , Stack */ } from "@mui/material";
|
||||
const IncidentSkeleton = () => {
|
||||
return (
|
||||
<>
|
||||
<Skeleton
|
||||
animation={"wave"}
|
||||
variant="rounded"
|
||||
width="100%"
|
||||
height={300}
|
||||
/>
|
||||
<Skeleton
|
||||
animation={"wave"}
|
||||
variant="rounded"
|
||||
width="100%"
|
||||
height={100}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export { IncidentSkeleton };
|
||||
@@ -24,6 +24,8 @@ import { useTheme } from "@emotion/react";
|
||||
import { formatDateWithTz } from "../../../Utils/timeUtils";
|
||||
import PlaceholderLight from "../../../assets/Images/data_placeholder.svg?react";
|
||||
import PlaceholderDark from "../../../assets/Images/data_placeholder_dark.svg?react";
|
||||
import { Empty } from "./Empty/Empty";
|
||||
import { IncidentSkeleton } from "./Skeleton/Skeleton";
|
||||
|
||||
const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
|
||||
const uiTimezone = useSelector((state) => state.ui.timezone);
|
||||
@@ -37,6 +39,7 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
|
||||
page: 0,
|
||||
rowsPerPage: 14,
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
setPaginationController((prevPaginationController) => ({
|
||||
@@ -51,6 +54,7 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setIsLoading(true);
|
||||
let res;
|
||||
if (selectedMonitor === "0") {
|
||||
res = await networkService.getChecksByTeam({
|
||||
@@ -79,6 +83,8 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
|
||||
setChecksCount(res.data.data.checksCount);
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
fetchPage();
|
||||
@@ -129,24 +135,20 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
|
||||
p: theme.spacing(30),
|
||||
};
|
||||
|
||||
const hasChecks = checks?.length === 0;
|
||||
const noIncidentsRecordedYet = hasChecks && selectedMonitor === "0";
|
||||
const noIncidentsForThatMonitor = hasChecks && selectedMonitor !== "0";
|
||||
|
||||
return (
|
||||
<>
|
||||
{checks?.length === 0 && selectedMonitor === "0" ? (
|
||||
<Box sx={{ ...sharedStyles }}>
|
||||
<Box
|
||||
textAlign="center"
|
||||
pb={theme.spacing(20)}
|
||||
>
|
||||
{mode === "light" ? <PlaceholderLight /> : <PlaceholderDark />}
|
||||
</Box>
|
||||
<Typography
|
||||
textAlign="center"
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
No incidents recorded yet.
|
||||
</Typography>
|
||||
</Box>
|
||||
) : checks?.length === 0 ? (
|
||||
{isLoading ? (
|
||||
<IncidentSkeleton />
|
||||
) : noIncidentsRecordedYet ? (
|
||||
<Empty
|
||||
mode={mode}
|
||||
styles={sharedStyles}
|
||||
/>
|
||||
) : noIncidentsForThatMonitor ? (
|
||||
<Box sx={{ ...sharedStyles }}>
|
||||
<Box
|
||||
textAlign="center"
|
||||
|
||||
@@ -17,56 +17,62 @@ const Incidents = () => {
|
||||
|
||||
const [monitors, setMonitors] = useState({});
|
||||
const [selectedMonitor, setSelectedMonitor] = useState("0");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
// TODO do something with these filters
|
||||
const [filter, setFilter] = useState("all");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMonitors = async () => {
|
||||
setLoading(true);
|
||||
const res = await networkService.getMonitorsByTeamId({
|
||||
authToken: authState.authToken,
|
||||
teamId: authState.user.teamId,
|
||||
limit: -1,
|
||||
types: null,
|
||||
status: null,
|
||||
checkOrder: null,
|
||||
normalize: null,
|
||||
page: null,
|
||||
rowsPerPage: null,
|
||||
filter: null,
|
||||
field: null,
|
||||
order: null,
|
||||
});
|
||||
// Reduce to a lookup object for 0(1) lookup
|
||||
if (res?.data?.data?.monitors?.length > 0) {
|
||||
const monitorLookup = res.data.data.monitors.reduce((acc, monitor) => {
|
||||
acc[monitor._id] = monitor;
|
||||
return acc;
|
||||
}, {});
|
||||
setMonitors(monitorLookup);
|
||||
monitorId !== undefined && setSelectedMonitor(monitorId);
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const res = await networkService.getMonitorsByTeamId({
|
||||
authToken: authState.authToken,
|
||||
teamId: authState.user.teamId,
|
||||
limit: -1,
|
||||
types: null,
|
||||
status: null,
|
||||
checkOrder: null,
|
||||
normalize: null,
|
||||
page: null,
|
||||
rowsPerPage: null,
|
||||
filter: null,
|
||||
field: null,
|
||||
order: null,
|
||||
});
|
||||
// Reduce to a lookup object for 0(1) lookup
|
||||
if (res?.data?.data?.monitors?.length > 0) {
|
||||
const monitorLookup = res.data.data.monitors.reduce((acc, monitor) => {
|
||||
acc[monitor._id] = monitor;
|
||||
return acc;
|
||||
}, {});
|
||||
setMonitors(monitorLookup);
|
||||
monitorId !== undefined && setSelectedMonitor(monitorId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.info(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
fetchMonitors();
|
||||
}, [authState]);
|
||||
|
||||
useEffect(() => {}, []);
|
||||
useEffect(() => {}, [monitors]);
|
||||
|
||||
const handleSelect = (event) => {
|
||||
setSelectedMonitor(event.target.value);
|
||||
};
|
||||
|
||||
const isActuallyLoading = isLoading && Object.keys(monitors)?.length === 0;
|
||||
|
||||
return (
|
||||
<Stack
|
||||
className="incidents table-container"
|
||||
className="incidents"
|
||||
pt={theme.spacing(6)}
|
||||
gap={theme.spacing(12)}
|
||||
>
|
||||
{loading ? (
|
||||
{isActuallyLoading ? (
|
||||
<SkeletonLayout />
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
MS_PER_WEEK,
|
||||
} from "../../../Utils/timeUtils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { buildErrors, hasValidationErrors } from "../../../Validation/error";
|
||||
|
||||
const getDurationAndUnit = (durationInMs) => {
|
||||
if (durationInMs % MS_PER_DAY === 0) {
|
||||
@@ -146,36 +145,46 @@ const CreateMaintenance = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await networkService.getMaintenanceWindowById({
|
||||
authToken: authToken,
|
||||
maintenanceWindowId: maintenanceWindowId,
|
||||
});
|
||||
const maintenanceWindow = res.data.data;
|
||||
const { name, start, end, repeat, monitorId } = maintenanceWindow;
|
||||
const startTime = dayjs(start);
|
||||
const endTime = dayjs(end);
|
||||
const durationInMs = endTime.diff(startTime, "milliseconds").toString();
|
||||
const { duration, durationUnit } = getDurationAndUnit(durationInMs);
|
||||
const monitor = monitors.find((monitor) => monitor._id === monitorId);
|
||||
setForm({
|
||||
...form,
|
||||
name,
|
||||
repeat: REVERSE_REPEAT_LOOKUP[repeat],
|
||||
startDate: startTime,
|
||||
startTime,
|
||||
duration,
|
||||
durationUnit,
|
||||
monitors: monitor ? [monitor] : [],
|
||||
});
|
||||
} catch (error) {
|
||||
createToast({ body: "Failed to fetch data" });
|
||||
logger.error("Failed to fetch monitors", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
fetchMonitors();
|
||||
}, [authToken, user]);
|
||||
const res = await networkService.getMaintenanceWindowById({
|
||||
authToken: authToken,
|
||||
maintenanceWindowId: maintenanceWindowId,
|
||||
});
|
||||
const maintenanceWindow = res.data.data;
|
||||
const { name, start, end, repeat, monitorId } = maintenanceWindow;
|
||||
const startTime = dayjs(start);
|
||||
const endTime = dayjs(end);
|
||||
const durationInMs = endTime.diff(startTime, "milliseconds").toString();
|
||||
const { duration, durationUnit } = getDurationAndUnit(durationInMs);
|
||||
const monitor = monitors.find((monitor) => monitor._id === monitorId);
|
||||
setForm({
|
||||
...form,
|
||||
name,
|
||||
repeat: REVERSE_REPEAT_LOOKUP[repeat],
|
||||
startDate: startTime,
|
||||
startTime,
|
||||
duration,
|
||||
durationUnit,
|
||||
monitors: monitor ? [monitor] : [],
|
||||
});
|
||||
} catch (error) {
|
||||
createToast({ body: "Failed to fetch data" });
|
||||
logger.error("Failed to fetch monitors", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
fetchMonitors();
|
||||
}, [authToken, user]);
|
||||
|
||||
const buildErrors = (prev, id, error) => {
|
||||
const updatedErrors = { ...prev };
|
||||
if (error) {
|
||||
updatedErrors[id] = error.details[0].message;
|
||||
} else {
|
||||
delete updatedErrors[id];
|
||||
}
|
||||
return updatedErrors;
|
||||
};
|
||||
|
||||
const handleSearch = (value) => {
|
||||
setSearch(value);
|
||||
@@ -214,17 +223,29 @@ const CreateMaintenance = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if(hasValidationErrors(form, maintenanceWindowValidation, setErrors))
|
||||
return;
|
||||
// Build timestamp for maintenance window from startDate and startTime
|
||||
const start = dayjs(form.startDate)
|
||||
.set("hour", form.startTime.hour())
|
||||
.set("minute", form.startTime.minute());
|
||||
// Build end timestamp for maintenance window
|
||||
const MS_MULTIPLIER = MS_LOOKUP[form.durationUnit];
|
||||
const durationInMs = form.duration * MS_MULTIPLIER;
|
||||
const end = start.add(durationInMs);
|
||||
const handleSubmit = async () => {
|
||||
const { error } = maintenanceWindowValidation.validate(form, {
|
||||
abortEarly: false,
|
||||
});
|
||||
|
||||
// If errors, return early
|
||||
if (error) {
|
||||
const newErrors = {};
|
||||
error.details.forEach((err) => {
|
||||
newErrors[err.path[0]] = err.message;
|
||||
});
|
||||
setErrors(newErrors);
|
||||
logger.error(error);
|
||||
return;
|
||||
}
|
||||
// Build timestamp for maintenance window from startDate and startTime
|
||||
const start = dayjs(form.startDate)
|
||||
.set("hour", form.startTime.hour())
|
||||
.set("minute", form.startTime.minute());
|
||||
// Build end timestamp for maintenance window
|
||||
const MS_MULTIPLIER = MS_LOOKUP[form.durationUnit];
|
||||
const durationInMs = form.duration * MS_MULTIPLIER;
|
||||
const end = start.add(durationInMs);
|
||||
|
||||
// Get repeat value in milliseconds
|
||||
const repeat = REPEAT_LOOKUP[form.repeat];
|
||||
|
||||
79
Client/src/Pages/Monitors/Home/CurrentMonitoring/index.jsx
Normal file
79
Client/src/Pages/Monitors/Home/CurrentMonitoring/index.jsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Box, Stack, Typography } from "@mui/material";
|
||||
import Search from "../../../../Components/Inputs/Search";
|
||||
import MemoizedMonitorTable from "../MonitorTable";
|
||||
import { useState } from "react";
|
||||
import useDebounce from "../../../../Utils/debounce";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const CurrentMonitoring = ({ totalMonitors, monitors, isAdmin }) => {
|
||||
const theme = useTheme();
|
||||
const [search, setSearch] = useState("");
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const debouncedFilter = useDebounce(search, 500);
|
||||
const handleSearch = (value) => {
|
||||
setIsSearching(true);
|
||||
setSearch(value);
|
||||
};
|
||||
return (
|
||||
<Box
|
||||
flex={1}
|
||||
px={theme.spacing(10)}
|
||||
py={theme.spacing(8)}
|
||||
border={1}
|
||||
borderColor={theme.palette.border.light}
|
||||
borderRadius={theme.shape.borderRadius}
|
||||
backgroundColor={theme.palette.background.main}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
mb={theme.spacing(8)}
|
||||
>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="h2"
|
||||
fontWeight={500}
|
||||
letterSpacing={-0.2}
|
||||
>
|
||||
Actively monitoring
|
||||
</Typography>
|
||||
<Box
|
||||
className="current-monitors-counter"
|
||||
color={theme.palette.text.primary}
|
||||
border={1}
|
||||
borderColor={theme.palette.border.light}
|
||||
backgroundColor={theme.palette.background.accent}
|
||||
>
|
||||
{totalMonitors}
|
||||
</Box>
|
||||
<Box
|
||||
width="25%"
|
||||
minWidth={150}
|
||||
ml="auto"
|
||||
>
|
||||
<Search
|
||||
options={monitors}
|
||||
filteredBy="name"
|
||||
inputValue={search}
|
||||
handleInputChange={handleSearch}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
<MemoizedMonitorTable
|
||||
isAdmin={isAdmin}
|
||||
filter={debouncedFilter}
|
||||
setIsSearching={setIsSearching}
|
||||
isSearching={isSearching}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
CurrentMonitoring.propTypes = {
|
||||
totalMonitors: PropTypes.number,
|
||||
monitors: PropTypes.array,
|
||||
isAdmin: PropTypes.bool,
|
||||
};
|
||||
|
||||
export { CurrentMonitoring };
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Skeleton, TableCell, TableRow } from "@mui/material";
|
||||
const ROWS_NUMBER = 7;
|
||||
const ROWS_ARRAY = Array.from({ length: ROWS_NUMBER }, (_, i) => i);
|
||||
|
||||
const TableBodySkeleton = () => {
|
||||
return (
|
||||
<>
|
||||
{ROWS_ARRAY.map((row) => (
|
||||
<TableRow key={row}>
|
||||
<TableCell>
|
||||
<Skeleton />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Skeleton />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Skeleton />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Skeleton />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Skeleton />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export { TableBodySkeleton };
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
Stack,
|
||||
Typography,
|
||||
Button,
|
||||
CircularProgress,
|
||||
} from "@mui/material";
|
||||
import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded";
|
||||
import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded";
|
||||
@@ -34,6 +35,7 @@ import RightArrow from "../../../../assets/icons/right-arrow.svg?react";
|
||||
import SelectorVertical from "../../../../assets/icons/selector-vertical.svg?react";
|
||||
import ActionsMenu from "../actionsMenu";
|
||||
import useUtils from "../../utils";
|
||||
import { TableBodySkeleton } from "./Skeleton";
|
||||
|
||||
/**
|
||||
* Component for pagination actions (first, previous, next, last).
|
||||
@@ -107,17 +109,17 @@ TablePaginationActions.propTypes = {
|
||||
onPageChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const MonitorTable = ({ isAdmin, filter, setLoading }) => {
|
||||
const MonitorTable = ({ isAdmin, filter, setIsSearching, isSearching }) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const { determineState } = useUtils();
|
||||
|
||||
const { rowsPerPage } = useSelector((state) => state.ui.monitors);
|
||||
const authState = useSelector((state) => state.auth);
|
||||
const [page, setPage] = useState(0);
|
||||
const [monitors, setMonitors] = useState([]);
|
||||
const [monitorCount, setMonitorCount] = useState(0);
|
||||
const authState = useSelector((state) => state.auth);
|
||||
const [updateTrigger, setUpdateTrigger] = useState(false);
|
||||
const [sort, setSort] = useState({});
|
||||
const prevFilter = useRef(filter);
|
||||
@@ -160,15 +162,25 @@ const MonitorTable = ({ isAdmin, filter, setLoading }) => {
|
||||
});
|
||||
setMonitors(res?.data?.data?.monitors ?? []);
|
||||
setMonitorCount(res?.data?.data?.monitorCount ?? 0);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
} finally {
|
||||
setIsSearching(false);
|
||||
}
|
||||
}, [authState, page, rowsPerPage, filter, sort, setLoading]);
|
||||
}, [authState, page, rowsPerPage, filter, sort, setIsSearching]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchPage();
|
||||
}, [updateTrigger, authState, page, rowsPerPage, filter, sort, setLoading, fetchPage]);
|
||||
}, [
|
||||
updateTrigger,
|
||||
authState,
|
||||
page,
|
||||
rowsPerPage,
|
||||
filter,
|
||||
sort,
|
||||
setIsSearching,
|
||||
fetchPage,
|
||||
]);
|
||||
|
||||
// Listen for changes in filter, if new value reset the page
|
||||
useEffect(() => {
|
||||
@@ -220,7 +232,37 @@ const MonitorTable = ({ isAdmin, filter, setLoading }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box position="relative">
|
||||
{isSearching && (
|
||||
<>
|
||||
<Box
|
||||
width="100%"
|
||||
height="100%"
|
||||
position="absolute"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.main,
|
||||
opacity: 0.8,
|
||||
zIndex: 100,
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
height="100%"
|
||||
position="absolute"
|
||||
top="20%"
|
||||
left="50%"
|
||||
sx={{
|
||||
transform: "translateX(-50%)",
|
||||
zIndex: 101,
|
||||
}}
|
||||
>
|
||||
<CircularProgress
|
||||
sx={{
|
||||
color: theme.palette.other.icon,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
@@ -271,77 +313,82 @@ const MonitorTable = ({ isAdmin, filter, setLoading }) => {
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{monitors.map((monitor) => {
|
||||
let uptimePercentage = "";
|
||||
let percentageColor = theme.palette.percentage.uptimeExcellent;
|
||||
{/* TODO add empty state. Check if is searching, and empty => skeleton. Is empty, not searching => skeleton */}
|
||||
{monitors.length > 0 ? (
|
||||
monitors.map((monitor) => {
|
||||
let uptimePercentage = "";
|
||||
let percentageColor = theme.palette.percentage.uptimeExcellent;
|
||||
|
||||
// Determine uptime percentage and color based on the monitor's uptimePercentage value
|
||||
if (monitor.uptimePercentage !== undefined) {
|
||||
uptimePercentage =
|
||||
monitor.uptimePercentage === 0
|
||||
? "0"
|
||||
: (monitor.uptimePercentage * 100).toFixed(2);
|
||||
// Determine uptime percentage and color based on the monitor's uptimePercentage value
|
||||
if (monitor.uptimePercentage !== undefined) {
|
||||
uptimePercentage =
|
||||
monitor.uptimePercentage === 0
|
||||
? "0"
|
||||
: (monitor.uptimePercentage * 100).toFixed(2);
|
||||
|
||||
percentageColor =
|
||||
monitor.uptimePercentage < 0.25
|
||||
? theme.palette.percentage.uptimePoor
|
||||
: monitor.uptimePercentage < 0.5
|
||||
? theme.palette.percentage.uptimeFair
|
||||
: monitor.uptimePercentage < 0.75
|
||||
? theme.palette.percentage.uptimeGood
|
||||
: theme.palette.percentage.uptimeExcellent;
|
||||
}
|
||||
percentageColor =
|
||||
monitor.uptimePercentage < 0.25
|
||||
? theme.palette.percentage.uptimePoor
|
||||
: monitor.uptimePercentage < 0.5
|
||||
? theme.palette.percentage.uptimeFair
|
||||
: monitor.uptimePercentage < 0.75
|
||||
? theme.palette.percentage.uptimeGood
|
||||
: theme.palette.percentage.uptimeExcellent;
|
||||
}
|
||||
|
||||
const params = {
|
||||
url: monitor.url,
|
||||
title: monitor.name,
|
||||
percentage: uptimePercentage,
|
||||
percentageColor,
|
||||
status: determineState(monitor),
|
||||
};
|
||||
const params = {
|
||||
url: monitor.url,
|
||||
title: monitor.name,
|
||||
percentage: uptimePercentage,
|
||||
percentageColor,
|
||||
status: determineState(monitor),
|
||||
};
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
key={monitor._id}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.background.accent,
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
navigate(`/monitors/${monitor._id}`);
|
||||
}}
|
||||
>
|
||||
<TableCell>
|
||||
<Host
|
||||
key={monitor._id}
|
||||
params={params}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<StatusLabel
|
||||
status={params.status}
|
||||
text={params.status}
|
||||
customStyles={{ textTransform: "capitalize" }}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<BarChart checks={monitor.checks.slice().reverse()} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span style={{ textTransform: "uppercase" }}>{monitor.type}</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<ActionsMenu
|
||||
monitor={monitor}
|
||||
isAdmin={isAdmin}
|
||||
updateCallback={handleActionMenuDelete}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
return (
|
||||
<TableRow
|
||||
key={monitor._id}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.background.accent,
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
navigate(`/monitors/${monitor._id}`);
|
||||
}}
|
||||
>
|
||||
<TableCell>
|
||||
<Host
|
||||
key={monitor._id}
|
||||
params={params}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<StatusLabel
|
||||
status={params.status}
|
||||
text={params.status}
|
||||
customStyles={{ textTransform: "capitalize" }}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<BarChart checks={monitor.checks.slice().reverse()} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span style={{ textTransform: "uppercase" }}>{monitor.type}</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<ActionsMenu
|
||||
monitor={monitor}
|
||||
isAdmin={isAdmin}
|
||||
updateCallback={handleActionMenuDelete}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<TableBodySkeleton />
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
@@ -415,14 +462,15 @@ const MonitorTable = ({ isAdmin, filter, setLoading }) => {
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
MonitorTable.propTypes = {
|
||||
isAdmin: PropTypes.bool,
|
||||
filter: PropTypes.string,
|
||||
setLoading: PropTypes.func,
|
||||
setIsSearching: PropTypes.func,
|
||||
isSearching: PropTypes.bool,
|
||||
};
|
||||
|
||||
const MemoizedMonitorTable = memo(MonitorTable);
|
||||
|
||||
@@ -1,79 +1,74 @@
|
||||
import "./index.css";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { getUptimeMonitorsByTeamId } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Box, Button, CircularProgress, Stack, Typography } from "@mui/material";
|
||||
import { Box, Button, Stack } from "@mui/material";
|
||||
import PropTypes from "prop-types";
|
||||
import SkeletonLayout from "./skeleton";
|
||||
import Fallback from "./fallback";
|
||||
import StatusBox from "./StatusBox";
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
import Greeting from "../../../Utils/greeting";
|
||||
import MonitorTable from "./MonitorTable";
|
||||
import Search from "../../../Components/Inputs/Search";
|
||||
import useDebounce from "../../../Utils/debounce";
|
||||
import { CurrentMonitoring } from "./CurrentMonitoring";
|
||||
|
||||
const Monitors = ({ isAdmin }) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const monitorState = useSelector((state) => state.uptimeMonitors);
|
||||
const authState = useSelector((state) => state.auth);
|
||||
const [search, setSearch] = useState("");
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const dispatch = useDispatch({});
|
||||
const debouncedFilter = useDebounce(search, 500);
|
||||
|
||||
const handleSearch = (value) => {
|
||||
setIsSearching(true);
|
||||
setSearch(value);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getUptimeMonitorsByTeamId(authState.authToken));
|
||||
}, [authState.authToken, dispatch]);
|
||||
|
||||
let loading =
|
||||
monitorState?.isLoading && monitorState?.monitorsSummary?.monitors?.length === 0;
|
||||
//TODO bring fetching to this component, like on pageSpeed
|
||||
|
||||
const loading = monitorState?.isLoading;
|
||||
|
||||
const totalMonitors = monitorState?.monitorsSummary?.monitorCounts?.total;
|
||||
|
||||
const hasMonitors = totalMonitors > 0;
|
||||
const noMonitors = !hasMonitors;
|
||||
const canAddMonitor = isAdmin && hasMonitors;
|
||||
|
||||
return (
|
||||
<Stack
|
||||
className="monitors table-container"
|
||||
className="monitors"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<Box>
|
||||
<Breadcrumbs list={[{ name: `monitors`, path: "/monitors" }]} />
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
mt={theme.spacing(5)}
|
||||
gap={theme.spacing(6)}
|
||||
>
|
||||
<Greeting type="uptime" />
|
||||
{canAddMonitor && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
navigate("/monitors/create");
|
||||
}}
|
||||
sx={{ fontWeight: 500 }}
|
||||
>
|
||||
Create monitor
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
{noMonitors && <Fallback isAdmin={isAdmin} />}
|
||||
{loading ? (
|
||||
<SkeletonLayout />
|
||||
) : (
|
||||
<>
|
||||
<Box>
|
||||
<Breadcrumbs list={[{ name: `monitors`, path: "/monitors" }]} />
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
mt={theme.spacing(5)}
|
||||
>
|
||||
<Greeting type="uptime" />
|
||||
{isAdmin && monitorState?.monitorsSummary?.monitors?.length !== 0 && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
navigate("/monitors/create");
|
||||
}}
|
||||
sx={{ fontWeight: 500 }}
|
||||
>
|
||||
Create monitor
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
{isAdmin && monitorState?.monitorsSummary?.monitors?.length === 0 && (
|
||||
<Fallback isAdmin={isAdmin} />
|
||||
)}
|
||||
|
||||
{monitorState?.monitorsSummary?.monitors?.length !== 0 && (
|
||||
{hasMonitors && (
|
||||
<>
|
||||
<Stack
|
||||
gap={theme.spacing(8)}
|
||||
@@ -93,88 +88,11 @@ const Monitors = ({ isAdmin }) => {
|
||||
value={monitorState?.monitorsSummary?.monitorCounts?.paused ?? 0}
|
||||
/>
|
||||
</Stack>
|
||||
<Box
|
||||
flex={1}
|
||||
px={theme.spacing(10)}
|
||||
py={theme.spacing(8)}
|
||||
border={1}
|
||||
borderColor={theme.palette.border.light}
|
||||
borderRadius={theme.shape.borderRadius}
|
||||
backgroundColor={theme.palette.background.main}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
mb={theme.spacing(8)}
|
||||
>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="h2"
|
||||
fontWeight={500}
|
||||
letterSpacing={-0.2}
|
||||
>
|
||||
Actively monitoring
|
||||
</Typography>
|
||||
<Box
|
||||
className="current-monitors-counter"
|
||||
color={theme.palette.text.primary}
|
||||
border={1}
|
||||
borderColor={theme.palette.border.light}
|
||||
backgroundColor={theme.palette.background.accent}
|
||||
>
|
||||
{monitorState?.monitorsSummary?.monitorCounts?.total || 0}
|
||||
</Box>
|
||||
<Box
|
||||
width="25%"
|
||||
minWidth={150}
|
||||
ml="auto"
|
||||
>
|
||||
<Search
|
||||
options={monitorState?.monitorsSummary?.monitors ?? []}
|
||||
filteredBy="name"
|
||||
inputValue={search}
|
||||
handleInputChange={handleSearch}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Box position="relative">
|
||||
{isSearching && (
|
||||
<>
|
||||
<Box
|
||||
width="100%"
|
||||
height="100%"
|
||||
position="absolute"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.main,
|
||||
opacity: 0.8,
|
||||
zIndex: 100,
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
height="100%"
|
||||
position="absolute"
|
||||
top="20%"
|
||||
left="50%"
|
||||
sx={{
|
||||
transform: "translateX(-50%)",
|
||||
zIndex: 101,
|
||||
}}
|
||||
>
|
||||
<CircularProgress
|
||||
sx={{
|
||||
color: theme.palette.other.icon,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
<MonitorTable
|
||||
isAdmin={isAdmin}
|
||||
filter={debouncedFilter}
|
||||
setLoading={setIsSearching}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<CurrentMonitoring
|
||||
isAdmin={isAdmin}
|
||||
monitors={monitorState.monitorsSummary.monitors}
|
||||
totalMonitors={totalMonitors}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -19,7 +19,7 @@ const PageSpeed = ({ isAdmin }) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { user, authToken } = useSelector((state) => state.auth);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [monitors, setMonitors] = useState([]);
|
||||
useEffect(() => {
|
||||
dispatch(getPageSpeedByTeamId(authToken));
|
||||
|
||||
@@ -22,4 +22,4 @@ const hasValidationErrors = (form, validation, setErrors) => {
|
||||
}
|
||||
return false;
|
||||
};
|
||||
export { buildErrors, hasValidationErrors };
|
||||
export { buildErrors, hasValidationErrors };
|
||||
@@ -166,5 +166,5 @@ export {
|
||||
monitorValidation,
|
||||
settingsValidation,
|
||||
maintenanceWindowValidation,
|
||||
advancedSettingsValidation,
|
||||
advancedSettingsValidation
|
||||
};
|
||||
|
||||
36
Docker/dist/build_images.sh
vendored
36
Docker/dist/build_images.sh
vendored
@@ -1,24 +1,26 @@
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# Change directory to root Server directory for correct Docker Context
|
||||
cd "$(dirname "$0")"
|
||||
cd ../..
|
||||
|
||||
#Client
|
||||
client="./Docker/dist/client.Dockerfile"
|
||||
# Define an array of services and their Dockerfiles
|
||||
declare -A services=(
|
||||
["bluewave/uptime_client"]="./Docker/dist/client.Dockerfile"
|
||||
["bluewave/database_mongo"]="./Docker/dist/mongoDB.Dockerfile"
|
||||
["bluewave/uptime_redis"]="./Docker/dist/redis.Dockerfile"
|
||||
["bluewave/uptime_server"]="./Docker/dist/server.Dockerfile"
|
||||
)
|
||||
|
||||
# MongoDB
|
||||
mongoDB="./Docker/dist/mongoDB.Dockerfile"
|
||||
# Loop through each service and build the corresponding image
|
||||
for service in "${!services[@]}"; do
|
||||
docker build -f "${services[$service]}" -t "$service" .
|
||||
|
||||
# Check if the build succeeded
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error building $service image. Exiting..."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# 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"
|
||||
echo "All images built successfully"
|
||||
@@ -1,24 +1,26 @@
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# Change directory to root directory for correct Docker Context
|
||||
cd "$(dirname "$0")"
|
||||
cd ../..
|
||||
|
||||
#Client
|
||||
client="./Docker/prod/client.Dockerfile"
|
||||
# Define an array of services and their Dockerfiles
|
||||
declare -A services=(
|
||||
["uptime_client"]="./Docker/prod/client.Dockerfile"
|
||||
["uptime_database_mongo"]="./Docker/prod/mongoDB.Dockerfile"
|
||||
["uptime_redis"]="./Docker/prod/redis.Dockerfile"
|
||||
["uptime_server"]="./Docker/prod/server.Dockerfile"
|
||||
)
|
||||
|
||||
# MongoDB
|
||||
mongoDB="./Docker/prod/mongoDB.Dockerfile"
|
||||
# Loop through each service and build the corresponding image
|
||||
for service in "${!services[@]}"; do
|
||||
docker build -f "${services[$service]}" -t "$service" .
|
||||
|
||||
# Check if the build succeeded
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error building $service image. Exiting..."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Redis
|
||||
redis="./Docker/prod/redis.Dockerfile"
|
||||
|
||||
# Server
|
||||
server="./Docker/prod/server.Dockerfile"
|
||||
|
||||
docker build -f $client -t uptime_client .
|
||||
docker build -f $mongoDB -t uptime_database_mongo .
|
||||
docker build -f $redis -t uptime_redis .
|
||||
docker build -f $server -t uptime_server .
|
||||
|
||||
echo "All images built"
|
||||
echo "All images built successfully"
|
||||
60
Server/db/models/HardwareCheck.js
Normal file
60
Server/db/models/HardwareCheck.js
Normal file
@@ -0,0 +1,60 @@
|
||||
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: 0 },
|
||||
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 discSchema = mongoose.Schema({
|
||||
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 HardwareCheckSchema = mongoose.Schema(
|
||||
{
|
||||
monitorId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "Monitor",
|
||||
immutable: true,
|
||||
},
|
||||
cpu: {
|
||||
type: cpuSchema,
|
||||
default: () => ({}),
|
||||
},
|
||||
memory: {
|
||||
type: memorySchema,
|
||||
default: () => ({}),
|
||||
},
|
||||
disk: {
|
||||
type: [discSchema],
|
||||
default: () => [],
|
||||
},
|
||||
host: {
|
||||
type: hostSchema,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
export default mongoose.model("HardwareCheck", HardwareCheckSchema);
|
||||
@@ -29,7 +29,7 @@ const MonitorSchema = mongoose.Schema(
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ["http", "ping", "pagespeed"],
|
||||
enum: ["http", "ping", "pagespeed", "hardware"],
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
|
||||
@@ -99,6 +99,11 @@ import {
|
||||
deletePageSpeedChecksByMonitorId,
|
||||
} from "./modules/pageSpeedCheckModule.js";
|
||||
|
||||
//****************************************
|
||||
// Hardware Checks
|
||||
//****************************************
|
||||
import { createHardwareCheck } from "./modules/hardwareCheckModule.js";
|
||||
|
||||
//****************************************
|
||||
// Checks
|
||||
//****************************************
|
||||
@@ -179,6 +184,7 @@ export default {
|
||||
createPageSpeedCheck,
|
||||
getPageSpeedChecks,
|
||||
deletePageSpeedChecksByMonitorId,
|
||||
createHardwareCheck,
|
||||
createMaintenanceWindow,
|
||||
getMaintenanceWindowsByTeamId,
|
||||
getMaintenanceWindowById,
|
||||
|
||||
16
Server/db/mongo/modules/hardwareCheckModule.js
Normal file
16
Server/db/mongo/modules/hardwareCheckModule.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import HardwareCheck from "../../models/HardwareCheck.js";
|
||||
const SERVICE_NAME = "hardwareCheckModule";
|
||||
const createHardwareCheck = async (hardwareCheckData) => {
|
||||
try {
|
||||
const hardwareCheck = await new HardwareCheck({
|
||||
...hardwareCheckData,
|
||||
}).save();
|
||||
return hardwareCheck;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "createHardwareCheck";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export { createHardwareCheck };
|
||||
@@ -34,9 +34,11 @@ import pkg from "handlebars";
|
||||
const { compile } = pkg;
|
||||
import mjml2html from "mjml";
|
||||
|
||||
// Settings Service and dependencies
|
||||
import SettingsService from "./service/settingsService.js";
|
||||
import AppSettings from "../db/models/AppSettings.js";
|
||||
|
||||
import db from "./db/mongo/MongoDB.js";
|
||||
import { fetchMonitorCertificate } from "./controllers/controllerUtils.js";
|
||||
const SERVICE_NAME = "Server";
|
||||
|
||||
let cleaningUp = false;
|
||||
@@ -142,7 +144,7 @@ const startApp = async () => {
|
||||
|
||||
// Create services
|
||||
await connectDbAndRunServer(app, db);
|
||||
const settingsService = new SettingsService();
|
||||
const settingsService = new SettingsService(AppSettings);
|
||||
|
||||
await settingsService.loadSettings();
|
||||
const emailService = new EmailService(
|
||||
|
||||
27
Server/package-lock.json
generated
27
Server/package-lock.json
generated
@@ -36,7 +36,7 @@
|
||||
"chai": "5.1.1",
|
||||
"esm": "3.2.25",
|
||||
"mocha": "10.7.3",
|
||||
"nodemon": "3.1.0",
|
||||
"nodemon": "3.1.7",
|
||||
"prettier": "^3.3.3",
|
||||
"sinon": "19.0.2"
|
||||
}
|
||||
@@ -4608,10 +4608,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nodemon": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz",
|
||||
"integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==",
|
||||
"version": "3.1.7",
|
||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz",
|
||||
"integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chokidar": "^3.5.2",
|
||||
"debug": "^4",
|
||||
@@ -4636,12 +4637,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nodemon/node_modules/debug": {
|
||||
"version": "4.3.5",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
|
||||
"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
@@ -4653,10 +4655,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nodemon/node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nopt": {
|
||||
"version": "5.0.0",
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"chai": "5.1.1",
|
||||
"esm": "3.2.25",
|
||||
"mocha": "10.7.3",
|
||||
"nodemon": "3.1.0",
|
||||
"nodemon": "3.1.7",
|
||||
"prettier": "^3.3.3",
|
||||
"sinon": "19.0.2"
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ class NetworkService {
|
||||
this.TYPE_PING = "ping";
|
||||
this.TYPE_HTTP = "http";
|
||||
this.TYPE_PAGESPEED = "pagespeed";
|
||||
this.TYPE_HARDWARE = "hardware";
|
||||
this.SERVICE_NAME = "NetworkService";
|
||||
this.NETWORK_ERROR = 5000;
|
||||
this.axios = axios;
|
||||
@@ -293,6 +294,85 @@ class NetworkService {
|
||||
}
|
||||
}
|
||||
|
||||
async handleHardware(job) {
|
||||
const url = job.data.url;
|
||||
let isAlive;
|
||||
//TODO Fetch hardware data
|
||||
//For now, fake hardware data:
|
||||
|
||||
const hardwareData = {
|
||||
monitorId: job.data._id,
|
||||
cpu: {
|
||||
physical_core: 1,
|
||||
logical_core: 1,
|
||||
frequency: 266,
|
||||
temperature: null,
|
||||
free_percent: null,
|
||||
usage_percent: null,
|
||||
},
|
||||
memory: {
|
||||
total_bytes: 4,
|
||||
available_bytes: 4,
|
||||
used_bytes: 2,
|
||||
usage_percent: 0.5,
|
||||
},
|
||||
disk: [
|
||||
{
|
||||
read_speed_bytes: 3,
|
||||
write_speed_bytes: 3,
|
||||
total_bytes: 10,
|
||||
free_bytes: 2,
|
||||
usage_percent: 0.8,
|
||||
},
|
||||
],
|
||||
host: {
|
||||
os: "Linux",
|
||||
platform: "Ubuntu",
|
||||
kernel_version: "24.04",
|
||||
},
|
||||
};
|
||||
try {
|
||||
isAlive = true;
|
||||
this.logAndStoreCheck(hardwareData, this.db.createHardwareCheck);
|
||||
} catch (error) {
|
||||
isAlive = false;
|
||||
const nullData = {
|
||||
monitorId: job.data._id,
|
||||
cpu: {
|
||||
physical_core: 0,
|
||||
logical_core: 0,
|
||||
frequency: 0,
|
||||
temperature: 0,
|
||||
free_percent: 0,
|
||||
usage_percent: 0,
|
||||
},
|
||||
memory: {
|
||||
total_bytes: 0,
|
||||
available_bytes: 0,
|
||||
used_bytes: 0,
|
||||
usage_percent: 0,
|
||||
},
|
||||
disk: [
|
||||
{
|
||||
read_speed_bytes: 0,
|
||||
write_speed_bytes: 0,
|
||||
total_bytes: 0,
|
||||
free_bytes: 0,
|
||||
usage_percent: 0,
|
||||
},
|
||||
],
|
||||
host: {
|
||||
os: "",
|
||||
platform: "",
|
||||
kernel_version: "",
|
||||
},
|
||||
};
|
||||
this.logAndStoreCheck(nullData, this.db.createHardwareCheck);
|
||||
} finally {
|
||||
this.handleStatusUpdate(job, isAlive);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the status of a given job based on its type.
|
||||
* For unsupported job types, it logs an error and returns false.
|
||||
@@ -308,6 +388,8 @@ class NetworkService {
|
||||
return await this.handleHttp(job);
|
||||
case this.TYPE_PAGESPEED:
|
||||
return await this.handlePagespeed(job);
|
||||
case this.TYPE_HARDWARE:
|
||||
return await this.handleHardware(job);
|
||||
default:
|
||||
this.logger.error(`Unsupported type: ${job.data.type}`, {
|
||||
service: this.SERVICE_NAME,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import AppSettings from "../db/models/AppSettings.js";
|
||||
const SERVICE_NAME = "SettingsService";
|
||||
const envConfig = {
|
||||
logLevel: undefined,
|
||||
@@ -30,7 +29,8 @@ class SettingsService {
|
||||
* Constructs a new SettingsService
|
||||
* @constructor
|
||||
* @throws {Error}
|
||||
*/ constructor() {
|
||||
*/ constructor(appSettings) {
|
||||
this.appSettings = appSettings;
|
||||
this.settings = { ...envConfig };
|
||||
}
|
||||
/**
|
||||
@@ -40,7 +40,7 @@ class SettingsService {
|
||||
* @throws Will throw an error if settings are not found in the database or if settings have not been loaded.
|
||||
*/ async loadSettings() {
|
||||
try {
|
||||
const dbSettings = await AppSettings.findOne();
|
||||
const dbSettings = await this.appSettings.findOne();
|
||||
if (!this.settings) {
|
||||
throw new Error("Settings not found");
|
||||
}
|
||||
@@ -51,10 +51,6 @@ class SettingsService {
|
||||
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;
|
||||
|
||||
@@ -625,6 +625,105 @@ describe("networkService - handlePagespeed", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("networkService - handleHardware", () => {
|
||||
let dbMock,
|
||||
axiosMock,
|
||||
jobMock,
|
||||
emailServiceMock,
|
||||
pingMock,
|
||||
loggerMock,
|
||||
httpMock,
|
||||
networkService,
|
||||
logAndStoreCheckStub,
|
||||
handleStatusUpdateStub;
|
||||
beforeEach(() => {
|
||||
jobMock = {
|
||||
data: {
|
||||
_id: "12345",
|
||||
url: "http://example.com",
|
||||
},
|
||||
};
|
||||
dbMock = { getMonitorById: sinon.stub() };
|
||||
axiosMock = { get: sinon.stub() };
|
||||
|
||||
emailServiceMock = sinon.stub();
|
||||
pingMock = { promise: { probe: sinon.stub() } };
|
||||
loggerMock = { error: sinon.stub() };
|
||||
httpMock = {
|
||||
STATUS_CODES: {
|
||||
200: "OK",
|
||||
500: "Internal Server Error",
|
||||
},
|
||||
};
|
||||
networkService = new NetworkService(
|
||||
dbMock,
|
||||
emailServiceMock,
|
||||
axiosMock,
|
||||
pingMock,
|
||||
loggerMock,
|
||||
httpMock
|
||||
);
|
||||
logAndStoreCheckStub = sinon.stub(networkService, "logAndStoreCheck").resolves();
|
||||
handleStatusUpdateStub = sinon.stub(networkService, "handleStatusUpdate").resolves();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it("should handle a successful Hardware response", async () => {
|
||||
const responseMock = {
|
||||
monitorId: jobMock.data._id,
|
||||
cpu: {
|
||||
physical_core: 1,
|
||||
logical_core: 1,
|
||||
frequency: 266,
|
||||
temperature: null,
|
||||
free_percent: null,
|
||||
usage_percent: null,
|
||||
},
|
||||
memory: {
|
||||
total_bytes: 4,
|
||||
available_bytes: 4,
|
||||
used_bytes: 2,
|
||||
usage_percent: 0.5,
|
||||
},
|
||||
disk: [
|
||||
{
|
||||
read_speed_bytes: 3,
|
||||
write_speed_bytes: 3,
|
||||
total_bytes: 10,
|
||||
free_bytes: 2,
|
||||
usage_percent: 0.8,
|
||||
},
|
||||
],
|
||||
host: {
|
||||
os: "Linux",
|
||||
platform: "Ubuntu",
|
||||
kernel_version: "24.04",
|
||||
},
|
||||
};
|
||||
axiosMock.get.resolves(responseMock);
|
||||
|
||||
await networkService.handleHardware(jobMock);
|
||||
expect(networkService.logAndStoreCheck.calledOnce).to.be.true;
|
||||
const hardwareData = networkService.logAndStoreCheck.getCall(0).args[0];
|
||||
expect(hardwareData.cpu).to.include({
|
||||
...responseMock.cpu,
|
||||
});
|
||||
expect(networkService.handleStatusUpdate.calledOnceWith(jobMock, true)).to.be.true;
|
||||
});
|
||||
|
||||
it("should handle an error Hardware response", async () => {
|
||||
logAndStoreCheckStub.throws(new Error("Hardware error"));
|
||||
try {
|
||||
await networkService.handleHardware(jobMock);
|
||||
} catch (error) {
|
||||
expect(error.message).to.equal("Hardware error");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("NetworkService - getStatus", () => {
|
||||
let dbMock, emailServiceMock, axiosMock, pingMock, loggerMock, httpMock, networkService;
|
||||
|
||||
@@ -685,6 +784,18 @@ describe("NetworkService - getStatus", () => {
|
||||
const result = await networkService.getStatus(job);
|
||||
expect(result).to.be.false;
|
||||
});
|
||||
it("should return true if the job type is hardware and handleHardware is successful", async () => {
|
||||
const job = { data: { type: networkService.TYPE_HARDWARE } };
|
||||
sinon.stub(networkService, "handleHardware").resolves(true);
|
||||
const result = await networkService.getStatus(job);
|
||||
expect(result).to.be.true;
|
||||
});
|
||||
it("should return false if the job type is hardware and handleHardware is not successful", async () => {
|
||||
const job = { data: { type: networkService.TYPE_HARDWARE } };
|
||||
sinon.stub(networkService, "handleHardware").resolves(false);
|
||||
const result = await networkService.getStatus(job);
|
||||
expect(result).to.be.false;
|
||||
});
|
||||
it("should log an error and return false if the job type is unknown", async () => {
|
||||
const job = { data: { type: "unknown" } };
|
||||
const result = await networkService.getStatus(job);
|
||||
|
||||
134
Server/tests/services/settingsService.test.js
Normal file
134
Server/tests/services/settingsService.test.js
Normal file
@@ -0,0 +1,134 @@
|
||||
import sinon from "sinon";
|
||||
import SettingsService from "../../service/settingsService.js";
|
||||
import { expect } from "chai";
|
||||
import NetworkService from "../../service/networkService.js";
|
||||
const SERVICE_NAME = "SettingsService";
|
||||
|
||||
describe("SettingsService", () => {
|
||||
let sandbox, mockAppSettings;
|
||||
beforeEach(function () {
|
||||
sandbox = sinon.createSandbox();
|
||||
sandbox.stub(process.env, "CLIENT_HOST").value("http://localhost");
|
||||
sandbox.stub(process.env, "JWT_SECRET").value("secret");
|
||||
sandbox.stub(process.env, "REFRESH_TOKEN_SECRET").value("refreshSecret");
|
||||
sandbox.stub(process.env, "DB_TYPE").value("postgres");
|
||||
sandbox
|
||||
.stub(process.env, "DB_CONNECTION_STRING")
|
||||
.value("postgres://user:pass@localhost/db");
|
||||
sandbox.stub(process.env, "REDIS_HOST").value("localhost");
|
||||
sandbox.stub(process.env, "REDIS_PORT").value("6379");
|
||||
sandbox.stub(process.env, "TOKEN_TTL").value("3600");
|
||||
sandbox.stub(process.env, "REFRESH_TOKEN_TTL").value("86400");
|
||||
sandbox.stub(process.env, "PAGESPEED_API_KEY").value("apiKey");
|
||||
sandbox.stub(process.env, "SYSTEM_EMAIL_HOST").value("smtp.mailtrap.io");
|
||||
sandbox.stub(process.env, "SYSTEM_EMAIL_PORT").value("2525");
|
||||
sandbox.stub(process.env, "SYSTEM_EMAIL_ADDRESS").value("test@example.com");
|
||||
sandbox.stub(process.env, "SYSTEM_EMAIL_PASSWORD").value("password");
|
||||
});
|
||||
|
||||
mockAppSettings = {
|
||||
settingOne: 123,
|
||||
settingTwo: 456,
|
||||
};
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
sinon.restore();
|
||||
});
|
||||
describe("constructor", () => {
|
||||
it("should construct a new SettingsService", () => {
|
||||
const settingsService = new SettingsService(mockAppSettings);
|
||||
expect(settingsService.appSettings).to.equal(mockAppSettings);
|
||||
});
|
||||
});
|
||||
describe("loadSettings", () => {
|
||||
it("should load settings from DB when environment variables are not set", async () => {
|
||||
const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" };
|
||||
const appSettings = { findOne: sinon.stub().returns(dbSettings) };
|
||||
const settingsService = new SettingsService(appSettings);
|
||||
settingsService.settings = {};
|
||||
const result = await settingsService.loadSettings();
|
||||
expect(result).to.deep.equal(dbSettings);
|
||||
});
|
||||
it("should throw an error if settings are not found", async function () {
|
||||
const appSettings = { findOne: sinon.stub().returns(null) };
|
||||
const settingsService = new SettingsService(appSettings);
|
||||
settingsService.settings = null;
|
||||
|
||||
try {
|
||||
await settingsService.loadSettings();
|
||||
} catch (error) {
|
||||
expect(error.message).to.equal("Settings not found");
|
||||
expect(error.service).to.equal(SERVICE_NAME);
|
||||
expect(error.method).to.equal("loadSettings");
|
||||
}
|
||||
});
|
||||
|
||||
it("should add its method and service name to error if not present", async () => {
|
||||
const appSettings = { findOne: sinon.stub().throws(new Error("Test error")) };
|
||||
const settingsService = new SettingsService(appSettings);
|
||||
try {
|
||||
await settingsService.loadSettings();
|
||||
} catch (error) {
|
||||
expect(error.message).to.equal("Test error");
|
||||
expect(error.service).to.equal(SERVICE_NAME);
|
||||
expect(error.method).to.equal("loadSettings");
|
||||
}
|
||||
});
|
||||
it("should not add its method and service name to error if present", async () => {
|
||||
const error = new Error("Test error");
|
||||
error.method = "otherMethod";
|
||||
error.service = "OTHER_SERVICE";
|
||||
const appSettings = { findOne: sinon.stub().throws(error) };
|
||||
const settingsService = new SettingsService(appSettings);
|
||||
try {
|
||||
await settingsService.loadSettings();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
expect(error.message).to.equal("Test error");
|
||||
expect(error.service).to.equal("OTHER_SERVICE");
|
||||
expect(error.method).to.equal("otherMethod");
|
||||
}
|
||||
});
|
||||
it("should merge DB settings with environment variables", async function () {
|
||||
const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" };
|
||||
const appSettings = { findOne: sinon.stub().returns(dbSettings) };
|
||||
const settingsService = new SettingsService(appSettings);
|
||||
const result = await settingsService.loadSettings();
|
||||
expect(result).to.deep.equal(settingsService.settings);
|
||||
expect(settingsService.settings.logLevel).to.equal("debug");
|
||||
expect(settingsService.settings.apiBaseUrl).to.equal("http://localhost");
|
||||
});
|
||||
});
|
||||
describe("reloadSettings", () => {
|
||||
it("should call loadSettings", async () => {
|
||||
const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" };
|
||||
const appSettings = { findOne: sinon.stub().returns(dbSettings) };
|
||||
const settingsService = new SettingsService(appSettings);
|
||||
settingsService.settings = {};
|
||||
const result = await settingsService.reloadSettings();
|
||||
expect(result).to.deep.equal(dbSettings);
|
||||
});
|
||||
});
|
||||
describe("getSettings", () => {
|
||||
it("should return the current settings", () => {
|
||||
const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" };
|
||||
const appSettings = { findOne: sinon.stub().returns(dbSettings) };
|
||||
const settingsService = new SettingsService(appSettings);
|
||||
settingsService.settings = dbSettings;
|
||||
const result = settingsService.getSettings();
|
||||
expect(result).to.deep.equal(dbSettings);
|
||||
});
|
||||
it("should throw an error if settings have not been loaded", () => {
|
||||
const appSettings = { findOne: sinon.stub().returns(null) };
|
||||
const settingsService = new SettingsService(appSettings);
|
||||
settingsService.settings = null;
|
||||
|
||||
try {
|
||||
settingsService.getSettings();
|
||||
} catch (error) {
|
||||
expect(error.message).to.equal("Settings have not been loaded");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
6
package-lock.json
generated
Normal file
6
package-lock.json
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "bluewave-uptime",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||
6
renovate.json
Normal file
6
renovate.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user