Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea698b6558 |
No files matched your search
@@ -46,11 +46,11 @@ You can see the latest build of [Checkmate](https://checkmate-demo.bluewavelabs.
|
|||||||
|
|
||||||
## User's guide
|
## User's guide
|
||||||
|
|
||||||
Usage instructions can be found [here](https://checkmate.so/docs).
|
Usage instructions can be found [here](https://docs.checkmate.so/checkmate-2.1). It's still WIP and some of the information there might be outdated as we continuously add features weekly. Rest assured, we are doing our best! :)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
See installation instructions in [Checkmate documentation portal](https://checkmate.so/docs).
|
See installation instructions in [Checkmate documentation portal](https://docs.checkmate.so/checkmate-2.1/users-guide/quickstart).
|
||||||
|
|
||||||
Alternatively, you can also use [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (South Africa) or [Pikapods](https://www.pikapods.com/) to quickly spin off a Checkmate instance. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions.
|
Alternatively, you can also use [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (South Africa) or [Pikapods](https://www.pikapods.com/) to quickly spin off a Checkmate instance. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions.
|
||||||
|
|
||||||
@@ -157,11 +157,12 @@ Here's how you can contribute:
|
|||||||
|
|
||||||
0. Star this repo :)
|
0. Star this repo :)
|
||||||
1. Check [Contributor's guideline](https://github.com/bluewave-labs/Checkmate/blob/develop/CONTRIBUTING.md). First timers are encouraged to check `good-first-issue` tag.
|
1. Check [Contributor's guideline](https://github.com/bluewave-labs/Checkmate/blob/develop/CONTRIBUTING.md). First timers are encouraged to check `good-first-issue` tag.
|
||||||
2. Read a detailed structure of [Checkmate](https://deepwiki.com/bluewave-labs/Checkmate) if you would like to deep dive into the architecture.
|
2. Check [project structure](https://docs.checkmate.so/checkmate-2.1/developers-guide/general-project-structure) and [high level overview](https://bluewavelabs.gitbook.io/checkmate/developers-guide/high-level-overview).
|
||||||
3. Open an issue if you believe you've encountered a bug.
|
3. Read a detailed structure of [Checkmate](https://deepwiki.com/bluewave-labs/Checkmate) if you would like to deep dive into the architecture.
|
||||||
4. Check for good-first-issue's if you are a newcomer.
|
4. Open an issue if you believe you've encountered a bug.
|
||||||
5. Make a pull request to add new features/make quality-of-life improvements/fix bugs.
|
5. Check for good-first-issue's if you are a newcomer.
|
||||||
6. Check out this interactive walkthrough of the `Checkmate` codebase on CodeCanvas [here](https://www.code-canvas.com/?session=unauthenticatedGithub&repo=Checkmate&owner=bluewave-labs&branch=develop&OnboardingTutorial=true). To refine existing dataflow simulation or create new ones, follow the quick tutorial [here](https://docs.code-canvas.com/updating-diagram).
|
6. Make a pull request to add new features/make quality-of-life improvements/fix bugs.
|
||||||
|
7. Check out this interactive walkthrough of the `Checkmate` codebase on CodeCanvas [here](https://www.code-canvas.com/?session=unauthenticatedGithub&repo=Checkmate&owner=bluewave-labs&branch=develop&OnboardingTutorial=true). To refine existing dataflow simulation or create new ones, follow the quick tutorial [here](https://docs.code-canvas.com/updating-diagram).
|
||||||
|
|
||||||
<a href="https://github.com/bluewave-labs/checkmate/graphs/contributors">
|
<a href="https://github.com/bluewave-labs/checkmate/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=bluewave-labs/checkmate" />
|
<img src="https://contrib.rocks/image?repo=bluewave-labs/checkmate" />
|
||||||
@@ -174,3 +175,9 @@ Here's how you can contribute:
|
|||||||
Thanks to [Gitbook](https://gitbook.io/) for giving us a free tier for their documentation platform, and [Poeditor](https://poeditor.com/) providing us a free account to use their i18n services. If you would like to sponsor Checkmate, please send an email to hello@bluewavelabs.ca
|
Thanks to [Gitbook](https://gitbook.io/) for giving us a free tier for their documentation platform, and [Poeditor](https://poeditor.com/) providing us a free account to use their i18n services. If you would like to sponsor Checkmate, please send an email to hello@bluewavelabs.ca
|
||||||
|
|
||||||
If you would like to sponsor a feature, [see this page](https://checkmate.so/sponsored-features).
|
If you would like to sponsor a feature, [see this page](https://checkmate.so/sponsored-features).
|
||||||
|
|
||||||
|
Also check other developer and contributor-friendly projects of BlueWave:
|
||||||
|
|
||||||
|
- [VerifyWise](https://github.com/bluewave-labs/verifywise), the first open source AI governance platform.
|
||||||
|
- [DataRoom](https://github.com/bluewave-labs/bluewave-dataroom), an secure file sharing application, aka dataroom.
|
||||||
|
- [Guidefox](https://github.com/bluewave-labs/guidefox), an application that helps new users learn how to use your product via hints, tours, popups and banners.
|
||||||
Generated
+168
-119
@@ -44,7 +44,7 @@
|
|||||||
"redux-persist": "6.0.0",
|
"redux-persist": "6.0.0",
|
||||||
"swr": "^2.3.6",
|
"swr": "^2.3.6",
|
||||||
"vite-plugin-svgr": "^4.2.0",
|
"vite-plugin-svgr": "^4.2.0",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.1.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "24.5.2",
|
"@types/node": "24.5.2",
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
"lint-staged": "^16.2.7",
|
"lint-staged": "^16.2.7",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"vite": "6.4.1"
|
"vite": "6.3.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
@@ -434,6 +434,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -450,6 +451,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -466,6 +468,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -482,6 +485,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -498,6 +502,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -514,6 +519,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -530,6 +536,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -546,6 +553,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -562,6 +570,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -578,6 +587,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -594,6 +604,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -610,6 +621,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -626,6 +638,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -642,6 +655,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -658,6 +672,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -674,6 +689,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -688,6 +704,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -704,6 +721,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -720,6 +738,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -736,6 +755,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -752,6 +772,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -768,6 +789,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -784,6 +806,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -800,6 +823,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -816,6 +840,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -832,6 +857,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1634,12 +1660,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
|
||||||
"integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
|
"integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1647,12 +1674,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
|
||||||
"integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
|
"integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1660,12 +1688,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
|
||||||
"integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
|
"integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1673,12 +1702,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
|
||||||
"integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
|
"integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1686,12 +1716,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
|
||||||
"integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
|
"integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1699,12 +1730,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
|
||||||
"integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
|
"integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1712,12 +1744,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
|
||||||
"integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
|
"integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1725,12 +1758,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
|
||||||
"integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
|
"integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1738,12 +1772,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
|
||||||
"integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
|
"integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1751,12 +1786,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
|
||||||
"integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
|
"integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1764,12 +1800,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
|
||||||
"integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
|
"integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1777,12 +1814,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
|
||||||
"integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
|
"integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1790,12 +1828,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
|
||||||
"integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
|
"integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1803,12 +1842,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
|
||||||
"integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
|
"integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1816,12 +1856,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
|
||||||
"integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
|
"integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1829,12 +1870,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
|
||||||
"integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
|
"integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1842,12 +1884,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
|
||||||
"integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
|
"integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1855,12 +1898,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
|
|
||||||
"integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
|
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1868,12 +1910,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
|
||||||
"integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
|
"integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1881,12 +1924,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-openbsd-x64": {
|
"node_modules/@rollup/rollup-openbsd-x64": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
|
||||||
"integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
|
"integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1894,12 +1938,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
|
||||||
"integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
|
"integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1907,12 +1952,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
|
||||||
"integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
|
"integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1920,12 +1966,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
|
||||||
"integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
|
"integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1933,12 +1980,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
|
||||||
"integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
|
"integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1946,12 +1994,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.59.0",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
|
||||||
"integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
|
"integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2301,7 +2350,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "24.5.2",
|
"version": "24.5.2",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.12.0"
|
"undici-types": "~7.12.0"
|
||||||
@@ -2317,6 +2366,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "18.3.27",
|
"version": "18.3.27",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
@@ -2455,9 +2505,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "6.14.0",
|
"version": "6.12.6",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
|
|
||||||
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
|
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -3518,6 +3566,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.25.12",
|
"version": "0.25.12",
|
||||||
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -3835,6 +3884,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/fdir": {
|
"node_modules/fdir": {
|
||||||
"version": "6.5.0",
|
"version": "6.5.0",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
@@ -3968,6 +4018,7 @@
|
|||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
@@ -5200,9 +5251,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.1.5",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
|
||||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -5268,6 +5317,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.11",
|
"version": "3.3.11",
|
||||||
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -5600,6 +5650,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.6",
|
"version": "8.5.6",
|
||||||
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -6107,8 +6158,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.57.1",
|
"version": "4.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
|
"dev": true,
|
||||||
"integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.8"
|
"@types/estree": "1.0.8"
|
||||||
@@ -6121,31 +6171,31 @@
|
|||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.59.0",
|
"@rollup/rollup-android-arm-eabi": "4.57.1",
|
||||||
"@rollup/rollup-android-arm64": "4.59.0",
|
"@rollup/rollup-android-arm64": "4.57.1",
|
||||||
"@rollup/rollup-darwin-arm64": "4.59.0",
|
"@rollup/rollup-darwin-arm64": "4.57.1",
|
||||||
"@rollup/rollup-darwin-x64": "4.59.0",
|
"@rollup/rollup-darwin-x64": "4.57.1",
|
||||||
"@rollup/rollup-freebsd-arm64": "4.59.0",
|
"@rollup/rollup-freebsd-arm64": "4.57.1",
|
||||||
"@rollup/rollup-freebsd-x64": "4.59.0",
|
"@rollup/rollup-freebsd-x64": "4.57.1",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "4.59.0",
|
"@rollup/rollup-linux-arm-musleabihf": "4.57.1",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.59.0",
|
"@rollup/rollup-linux-arm64-gnu": "4.57.1",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.59.0",
|
"@rollup/rollup-linux-arm64-musl": "4.57.1",
|
||||||
"@rollup/rollup-linux-loong64-gnu": "4.59.0",
|
"@rollup/rollup-linux-loong64-gnu": "4.57.1",
|
||||||
"@rollup/rollup-linux-loong64-musl": "4.59.0",
|
"@rollup/rollup-linux-loong64-musl": "4.57.1",
|
||||||
"@rollup/rollup-linux-ppc64-gnu": "4.59.0",
|
"@rollup/rollup-linux-ppc64-gnu": "4.57.1",
|
||||||
"@rollup/rollup-linux-ppc64-musl": "4.59.0",
|
"@rollup/rollup-linux-ppc64-musl": "4.57.1",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.59.0",
|
"@rollup/rollup-linux-riscv64-gnu": "4.57.1",
|
||||||
"@rollup/rollup-linux-riscv64-musl": "4.59.0",
|
"@rollup/rollup-linux-riscv64-musl": "4.57.1",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.59.0",
|
"@rollup/rollup-linux-s390x-gnu": "4.57.1",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.59.0",
|
"@rollup/rollup-linux-x64-gnu": "4.57.1",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.59.0",
|
"@rollup/rollup-linux-x64-musl": "4.57.1",
|
||||||
"@rollup/rollup-openbsd-x64": "4.59.0",
|
"@rollup/rollup-openbsd-x64": "4.57.1",
|
||||||
"@rollup/rollup-openharmony-arm64": "4.59.0",
|
"@rollup/rollup-openharmony-arm64": "4.57.1",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.59.0",
|
"@rollup/rollup-win32-arm64-msvc": "4.57.1",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.59.0",
|
"@rollup/rollup-win32-ia32-msvc": "4.57.1",
|
||||||
"@rollup/rollup-win32-x64-gnu": "4.59.0",
|
"@rollup/rollup-win32-x64-gnu": "4.57.1",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.59.0",
|
"@rollup/rollup-win32-x64-msvc": "4.57.1",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -6480,6 +6530,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
|
"dev": true,
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -6767,6 +6818,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.15",
|
"version": "0.2.15",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
@@ -6896,7 +6948,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
@@ -6940,7 +6992,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "7.12.0",
|
"version": "7.12.0",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/union-value": {
|
"node_modules/union-value": {
|
||||||
@@ -7029,9 +7081,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "6.4.1",
|
"version": "6.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
"dev": true,
|
||||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
@@ -7315,7 +7366,7 @@
|
|||||||
"version": "2.8.2",
|
"version": "2.8.2",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
|
||||||
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
|
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"yaml": "bin.mjs"
|
"yaml": "bin.mjs"
|
||||||
@@ -7340,8 +7391,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
"version": "4.3.6",
|
"version": "4.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
|
|
||||||
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
|||||||
+3
-3
@@ -13,7 +13,7 @@
|
|||||||
"format-check": "prettier --check ."
|
"format-check": "prettier --check ."
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"**/*": "prettier --write"
|
"**/*": "npm run format"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.13.3",
|
"@emotion/react": "^11.13.3",
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
"redux-persist": "6.0.0",
|
"redux-persist": "6.0.0",
|
||||||
"swr": "^2.3.6",
|
"swr": "^2.3.6",
|
||||||
"vite-plugin-svgr": "^4.2.0",
|
"vite-plugin-svgr": "^4.2.0",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.1.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "24.5.2",
|
"@types/node": "24.5.2",
|
||||||
@@ -66,6 +66,6 @@
|
|||||||
"lint-staged": "^16.2.7",
|
"lint-staged": "^16.2.7",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"vite": "6.4.1"
|
"vite": "6.3.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ export const HeatmapResponseTime = ({
|
|||||||
|
|
||||||
if (!checks || checks.length === 0) return null;
|
if (!checks || checks.length === 0) return null;
|
||||||
|
|
||||||
const latestChecks = checks.slice(-25);
|
const latestChecks = checks.slice(-25).reverse();
|
||||||
|
|
||||||
let data: Array<CheckSnapshot | PlaceholderCheck>;
|
let data: Array<CheckSnapshot | PlaceholderCheck>;
|
||||||
if (latestChecks.length !== 25) {
|
if (latestChecks.length !== 25) {
|
||||||
|
|||||||
@@ -57,8 +57,7 @@ export const HeatmapResponseTimeTooltip = ({
|
|||||||
{formatDateWithTz(check?.createdAt, "ddd, MMMM D, YYYY, HH:mm A", uiTimezone)}
|
{formatDateWithTz(check?.createdAt, "ddd, MMMM D, YYYY, HH:mm A", uiTimezone)}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography>
|
<Typography>
|
||||||
{t("common.labels.responseTime")}:{" "}
|
{t("common.labels.responseTime")}: {check.originalResponseTime.toFixed()} ms
|
||||||
{check?.originalResponseTime?.toFixed() ?? "N/A"} ms
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography textTransform={"capitalize"}>
|
<Typography textTransform={"capitalize"}>
|
||||||
Status:{" "}
|
Status:{" "}
|
||||||
|
|||||||
@@ -11,26 +11,20 @@ import type { ResponsiveStyleValue } from "@mui/system";
|
|||||||
interface SliderInputProps extends SliderProps {
|
interface SliderInputProps extends SliderProps {
|
||||||
sx?: SliderProps["sx"];
|
sx?: SliderProps["sx"];
|
||||||
showValue?: boolean;
|
showValue?: boolean;
|
||||||
formatDisplayValue?: (value: number) => string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SliderInput = forwardRef<HTMLSpanElement, SliderInputProps>(
|
export const SliderInput = forwardRef<HTMLSpanElement, SliderInputProps>(
|
||||||
({ sx, showValue = false, formatDisplayValue, ...props }, ref) => {
|
({ sx, showValue = false, ...props }, ref) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const additionalSx = Array.isArray(sx) ? sx : sx ? [sx] : [];
|
const additionalSx = Array.isArray(sx) ? sx : sx ? [sx] : [];
|
||||||
|
|
||||||
const displayValue =
|
|
||||||
showValue && formatDisplayValue && typeof props.value === "number"
|
|
||||||
? formatDisplayValue(props.value)
|
|
||||||
: props.value;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
gap={theme.spacing(8)}
|
gap={theme.spacing(8)}
|
||||||
direction={"row"}
|
direction={"row"}
|
||||||
alignItems={"center"}
|
alignItems={"center"}
|
||||||
>
|
>
|
||||||
{showValue && <Typography>{displayValue}</Typography>}
|
{showValue && <Typography>{props.value}</Typography>}
|
||||||
<Slider
|
<Slider
|
||||||
{...props}
|
{...props}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@@ -69,21 +63,12 @@ interface SliderWithLabelProps extends SliderProps {
|
|||||||
fieldLabel?: string;
|
fieldLabel?: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
showValue?: boolean;
|
showValue?: boolean;
|
||||||
formatDisplayValue?: (value: number) => string;
|
|
||||||
sliderMaxWidth?: ResponsiveStyleValue<number | string>;
|
sliderMaxWidth?: ResponsiveStyleValue<number | string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SliderWithLabel = forwardRef<HTMLSpanElement, SliderWithLabelProps>(
|
export const SliderWithLabel = forwardRef<HTMLSpanElement, SliderWithLabelProps>(
|
||||||
(
|
(
|
||||||
{
|
{ fieldLabel, required, showValue = true, value, sliderMaxWidth = "100%", ...props },
|
||||||
fieldLabel,
|
|
||||||
required,
|
|
||||||
showValue = true,
|
|
||||||
formatDisplayValue,
|
|
||||||
value,
|
|
||||||
sliderMaxWidth = "100%",
|
|
||||||
...props
|
|
||||||
},
|
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@@ -97,7 +82,6 @@ export const SliderWithLabel = forwardRef<HTMLSpanElement, SliderWithLabelProps>
|
|||||||
<SliderInput
|
<SliderInput
|
||||||
{...props}
|
{...props}
|
||||||
showValue={showValue}
|
showValue={showValue}
|
||||||
formatDisplayValue={formatDisplayValue}
|
|
||||||
value={value}
|
value={value}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { useTheme } from "@mui/material/styles";
|
import { useTheme } from "@mui/material/styles";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import BackgroundSVG from "@/assets/Images/background.svg";
|
||||||
|
import type { RootState } from "@/Types/state";
|
||||||
import { OfflineBanner } from "@/Components/design-elements";
|
import { OfflineBanner } from "@/Components/design-elements";
|
||||||
import { setServerUnreachableCallback, get } from "@/Utils/ApiClient";
|
import { setServerUnreachableCallback, get } from "@/Utils/ApiClient";
|
||||||
|
|
||||||
@@ -10,6 +13,7 @@ interface AppLayoutProps {
|
|||||||
|
|
||||||
const AppLayout = ({ children }: AppLayoutProps) => {
|
const AppLayout = ({ children }: AppLayoutProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const mode = useSelector((state: RootState) => state.ui.mode);
|
||||||
const [serverUnreachable, setServerUnreachable] = useState(false);
|
const [serverUnreachable, setServerUnreachable] = useState(false);
|
||||||
const retryIntervalRef = useRef<number | null>(null);
|
const retryIntervalRef = useRef<number | null>(null);
|
||||||
|
|
||||||
@@ -43,6 +47,10 @@ const AppLayout = ({ children }: AppLayoutProps) => {
|
|||||||
sx={{
|
sx={{
|
||||||
minHeight: "100vh",
|
minHeight: "100vh",
|
||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
|
backgroundImage: mode === "dark" ? `url("${BackgroundSVG}")` : "none",
|
||||||
|
backgroundSize: "100% 100%",
|
||||||
|
backgroundPosition: "center",
|
||||||
|
backgroundRepeat: "no-repeat",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<OfflineBanner visible={serverUnreachable} />
|
<OfflineBanner visible={serverUnreachable} />
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type { MonitorType } from "@/Types/Monitor";
|
|||||||
import { Typography, useTheme } from "@mui/material";
|
import { Typography, useTheme } from "@mui/material";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const types = ["http", "ping", "port", "docker", "game", "grpc", "websocket"];
|
const types = ["http", "ping", "port", "docker", "game", "grpc"];
|
||||||
const typeDisplayNames: Record<string, string> = {
|
const typeDisplayNames: Record<string, string> = {
|
||||||
http: "HTTP",
|
http: "HTTP",
|
||||||
ping: "Ping",
|
ping: "Ping",
|
||||||
@@ -15,7 +15,6 @@ const typeDisplayNames: Record<string, string> = {
|
|||||||
docker: "Docker",
|
docker: "Docker",
|
||||||
game: "Game",
|
game: "Game",
|
||||||
grpc: "gRPC",
|
grpc: "gRPC",
|
||||||
websocket: "WebSocket",
|
|
||||||
};
|
};
|
||||||
const statuses = ["up", "down"];
|
const statuses = ["up", "down"];
|
||||||
const states = ["active", "paused"];
|
const states = ["active", "paused"];
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ export const useMonitorForm = ({
|
|||||||
matchMethod: data?.matchMethod || "",
|
matchMethod: data?.matchMethod || "",
|
||||||
expectedValue: data?.expectedValue || "",
|
expectedValue: data?.expectedValue || "",
|
||||||
jsonPath: data?.jsonPath || "",
|
jsonPath: data?.jsonPath || "",
|
||||||
|
basicAuthUser: data?.basicAuthUser || "",
|
||||||
|
basicAuthPassword: data?.basicAuthPassword || "",
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case "ping":
|
case "ping":
|
||||||
@@ -103,14 +105,6 @@ export const useMonitorForm = ({
|
|||||||
selectedDisks: data?.selectedDisks || [],
|
selectedDisks: data?.selectedDisks || [],
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case "websocket":
|
|
||||||
defaults = {
|
|
||||||
...base,
|
|
||||||
type: "websocket",
|
|
||||||
url: data?.url || "",
|
|
||||||
ignoreTlsErrors: data?.ignoreTlsErrors || false,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
defaults = {
|
defaults = {
|
||||||
...base,
|
...base,
|
||||||
@@ -121,6 +115,8 @@ export const useMonitorForm = ({
|
|||||||
matchMethod: "",
|
matchMethod: "",
|
||||||
expectedValue: "",
|
expectedValue: "",
|
||||||
jsonPath: "",
|
jsonPath: "",
|
||||||
|
basicAuthUser: "",
|
||||||
|
basicAuthPassword: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { settingsSchema } from "@/Validation/settings";
|
import { settingsSchema } from "@/Validation/settings";
|
||||||
import type { Settings } from "@/Types/Settings";
|
import type { Settings } from "@/Types/Settings";
|
||||||
import type { SettingsFormInput } from "@/Validation/settings";
|
import type { SettingsFormData } from "@/Validation/settings";
|
||||||
|
|
||||||
interface UseSettingsFormOptions {
|
interface UseSettingsFormOptions {
|
||||||
data?: Settings | null;
|
data?: Settings | null;
|
||||||
}
|
}
|
||||||
export const useSettingsForm = ({ data = null }: UseSettingsFormOptions = {}) => {
|
export const useSettingsForm = ({ data = null }: UseSettingsFormOptions = {}) => {
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const defaults: SettingsFormInput = {
|
const defaults: SettingsFormData = {
|
||||||
systemEmailIgnoreTLS: data?.systemEmailIgnoreTLS ?? false,
|
systemEmailIgnoreTLS: data?.systemEmailIgnoreTLS ?? false,
|
||||||
systemEmailRequireTLS: data?.systemEmailRequireTLS ?? false,
|
systemEmailRequireTLS: data?.systemEmailRequireTLS ?? false,
|
||||||
systemEmailRejectUnauthorized: data?.systemEmailRejectUnauthorized ?? true,
|
systemEmailRejectUnauthorized: data?.systemEmailRejectUnauthorized ?? true,
|
||||||
|
|||||||
@@ -27,14 +27,12 @@ export const useStatusPageForm = ({
|
|||||||
companyName: data?.companyName || "",
|
companyName: data?.companyName || "",
|
||||||
url: data?.url || generateDefaultUrl(),
|
url: data?.url || generateDefaultUrl(),
|
||||||
timezone: data?.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
timezone: data?.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
type: data?.type || ["uptime"],
|
|
||||||
color: data?.color || "#4169E1",
|
color: data?.color || "#4169E1",
|
||||||
monitors: data?.monitors || [],
|
monitors: data?.monitors || [],
|
||||||
isPublished: data?.isPublished ?? false,
|
isPublished: data?.isPublished ?? false,
|
||||||
showCharts: data?.showCharts ?? true,
|
showCharts: data?.showCharts ?? true,
|
||||||
showUptimePercentage: data?.showUptimePercentage ?? true,
|
showUptimePercentage: data?.showUptimePercentage ?? true,
|
||||||
showAdminLoginLink: data?.showAdminLoginLink ?? false,
|
showAdminLoginLink: data?.showAdminLoginLink ?? false,
|
||||||
showInfrastructure: data?.showInfrastructure ?? false,
|
|
||||||
customCSS: data?.customCSS || "",
|
customCSS: data?.customCSS || "",
|
||||||
logo: transformLogo(data?.logo),
|
logo: transformLogo(data?.logo),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,17 +11,12 @@ import { ConfigBox, BasePage } from "@/Components/design-elements";
|
|||||||
import { UserRoles } from "@/Types/User";
|
import { UserRoles } from "@/Types/User";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import type { UserRole, User } from "@/Types/User";
|
import type { UserRole, User } from "@/Types/User";
|
||||||
import { useParams, useNavigate } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { useTheme } from "@mui/material";
|
import { useTheme } from "@mui/material";
|
||||||
import { useEditUserForm } from "@/Hooks/useEditUserForm";
|
import { useEditUserForm } from "@/Hooks/useEditUserForm";
|
||||||
import { useGet, usePatch, useDelete } from "@/Hooks/UseApi";
|
import { useGet, usePatch } from "@/Hooks/UseApi";
|
||||||
import type { EditUserFormData } from "@/Validation/editUser";
|
import type { EditUserFormData } from "@/Validation/editUser";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect } from "react";
|
||||||
import { DialogInput } from "@/Components/inputs/Dialog";
|
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import type { RootState } from "@/Types/state";
|
|
||||||
import { LAYOUT } from "@/Utils/Theme/constants";
|
|
||||||
import { useIsAdmin, useIsSuperAdmin } from "@/Hooks/useIsAdmin";
|
|
||||||
|
|
||||||
interface RoleOption {
|
interface RoleOption {
|
||||||
id: UserRole;
|
id: UserRole;
|
||||||
@@ -34,12 +29,6 @@ const EditUserPage = () => {
|
|||||||
const { userId } = useParams<{ userId: string }>();
|
const { userId } = useParams<{ userId: string }>();
|
||||||
const { resolver, defaults } = useEditUserForm();
|
const { resolver, defaults } = useEditUserForm();
|
||||||
const { patch, loading: isSaving } = usePatch();
|
const { patch, loading: isSaving } = usePatch();
|
||||||
const { deleteFn, loading: isDeleting } = useDelete();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
|
||||||
const currentUser = useSelector((state: RootState) => state.auth.user);
|
|
||||||
const isSuperAdmin = useIsSuperAdmin();
|
|
||||||
const isAdmin = useIsAdmin();
|
|
||||||
|
|
||||||
const { data: user, isLoading } = useGet<User>(`/auth/users/${userId}`);
|
const { data: user, isLoading } = useGet<User>(`/auth/users/${userId}`);
|
||||||
|
|
||||||
@@ -71,20 +60,6 @@ const EditUserPage = () => {
|
|||||||
setValue("role", newRoles, { shouldValidate: true });
|
setValue("role", newRoles, { shouldValidate: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
const canDeleteUser =
|
|
||||||
isAdmin &&
|
|
||||||
userId !== currentUser?.id &&
|
|
||||||
!user?.role?.includes("demo") &&
|
|
||||||
(isSuperAdmin || user?.role?.every((r) => r !== "admin" && r !== "superadmin"));
|
|
||||||
|
|
||||||
const handleDeleteUser = async () => {
|
|
||||||
const result = await deleteFn(`/auth/users/${userId}`);
|
|
||||||
setShowDeleteDialog(false);
|
|
||||||
if (result) {
|
|
||||||
navigate("/account", { state: { tab: "team" } });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSubmit = async (data: EditUserFormData) => {
|
const onSubmit = async (data: EditUserFormData) => {
|
||||||
await patch(`/auth/users/${userId}`, data);
|
await patch(`/auth/users/${userId}`, data);
|
||||||
};
|
};
|
||||||
@@ -190,45 +165,17 @@ const EditUserPage = () => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Stack
|
<Button
|
||||||
gap={LAYOUT.XS}
|
type="submit"
|
||||||
direction="row"
|
variant="contained"
|
||||||
justifyContent={"flex-end"}
|
color="primary"
|
||||||
width="100%"
|
loading={isSaving}
|
||||||
|
sx={{ alignSelf: "flex-end", minWidth: 100 }}
|
||||||
>
|
>
|
||||||
{canDeleteUser && (
|
{t("common.buttons.save")}
|
||||||
<Button
|
</Button>
|
||||||
variant="contained"
|
|
||||||
color="error"
|
|
||||||
onClick={() => setShowDeleteDialog(true)}
|
|
||||||
sx={{ minWidth: 100 }}
|
|
||||||
>
|
|
||||||
{t("common.buttons.removeUser")}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
loading={isSaving}
|
|
||||||
sx={{ minWidth: 100 }}
|
|
||||||
>
|
|
||||||
{t("common.buttons.save")}
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</form>
|
</form>
|
||||||
<DialogInput
|
|
||||||
open={showDeleteDialog}
|
|
||||||
title={t("pages.editUser.dialog.removeUser.title")}
|
|
||||||
content={t("pages.editUser.dialog.removeUser.content", {
|
|
||||||
name: `${user?.firstName} ${user?.lastName}`,
|
|
||||||
})}
|
|
||||||
onCancel={() => setShowDeleteDialog(false)}
|
|
||||||
onConfirm={handleDeleteUser}
|
|
||||||
confirmText={t("common.buttons.removeUser")}
|
|
||||||
loading={isDeleting}
|
|
||||||
/>
|
|
||||||
</BasePage>
|
</BasePage>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Table } from "@/Components/design-elements";
|
import { Table } from "@/Components/design-elements";
|
||||||
import type { Header } from "@/Components/design-elements/Table";
|
import type { Header } from "@/Components/design-elements/Table";
|
||||||
import { useIsAdmin } from "@/Hooks/useIsAdmin";
|
import { useIsSuperAdmin } from "@/Hooks/useIsAdmin";
|
||||||
import type { User } from "@/Types/User";
|
import type { User } from "@/Types/User";
|
||||||
|
|
||||||
interface TeamTableProps {
|
interface TeamTableProps {
|
||||||
@@ -13,7 +13,7 @@ interface TeamTableProps {
|
|||||||
export const TeamTable = ({ users }: TeamTableProps) => {
|
export const TeamTable = ({ users }: TeamTableProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const isAdmin = useIsAdmin();
|
const isSuperAdmin = useIsSuperAdmin();
|
||||||
|
|
||||||
const headers: Header<User>[] = [
|
const headers: Header<User>[] = [
|
||||||
{
|
{
|
||||||
@@ -45,7 +45,7 @@ export const TeamTable = ({ users }: TeamTableProps) => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const handleRowClick = (row: User) => {
|
const handleRowClick = (row: User) => {
|
||||||
if (isAdmin) {
|
if (isSuperAdmin) {
|
||||||
navigate(`/account/team/${row.id}`);
|
navigate(`/account/team/${row.id}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -54,7 +54,7 @@ export const TeamTable = ({ users }: TeamTableProps) => {
|
|||||||
<Table
|
<Table
|
||||||
headers={headers}
|
headers={headers}
|
||||||
data={users}
|
data={users}
|
||||||
onRowClick={isAdmin ? handleRowClick : undefined}
|
onRowClick={isSuperAdmin ? handleRowClick : undefined}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -32,12 +32,7 @@ import {
|
|||||||
import { SPACING, LAYOUT } from "@/Utils/Theme/constants";
|
import { SPACING, LAYOUT } from "@/Utils/Theme/constants";
|
||||||
import { useGet, usePost, usePatch, useDelete } from "@/Hooks/UseApi";
|
import { useGet, usePost, usePatch, useDelete } from "@/Hooks/UseApi";
|
||||||
import { useMonitorForm } from "@/Hooks/useMonitorForm";
|
import { useMonitorForm } from "@/Hooks/useMonitorForm";
|
||||||
import {
|
import type { Monitor, MonitorType, GamesMap } from "@/Types/Monitor";
|
||||||
type Monitor,
|
|
||||||
type MonitorType,
|
|
||||||
type GamesMap,
|
|
||||||
supportsGeoCheck,
|
|
||||||
} from "@/Types/Monitor";
|
|
||||||
import type { Notification } from "@/Types/Notification";
|
import type { Notification } from "@/Types/Notification";
|
||||||
import type { MonitorFormData } from "@/Validation/monitor";
|
import type { MonitorFormData } from "@/Validation/monitor";
|
||||||
|
|
||||||
@@ -146,17 +141,6 @@ const getGeneralSettingsConfig = (
|
|||||||
showGrpcServiceName: false,
|
showGrpcServiceName: false,
|
||||||
showIgnoreTls: false,
|
showIgnoreTls: false,
|
||||||
},
|
},
|
||||||
websocket: {
|
|
||||||
urlLabel: t("pages.createMonitor.form.general.option.wsUrl.label"),
|
|
||||||
urlPlaceholder: t("pages.createMonitor.form.general.option.wsUrl.placeholder"),
|
|
||||||
namePlaceholder: t("pages.createMonitor.form.general.option.name.placeholder"),
|
|
||||||
showUrl: true,
|
|
||||||
showPort: false,
|
|
||||||
showGameSelect: false,
|
|
||||||
showSecret: false,
|
|
||||||
showGrpcServiceName: false,
|
|
||||||
showIgnoreTls: true,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
return configs[type] || configs.http;
|
return configs[type] || configs.http;
|
||||||
};
|
};
|
||||||
@@ -302,53 +286,46 @@ const CreateMonitorPage = () => {
|
|||||||
>
|
>
|
||||||
<RadioWithDescription
|
<RadioWithDescription
|
||||||
value="http"
|
value="http"
|
||||||
label={t("pages.common.monitors.monitorTypes.optionHttp")}
|
label={t("pages.createMonitor.form.type.optionHttp")}
|
||||||
description={t(
|
description={t(
|
||||||
"pages.createMonitor.form.type.optionHttpDescription"
|
"pages.createMonitor.form.type.optionHttpDescription"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<RadioWithDescription
|
<RadioWithDescription
|
||||||
value="ping"
|
value="ping"
|
||||||
label={t("pages.common.monitors.monitorTypes.optionPing")}
|
label={t("pages.createMonitor.form.type.optionPing")}
|
||||||
description={t(
|
description={t(
|
||||||
"pages.createMonitor.form.type.optionPingDescription"
|
"pages.createMonitor.form.type.optionPingDescription"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<RadioWithDescription
|
<RadioWithDescription
|
||||||
value="docker"
|
value="docker"
|
||||||
label={t("pages.common.monitors.monitorTypes.optionDocker")}
|
label={t("pages.createMonitor.form.type.optionDocker")}
|
||||||
description={t(
|
description={t(
|
||||||
"pages.createMonitor.form.type.optionDockerDescription"
|
"pages.createMonitor.form.type.optionDockerDescription"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<RadioWithDescription
|
<RadioWithDescription
|
||||||
value="port"
|
value="port"
|
||||||
label={t("pages.common.monitors.monitorTypes.optionPort")}
|
label={t("pages.createMonitor.form.type.optionPort")}
|
||||||
description={t(
|
description={t(
|
||||||
"pages.createMonitor.form.type.optionPortDescription"
|
"pages.createMonitor.form.type.optionPortDescription"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<RadioWithDescription
|
<RadioWithDescription
|
||||||
value="game"
|
value="game"
|
||||||
label={t("pages.common.monitors.monitorTypes.optionGame")}
|
label={t("pages.createMonitor.form.type.optionGame")}
|
||||||
description={t(
|
description={t(
|
||||||
"pages.createMonitor.form.type.optionGameDescription"
|
"pages.createMonitor.form.type.optionGameDescription"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<RadioWithDescription
|
<RadioWithDescription
|
||||||
value="grpc"
|
value="grpc"
|
||||||
label={t("pages.common.monitors.monitorTypes.optionGrpc")}
|
label={t("pages.createMonitor.form.type.optionGrpc")}
|
||||||
description={t(
|
description={t(
|
||||||
"pages.createMonitor.form.type.optionGrpcDescription"
|
"pages.createMonitor.form.type.optionGrpcDescription"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<RadioWithDescription
|
|
||||||
value="websocket"
|
|
||||||
label={t("pages.common.monitors.monitorTypes.optionWebSocket")}
|
|
||||||
description={t(
|
|
||||||
"pages.createMonitor.form.type.optionWebSocketDescription"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
@@ -765,9 +742,7 @@ const CreateMonitorPage = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{(watchedType === "http" ||
|
{(watchedType === "http" || watchedType === "grpc") && (
|
||||||
watchedType === "grpc" ||
|
|
||||||
watchedType === "websocket") && (
|
|
||||||
<ConfigBox
|
<ConfigBox
|
||||||
title={t("pages.createMonitor.form.ignoreTls.title")}
|
title={t("pages.createMonitor.form.ignoreTls.title")}
|
||||||
subtitle={t("pages.createMonitor.form.ignoreTls.description")}
|
subtitle={t("pages.createMonitor.form.ignoreTls.description")}
|
||||||
@@ -795,6 +770,50 @@ const CreateMonitorPage = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{watchedType === "http" && (
|
||||||
|
<ConfigBox
|
||||||
|
title={t("pages.createMonitor.form.basicAuth.title")}
|
||||||
|
subtitle={t("pages.createMonitor.form.basicAuth.description")}
|
||||||
|
rightContent={
|
||||||
|
<Stack spacing={theme.spacing(LAYOUT.MD)}>
|
||||||
|
<Controller
|
||||||
|
name="basicAuthUser"
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState }) => (
|
||||||
|
<TextField
|
||||||
|
{...field}
|
||||||
|
value={field.value ?? ""}
|
||||||
|
fieldLabel={t(
|
||||||
|
"pages.createMonitor.form.basicAuth.option.username.label"
|
||||||
|
)}
|
||||||
|
fullWidth
|
||||||
|
error={!!fieldState.error}
|
||||||
|
helperText={fieldState.error?.message ?? ""}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="basicAuthPassword"
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState }) => (
|
||||||
|
<TextField
|
||||||
|
{...field}
|
||||||
|
value={field.value ?? ""}
|
||||||
|
type="password"
|
||||||
|
fieldLabel={t(
|
||||||
|
"pages.createMonitor.form.basicAuth.option.password.label"
|
||||||
|
)}
|
||||||
|
fullWidth
|
||||||
|
error={!!fieldState.error}
|
||||||
|
helperText={fieldState.error?.message ?? ""}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{watchedType === "http" && (
|
{watchedType === "http" && (
|
||||||
<ConfigBox
|
<ConfigBox
|
||||||
title={t("pages.createMonitor.form.advanced.title")}
|
title={t("pages.createMonitor.form.advanced.title")}
|
||||||
@@ -910,7 +929,7 @@ const CreateMonitorPage = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{supportsGeoCheck(watchedType) && (
|
{watchedType === "http" && (
|
||||||
<ConfigBox
|
<ConfigBox
|
||||||
title={t("pages.createMonitor.form.geoChecks.title")}
|
title={t("pages.createMonitor.form.geoChecks.title")}
|
||||||
subtitle={t("pages.createMonitor.form.geoChecks.description")}
|
subtitle={t("pages.createMonitor.form.geoChecks.description")}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { useGet, usePatch, usePost, useLazyGet } from "@/Hooks/UseApi";
|
|||||||
import { useToast } from "@/Hooks/UseToast";
|
import { useToast } from "@/Hooks/UseToast";
|
||||||
import { useSettingsForm } from "@/Hooks/useSettingsForm";
|
import { useSettingsForm } from "@/Hooks/useSettingsForm";
|
||||||
import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
|
import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
|
||||||
import type { SettingsFormData, SettingsFormInput } from "@/Validation/settings";
|
import type { SettingsFormData } from "@/Validation/settings";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Controller } from "react-hook-form";
|
import { Controller } from "react-hook-form";
|
||||||
import { TextField, Button, FieldLabel, SliderWithLabel } from "@/Components/inputs";
|
import { TextField, Button, FieldLabel, SliderWithLabel } from "@/Components/inputs";
|
||||||
@@ -37,7 +37,6 @@ import {
|
|||||||
} from "@/Features/UI/uiSlice.js";
|
} from "@/Features/UI/uiSlice.js";
|
||||||
import timezones from "@/Utils/timezones.json";
|
import timezones from "@/Utils/timezones.json";
|
||||||
import type { RootState } from "@/Types/state";
|
import type { RootState } from "@/Types/state";
|
||||||
import { CHECK_TTL_SENTINEL } from "@/Types/Check";
|
|
||||||
|
|
||||||
interface Timezone {
|
interface Timezone {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -89,7 +88,7 @@ export const SettingsPage = () => {
|
|||||||
// Initialize form with schema and defaults
|
// Initialize form with schema and defaults
|
||||||
const { schema, defaults } = useSettingsForm({ data: fetchedSettings?.settings });
|
const { schema, defaults } = useSettingsForm({ data: fetchedSettings?.settings });
|
||||||
|
|
||||||
const form = useForm<SettingsFormInput, unknown, SettingsFormData>({
|
const form = useForm<SettingsFormData>({
|
||||||
resolver: zodResolver(schema),
|
resolver: zodResolver(schema),
|
||||||
defaultValues: defaults,
|
defaultValues: defaults,
|
||||||
mode: "onChange",
|
mode: "onChange",
|
||||||
@@ -256,7 +255,28 @@ export const SettingsPage = () => {
|
|||||||
delete (dataToSend as any).systemEmailPassword;
|
delete (dataToSend as any).systemEmailPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await patch("/settings", dataToSend as SettingsFormData);
|
// Convert undefined to empty string for backend unsetting
|
||||||
|
const processedData = Object.entries(dataToSend).reduce((acc, [key, value]) => {
|
||||||
|
const typedKey = key as keyof SettingsFormData;
|
||||||
|
if (value === undefined) {
|
||||||
|
(acc as any)[typedKey] = "";
|
||||||
|
} else if (typeof value === "object" && value !== null) {
|
||||||
|
// Handle nested objects like globalThresholds
|
||||||
|
// Convert 0 to "" for unsetting thresholds
|
||||||
|
(acc as any)[typedKey] = Object.entries(value).reduce(
|
||||||
|
(nested, [nestedKey, nestedValue]) => ({
|
||||||
|
...nested,
|
||||||
|
[nestedKey]: nestedValue === undefined ? "" : nestedValue,
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
(acc as any)[typedKey] = value;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {} as Partial<SettingsFormData>);
|
||||||
|
|
||||||
|
const result = await patch("/settings", processedData as SettingsFormData);
|
||||||
|
|
||||||
if (result?.success) {
|
if (result?.success) {
|
||||||
// Update API key state from response
|
// Update API key state from response
|
||||||
@@ -442,43 +462,6 @@ export const SettingsPage = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Check Retention */}
|
|
||||||
{isAdmin && (
|
|
||||||
<ConfigBox
|
|
||||||
title={t("pages.settings.form.retention.title")}
|
|
||||||
subtitle={t("pages.settings.form.retention.description")}
|
|
||||||
rightContent={
|
|
||||||
<Controller
|
|
||||||
name="checkTTL"
|
|
||||||
control={form.control}
|
|
||||||
defaultValue={defaults.checkTTL}
|
|
||||||
render={({ field }) => (
|
|
||||||
<SliderWithLabel
|
|
||||||
{...field}
|
|
||||||
fieldLabel={t("pages.settings.form.retention.option.days.label")}
|
|
||||||
min={1}
|
|
||||||
max={CHECK_TTL_SENTINEL}
|
|
||||||
sliderMaxWidth={{ xs: "100%", md: "50%" }}
|
|
||||||
value={field.value || 30}
|
|
||||||
onChange={(_, value) => field.onChange(value)}
|
|
||||||
valueLabelDisplay="auto"
|
|
||||||
valueLabelFormat={(value: number) =>
|
|
||||||
value >= CHECK_TTL_SENTINEL
|
|
||||||
? t("pages.settings.form.retention.option.days.unlimited")
|
|
||||||
: `${value}`
|
|
||||||
}
|
|
||||||
formatDisplayValue={(value: number) =>
|
|
||||||
value >= CHECK_TTL_SENTINEL
|
|
||||||
? t("pages.settings.form.retention.option.days.unlimited")
|
|
||||||
: `${value}`
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Global Thresholds */}
|
{/* Global Thresholds */}
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<ConfigBox
|
<ConfigBox
|
||||||
|
|||||||
@@ -21,28 +21,19 @@ import {
|
|||||||
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
import { useTheme } from "@mui/material/styles";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm, Controller } from "react-hook-form";
|
import { useForm, Controller } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useStatusPageForm } from "@/Hooks/useStatusPageForm";
|
import { useStatusPageForm } from "@/Hooks/useStatusPageForm";
|
||||||
import type { StatusPageFormData } from "@/Validation/statusPage";
|
import type { StatusPageFormData } from "@/Validation/statusPage";
|
||||||
import { useGet, usePost, usePut, useDelete } from "@/Hooks/UseApi";
|
import { useGet, usePost, usePut, useDelete } from "@/Hooks/UseApi";
|
||||||
import type { Monitor } from "@/Types/Monitor";
|
import type { Monitor } from "@/Types/Monitor";
|
||||||
import type { MonitorDisplayType, StatusPageResponse } from "@/Types/StatusPage";
|
import type { StatusPageResponse } from "@/Types/StatusPage";
|
||||||
import { getMonitorTypeLabel } from "@/Types/StatusPage";
|
|
||||||
import timezones from "@/Utils/timezones.json";
|
import timezones from "@/Utils/timezones.json";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { HeaderConfigStatusControls } from "./Components/HeaderConfigStatusControls";
|
import { HeaderConfigStatusControls } from "./Components/HeaderConfigStatusControls";
|
||||||
|
|
||||||
const monitorsUrl = (() => {
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
["http", "ping", "port", "docker", "game", "grpc", "websocket", "hardware"].forEach(
|
|
||||||
(type) => params.append("type", type)
|
|
||||||
);
|
|
||||||
return `/monitors/team?${params.toString()}`;
|
|
||||||
})();
|
|
||||||
|
|
||||||
interface TimezoneOption {
|
interface TimezoneOption {
|
||||||
_id: string;
|
_id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -58,11 +49,11 @@ const CreateStatusPage = () => {
|
|||||||
|
|
||||||
// Fetch existing status page data when configuring
|
// Fetch existing status page data when configuring
|
||||||
const { data: statusPageData, isLoading: isLoadingStatusPage } =
|
const { data: statusPageData, isLoading: isLoadingStatusPage } =
|
||||||
useGet<StatusPageResponse>(
|
useGet<StatusPageResponse>(isCreate ? null : `/status-page/${url}?type=uptime`);
|
||||||
isCreate ? null : `/status-page/${url}?type=uptime&type=infrastructure`
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: monitorsResponse } = useGet<Monitor[]>(monitorsUrl);
|
const { data: monitorsResponse } = useGet<Monitor[]>(
|
||||||
|
"/monitors/team?type=http&type=ping&type=port&type=docker"
|
||||||
|
);
|
||||||
const monitors = monitorsResponse ?? [];
|
const monitors = monitorsResponse ?? [];
|
||||||
|
|
||||||
const { post, loading: isSubmittingPost } = usePost();
|
const { post, loading: isSubmittingPost } = usePost();
|
||||||
@@ -90,24 +81,6 @@ const CreateStatusPage = () => {
|
|||||||
reset(defaults);
|
reset(defaults);
|
||||||
}, [defaults, reset]);
|
}, [defaults, reset]);
|
||||||
|
|
||||||
const watchedMonitorIds: string[] = form.watch("monitors") ?? [];
|
|
||||||
const computedTypes: MonitorDisplayType[] = useMemo(() => {
|
|
||||||
const selectedMonitors = (watchedMonitorIds ?? [])
|
|
||||||
.map((id) => monitors.find((m) => m.id === id))
|
|
||||||
.filter((m): m is Monitor => m !== undefined);
|
|
||||||
|
|
||||||
const typesSet = new Set<MonitorDisplayType>();
|
|
||||||
selectedMonitors.forEach((m) => {
|
|
||||||
typesSet.add(m.type === "hardware" ? "infrastructure" : "uptime");
|
|
||||||
});
|
|
||||||
|
|
||||||
return typesSet.size ? Array.from(typesSet) : ["uptime"];
|
|
||||||
}, [watchedMonitorIds, monitors]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
form.setValue("type", computedTypes);
|
|
||||||
}, [computedTypes]);
|
|
||||||
|
|
||||||
const onError = (errors: any) => {
|
const onError = (errors: any) => {
|
||||||
logger.debug("Status page validation errors", errors);
|
logger.debug("Status page validation errors", errors);
|
||||||
};
|
};
|
||||||
@@ -130,6 +103,7 @@ const CreateStatusPage = () => {
|
|||||||
|
|
||||||
const onSubmit = async (data: StatusPageFormData) => {
|
const onSubmit = async (data: StatusPageFormData) => {
|
||||||
const fd = new FormData();
|
const fd = new FormData();
|
||||||
|
fd.append("type", "uptime");
|
||||||
fd.append("isPublished", String(data.isPublished));
|
fd.append("isPublished", String(data.isPublished));
|
||||||
if (data.companyName) fd.append("companyName", data.companyName);
|
if (data.companyName) fd.append("companyName", data.companyName);
|
||||||
if (data.url) fd.append("url", data.url);
|
if (data.url) fd.append("url", data.url);
|
||||||
@@ -138,16 +112,11 @@ const CreateStatusPage = () => {
|
|||||||
fd.append("showCharts", String(data.showCharts));
|
fd.append("showCharts", String(data.showCharts));
|
||||||
fd.append("showUptimePercentage", String(data.showUptimePercentage));
|
fd.append("showUptimePercentage", String(data.showUptimePercentage));
|
||||||
fd.append("showAdminLoginLink", String(data.showAdminLoginLink));
|
fd.append("showAdminLoginLink", String(data.showAdminLoginLink));
|
||||||
fd.append("showInfrastructure", String(data.showInfrastructure));
|
|
||||||
|
|
||||||
data.monitors.forEach((monitorId) => {
|
data.monitors.forEach((monitorId) => {
|
||||||
fd.append("monitors[]", monitorId);
|
fd.append("monitors[]", monitorId);
|
||||||
});
|
});
|
||||||
|
|
||||||
data.type.forEach((type) => {
|
|
||||||
fd.append("type[]", type);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle logo upload
|
// Handle logo upload
|
||||||
if (data.logo === null) {
|
if (data.logo === null) {
|
||||||
// Signal to remove the logo
|
// Signal to remove the logo
|
||||||
@@ -334,9 +303,7 @@ const CreateStatusPage = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<GripVertical size={20} />
|
<GripVertical size={20} />
|
||||||
<Typography
|
<Typography flexGrow={1}>{monitor.name}</Typography>
|
||||||
flexGrow={1}
|
|
||||||
>{`${monitor.name} (${getMonitorTypeLabel(monitor.type, t)})`}</Typography>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -458,23 +425,6 @@ const CreateStatusPage = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Controller
|
|
||||||
name="showInfrastructure"
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormControlLabel
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
checked={field.value}
|
|
||||||
onChange={field.onChange}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={t(
|
|
||||||
"pages.statusPages.form.features.option.showInfrastructure.label"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{/* <Controller
|
{/* <Controller
|
||||||
name="showUptimePercentage"
|
name="showUptimePercentage"
|
||||||
control={control}
|
control={control}
|
||||||
|
|||||||
@@ -1,246 +0,0 @@
|
|||||||
import Stack from "@mui/material/Stack";
|
|
||||||
import Typography from "@mui/material/Typography";
|
|
||||||
import { Gauge } from "@/Components/design-elements";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import prettyBytes from "pretty-bytes";
|
|
||||||
import type { Monitor } from "@/Types/Monitor";
|
|
||||||
import Grid from "@mui/material/Grid";
|
|
||||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
|
||||||
import Box from "@mui/material/Box";
|
|
||||||
import { LAYOUT, SPACING } from "@/Utils/Theme/constants";
|
|
||||||
|
|
||||||
const GAUGE_RADIUS = 60;
|
|
||||||
const GAUGE_STROKE_WIDTH = 12;
|
|
||||||
const PERCENTAGE_MULTIPLIER = 100;
|
|
||||||
|
|
||||||
interface StatusPageMonitor extends Monitor {
|
|
||||||
checks?: Monitor["recentChecks"];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MetricDetail {
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MetricConfig {
|
|
||||||
key: string;
|
|
||||||
label: string;
|
|
||||||
hasData: boolean;
|
|
||||||
progress: number;
|
|
||||||
details: MetricDetail[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const MetricDetailRow = ({ label, value }: MetricDetail) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<Stack
|
|
||||||
direction="row"
|
|
||||||
justifyContent="space-between"
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
color={theme.palette.text.secondary}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="body2">{value}</Typography>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface MetricItemProps {
|
|
||||||
label: string;
|
|
||||||
progress: number;
|
|
||||||
details?: MetricDetail[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const MetricItem = ({ label, progress, details }: MetricItemProps) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
|
|
||||||
return (
|
|
||||||
<Grid
|
|
||||||
size={isSmall ? 12 : 4}
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
textAlign: "center",
|
|
||||||
gap: theme.spacing(SPACING.LG),
|
|
||||||
padding: theme.spacing(LAYOUT.XS),
|
|
||||||
borderRight: isSmall ? "none" : `1px solid ${theme.palette.divider}`,
|
|
||||||
borderBottom: isSmall ? `1px solid ${theme.palette.divider}` : "none",
|
|
||||||
"&:last-child": {
|
|
||||||
borderRight: "none",
|
|
||||||
borderBottom: "none",
|
|
||||||
paddingBottom: theme.spacing(SPACING.LG),
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
display="flex"
|
|
||||||
flexDirection="column"
|
|
||||||
alignItems="center"
|
|
||||||
>
|
|
||||||
<Gauge
|
|
||||||
progress={progress}
|
|
||||||
radius={GAUGE_RADIUS}
|
|
||||||
strokeWidth={GAUGE_STROKE_WIDTH}
|
|
||||||
/>
|
|
||||||
<Typography variant="body2">{label}</Typography>
|
|
||||||
</Box>
|
|
||||||
{details && details.length > 0 && (
|
|
||||||
<Box
|
|
||||||
width="100%"
|
|
||||||
paddingX={theme.spacing(LAYOUT.LG)}
|
|
||||||
>
|
|
||||||
{details.map((detail) => (
|
|
||||||
<MetricDetailRow
|
|
||||||
key={detail.label}
|
|
||||||
label={detail.label}
|
|
||||||
value={detail.value}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
type LatestCheck = NonNullable<Monitor["recentChecks"]>[number];
|
|
||||||
|
|
||||||
const buildCpuMetric = (
|
|
||||||
check: LatestCheck,
|
|
||||||
t: (key: string) => string
|
|
||||||
): MetricConfig | null => {
|
|
||||||
if (!check.cpu || typeof check.cpu.usage_percent !== "number") {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const usagePercent = (check.cpu.usage_percent ?? 0) * PERCENTAGE_MULTIPLIER;
|
|
||||||
return {
|
|
||||||
key: "cpu",
|
|
||||||
label: t("pages.statusPages.monitorsList.infrastructure.cpu"),
|
|
||||||
hasData: true,
|
|
||||||
progress: usagePercent,
|
|
||||||
details: [
|
|
||||||
{
|
|
||||||
label: t("pages.statusPages.monitorsList.infrastructure.usage"),
|
|
||||||
value: `${usagePercent.toFixed(2)}%`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const buildMemoryMetric = (
|
|
||||||
check: LatestCheck,
|
|
||||||
t: (key: string) => string
|
|
||||||
): MetricConfig | null => {
|
|
||||||
if (
|
|
||||||
!check.memory ||
|
|
||||||
typeof check.memory.usage_percent !== "number" ||
|
|
||||||
typeof check.memory.used_bytes !== "number" ||
|
|
||||||
typeof check.memory.total_bytes !== "number"
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
key: "memory",
|
|
||||||
label: t("pages.statusPages.monitorsList.infrastructure.memory"),
|
|
||||||
hasData: true,
|
|
||||||
progress: (check.memory.usage_percent ?? 0) * PERCENTAGE_MULTIPLIER,
|
|
||||||
details: [
|
|
||||||
{
|
|
||||||
label: t("pages.statusPages.monitorsList.infrastructure.used"),
|
|
||||||
value: prettyBytes(check.memory.used_bytes ?? 0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t("pages.statusPages.monitorsList.infrastructure.total"),
|
|
||||||
value: prettyBytes(check.memory.total_bytes ?? 0),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const buildDiskMetric = (
|
|
||||||
check: LatestCheck,
|
|
||||||
t: (key: string) => string
|
|
||||||
): MetricConfig | null => {
|
|
||||||
if (!check.disk || check.disk.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const disks = check.disk;
|
|
||||||
const avgUsagePercent =
|
|
||||||
(disks.reduce((acc, disk) => acc + (disk?.usage_percent ?? 0), 0) / disks.length) *
|
|
||||||
PERCENTAGE_MULTIPLIER;
|
|
||||||
const totalUsedBytes = disks.reduce((acc, disk) => acc + (disk?.used_bytes ?? 0), 0);
|
|
||||||
const totalTotalBytes = disks.reduce((acc, disk) => acc + (disk?.total_bytes ?? 0), 0);
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: "disk",
|
|
||||||
label: t("pages.statusPages.monitorsList.infrastructure.disk"),
|
|
||||||
hasData: true,
|
|
||||||
progress: avgUsagePercent,
|
|
||||||
details: [
|
|
||||||
{
|
|
||||||
label: t("pages.statusPages.monitorsList.infrastructure.used"),
|
|
||||||
value: prettyBytes(totalUsedBytes),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t("pages.statusPages.monitorsList.infrastructure.total"),
|
|
||||||
value: prettyBytes(totalTotalBytes),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const InfrastructureMetrics = ({ monitor }: { monitor: StatusPageMonitor }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const latestCheck = monitor.recentChecks?.[0] ?? monitor.checks?.[0];
|
|
||||||
|
|
||||||
if (!latestCheck) {
|
|
||||||
return (
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
color={theme.palette.text.secondary}
|
|
||||||
>
|
|
||||||
{t("pages.statusPages.monitorsList.noData")}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const metrics: MetricConfig[] = [
|
|
||||||
buildCpuMetric(latestCheck, t),
|
|
||||||
buildMemoryMetric(latestCheck, t),
|
|
||||||
buildDiskMetric(latestCheck, t),
|
|
||||||
].filter((m): m is MetricConfig => m !== null);
|
|
||||||
|
|
||||||
if (metrics.length === 0) {
|
|
||||||
return (
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
color={theme.palette.text.secondary}
|
|
||||||
>
|
|
||||||
{t("pages.statusPages.monitorsList.noData")}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Grid
|
|
||||||
container
|
|
||||||
alignItems="center"
|
|
||||||
padding={theme.spacing(LAYOUT.XS)}
|
|
||||||
>
|
|
||||||
{metrics.map(({ key, label, progress, details }) => (
|
|
||||||
<MetricItem
|
|
||||||
key={key}
|
|
||||||
label={label}
|
|
||||||
progress={progress}
|
|
||||||
details={details}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -5,16 +5,13 @@ import Typography from "@mui/material/Typography";
|
|||||||
import { HistogramResponseTime, HeatmapResponseTime } from "@/Components/common";
|
import { HistogramResponseTime, HeatmapResponseTime } from "@/Components/common";
|
||||||
import { StatusLabel, BaseBox } from "@/Components/design-elements";
|
import { StatusLabel, BaseBox } from "@/Components/design-elements";
|
||||||
import { SwitchComponent } from "@/Components/inputs";
|
import { SwitchComponent } from "@/Components/inputs";
|
||||||
import { InfrastructureMetrics } from "@/Pages/StatusPage/Status/Components/InfrastructureMetrics";
|
|
||||||
|
|
||||||
import { useTheme, type Theme } from "@mui/material/styles";
|
import { useTheme } from "@mui/material/styles";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import type { Monitor } from "@/Types/Monitor";
|
import type { Monitor } from "@/Types/Monitor";
|
||||||
import type { StatusPage } from "@/Types/StatusPage";
|
import type { StatusPage } from "@/Types/StatusPage";
|
||||||
import { getMonitorTypeLabel } from "@/Types/StatusPage";
|
|
||||||
import type { RootState } from "@/Types/state";
|
import type { RootState } from "@/Types/state";
|
||||||
import { LAYOUT, SPACING } from "@/Utils/Theme/constants";
|
|
||||||
|
|
||||||
interface StatusPageMonitor extends Monitor {
|
interface StatusPageMonitor extends Monitor {
|
||||||
checks?: Monitor["recentChecks"];
|
checks?: Monitor["recentChecks"];
|
||||||
@@ -25,124 +22,29 @@ interface MonitorsListProps {
|
|||||||
monitors: StatusPageMonitor[];
|
monitors: StatusPageMonitor[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const getMonitorBadgeStyles = (monitorType: string, theme: Theme) => {
|
|
||||||
const bg =
|
|
||||||
monitorType === "hardware" ? theme.palette.info.light : theme.palette.success.light;
|
|
||||||
return {
|
|
||||||
backgroundColor: bg,
|
|
||||||
color: theme.palette.background.paper,
|
|
||||||
padding: `${theme.spacing(SPACING.SM)} ${theme.spacing(SPACING.LG)}`,
|
|
||||||
borderRadius: theme.shape.borderRadius,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const MonitorHeader = ({
|
|
||||||
monitor,
|
|
||||||
showURL,
|
|
||||||
}: {
|
|
||||||
monitor: StatusPageMonitor;
|
|
||||||
showURL: boolean;
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack
|
|
||||||
direction="row"
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="space-between"
|
|
||||||
gap={theme.spacing(LAYOUT.XS)}
|
|
||||||
mb={theme.spacing(LAYOUT.XS)}
|
|
||||||
>
|
|
||||||
<Box sx={{ overflow: "hidden", minWidth: 0, flex: 1 }}>
|
|
||||||
<Stack
|
|
||||||
direction="row"
|
|
||||||
alignItems="center"
|
|
||||||
gap={theme.spacing(SPACING.LG)}
|
|
||||||
mb={theme.spacing(SPACING.SM)}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="h6"
|
|
||||||
sx={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}
|
|
||||||
>
|
|
||||||
{monitor.name}
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
variant="caption"
|
|
||||||
sx={getMonitorBadgeStyles(monitor.type ?? "", theme)}
|
|
||||||
>
|
|
||||||
{getMonitorTypeLabel(monitor.type, t)}
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
{showURL && monitor.url && (
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
color={theme.palette.text.secondary}
|
|
||||||
sx={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}
|
|
||||||
>
|
|
||||||
{monitor.url}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
<StatusLabel status={monitor.status} />
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const MonitorContent = ({
|
|
||||||
monitor,
|
|
||||||
statusPage,
|
|
||||||
chartType,
|
|
||||||
}: {
|
|
||||||
monitor: StatusPageMonitor;
|
|
||||||
statusPage: StatusPage;
|
|
||||||
chartType: string;
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
if (monitor.type === "hardware") {
|
|
||||||
if (statusPage.showInfrastructure === false) return null;
|
|
||||||
return <InfrastructureMetrics monitor={monitor} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (statusPage.showCharts === false) return null;
|
|
||||||
|
|
||||||
const checks = monitor.checks?.slice().reverse() ?? [];
|
|
||||||
return (
|
|
||||||
<Box sx={{ overflow: "hidden", minWidth: 0, flex: 1, mb: theme.spacing(SPACING.LG) }}>
|
|
||||||
{chartType === "histogram" ? (
|
|
||||||
<HistogramResponseTime
|
|
||||||
height={{ xs: 50, md: 100 }}
|
|
||||||
gap={{ xs: theme.spacing(SPACING.SM), md: theme.spacing(LAYOUT.SM) }}
|
|
||||||
checks={checks}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<HeatmapResponseTime checks={checks} />
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MonitorsList = ({ statusPage, monitors }: MonitorsListProps) => {
|
export const MonitorsList = ({ statusPage, monitors }: MonitorsListProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const showURL = useSelector((state: RootState) => state.ui?.showURL);
|
const showURL = useSelector((state: RootState) => state.ui?.showURL);
|
||||||
|
|
||||||
const [chartType, setChartType] = useState<"histogram" | "heatmap">("histogram");
|
const [chartType, setChartType] = useState<"histogram" | "heatmap">("histogram");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap={theme.spacing(LAYOUT.MD)}>
|
<Stack gap={theme.spacing(8)}>
|
||||||
{statusPage.showCharts && (
|
{statusPage.showCharts !== false && (
|
||||||
<Stack
|
<Stack
|
||||||
direction="row"
|
direction={"row"}
|
||||||
alignItems="center"
|
alignItems={"center"}
|
||||||
gap={theme.spacing(LAYOUT.SM)}
|
|
||||||
>
|
>
|
||||||
<Typography>{t("pages.statusPages.monitorsList.chartTypeHeatmap")}</Typography>
|
<Typography>{t("pages.statusPages.monitorsList.chartTypeHeatmap")}</Typography>
|
||||||
<SwitchComponent
|
<SwitchComponent
|
||||||
dualOption
|
dualOption
|
||||||
value={chartType}
|
value={chartType}
|
||||||
checked={chartType === "histogram"}
|
checked={chartType === "histogram"}
|
||||||
onChange={(e) => setChartType(e.target.checked ? "histogram" : "heatmap")}
|
onChange={(e) => {
|
||||||
|
setChartType(e.target.checked ? "histogram" : "heatmap");
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Typography>
|
<Typography>
|
||||||
{t("pages.statusPages.monitorsList.chartTypeHistogram")}
|
{t("pages.statusPages.monitorsList.chartTypeHistogram")}
|
||||||
@@ -150,22 +52,59 @@ export const MonitorsList = ({ statusPage, monitors }: MonitorsListProps) => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{monitors.map((monitor) => (
|
{monitors?.map((monitor) => {
|
||||||
<BaseBox
|
return (
|
||||||
key={monitor.id}
|
<BaseBox
|
||||||
padding={theme.spacing(LAYOUT.MD)}
|
key={monitor.id}
|
||||||
>
|
padding={theme.spacing(4)}
|
||||||
<MonitorHeader
|
>
|
||||||
monitor={monitor}
|
<Stack
|
||||||
showURL={showURL}
|
direction="row"
|
||||||
/>
|
alignItems="center"
|
||||||
<MonitorContent
|
justifyContent="space-between"
|
||||||
monitor={monitor}
|
gap={theme.spacing(4)}
|
||||||
statusPage={statusPage}
|
mb={statusPage.showCharts !== false ? theme.spacing(4) : 0}
|
||||||
chartType={chartType}
|
>
|
||||||
/>
|
<Box sx={{ overflow: "hidden", minWidth: 0, flex: 1 }}>
|
||||||
</BaseBox>
|
<Typography
|
||||||
))}
|
variant="h6"
|
||||||
|
sx={{
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{monitor.name}
|
||||||
|
</Typography>
|
||||||
|
{showURL && (
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="text.secondary"
|
||||||
|
>
|
||||||
|
{monitor.url}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<StatusLabel status={monitor.status} />
|
||||||
|
</Stack>
|
||||||
|
{statusPage.showCharts !== false && (
|
||||||
|
<Box sx={{ overflow: "hidden", minWidth: 0, flex: 1 }}>
|
||||||
|
{chartType === "histogram" ? (
|
||||||
|
<HistogramResponseTime
|
||||||
|
height={{ xs: 50, md: 100 }}
|
||||||
|
gap={{ xs: theme.spacing(0.5), md: theme.spacing(5) }}
|
||||||
|
checks={monitor?.checks?.slice().reverse() ?? []}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<HeatmapResponseTime
|
||||||
|
checks={monitor?.checks?.slice().reverse() ?? []}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</BaseBox>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -23,22 +23,16 @@ const StatusPageView = () => {
|
|||||||
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
|
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
|
||||||
const isPublic = location.pathname.startsWith("/status/public");
|
const isPublic = location.pathname.startsWith("/status/public");
|
||||||
|
|
||||||
const apiUrl = url ? `/status-page/${url}?type=uptime&type=infrastructure` : null;
|
const apiUrl = url ? `/status-page/${url}?type=uptime` : null;
|
||||||
|
|
||||||
const { data, isLoading, error } = useGet<StatusPageResponse>(
|
const { data, isLoading, error } = useGet<StatusPageResponse>(apiUrl);
|
||||||
apiUrl,
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
refreshInterval: 10000,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const statusPage = data?.statusPage;
|
const statusPage = data?.statusPage;
|
||||||
const monitors = data?.monitors ?? [];
|
const monitors = data?.monitors ?? [];
|
||||||
|
|
||||||
if (!statusPage) return null;
|
if (!statusPage) return null;
|
||||||
|
|
||||||
if (monitors.length === 0) {
|
if (monitors?.length === 0) {
|
||||||
return (
|
return (
|
||||||
<BasePage
|
<BasePage
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export const StatusPagesTable = ({
|
|||||||
{
|
{
|
||||||
id: "type",
|
id: "type",
|
||||||
content: t("common.table.headers.type"),
|
content: t("common.table.headers.type"),
|
||||||
render: (row) => row.type.join(", "),
|
render: (row) => row.type,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "status",
|
id: "status",
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { useState, useMemo } from "react";
|
|||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { useGet } from "@/Hooks/UseApi";
|
import { useGet } from "@/Hooks/UseApi";
|
||||||
import { type MonitorDetailsResponse, supportsGeoCheck } from "@/Types/Monitor";
|
import type { MonitorDetailsResponse } from "@/Types/Monitor";
|
||||||
import type { ChecksResponse } from "@/Types/Check";
|
import type { ChecksResponse } from "@/Types/Check";
|
||||||
import type {
|
import type {
|
||||||
GeoChecksResult,
|
GeoChecksResult,
|
||||||
@@ -122,7 +122,7 @@ const UptimeDetailsPage = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const geoChecksUrl = useMemo(() => {
|
const geoChecksUrl = useMemo(() => {
|
||||||
if (!monitorId || !supportsGeoCheck(monitor?.type) || !monitor?.geoCheckEnabled) {
|
if (!monitorId || monitor?.type !== "http" || !monitor?.geoCheckEnabled) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
@@ -141,7 +141,7 @@ const UptimeDetailsPage = () => {
|
|||||||
|
|
||||||
// Fetch paginated geo checks for the table
|
// Fetch paginated geo checks for the table
|
||||||
const geoChecksTableUrl = useMemo(() => {
|
const geoChecksTableUrl = useMemo(() => {
|
||||||
if (!monitorId || !supportsGeoCheck(monitor?.type) || !monitor?.geoCheckEnabled) {
|
if (!monitorId || monitor?.type !== "http" || !monitor?.geoCheckEnabled) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ const UptimeMonitorsPage = () => {
|
|||||||
const effectiveTypes =
|
const effectiveTypes =
|
||||||
selectedTypes.length > 0
|
selectedTypes.length > 0
|
||||||
? selectedTypes
|
? selectedTypes
|
||||||
: ["http", "ping", "docker", "port", "game", "grpc", "websocket"];
|
: ["http", "ping", "docker", "port", "game", "grpc"];
|
||||||
|
|
||||||
// Build URL for monitors with checks
|
// Build URL for monitors with checks
|
||||||
const monitorsWithChecksUrl = useMemo(() => {
|
const monitorsWithChecksUrl = useMemo(() => {
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
export const CHECK_TTL_SENTINEL = 366;
|
|
||||||
|
|
||||||
export interface CheckMetadata {
|
export interface CheckMetadata {
|
||||||
monitorId: string;
|
monitorId: string;
|
||||||
teamId: string;
|
teamId: string;
|
||||||
@@ -127,6 +125,9 @@ export interface Check {
|
|||||||
timings?: CheckTimings;
|
timings?: CheckTimings;
|
||||||
statusCode: number;
|
statusCode: number;
|
||||||
message: string;
|
message: string;
|
||||||
|
ack: boolean;
|
||||||
|
ackAt?: string | null;
|
||||||
|
expiry: string;
|
||||||
cpu?: CheckCpuInfo;
|
cpu?: CheckCpuInfo;
|
||||||
memory?: CheckMemoryInfo;
|
memory?: CheckMemoryInfo;
|
||||||
disk?: CheckDiskInfo[];
|
disk?: CheckDiskInfo[];
|
||||||
@@ -139,6 +140,7 @@ export interface Check {
|
|||||||
seo?: number;
|
seo?: number;
|
||||||
performance?: number;
|
performance?: number;
|
||||||
audits?: CheckAudits;
|
audits?: CheckAudits;
|
||||||
|
__v: number;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
}
|
}
|
||||||
@@ -241,7 +243,10 @@ export interface ChecksSummary {
|
|||||||
downChecks: number;
|
downChecks: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CheckSnapshot = Omit<Check, "metadata" | "__v" | "updatedAt"> & {
|
export type CheckSnapshot = Omit<
|
||||||
|
Check,
|
||||||
|
"metadata" | "ack" | "ackAt" | "expiry" | "__v" | "updatedAt"
|
||||||
|
> & {
|
||||||
originalResponseTime: number;
|
originalResponseTime: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,20 +12,10 @@ export const MonitorTypes = [
|
|||||||
"port",
|
"port",
|
||||||
"game",
|
"game",
|
||||||
"grpc",
|
"grpc",
|
||||||
"websocket",
|
|
||||||
"unknown",
|
"unknown",
|
||||||
] as const;
|
] as const;
|
||||||
export type MonitorType = (typeof MonitorTypes)[number];
|
export type MonitorType = (typeof MonitorTypes)[number];
|
||||||
|
|
||||||
export const GeoCheckSupportedTypes: readonly MonitorType[] = ["http", "ping"] as const;
|
|
||||||
|
|
||||||
export const supportsGeoCheck = (type: MonitorType | undefined): boolean => {
|
|
||||||
if (!type) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return GeoCheckSupportedTypes.includes(type);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MonitorStatuses = [
|
export const MonitorStatuses = [
|
||||||
"up",
|
"up",
|
||||||
"down",
|
"down",
|
||||||
@@ -54,6 +44,8 @@ export interface Monitor {
|
|||||||
jsonPath?: string;
|
jsonPath?: string;
|
||||||
expectedValue?: string;
|
expectedValue?: string;
|
||||||
matchMethod?: MonitorMatchMethod;
|
matchMethod?: MonitorMatchMethod;
|
||||||
|
basicAuthUser?: string;
|
||||||
|
basicAuthPassword?: string;
|
||||||
url: string;
|
url: string;
|
||||||
port?: number;
|
port?: number;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ export const NotificationChannels = [
|
|||||||
"webhook",
|
"webhook",
|
||||||
"pager_duty",
|
"pager_duty",
|
||||||
"matrix",
|
"matrix",
|
||||||
"teams",
|
|
||||||
] as const;
|
] as const;
|
||||||
export type NotificationChannel = (typeof NotificationChannels)[number];
|
export type NotificationChannel = (typeof NotificationChannels)[number];
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,10 @@
|
|||||||
import type { Monitor, MonitorType } from "@/Types/Monitor";
|
import type { Monitor } from "@/Types/Monitor";
|
||||||
export type MonitorDisplayType = "uptime" | "infrastructure";
|
|
||||||
|
|
||||||
export const MONITOR_TYPE_KEYS: Partial<Record<MonitorType, string>> = {
|
|
||||||
http: "pages.common.monitors.monitorTypes.optionHttp",
|
|
||||||
ping: "pages.common.monitors.monitorTypes.optionPing",
|
|
||||||
docker: "pages.common.monitors.monitorTypes.optionDocker",
|
|
||||||
port: "pages.common.monitors.monitorTypes.optionPort",
|
|
||||||
game: "pages.common.monitors.monitorTypes.optionGame",
|
|
||||||
grpc: "pages.common.monitors.monitorTypes.optionGrpc",
|
|
||||||
websocket: "pages.common.monitors.monitorTypes.optionWebSocket",
|
|
||||||
hardware: "pages.common.monitors.monitorTypes.optionHardware",
|
|
||||||
pagespeed: "pages.common.monitors.monitorTypes.optionPagespeed",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getMonitorTypeLabel = (
|
|
||||||
type: MonitorType,
|
|
||||||
t: (key: string) => string
|
|
||||||
): string => {
|
|
||||||
const key = MONITOR_TYPE_KEYS[type];
|
|
||||||
return key ? t(key) : type;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface StatusPage {
|
export interface StatusPage {
|
||||||
id: string;
|
id: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
teamId: string;
|
teamId: string;
|
||||||
type: MonitorDisplayType[];
|
type: string;
|
||||||
companyName: string;
|
companyName: string;
|
||||||
url: string;
|
url: string;
|
||||||
timezone?: string;
|
timezone?: string;
|
||||||
@@ -41,7 +20,6 @@ export interface StatusPage {
|
|||||||
showCharts: boolean;
|
showCharts: boolean;
|
||||||
showUptimePercentage: boolean;
|
showUptimePercentage: boolean;
|
||||||
showAdminLoginLink: boolean;
|
showAdminLoginLink: boolean;
|
||||||
showInfrastructure: boolean;
|
|
||||||
customCSS: string;
|
customCSS: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ export const getMonitorPath = (type: MonitorType): string => {
|
|||||||
ping: "uptime",
|
ping: "uptime",
|
||||||
game: "uptime",
|
game: "uptime",
|
||||||
grpc: "uptime",
|
grpc: "uptime",
|
||||||
websocket: "uptime",
|
|
||||||
unknown: "uptime",
|
unknown: "uptime",
|
||||||
docker: "uptime",
|
docker: "uptime",
|
||||||
hardware: "infrastructure",
|
hardware: "infrastructure",
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ export const typographyLevels = {
|
|||||||
export const colors = {
|
export const colors = {
|
||||||
gray200: "#EFEFEF",
|
gray200: "#EFEFEF",
|
||||||
gray700: "#313131",
|
gray700: "#313131",
|
||||||
gray900: "#151518",
|
|
||||||
gray850: "#1c1c21",
|
|
||||||
blueBlueWave: "#1570EF",
|
blueBlueWave: "#1570EF",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -33,8 +31,4 @@ export const darkPalette = {
|
|||||||
secondary: {
|
secondary: {
|
||||||
main: colors.gray700,
|
main: colors.gray700,
|
||||||
},
|
},
|
||||||
background: {
|
|
||||||
default: colors.gray900,
|
|
||||||
paper: colors.gray850,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
@@ -38,6 +38,8 @@ const httpSchema = baseSchema.extend({
|
|||||||
matchMethod: z.enum(["equal", "include", "regex", ""]).optional(),
|
matchMethod: z.enum(["equal", "include", "regex", ""]).optional(),
|
||||||
expectedValue: z.string().optional(),
|
expectedValue: z.string().optional(),
|
||||||
jsonPath: z.string().optional(),
|
jsonPath: z.string().optional(),
|
||||||
|
basicAuthUser: z.string().optional(),
|
||||||
|
basicAuthPassword: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ping monitor schema
|
// Ping monitor schema
|
||||||
@@ -115,13 +117,6 @@ const hardwareSchema = baseSchema.extend({
|
|||||||
selectedDisks: z.array(z.string()),
|
selectedDisks: z.array(z.string()),
|
||||||
});
|
});
|
||||||
|
|
||||||
// WebSocket monitor schema
|
|
||||||
const websocketSchema = baseSchema.extend({
|
|
||||||
type: z.literal("websocket"),
|
|
||||||
url: z.string().min(1, "WebSocket URL is required"),
|
|
||||||
ignoreTlsErrors: z.boolean(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Discriminated union of all monitor types
|
// Discriminated union of all monitor types
|
||||||
export const monitorSchema = z.discriminatedUnion("type", [
|
export const monitorSchema = z.discriminatedUnion("type", [
|
||||||
httpSchema,
|
httpSchema,
|
||||||
@@ -132,7 +127,6 @@ export const monitorSchema = z.discriminatedUnion("type", [
|
|||||||
grpcSchema,
|
grpcSchema,
|
||||||
pagespeedSchema,
|
pagespeedSchema,
|
||||||
hardwareSchema,
|
hardwareSchema,
|
||||||
websocketSchema,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export type MonitorFormData = z.infer<typeof monitorSchema>;
|
export type MonitorFormData = z.infer<typeof monitorSchema>;
|
||||||
@@ -147,5 +141,4 @@ export {
|
|||||||
grpcSchema,
|
grpcSchema,
|
||||||
pagespeedSchema,
|
pagespeedSchema,
|
||||||
hardwareSchema,
|
hardwareSchema,
|
||||||
websocketSchema,
|
|
||||||
};
|
};
|
||||||
@@ -45,11 +45,6 @@ const matrixSchema = baseSchema.extend({
|
|||||||
accessToken: z.string().min(1, "Access token is required"),
|
accessToken: z.string().min(1, "Access token is required"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const teamsSchema = baseSchema.extend({
|
|
||||||
type: z.literal("teams"),
|
|
||||||
address: z.string().min(1, "Webhook URL is required").url("Please enter a valid URL"),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const notificationSchema = z.discriminatedUnion("type", [
|
export const notificationSchema = z.discriminatedUnion("type", [
|
||||||
emailSchema,
|
emailSchema,
|
||||||
slackSchema,
|
slackSchema,
|
||||||
@@ -57,7 +52,6 @@ export const notificationSchema = z.discriminatedUnion("type", [
|
|||||||
webhookSchema,
|
webhookSchema,
|
||||||
pagerDutySchema,
|
pagerDutySchema,
|
||||||
matrixSchema,
|
matrixSchema,
|
||||||
teamsSchema,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export type NotificationFormData = z.infer<typeof notificationSchema>;
|
export type NotificationFormData = z.infer<typeof notificationSchema>;
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { CHECK_TTL_SENTINEL } from "@/Types/Check";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export const settingsSchema = z.object({
|
export const settingsSchema = z.object({
|
||||||
@@ -7,42 +6,39 @@ export const settingsSchema = z.object({
|
|||||||
systemEmailRejectUnauthorized: z.boolean(),
|
systemEmailRejectUnauthorized: z.boolean(),
|
||||||
systemEmailConnectionHost: z
|
systemEmailConnectionHost: z
|
||||||
.string()
|
.string()
|
||||||
.transform((val) => (val.trim() === "" ? null : val.trim()))
|
.transform((val) => (val.trim() === "" ? undefined : val.trim()))
|
||||||
.optional(),
|
.optional(),
|
||||||
systemEmailSecure: z.boolean().optional(),
|
systemEmailSecure: z.boolean().optional(),
|
||||||
systemEmailPool: z.boolean().optional(),
|
systemEmailPool: z.boolean().optional(),
|
||||||
showURL: z.boolean().optional(),
|
showURL: z.boolean().optional(),
|
||||||
checkTTL: z
|
checkTTL: z.number().int().min(1, "Please enter a value").max(365, "Maximum 365 days"),
|
||||||
.number()
|
|
||||||
.int()
|
|
||||||
.min(1, "Please enter a value")
|
|
||||||
.max(CHECK_TTL_SENTINEL, `Maximum ${CHECK_TTL_SENTINEL}`),
|
|
||||||
pagespeedApiKey: z
|
pagespeedApiKey: z
|
||||||
.string()
|
.string()
|
||||||
.transform((val) => (val.trim() === "" ? null : val.trim()))
|
.transform((val) => (val.trim() === "" ? undefined : val.trim()))
|
||||||
.optional(),
|
.optional(),
|
||||||
systemEmailHost: z
|
systemEmailHost: z
|
||||||
.string()
|
.string()
|
||||||
.regex(/^[a-zA-Z0-9.-]*$/, "Invalid hostname or IP address")
|
.regex(/^[a-zA-Z0-9.-]*$/, "Invalid hostname or IP address")
|
||||||
.transform((val) => (val.trim() === "" ? null : val.trim()))
|
.transform((val) => (val.trim() === "" ? undefined : val.trim()))
|
||||||
.optional(),
|
.optional(),
|
||||||
systemEmailPort: z.number().int().min(1).max(65535).optional(),
|
systemEmailPort: z.number().int().min(1).max(65535).optional(),
|
||||||
systemEmailAddress: z
|
systemEmailAddress: z
|
||||||
|
.string()
|
||||||
.email("Please enter a valid email address")
|
.email("Please enter a valid email address")
|
||||||
.or(z.literal(""))
|
.or(z.literal(""))
|
||||||
.transform((val) => (val === "" ? null : val.toLowerCase().trim()))
|
.transform((val) => (val === "" ? undefined : val.toLowerCase().trim()))
|
||||||
.optional(),
|
.optional(),
|
||||||
systemEmailUser: z
|
systemEmailUser: z
|
||||||
.string()
|
.string()
|
||||||
.transform((val) => (val.trim() === "" ? null : val.trim()))
|
.transform((val) => (val.trim() === "" ? undefined : val.trim()))
|
||||||
.optional(),
|
.optional(),
|
||||||
systemEmailPassword: z
|
systemEmailPassword: z
|
||||||
.string()
|
.string()
|
||||||
.transform((val) => (val.trim() === "" ? null : val.trim()))
|
.transform((val) => (val.trim() === "" ? undefined : val.trim()))
|
||||||
.optional(),
|
.optional(),
|
||||||
systemEmailTLSServername: z
|
systemEmailTLSServername: z
|
||||||
.string()
|
.string()
|
||||||
.transform((val) => (val.trim() === "" ? null : val.trim()))
|
.transform((val) => (val.trim() === "" ? undefined : val.trim()))
|
||||||
.optional(),
|
.optional(),
|
||||||
globalThresholds: z.object({
|
globalThresholds: z.object({
|
||||||
cpu: z.number().int().min(1).max(100),
|
cpu: z.number().int().min(1).max(100),
|
||||||
@@ -52,5 +48,4 @@ export const settingsSchema = z.object({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type SettingsFormInput = z.input<typeof settingsSchema>;
|
|
||||||
export type SettingsFormData = z.infer<typeof settingsSchema>;
|
export type SettingsFormData = z.infer<typeof settingsSchema>;
|
||||||
@@ -14,16 +14,12 @@ export const statusPageSchema = z.object({
|
|||||||
"URL can only contain lowercase letters, numbers, and hyphens"
|
"URL can only contain lowercase letters, numbers, and hyphens"
|
||||||
),
|
),
|
||||||
timezone: z.string().optional(),
|
timezone: z.string().optional(),
|
||||||
type: z
|
|
||||||
.array(z.enum(["uptime", "infrastructure"]))
|
|
||||||
.min(1, "At least one type is required"),
|
|
||||||
color: z.string().min(1, "Color is required"),
|
color: z.string().min(1, "Color is required"),
|
||||||
monitors: z.array(z.string()).min(1, "At least one monitor is required"),
|
monitors: z.array(z.string()).min(1, "At least one monitor is required"),
|
||||||
isPublished: z.boolean(),
|
isPublished: z.boolean(),
|
||||||
showCharts: z.boolean(),
|
showCharts: z.boolean(),
|
||||||
showUptimePercentage: z.boolean(),
|
showUptimePercentage: z.boolean(),
|
||||||
showAdminLoginLink: z.boolean(),
|
showAdminLoginLink: z.boolean(),
|
||||||
showInfrastructure: z.boolean(),
|
|
||||||
customCSS: z.string().optional(),
|
customCSS: z.string().optional(),
|
||||||
logo: z
|
logo: z
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@@ -288,13 +288,7 @@ const logoImageValidation = joi
|
|||||||
.optional(); // Make entire object optional
|
.optional(); // Make entire object optional
|
||||||
|
|
||||||
const statusPageValidation = joi.object({
|
const statusPageValidation = joi.object({
|
||||||
type: joi
|
type: joi.string().valid("uptime").required(),
|
||||||
.alternatives()
|
|
||||||
.try(
|
|
||||||
joi.string().valid("uptime", "infrastructure"),
|
|
||||||
joi.array().items(joi.string().valid("uptime", "infrastructure"))
|
|
||||||
)
|
|
||||||
.required(),
|
|
||||||
isPublished: joi.bool(),
|
isPublished: joi.bool(),
|
||||||
companyName: joi
|
companyName: joi
|
||||||
.string()
|
.string()
|
||||||
@@ -323,7 +317,6 @@ const statusPageValidation = joi.object({
|
|||||||
showUptimePercentage: joi.boolean(),
|
showUptimePercentage: joi.boolean(),
|
||||||
showCharts: joi.boolean(),
|
showCharts: joi.boolean(),
|
||||||
showAdminLoginLink: joi.boolean(),
|
showAdminLoginLink: joi.boolean(),
|
||||||
showInfrastructure: joi.boolean(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const settingsValidation = joi.object({
|
const settingsValidation = joi.object({
|
||||||
|
|||||||
+22
-56
@@ -46,8 +46,7 @@
|
|||||||
"sendTestEmail": "Send test email",
|
"sendTestEmail": "Send test email",
|
||||||
"exportToJSON": "Export to JSON",
|
"exportToJSON": "Export to JSON",
|
||||||
"importFromJSON": "Import from JSON",
|
"importFromJSON": "Import from JSON",
|
||||||
"clearFilters": "Clear filters",
|
"clearFilters": "Clear filters"
|
||||||
"removeUser": "Remove user"
|
|
||||||
},
|
},
|
||||||
"charts": {
|
"charts": {
|
||||||
"labels": {
|
"labels": {
|
||||||
@@ -401,23 +400,12 @@
|
|||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"down": "down",
|
"down": "down",
|
||||||
"breached": "threshold exceeded",
|
"breached": "breached",
|
||||||
"initializing": "initializing",
|
"initializing": "initializing",
|
||||||
"maintenance": "maintenance",
|
"maintenance": "maintenance",
|
||||||
"paused": "paused",
|
"paused": "paused",
|
||||||
"total": "total",
|
"total": "total",
|
||||||
"up": "up"
|
"up": "up"
|
||||||
},
|
|
||||||
"monitorTypes": {
|
|
||||||
"optionDocker": "Docker",
|
|
||||||
"optionGame": "Game",
|
|
||||||
"optionHttp": "HTTP(S)",
|
|
||||||
"optionPing": "Ping",
|
|
||||||
"optionPort": "Port",
|
|
||||||
"optionPagespeed": "PageSpeed",
|
|
||||||
"optionHardware": "Infrastructure",
|
|
||||||
"optionGrpc": "gRPC",
|
|
||||||
"optionWebSocket": "WebSocket"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -499,10 +487,6 @@
|
|||||||
"grpcServiceName": {
|
"grpcServiceName": {
|
||||||
"label": "Service name",
|
"label": "Service name",
|
||||||
"placeholder": "e.g. my.service.v1 (leave empty for overall health)"
|
"placeholder": "e.g. my.service.v1 (leave empty for overall health)"
|
||||||
},
|
|
||||||
"wsUrl": {
|
|
||||||
"label": "WebSocket URL",
|
|
||||||
"placeholder": "wss://example.com/socket"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "General settings",
|
"title": "General settings",
|
||||||
@@ -514,10 +498,21 @@
|
|||||||
"game": "Enter the IP address or hostname and the port number to ping (e.g., 192.168.1.100 or example.com) and choose game type.",
|
"game": "Enter the IP address or hostname and the port number to ping (e.g., 192.168.1.100 or example.com) and choose game type.",
|
||||||
"pagespeed": "Track page load performance, Core Web Vitals, and optimization scores for your website.",
|
"pagespeed": "Track page load performance, Core Web Vitals, and optimization scores for your website.",
|
||||||
"grpc": "Enter the hostname and port of the gRPC server, optionally specify a service name for the health check, and add a display name.",
|
"grpc": "Enter the hostname and port of the gRPC server, optionally specify a service name for the health check, and add a display name.",
|
||||||
"websocket": "Enter the WebSocket URL to monitor (e.g., wss://example.com/socket) and add a display name.",
|
|
||||||
"hardware": "Monitor CPU, memory, disk usage, and temperature for your infrastructure."
|
"hardware": "Monitor CPU, memory, disk usage, and temperature for your infrastructure."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"basicAuth": {
|
||||||
|
"description": "Configure HTTP basic authentication credentials for this monitor.",
|
||||||
|
"option": {
|
||||||
|
"username": {
|
||||||
|
"label": "Username"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"label": "Password"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Basic authentication"
|
||||||
|
},
|
||||||
"ignoreTls": {
|
"ignoreTls": {
|
||||||
"description": "Configure TLS/SSL certificate validation for HTTPS connections.",
|
"description": "Configure TLS/SSL certificate validation for HTTPS connections.",
|
||||||
"option": {
|
"option": {
|
||||||
@@ -545,12 +540,17 @@
|
|||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"description": "Select the type of check to perform",
|
"description": "Select the type of check to perform",
|
||||||
|
"optionDocker": "Docker",
|
||||||
"optionDockerDescription": "Use Docker to monitor if a container is running.",
|
"optionDockerDescription": "Use Docker to monitor if a container is running.",
|
||||||
|
"optionGame": "Game",
|
||||||
"optionGameDescription": "Monitor if a specific game server is online.",
|
"optionGameDescription": "Monitor if a specific game server is online.",
|
||||||
|
"optionGrpc": "gRPC",
|
||||||
"optionGrpcDescription": "Monitor gRPC services using the standard Health Checking Protocol.",
|
"optionGrpcDescription": "Monitor gRPC services using the standard Health Checking Protocol.",
|
||||||
"optionWebSocketDescription": "Monitor WebSocket endpoints for connection health and response time.",
|
"optionHttp": "HTTP(S)",
|
||||||
"optionHttpDescription": "Use HTTP(S) to monitor your website or API endpoint.",
|
"optionHttpDescription": "Use HTTP(S) to monitor your website or API endpoint.",
|
||||||
|
"optionPing": "Ping",
|
||||||
"optionPingDescription": "Use ICMP Ping to monitor if a server is online.",
|
"optionPingDescription": "Use ICMP Ping to monitor if a server is online.",
|
||||||
|
"optionPort": "Port",
|
||||||
"optionPortDescription": "Monitor if a specific port on a server is open.",
|
"optionPortDescription": "Monitor if a specific port on a server is open.",
|
||||||
"title": "Type"
|
"title": "Type"
|
||||||
},
|
},
|
||||||
@@ -631,12 +631,6 @@
|
|||||||
"title": "Roles",
|
"title": "Roles",
|
||||||
"description": "Assign roles to the user. Multiple roles can be selected."
|
"description": "Assign roles to the user. Multiple roles can be selected."
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"dialog": {
|
|
||||||
"removeUser": {
|
|
||||||
"title": "Remove user",
|
|
||||||
"content": "Are you sure you want to remove {{name}} from your team? This action cannot be undone."
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"incidents": {
|
"incidents": {
|
||||||
@@ -1045,7 +1039,7 @@
|
|||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"title": "Monitor history",
|
"title": "Monitor history",
|
||||||
"description": "Clear all monitoring history and statistics for your team. This action is irreversible.",
|
"description": "Define how long you want to retain historical data. You can also clear all existing data.",
|
||||||
"option": {
|
"option": {
|
||||||
"clear": {
|
"clear": {
|
||||||
"label": "Clear all stats. This is irreversible."
|
"label": "Clear all stats. This is irreversible."
|
||||||
@@ -1056,16 +1050,6 @@
|
|||||||
"description": "This cannot be undone"
|
"description": "This cannot be undone"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"retention": {
|
|
||||||
"title": "Check retention",
|
|
||||||
"description": "Set how long check data is retained before being automatically cleaned up.",
|
|
||||||
"option": {
|
|
||||||
"days": {
|
|
||||||
"label": "Retention period (days)",
|
|
||||||
"unlimited": "Unlimited"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"thresholds": {
|
"thresholds": {
|
||||||
"title": "Global Thresholds",
|
"title": "Global Thresholds",
|
||||||
"description": "Set global CPU, Memory, Disk, and Temperature thresholds for infrastructure monitoring. These apply to all hardware monitors unless overridden.",
|
"description": "Set global CPU, Memory, Disk, and Temperature thresholds for infrastructure monitoring. These apply to all hardware monitors unless overridden.",
|
||||||
@@ -1182,22 +1166,7 @@
|
|||||||
},
|
},
|
||||||
"monitorsList": {
|
"monitorsList": {
|
||||||
"chartTypeHeatmap": "Heatmap",
|
"chartTypeHeatmap": "Heatmap",
|
||||||
"chartTypeHistogram": "Histogram",
|
"chartTypeHistogram": "Histogram"
|
||||||
"noData": "No data available",
|
|
||||||
"infrastructure": {
|
|
||||||
"title": "Infrastructure",
|
|
||||||
"cpu": "CPU",
|
|
||||||
"memory": "RAM",
|
|
||||||
"memoryText": "Memory",
|
|
||||||
"disk": "Disk",
|
|
||||||
"usage": "Usage",
|
|
||||||
"used": "Used",
|
|
||||||
"total": "Total"
|
|
||||||
},
|
|
||||||
"uptime": {
|
|
||||||
"title": "Uptime",
|
|
||||||
"responseTime": "Response time"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"statusBar": {
|
"statusBar": {
|
||||||
"allDown": "All systems are down",
|
"allDown": "All systems are down",
|
||||||
@@ -1289,9 +1258,6 @@
|
|||||||
},
|
},
|
||||||
"showAdminLoginLink": {
|
"showAdminLoginLink": {
|
||||||
"label": "Show admin login link"
|
"label": "Show admin login link"
|
||||||
},
|
|
||||||
"showInfrastructure": {
|
|
||||||
"label": "Show infrastructure metrics"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const __filename = fileURLToPath(import.meta.url);
|
|||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
export default defineConfig(({}) => {
|
export default defineConfig(({}) => {
|
||||||
let version = "3.5.1";
|
let version = "3.5.0";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
base: "/",
|
base: "/",
|
||||||
|
|||||||
@@ -32,14 +32,11 @@ export default [
|
|||||||
languageOptions: {
|
languageOptions: {
|
||||||
parser: tseslint.parser,
|
parser: tseslint.parser,
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
projectService: {
|
projectService: true,
|
||||||
allowDefaultProject: ["jest.config.ts"],
|
|
||||||
},
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
tsconfigRootDir: import.meta.dirname,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], // Ignore unused variables that start with '_'
|
|
||||||
"@typescript-eslint/no-explicit-any": "warn", // Warn on 'any' types
|
"@typescript-eslint/no-explicit-any": "warn", // Warn on 'any' types
|
||||||
"mocha/max-top-level-suites": "warn", // Warn if there are too many top-level suites instead of failing
|
"mocha/max-top-level-suites": "warn", // Warn if there are too many top-level suites instead of failing
|
||||||
},
|
},
|
||||||
|
|||||||
+135
-172
@@ -510,24 +510,42 @@
|
|||||||
"summary": "Get monitors by team",
|
"summary": "Get monitors by team",
|
||||||
"description": "Get monitors filtered by team with optional parameters",
|
"description": "Get monitors filtered by team with optional parameters",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "status",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Filter by monitor status (up/down)",
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "type",
|
"name": "type",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
"description": "Filter by monitor type",
|
"description": "Filter by monitor type",
|
||||||
"schema": {
|
"schema": {
|
||||||
"oneOf": [
|
"type": "string",
|
||||||
{
|
"enum": ["http", "ping", "pagespeed", "hardware", "docker", "port"]
|
||||||
"type": "string",
|
}
|
||||||
"enum": ["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc", "websocket"]
|
},
|
||||||
},
|
{
|
||||||
{
|
"name": "page",
|
||||||
"type": "array",
|
"in": "query",
|
||||||
"items": {
|
"description": "Page number for pagination",
|
||||||
"type": "string",
|
"schema": {
|
||||||
"enum": ["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc", "websocket"]
|
"type": "integer",
|
||||||
}
|
"minimum": 1,
|
||||||
}
|
"default": 1
|
||||||
]
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rowsPerPage",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Number of monitors per page",
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 100,
|
||||||
|
"default": 10
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -537,6 +555,15 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "field",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Field to filter on",
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["name", "url", "description"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@@ -729,7 +756,7 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"patch": {
|
"put": {
|
||||||
"tags": ["monitors"],
|
"tags": ["monitors"],
|
||||||
"summary": "Update monitor",
|
"summary": "Update monitor",
|
||||||
"description": "Update an existing monitor",
|
"description": "Update an existing monitor",
|
||||||
@@ -3541,19 +3568,22 @@
|
|||||||
},
|
},
|
||||||
"CreateMonitorRequest": {
|
"CreateMonitorRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["name", "type", "url"],
|
"required": ["name", "description", "type", "url", "interval"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 100,
|
||||||
"example": "My Website Monitor"
|
"example": "My Website Monitor"
|
||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"maxLength": 500,
|
||||||
"example": "Monitors the main website homepage"
|
"example": "Monitors the main website homepage"
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket"],
|
"enum": ["http", "ping", "pagespeed", "hardware", "docker", "port"],
|
||||||
"example": "http"
|
"example": "http"
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
@@ -3563,34 +3593,14 @@
|
|||||||
},
|
},
|
||||||
"interval": {
|
"interval": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
"minimum": 30,
|
||||||
|
"maximum": 3600,
|
||||||
"example": 300,
|
"example": 300,
|
||||||
"description": "Check interval in seconds"
|
"description": "Check interval in seconds"
|
||||||
},
|
},
|
||||||
"statusWindowSize": {
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 1,
|
|
||||||
"maximum": 20,
|
|
||||||
"default": 5
|
|
||||||
},
|
|
||||||
"statusWindowThreshold": {
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 1,
|
|
||||||
"maximum": 100,
|
|
||||||
"default": 60
|
|
||||||
},
|
|
||||||
"isActive": {
|
"isActive": {
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"ignoreTlsErrors": {
|
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": false
|
"default": true
|
||||||
},
|
|
||||||
"useAdvancedMatching": {
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"port": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
@@ -3599,62 +3609,51 @@
|
|||||||
},
|
},
|
||||||
"description": "Array of notification IDs to associate with this monitor"
|
"description": "Array of notification IDs to associate with this monitor"
|
||||||
},
|
},
|
||||||
"secret": {
|
"httpOptions": {
|
||||||
"type": "string"
|
"type": "object",
|
||||||
},
|
"properties": {
|
||||||
"jsonPath": {
|
"method": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
},
|
"enum": ["GET", "POST", "PUT", "DELETE", "HEAD"],
|
||||||
"expectedValue": {
|
"default": "GET"
|
||||||
"type": "string"
|
},
|
||||||
},
|
"headers": {
|
||||||
"matchMethod": {
|
"type": "object",
|
||||||
"type": "string",
|
"additionalProperties": {
|
||||||
"enum": ["equal", "include", "regex", ""]
|
"type": "string"
|
||||||
},
|
}
|
||||||
"cpuAlertThreshold": {
|
},
|
||||||
"type": "number"
|
"body": {
|
||||||
},
|
"type": "string",
|
||||||
"memoryAlertThreshold": {
|
"description": "Request body for POST/PUT requests"
|
||||||
"type": "number"
|
},
|
||||||
},
|
"timeout": {
|
||||||
"diskAlertThreshold": {
|
"type": "integer",
|
||||||
"type": "number"
|
"minimum": 1000,
|
||||||
},
|
"maximum": 30000,
|
||||||
"tempAlertThreshold": {
|
"default": 5000,
|
||||||
"type": "number"
|
"description": "Request timeout in milliseconds"
|
||||||
},
|
}
|
||||||
"gameId": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"grpcServiceName": {
|
|
||||||
"type": "string",
|
|
||||||
"default": ""
|
|
||||||
},
|
|
||||||
"selectedDisks": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"group": {
|
"assertions": {
|
||||||
"type": "string",
|
|
||||||
"nullable": true,
|
|
||||||
"maxLength": 50
|
|
||||||
},
|
|
||||||
"geoCheckEnabled": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"geoCheckLocations": {
|
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string",
|
"type": "object",
|
||||||
"enum": ["northAmerica", "southAmerica", "europe", "asia", "africa", "oceania"]
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["status-code", "response-time", "body-contains", "header-contains"]
|
||||||
|
},
|
||||||
|
"comparison": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["equals", "not-equals", "greater-than", "less-than", "contains", "not-contains"]
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"geoCheckInterval": {
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 300000
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3662,32 +3661,21 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "unknown"]
|
"minLength": 1,
|
||||||
},
|
"maxLength": 100
|
||||||
"url": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"statusWindowSize": {
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 1,
|
|
||||||
"maximum": 20,
|
|
||||||
"default": 5
|
|
||||||
},
|
|
||||||
"statusWindowThreshold": {
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 1,
|
|
||||||
"maximum": 100,
|
|
||||||
"default": 60
|
|
||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"maxLength": 500
|
||||||
},
|
},
|
||||||
"interval": {
|
"interval": {
|
||||||
"type": "integer"
|
"type": "integer",
|
||||||
|
"minimum": 30,
|
||||||
|
"maximum": 3600
|
||||||
|
},
|
||||||
|
"isActive": {
|
||||||
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
@@ -3695,72 +3683,47 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"secret": {
|
"httpOptions": {
|
||||||
"type": "string"
|
"type": "object",
|
||||||
},
|
"properties": {
|
||||||
"ignoreTlsErrors": {
|
"method": {
|
||||||
"type": "boolean"
|
"type": "string",
|
||||||
},
|
"enum": ["GET", "POST", "PUT", "DELETE", "HEAD"]
|
||||||
"useAdvancedMatching": {
|
},
|
||||||
"type": "boolean"
|
"headers": {
|
||||||
},
|
"type": "object",
|
||||||
"jsonPath": {
|
"additionalProperties": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
}
|
||||||
"expectedValue": {
|
},
|
||||||
"type": "string"
|
"body": {
|
||||||
},
|
"type": "string"
|
||||||
"matchMethod": {
|
},
|
||||||
"type": "string",
|
"timeout": {
|
||||||
"enum": ["equal", "include", "regex", ""]
|
"type": "integer",
|
||||||
},
|
"minimum": 1000,
|
||||||
"port": {
|
"maximum": 30000
|
||||||
"type": "integer",
|
}
|
||||||
"minimum": 1,
|
|
||||||
"maximum": 65535
|
|
||||||
},
|
|
||||||
"cpuAlertThreshold": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"memoryAlertThreshold": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"diskAlertThreshold": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"tempAlertThreshold": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"gameId": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"grpcServiceName": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"selectedDisks": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"group": {
|
"assertions": {
|
||||||
"type": "string",
|
|
||||||
"nullable": true,
|
|
||||||
"maxLength": 50
|
|
||||||
},
|
|
||||||
"geoCheckEnabled": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"geoCheckLocations": {
|
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string",
|
"type": "object",
|
||||||
"enum": ["northAmerica", "southAmerica", "europe", "asia", "africa", "oceania"]
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["status-code", "response-time", "body-contains", "header-contains"]
|
||||||
|
},
|
||||||
|
"comparison": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["equals", "not-equals", "greater-than", "less-than", "contains", "not-contains"]
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"geoCheckInterval": {
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 300000
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+705
-657
File diff suppressed because it is too large.
Load diff
+4
-6
@@ -8,14 +8,14 @@
|
|||||||
"test": "NODE_OPTIONS=--experimental-vm-modules c8 jest --runInBand",
|
"test": "NODE_OPTIONS=--experimental-vm-modules c8 jest --runInBand",
|
||||||
"dev": "nodemon --exec tsx src/index.js",
|
"dev": "nodemon --exec tsx src/index.js",
|
||||||
"start": "node --watch ./dist/index.js",
|
"start": "node --watch ./dist/index.js",
|
||||||
"build": "tsc && tsc-alias && cp -r src/templates dist/templates",
|
"build": "tsc && tsc-alias",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint-fix": "eslint --fix .",
|
"lint-fix": "eslint --fix .",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"format-check": "prettier --check ."
|
"format-check": "prettier --check ."
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"**/*": "prettier --write"
|
"**/*": "npm run format"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"express-rate-limit": "8.0.1",
|
"express-rate-limit": "8.0.1",
|
||||||
"gamedig": "5.2.0",
|
"gamedig": "^5.3.1",
|
||||||
"got": "14.4.7",
|
"got": "14.4.7",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"helmet": "^8.0.0",
|
"helmet": "^8.0.0",
|
||||||
@@ -51,14 +51,13 @@
|
|||||||
"mjml": "^5.0.0-alpha.4",
|
"mjml": "^5.0.0-alpha.4",
|
||||||
"mongoose": "^8.3.3",
|
"mongoose": "^8.3.3",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"nodemailer": "8.0.1",
|
"nodemailer": "^6.9.14",
|
||||||
"ping": "0.4.4",
|
"ping": "0.4.4",
|
||||||
"sharp": "0.33.5",
|
"sharp": "0.33.5",
|
||||||
"ssl-checker": "2.0.10",
|
"ssl-checker": "2.0.10",
|
||||||
"super-simple-scheduler": "1.4.5",
|
"super-simple-scheduler": "1.4.5",
|
||||||
"swagger-ui-express": "5.0.1",
|
"swagger-ui-express": "5.0.1",
|
||||||
"winston": "^3.13.0",
|
"winston": "^3.13.0",
|
||||||
"ws": "^8.19.0",
|
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -79,7 +78,6 @@
|
|||||||
"@types/papaparse": "^5.5.2",
|
"@types/papaparse": "^5.5.2",
|
||||||
"@types/ping": "0.4.4",
|
"@types/ping": "0.4.4",
|
||||||
"@types/swagger-ui-express": "4.1.8",
|
"@types/swagger-ui-express": "4.1.8",
|
||||||
"@types/ws": "^8.18.1",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
||||||
"@typescript-eslint/parser": "^8.56.1",
|
"@typescript-eslint/parser": "^8.56.1",
|
||||||
"c8": "10.1.3",
|
"c8": "10.1.3",
|
||||||
|
|||||||
+4
-18
@@ -4,7 +4,7 @@ import cors from "cors";
|
|||||||
import helmet from "helmet";
|
import helmet from "helmet";
|
||||||
import compression from "compression";
|
import compression from "compression";
|
||||||
import cookieParser from "cookie-parser";
|
import cookieParser from "cookie-parser";
|
||||||
import swaggerUi, { type JsonObject } from "swagger-ui-express";
|
import swaggerUi from "swagger-ui-express";
|
||||||
import { handleErrors } from "@/middleware/handleErrors.js";
|
import { handleErrors } from "@/middleware/handleErrors.js";
|
||||||
import { generalApiLimiter } from "@/middleware/rateLimiter.js";
|
import { generalApiLimiter } from "@/middleware/rateLimiter.js";
|
||||||
import { sanitizeBody, sanitizeQuery } from "@/middleware/sanitization.js";
|
import { sanitizeBody, sanitizeQuery } from "@/middleware/sanitization.js";
|
||||||
@@ -24,7 +24,7 @@ export const createApp = ({
|
|||||||
controllers: InitializedControllers;
|
controllers: InitializedControllers;
|
||||||
envSettings: EnvConfig;
|
envSettings: EnvConfig;
|
||||||
frontendPath: string;
|
frontendPath: string;
|
||||||
openApiSpec: JsonObject;
|
openApiSpec: any;
|
||||||
}) => {
|
}) => {
|
||||||
const allowedOrigin = envSettings.clientHost;
|
const allowedOrigin = envSettings.clientHost;
|
||||||
const app = express();
|
const app = express();
|
||||||
@@ -74,22 +74,8 @@ export const createApp = ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
// Swagger UI — dynamically set server URL from request
|
// Swagger UI
|
||||||
app.use("/api-docs", swaggerUi.serve, (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(openApiSpec));
|
||||||
const protocol = req.protocol;
|
|
||||||
const host = req.get("host");
|
|
||||||
const dynamicSpec = {
|
|
||||||
...openApiSpec,
|
|
||||||
servers: [
|
|
||||||
{
|
|
||||||
url: `${protocol}://${host}/api/v1`,
|
|
||||||
description: "Current Server",
|
|
||||||
},
|
|
||||||
...openApiSpec.servers,
|
|
||||||
],
|
|
||||||
};
|
|
||||||
swaggerUi.setup(dynamicSpec)(req, res, next);
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use("/api/v1/health", (req, res) => {
|
app.use("/api/v1/health", (req, res) => {
|
||||||
res.json({
|
res.json({
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export interface InitializedControllers {
|
|||||||
export const initializeControllers = (services: InitializedServices): InitializedControllers => {
|
export const initializeControllers = (services: InitializedServices): InitializedControllers => {
|
||||||
return {
|
return {
|
||||||
authController: new AuthController(services.userService),
|
authController: new AuthController(services.userService),
|
||||||
monitorController: new MonitorController(services.monitorService, services.notificationsService),
|
monitorController: new MonitorController(services.monitorService),
|
||||||
settingsController: new SettingsController(services.settingsService, services.emailService),
|
settingsController: new SettingsController(services.settingsService, services.emailService),
|
||||||
checkController: new CheckController(services.checkService),
|
checkController: new CheckController(services.checkService),
|
||||||
geoCheckController: new GeoCheckController(services.geoChecksService),
|
geoCheckController: new GeoCheckController(services.geoChecksService),
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import type { Application } from "express";
|
|
||||||
import { createVerifyJWT } from "../middleware/verifyJWT.js";
|
import { createVerifyJWT } from "../middleware/verifyJWT.js";
|
||||||
import { createVerifyStatusPageAccess } from "../middleware/verifyStatusPageAccess.js";
|
import { createVerifyStatusPageAccess } from "../middleware/verifyStatusPageAccess.js";
|
||||||
import { authApiLimiter } from "../middleware/rateLimiter.js";
|
import { authApiLimiter } from "../middleware/rateLimiter.js";
|
||||||
import type { InitializedControllers } from "./controllers.js";
|
|
||||||
import type { InitializedServices } from "./services.js";
|
|
||||||
|
|
||||||
import AuthRoutes from "../routes/authRoute.js";
|
import AuthRoutes from "../routes/authRoute.js";
|
||||||
import InviteRoutes from "../routes/inviteRoute.js";
|
import InviteRoutes from "../routes/inviteRoute.js";
|
||||||
@@ -20,7 +17,7 @@ import NotificationRoutes from "../routes/notificationRoute.js";
|
|||||||
|
|
||||||
import IncidentRoutes from "../routes/incidentRoute.js";
|
import IncidentRoutes from "../routes/incidentRoute.js";
|
||||||
|
|
||||||
export const setupRoutes = (app: Application, controllers: InitializedControllers, services: InitializedServices) => {
|
export const setupRoutes = (app: any, controllers: Record<string, any>, services: Record<string, any>) => {
|
||||||
const verifyJWT = createVerifyJWT(services.settingsService);
|
const verifyJWT = createVerifyJWT(services.settingsService);
|
||||||
const authRoutes = new AuthRoutes(controllers.authController, verifyJWT);
|
const authRoutes = new AuthRoutes(controllers.authController, verifyJWT);
|
||||||
const monitorRoutes = new MonitorRoutes(controllers.monitorController);
|
const monitorRoutes = new MonitorRoutes(controllers.monitorController);
|
||||||
|
|||||||
@@ -1,70 +1,35 @@
|
|||||||
import MongoDB from "../db/MongoDB.js";
|
import MongoDB from "../db/MongoDB.js";
|
||||||
import { IDb } from "@/db/IDb.js";
|
import NetworkService from "../service/infrastructure/networkService.js";
|
||||||
|
import EmailService from "../service/infrastructure/emailService.js";
|
||||||
|
import BufferService from "../service/infrastructure/bufferService.js";
|
||||||
import {
|
import {
|
||||||
// Service classes
|
|
||||||
NetworkService,
|
|
||||||
EmailService,
|
|
||||||
BufferService,
|
|
||||||
GlobalPingService,
|
|
||||||
SuperSimpleQueue,
|
|
||||||
SuperSimpleQueueHelper,
|
|
||||||
NotificationsService,
|
NotificationsService,
|
||||||
StatusService,
|
StatusService,
|
||||||
NotificationMessageBuilder,
|
|
||||||
MonitorService,
|
|
||||||
StatusPageService,
|
|
||||||
UserService,
|
|
||||||
CheckService,
|
|
||||||
GeoChecksService,
|
|
||||||
DiagnosticService,
|
|
||||||
InviteService,
|
|
||||||
MaintenanceWindowService,
|
|
||||||
IncidentService,
|
|
||||||
// Notification providers
|
|
||||||
WebhookProvider,
|
WebhookProvider,
|
||||||
SlackProvider,
|
SlackProvider,
|
||||||
EmailProvider,
|
EmailProvider,
|
||||||
DiscordProvider,
|
DiscordProvider,
|
||||||
PagerDutyProvider,
|
PagerDutyProvider,
|
||||||
MatrixProvider,
|
MatrixProvider,
|
||||||
TeamsProvider,
|
|
||||||
// Interfaces
|
|
||||||
INetworkService,
|
|
||||||
IEmailService,
|
|
||||||
IBufferService,
|
|
||||||
ISuperSimpleQueue,
|
|
||||||
INotificationsService,
|
INotificationsService,
|
||||||
IStatusService,
|
|
||||||
IMonitorService,
|
|
||||||
IUserService,
|
|
||||||
ICheckService,
|
|
||||||
IGeoChecksService,
|
|
||||||
IDiagnosticService,
|
|
||||||
IInviteService,
|
|
||||||
IMaintenanceWindowService,
|
|
||||||
IStatusPageService,
|
|
||||||
IIncidentService,
|
|
||||||
INotificationMessageBuilder,
|
|
||||||
ISettingsService,
|
|
||||||
EnvConfig,
|
|
||||||
} from "@/service/index.js";
|
} from "@/service/index.js";
|
||||||
|
import SuperSimpleQueueHelper from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js";
|
||||||
// Network providers
|
import SuperSimpleQueue from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js";
|
||||||
import { PingProvider } from "@/service/infrastructure/network/PingProvider.js";
|
import UserService from "../service/business/userService.js";
|
||||||
import { HttpProvider } from "@/service/infrastructure/network/HttpProvider.js";
|
import CheckService from "../service/business/checkService.js";
|
||||||
import { AdvancedMatcher } from "@/service/infrastructure/network/AdvancedMatcher.js";
|
import GeoChecksService from "../service/business/geoChecksService.js";
|
||||||
import { PageSpeedProvider } from "@/service/infrastructure/network/PageSpeedProvider.js";
|
import GlobalPingService from "../service/infrastructure/globalPingService.js";
|
||||||
import { HardwareProvider } from "@/service/infrastructure/network/HardwareProvider.js";
|
import DiagnosticService from "../service/business/diagnosticService.js";
|
||||||
import { DockerProvider } from "@/service/infrastructure/network/DockerProvider.js";
|
import InviteService from "../service/business/inviteService.js";
|
||||||
import { PortProvider } from "@/service/infrastructure/network/PortProvider.js";
|
import MaintenanceWindowService from "../service/business/maintenanceWindowService.js";
|
||||||
import { GameProvider } from "@/service/infrastructure/network/GameProvider.js";
|
import { MonitorService } from "@/service/index.js";
|
||||||
import { GrpcProvider } from "@/service/infrastructure/network/GrpcProvider.js";
|
import { StatusPageService, IStatusPageService } from "../service/business/statusPageService.js";
|
||||||
import { WebSocketProvider } from "@/service/infrastructure/network/WebSocketProvider.js";
|
import IncidentService from "../service/business/incidentService.js";
|
||||||
|
import { NotificationMessageBuilder, INotificationMessageBuilder } from "../service/infrastructure/notificationMessageBuilder.js";
|
||||||
// Third-party
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import got from "got";
|
import got from "got";
|
||||||
import ping from "ping";
|
import ping from "ping";
|
||||||
|
import https from "https";
|
||||||
import Docker from "dockerode";
|
import Docker from "dockerode";
|
||||||
import net from "net";
|
import net from "net";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
@@ -79,9 +44,8 @@ import { games, GameDig } from "gamedig";
|
|||||||
import jmespath from "jmespath";
|
import jmespath from "jmespath";
|
||||||
import * as grpc from "@grpc/grpc-js";
|
import * as grpc from "@grpc/grpc-js";
|
||||||
import * as protoLoader from "@grpc/proto-loader";
|
import * as protoLoader from "@grpc/proto-loader";
|
||||||
import WebSocket from "ws";
|
|
||||||
|
|
||||||
// Repositories
|
// repositories
|
||||||
import {
|
import {
|
||||||
MongoMonitorsRepository,
|
MongoMonitorsRepository,
|
||||||
MongoChecksRepository,
|
MongoChecksRepository,
|
||||||
@@ -110,23 +74,24 @@ import {
|
|||||||
IMaintenanceWindowsRepository,
|
IMaintenanceWindowsRepository,
|
||||||
} from "@/repositories/index.js";
|
} from "@/repositories/index.js";
|
||||||
import { ILogger } from "@/utils/logger.js";
|
import { ILogger } from "@/utils/logger.js";
|
||||||
|
import { EnvConfig } from "@/service/system/settingsService.js";
|
||||||
|
|
||||||
export type InitializedServices = {
|
export type InitializedServices = {
|
||||||
settingsService: ISettingsService;
|
settingsService: any;
|
||||||
db: IDb;
|
db: any;
|
||||||
networkService: INetworkService;
|
networkService: any;
|
||||||
emailService: IEmailService;
|
emailService: any;
|
||||||
bufferService: IBufferService;
|
bufferService: any;
|
||||||
statusService: IStatusService;
|
statusService: any;
|
||||||
jobQueue: ISuperSimpleQueue;
|
jobQueue: any;
|
||||||
userService: IUserService;
|
userService: any;
|
||||||
checkService: ICheckService;
|
checkService: any;
|
||||||
geoChecksService: IGeoChecksService;
|
geoChecksService: any;
|
||||||
diagnosticService: IDiagnosticService;
|
diagnosticService: any;
|
||||||
inviteService: IInviteService;
|
inviteService: any;
|
||||||
maintenanceWindowService: IMaintenanceWindowService;
|
maintenanceWindowService: any;
|
||||||
monitorService: IMonitorService;
|
monitorService: any;
|
||||||
incidentService: IIncidentService;
|
incidentService: any;
|
||||||
logger: ILogger;
|
logger: ILogger;
|
||||||
notificationsService: INotificationsService;
|
notificationsService: INotificationsService;
|
||||||
statusPageService: IStatusPageService;
|
statusPageService: IStatusPageService;
|
||||||
@@ -156,7 +121,7 @@ export const initializeServices = async ({
|
|||||||
}: {
|
}: {
|
||||||
logger: ILogger;
|
logger: ILogger;
|
||||||
envSettings: EnvConfig;
|
envSettings: EnvConfig;
|
||||||
settingsService: ISettingsService;
|
settingsService: any;
|
||||||
settingsRepository: ISettingsRepository;
|
settingsRepository: ISettingsRepository;
|
||||||
}): Promise<InitializedServices> => {
|
}): Promise<InitializedServices> => {
|
||||||
// Create DB
|
// Create DB
|
||||||
@@ -179,28 +144,22 @@ export const initializeServices = async ({
|
|||||||
const teamsRepository = new MongoTeamsRepository();
|
const teamsRepository = new MongoTeamsRepository();
|
||||||
const maintenanceWindowsRepository = new MongoMaintenanceWindowsRepository();
|
const maintenanceWindowsRepository = new MongoMaintenanceWindowsRepository();
|
||||||
|
|
||||||
// Network providers
|
const networkService = new NetworkService(
|
||||||
const pingProvider = new PingProvider(ping);
|
axios,
|
||||||
const httpProvider = new HttpProvider(got, new AdvancedMatcher(jmespath));
|
got,
|
||||||
const pageSpeedProvider = new PageSpeedProvider(httpProvider, settingsService, logger);
|
https,
|
||||||
const hardwareProvider = new HardwareProvider(httpProvider);
|
jmespath,
|
||||||
const dockerProvider = new DockerProvider(logger, Docker);
|
GameDig as unknown as {
|
||||||
const portProvider = new PortProvider(net);
|
query: (options: { type: string; host: string; port?: number }) => Promise<{ ping?: number } & { [key: string]: unknown }>;
|
||||||
const gameProvider = new GameProvider(logger, GameDig);
|
},
|
||||||
const grpcProvider = new GrpcProvider(grpc, protoLoader);
|
ping,
|
||||||
const webSocketProvider = new WebSocketProvider(WebSocket);
|
logger,
|
||||||
|
Docker,
|
||||||
const networkService = new NetworkService(axios, logger, [
|
net,
|
||||||
pingProvider,
|
settingsService,
|
||||||
httpProvider,
|
grpc,
|
||||||
pageSpeedProvider,
|
protoLoader
|
||||||
hardwareProvider,
|
);
|
||||||
dockerProvider,
|
|
||||||
portProvider,
|
|
||||||
gameProvider,
|
|
||||||
grpcProvider,
|
|
||||||
webSocketProvider,
|
|
||||||
]);
|
|
||||||
const emailService = new EmailService(settingsService, fs, path, compile, mjml2html, nodemailer, logger);
|
const emailService = new EmailService(settingsService, fs, path, compile, mjml2html, nodemailer, logger);
|
||||||
|
|
||||||
const notificationMessageBuilder = new NotificationMessageBuilder();
|
const notificationMessageBuilder = new NotificationMessageBuilder();
|
||||||
@@ -222,14 +181,12 @@ export const initializeServices = async ({
|
|||||||
|
|
||||||
const statusService = new StatusService(logger, bufferService, monitorsRepository, monitorStatsRepository, checksRepository);
|
const statusService = new StatusService(logger, bufferService, monitorsRepository, monitorStatsRepository, checksRepository);
|
||||||
|
|
||||||
// Notification providers
|
|
||||||
const webhookProvider = new WebhookProvider(logger);
|
const webhookProvider = new WebhookProvider(logger);
|
||||||
const slackProvider = new SlackProvider(logger);
|
const slackProvider = new SlackProvider(logger);
|
||||||
const emailProvider = new EmailProvider(emailService, logger);
|
const emailProvider = new EmailProvider(emailService, logger);
|
||||||
const discordProvider = new DiscordProvider(logger);
|
const discordProvider = new DiscordProvider(logger);
|
||||||
const pagerDutyProvider = new PagerDutyProvider(logger);
|
const pagerDutyProvider = new PagerDutyProvider(logger);
|
||||||
const matrixProvider = new MatrixProvider(logger);
|
const matrixProvider = new MatrixProvider(logger);
|
||||||
const teamsProvider = new TeamsProvider(logger);
|
|
||||||
|
|
||||||
const notificationsService = new NotificationsService(
|
const notificationsService = new NotificationsService(
|
||||||
notificationsRepository,
|
notificationsRepository,
|
||||||
@@ -240,7 +197,6 @@ export const initializeServices = async ({
|
|||||||
discordProvider,
|
discordProvider,
|
||||||
pagerDutyProvider,
|
pagerDutyProvider,
|
||||||
matrixProvider,
|
matrixProvider,
|
||||||
teamsProvider,
|
|
||||||
settingsService,
|
settingsService,
|
||||||
logger,
|
logger,
|
||||||
notificationMessageBuilder
|
notificationMessageBuilder
|
||||||
@@ -252,7 +208,6 @@ export const initializeServices = async ({
|
|||||||
statusService,
|
statusService,
|
||||||
notificationsService,
|
notificationsService,
|
||||||
checkService,
|
checkService,
|
||||||
settingsService,
|
|
||||||
bufferService,
|
bufferService,
|
||||||
incidentService,
|
incidentService,
|
||||||
maintenanceWindowsRepository,
|
maintenanceWindowsRepository,
|
||||||
@@ -309,6 +264,7 @@ export const initializeServices = async ({
|
|||||||
const statusPageService = new StatusPageService(statusPagesRepository);
|
const statusPageService = new StatusPageService(statusPagesRepository);
|
||||||
|
|
||||||
const services = {
|
const services = {
|
||||||
|
//v1
|
||||||
settingsService,
|
settingsService,
|
||||||
db,
|
db,
|
||||||
networkService,
|
networkService,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import { requireTeamId, requireUserEmail, requireUserId, requireUserRoles } from "@/controllers/controllerUtils.js";
|
import { requireTeamId, requireUserRoles } from "./controllerUtils.js";
|
||||||
|
import type { UserRole } from "@/types/user.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
registrationBodyValidation,
|
registrationBodyValidation,
|
||||||
@@ -19,33 +20,15 @@ import {
|
|||||||
editSuperadminUserByIdBodyValidation,
|
editSuperadminUserByIdBodyValidation,
|
||||||
editUserPasswordByIdBodyValidation,
|
editUserPasswordByIdBodyValidation,
|
||||||
} from "@/validation/userValidation.js";
|
} from "@/validation/userValidation.js";
|
||||||
import { IUserService } from "@/service/index.js";
|
|
||||||
|
|
||||||
const SERVICE_NAME = "authController";
|
const SERVICE_NAME = "authController";
|
||||||
|
|
||||||
export interface IAuthController {
|
class AuthController {
|
||||||
registerUser(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
createUser(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
loginUser(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
editUser(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
checkSuperadminExists(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
requestRecovery(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
validateRecovery(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
resetPassword(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
deleteUser(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
deleteUserById(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
getAllUsers(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
getUserById(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
editUserById(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
editUserPasswordById(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
class AuthController implements IAuthController {
|
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
|
|
||||||
private userService: IUserService;
|
private userService: any;
|
||||||
|
|
||||||
constructor(userService: IUserService) {
|
constructor(userService: any) {
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,12 +41,11 @@ class AuthController implements IAuthController {
|
|||||||
const newUser = req.body.user;
|
const newUser = req.body.user;
|
||||||
const newUserToken = req.body.token;
|
const newUserToken = req.body.token;
|
||||||
if (newUser?.email) {
|
if (newUser?.email) {
|
||||||
const newUserEmail = requireUserEmail(newUser.email);
|
newUser.email = newUser.email.toLowerCase();
|
||||||
newUser.email = newUserEmail.toLowerCase();
|
|
||||||
}
|
}
|
||||||
const validatedBody = registrationBodyValidation.parse(newUser);
|
registrationBodyValidation.parse(newUser);
|
||||||
|
|
||||||
const { user, token } = await this.userService.registerUser(validatedBody, newUserToken, req?.file ?? null);
|
const { user, token } = await this.userService.registerUser(newUser, newUserToken, req.file);
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
msg: "User registered successfully",
|
msg: "User registered successfully",
|
||||||
@@ -80,11 +62,11 @@ class AuthController implements IAuthController {
|
|||||||
if (userData?.email) {
|
if (userData?.email) {
|
||||||
userData.email = userData.email.toLowerCase();
|
userData.email = userData.email.toLowerCase();
|
||||||
}
|
}
|
||||||
const validatedBody = createUserBodyValidation.parse(userData);
|
createUserBodyValidation.parse(userData);
|
||||||
|
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = requireTeamId(req.user?.teamId);
|
||||||
const actorRoles = requireUserRoles(req.user?.role);
|
const actorRoles = requireUserRoles(req.user?.role) as UserRole[];
|
||||||
const newUser = await this.userService.createUser(validatedBody, teamId, actorRoles, req?.file ?? null);
|
const newUser = await this.userService.createUser(userData, teamId, actorRoles, req.file);
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
msg: "User created successfully",
|
msg: "User created successfully",
|
||||||
@@ -118,10 +100,8 @@ class AuthController implements IAuthController {
|
|||||||
|
|
||||||
editUser = async (req: Request, res: Response, next: NextFunction) => {
|
editUser = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedBody = editUserBodyValidation.parse(req.body);
|
editUserBodyValidation.parse(req.body);
|
||||||
const userId = requireUserId(req.user?.id);
|
const updatedUser = await this.userService.editUser(req.body, req.file, req.user);
|
||||||
const userEmail = requireUserEmail(req.user?.email);
|
|
||||||
const updatedUser = await this.userService.editUser(validatedBody, req?.file ?? null, userId, userEmail);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -190,15 +170,7 @@ class AuthController implements IAuthController {
|
|||||||
|
|
||||||
deleteUser = async (req: Request, res: Response, next: NextFunction) => {
|
deleteUser = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const userId = requireUserId(req.user?.id);
|
await this.userService.deleteUser(req.user);
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
|
||||||
const roles = requireUserRoles(req.user?.role);
|
|
||||||
|
|
||||||
await this.userService.deleteUser({
|
|
||||||
userId,
|
|
||||||
teamId,
|
|
||||||
roles,
|
|
||||||
});
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
msg: "User deleted successfully",
|
msg: "User deleted successfully",
|
||||||
@@ -208,23 +180,6 @@ class AuthController implements IAuthController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteUserById = async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
try {
|
|
||||||
const validatedParams = getUserByIdParamValidation.parse(req.params);
|
|
||||||
const targetUserId = validatedParams.userId;
|
|
||||||
const actorId = requireUserId(req.user?.id);
|
|
||||||
const actorTeamId = requireTeamId(req.user?.teamId);
|
|
||||||
const actorRoles = requireUserRoles(req.user?.role);
|
|
||||||
await this.userService.deleteUserById({ actorId, actorTeamId, actorRoles, targetUserId });
|
|
||||||
return res.status(200).json({
|
|
||||||
success: true,
|
|
||||||
msg: "User removed successfully",
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
getAllUsers = async (req: Request, res: Response, next: NextFunction) => {
|
getAllUsers = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const allUsers = await this.userService.getAllUsers();
|
const allUsers = await this.userService.getAllUsers();
|
||||||
@@ -240,9 +195,19 @@ class AuthController implements IAuthController {
|
|||||||
|
|
||||||
getUserById = async (req: Request, res: Response, next: NextFunction) => {
|
getUserById = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedParams = getUserByIdParamValidation.parse(req.params);
|
getUserByIdParamValidation.parse(req.params);
|
||||||
const actorRoles = requireUserRoles(req.user?.role);
|
const userId = req?.params?.userId;
|
||||||
const user = await this.userService.getUserById(actorRoles, validatedParams.userId);
|
const roles = req?.user?.role;
|
||||||
|
|
||||||
|
if (!userId) {
|
||||||
|
throw new Error("No user ID in request");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!roles || roles.length === 0) {
|
||||||
|
throw new Error("No roles in request");
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await this.userService.getUserById(roles, userId);
|
||||||
|
|
||||||
return res.status(200).json({ success: true, msg: "ok", data: user });
|
return res.status(200).json({ success: true, msg: "ok", data: user });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -252,19 +217,24 @@ class AuthController implements IAuthController {
|
|||||||
|
|
||||||
editUserById = async (req: Request, res: Response, next: NextFunction) => {
|
editUserById = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const actorRoles = requireUserRoles(req.user?.role);
|
const roles = req?.user?.role;
|
||||||
const actorId = requireUserId(req.user?.id);
|
|
||||||
|
|
||||||
if (!actorRoles.includes("superadmin")) {
|
if (!roles || !roles.includes("superadmin")) {
|
||||||
throw new AppError({ message: "Unauthorized", status: 403 });
|
throw new AppError({ message: "Unauthorized", status: 403 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const validatedParams = editUserByIdParamValidation.parse(req.params);
|
const userId = req.params.userId as string;
|
||||||
// If this is superadmin self edit, allow "superadmin" role
|
const user = { ...req.body };
|
||||||
const validatedBody =
|
|
||||||
validatedParams.userId === actorId ? editSuperadminUserByIdBodyValidation.parse(req.body) : editUserByIdBodyValidation.parse(req.body);
|
|
||||||
|
|
||||||
await this.userService.editUserById(validatedParams.userId, validatedBody);
|
editUserByIdParamValidation.parse(req.params);
|
||||||
|
// If this is superadmin self edit, allow "superadmin" role
|
||||||
|
if (userId === req.user?.id) {
|
||||||
|
editSuperadminUserByIdBodyValidation.parse(req.body);
|
||||||
|
} else {
|
||||||
|
editUserByIdBodyValidation.parse(req.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.userService.editUserById(userId, user);
|
||||||
return res.status(200).json({ success: true, msg: "ok" });
|
return res.status(200).json({ success: true, msg: "ok" });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
@@ -273,14 +243,16 @@ class AuthController implements IAuthController {
|
|||||||
|
|
||||||
editUserPasswordById = async (req: Request, res: Response, next: NextFunction) => {
|
editUserPasswordById = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const actorRoles = requireUserRoles(req.user?.role);
|
const roles = req?.user?.role;
|
||||||
if (!actorRoles.includes("superadmin")) {
|
if (!roles || !roles.includes("superadmin")) {
|
||||||
throw new AppError({ message: "Unauthorized", status: 403 });
|
throw new AppError({ message: "Unauthorized", status: 403 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const validatedParams = editUserByIdParamValidation.parse(req.params);
|
const userId = req.params.userId as string;
|
||||||
const validatedBody = editUserPasswordByIdBodyValidation.parse(req.body);
|
editUserByIdParamValidation.parse(req.params);
|
||||||
await this.userService.setPasswordByUserId(validatedParams.userId, validatedBody.password);
|
editUserPasswordByIdBodyValidation.parse(req.body);
|
||||||
|
const updatedPassword = req.body.password;
|
||||||
|
await this.userService.setPasswordByUserId(userId, updatedPassword);
|
||||||
return res.status(200).json({ success: true, msg: "Password reset successfully" });
|
return res.status(200).json({ success: true, msg: "Password reset successfully" });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
|
|||||||
@@ -4,28 +4,18 @@ import {
|
|||||||
getChecksParamValidation,
|
getChecksParamValidation,
|
||||||
getChecksQueryValidation,
|
getChecksQueryValidation,
|
||||||
getTeamChecksQueryValidation,
|
getTeamChecksQueryValidation,
|
||||||
getChecksSummaryByTeamIdQueryValidation,
|
|
||||||
deleteChecksParamValidation,
|
deleteChecksParamValidation,
|
||||||
|
deleteChecksByTeamIdParamValidation,
|
||||||
|
updateChecksTTLBodyValidation,
|
||||||
} from "@/validation/checkValidation.js";
|
} from "@/validation/checkValidation.js";
|
||||||
import { ICheckService } from "@/service/index.js";
|
|
||||||
import { requireTeamId } from "@/controllers/controllerUtils.js";
|
|
||||||
|
|
||||||
const SERVICE_NAME = "checkController";
|
const SERVICE_NAME = "checkController";
|
||||||
|
|
||||||
export interface ICheckController {
|
class CheckController {
|
||||||
serviceName: string;
|
|
||||||
getChecksByMonitor: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
getChecksByTeam: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
getChecksSummaryByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
deleteChecks: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
deleteChecksByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
class CheckController implements ICheckController {
|
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
|
|
||||||
private checkService: ICheckService;
|
private checkService: any;
|
||||||
constructor(checkService: ICheckService) {
|
constructor(checkService: any) {
|
||||||
this.checkService = checkService;
|
this.checkService = checkService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,20 +25,13 @@ class CheckController implements ICheckController {
|
|||||||
|
|
||||||
getChecksByMonitor = async (req: Request, res: Response, next: NextFunction) => {
|
getChecksByMonitor = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedParams = getChecksParamValidation.parse(req.params);
|
getChecksParamValidation.parse(req.params);
|
||||||
const validatedQuery = getChecksQueryValidation.parse(req.query);
|
getChecksQueryValidation.parse(req.query);
|
||||||
|
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
|
||||||
|
|
||||||
const result = await this.checkService.getChecksByMonitor({
|
const result = await this.checkService.getChecksByMonitor({
|
||||||
monitorId: validatedParams.monitorId,
|
monitorId: req?.params?.monitorId,
|
||||||
teamId,
|
query: req?.query,
|
||||||
sortOrder: validatedQuery.sortOrder,
|
teamId: req?.user?.teamId,
|
||||||
dateRange: validatedQuery.dateRange,
|
|
||||||
filter: validatedQuery.filter,
|
|
||||||
page: validatedQuery.page,
|
|
||||||
rowsPerPage: validatedQuery.rowsPerPage,
|
|
||||||
status: validatedQuery.status,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
@@ -63,16 +46,10 @@ class CheckController implements ICheckController {
|
|||||||
|
|
||||||
getChecksByTeam = async (req: Request, res: Response, next: NextFunction) => {
|
getChecksByTeam = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedQuery = getTeamChecksQueryValidation.parse(req.query);
|
getTeamChecksQueryValidation.parse(req.query);
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
|
||||||
|
|
||||||
const checkData = await this.checkService.getChecksByTeam({
|
const checkData = await this.checkService.getChecksByTeam({
|
||||||
teamId,
|
teamId: req?.user?.teamId,
|
||||||
sortOrder: validatedQuery.sortOrder,
|
query: req?.query,
|
||||||
dateRange: validatedQuery.dateRange,
|
|
||||||
page: validatedQuery.page,
|
|
||||||
rowsPerPage: validatedQuery.rowsPerPage,
|
|
||||||
filter: validatedQuery.filter,
|
|
||||||
});
|
});
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -86,11 +63,8 @@ class CheckController implements ICheckController {
|
|||||||
|
|
||||||
getChecksSummaryByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
getChecksSummaryByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedQuery = getChecksSummaryByTeamIdQueryValidation.parse(req.query);
|
const dateRange = req.query?.dateRange || "hour";
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const summary = await this.checkService.getChecksSummaryByTeamId({ teamId: req?.user?.teamId, dateRange });
|
||||||
const dateRange = validatedQuery.dateRange ?? "hour";
|
|
||||||
|
|
||||||
const summary = await this.checkService.getChecksSummaryByTeamId({ teamId, dateRange });
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
msg: "Checks summary retrieved successfully",
|
msg: "Checks summary retrieved successfully",
|
||||||
@@ -103,12 +77,11 @@ class CheckController implements ICheckController {
|
|||||||
|
|
||||||
deleteChecks = async (req: Request, res: Response, next: NextFunction) => {
|
deleteChecks = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedParams = deleteChecksParamValidation.parse(req.params);
|
deleteChecksParamValidation.parse(req.params);
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
|
||||||
|
|
||||||
const deletedCount = await this.checkService.deleteChecks({
|
const deletedCount = await this.checkService.deleteChecks({
|
||||||
monitorId: validatedParams.monitorId,
|
monitorId: req.params.monitorId as string,
|
||||||
teamId,
|
teamId: req?.user?.teamId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
@@ -123,9 +96,9 @@ class CheckController implements ICheckController {
|
|||||||
|
|
||||||
deleteChecksByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
deleteChecksByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
deleteChecksByTeamIdParamValidation.parse(req.params);
|
||||||
|
|
||||||
const deletedCount = await this.checkService.deleteChecksByTeamId({ teamId });
|
const deletedCount = await this.checkService.deleteChecksByTeamId({ teamId: req?.user?.teamId });
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -136,6 +109,24 @@ class CheckController implements ICheckController {
|
|||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
updateChecksTTL = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
updateChecksTTLBodyValidation.parse(req.body);
|
||||||
|
|
||||||
|
await this.checkService.updateChecksTTL({
|
||||||
|
teamId: req?.user?.teamId,
|
||||||
|
ttl: req?.body?.ttl,
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
msg: "Checks TTL updated successfully",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CheckController;
|
export default CheckController;
|
||||||
@@ -1,25 +1,24 @@
|
|||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import { Monitor, type MonitorType, MonitorTypes, UserRole } from "@/types/index.js";
|
import { type MonitorType, MonitorTypes } from "@/types/index.js";
|
||||||
import sslChecker, { SSLDetails } from "ssl-checker";
|
|
||||||
type SSLCheckerType = typeof sslChecker;
|
|
||||||
|
|
||||||
export const fetchMonitorCertificate = async (checker: SSLCheckerType, monitor: Monitor): Promise<SSLDetails> => {
|
const fetchMonitorCertificate = async (sslChecker: any, monitor: any): Promise<any> => {
|
||||||
const monitorUrl = new URL(monitor.url);
|
const monitorUrl = new URL(monitor.url);
|
||||||
const hostname = monitorUrl.hostname;
|
const hostname = monitorUrl.hostname;
|
||||||
const cert = await checker(hostname);
|
const cert = await sslChecker(hostname);
|
||||||
|
// Throw an error if no cert or if cert.validTo is not present
|
||||||
if (cert?.validTo === null || cert?.validTo === undefined) {
|
if (cert?.validTo === null || cert?.validTo === undefined) {
|
||||||
throw new Error("Certificate not found");
|
throw new Error("Certificate not found");
|
||||||
}
|
}
|
||||||
return cert;
|
return cert;
|
||||||
};
|
};
|
||||||
export const requireString = (value: unknown, fieldName: string): string => {
|
const requireString = (value: unknown, fieldName: string): string => {
|
||||||
if (typeof value === "string" && value.trim().length > 0) {
|
if (typeof value === "string" && value.trim().length > 0) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
throw new AppError({ message: `${fieldName} is required`, status: 400 });
|
throw new AppError({ message: `${fieldName} is required`, status: 400 });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const optionalString = (value: unknown, fieldName: string): string | undefined => {
|
const optionalString = (value: unknown, fieldName: string): string | undefined => {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -29,7 +28,7 @@ export const optionalString = (value: unknown, fieldName: string): string | unde
|
|||||||
throw new AppError({ message: `${fieldName} must be a string`, status: 400 });
|
throw new AppError({ message: `${fieldName} must be a string`, status: 400 });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const optionalNumber = (value: unknown, fieldName: string): number | undefined => {
|
const optionalNumber = (value: unknown, fieldName: string): number | undefined => {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -45,7 +44,7 @@ export const optionalNumber = (value: unknown, fieldName: string): number | unde
|
|||||||
throw new AppError({ message: `${fieldName} must be a number`, status: 400 });
|
throw new AppError({ message: `${fieldName} must be a number`, status: 400 });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const optionalBoolean = (value: unknown, fieldName: string): boolean | undefined => {
|
const optionalBoolean = (value: unknown, fieldName: string): boolean | undefined => {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -63,7 +62,7 @@ export const optionalBoolean = (value: unknown, fieldName: string): boolean | un
|
|||||||
throw new AppError({ message: `${fieldName} must be a boolean`, status: 400 });
|
throw new AppError({ message: `${fieldName} must be a boolean`, status: 400 });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const parseMonitorTypeFilter = (value: unknown): MonitorType | MonitorType[] | undefined => {
|
const parseMonitorTypeFilter = (value: unknown): MonitorType | MonitorType[] | undefined => {
|
||||||
const parseSingle = (input: unknown): MonitorType => {
|
const parseSingle = (input: unknown): MonitorType => {
|
||||||
if (typeof input !== "string") {
|
if (typeof input !== "string") {
|
||||||
throw new AppError({ message: "Monitor type must be a string", status: 400 });
|
throw new AppError({ message: "Monitor type must be a string", status: 400 });
|
||||||
@@ -83,7 +82,7 @@ export const parseMonitorTypeFilter = (value: unknown): MonitorType | MonitorTyp
|
|||||||
return parseSingle(value);
|
return parseSingle(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const parseSortOrder = (value: unknown): "asc" | "desc" | undefined => {
|
const parseSortOrder = (value: unknown): "asc" | "desc" | undefined => {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -93,36 +92,42 @@ export const parseSortOrder = (value: unknown): "asc" | "desc" | undefined => {
|
|||||||
throw new AppError({ message: "order must be either 'asc' or 'desc'", status: 400 });
|
throw new AppError({ message: "order must be either 'asc' or 'desc'", status: 400 });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const requireTeamId = (teamId?: string): string => {
|
const requireTeamId = (teamId?: string): string => {
|
||||||
if (!teamId) {
|
if (!teamId) {
|
||||||
throw new AppError({ message: "Team ID is required", status: 400 });
|
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||||
}
|
}
|
||||||
return teamId;
|
return teamId;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const requireUserId = (userId?: string): string => {
|
const requireUserId = (userId?: string): string => {
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
throw new AppError({ message: "User ID is required", status: 400 });
|
throw new AppError({ message: "User ID is required", status: 400 });
|
||||||
}
|
}
|
||||||
return userId;
|
return userId;
|
||||||
};
|
};
|
||||||
export const requireUserEmail = (userEmail?: string): string => {
|
const requireUserEmail = (userEmail?: string): string => {
|
||||||
if (!userEmail) {
|
if (!userEmail) {
|
||||||
throw new AppError({ message: "User email is required", status: 400 });
|
throw new AppError({ message: "User email is required", status: 400 });
|
||||||
}
|
}
|
||||||
return userEmail;
|
return userEmail;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const requireFirstName = (firstName?: string): string => {
|
export const requireUserRoles = (userRoles?: string[]): string[] => {
|
||||||
if (!firstName) {
|
|
||||||
throw new AppError({ message: "First name is required", status: 400 });
|
|
||||||
}
|
|
||||||
return firstName;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const requireUserRoles = (userRoles?: UserRole[]): UserRole[] => {
|
|
||||||
if (!userRoles || userRoles.length === 0) {
|
if (!userRoles || userRoles.length === 0) {
|
||||||
throw new AppError({ message: "User roles are required", status: 400 });
|
throw new AppError({ message: "User roles are required", status: 400 });
|
||||||
}
|
}
|
||||||
return userRoles;
|
return userRoles;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
fetchMonitorCertificate,
|
||||||
|
requireString,
|
||||||
|
optionalString,
|
||||||
|
optionalNumber,
|
||||||
|
optionalBoolean,
|
||||||
|
parseMonitorTypeFilter,
|
||||||
|
parseSortOrder,
|
||||||
|
requireTeamId,
|
||||||
|
requireUserId,
|
||||||
|
requireUserEmail,
|
||||||
|
};
|
||||||
@@ -1,18 +1,12 @@
|
|||||||
import { IDiagnosticService } from "@/service/index.js";
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
|
||||||
const SERVICE_NAME = "diagnosticController";
|
const SERVICE_NAME = "diagnosticController";
|
||||||
|
class DiagnosticController {
|
||||||
export interface IDiagnosticController {
|
|
||||||
getSystemStats: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
class DiagnosticController implements IDiagnosticController {
|
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
|
|
||||||
private diagnosticService: IDiagnosticService;
|
private diagnosticService: any;
|
||||||
|
|
||||||
constructor(diagnosticService: IDiagnosticService) {
|
constructor(diagnosticService: any) {
|
||||||
this.diagnosticService = diagnosticService;
|
this.diagnosticService = diagnosticService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { getChecksParamValidation, getChecksQueryValidation } from "@/validation/checkValidation.js";
|
import { getChecksParamValidation, getChecksQueryValidation } from "@/validation/checkValidation.js";
|
||||||
import type { IGeoChecksService } from "@/service/business/geoChecksService.js";
|
import type { IGeoChecksService } from "@/service/business/geoChecksService.js";
|
||||||
import { requireTeamId } from "./controllerUtils.js";
|
|
||||||
import { AppError } from "@/utils/AppError.js";
|
|
||||||
|
|
||||||
const SERVICE_NAME = "geoCheckController";
|
const SERVICE_NAME = "geoCheckController";
|
||||||
|
|
||||||
export interface IGeoCheckController {
|
class GeoCheckController {
|
||||||
getGeoChecksByMonitor: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
}
|
|
||||||
class GeoCheckController implements IGeoCheckController {
|
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
|
|
||||||
private geoChecksService: IGeoChecksService;
|
private geoChecksService: IGeoChecksService;
|
||||||
@@ -23,29 +18,13 @@ class GeoCheckController implements IGeoCheckController {
|
|||||||
|
|
||||||
getGeoChecksByMonitor = async (req: Request, res: Response, next: NextFunction) => {
|
getGeoChecksByMonitor = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedParams = getChecksParamValidation.parse(req.params);
|
getChecksParamValidation.parse(req.params);
|
||||||
const validatedQuery = getChecksQueryValidation.parse(req.query);
|
getChecksQueryValidation.parse(req.query);
|
||||||
|
|
||||||
if (!validatedQuery.continent || validatedQuery.continent.length === 0) {
|
|
||||||
throw new AppError({
|
|
||||||
message: "At least one continent must be specified",
|
|
||||||
service: SERVICE_NAME,
|
|
||||||
method: "getGeoChecksByMonitor",
|
|
||||||
details: { monitorId: validatedParams.monitorId },
|
|
||||||
status: 400,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
|
||||||
|
|
||||||
const result = await this.geoChecksService.getGeoChecksByMonitor({
|
const result = await this.geoChecksService.getGeoChecksByMonitor({
|
||||||
monitorId: validatedParams.monitorId,
|
monitorId: req?.params?.monitorId as string,
|
||||||
teamId: teamId,
|
query: req?.query,
|
||||||
sortOrder: validatedQuery.sortOrder,
|
teamId: req?.user?.teamId as string,
|
||||||
dateRange: validatedQuery.dateRange,
|
|
||||||
page: validatedQuery.page,
|
|
||||||
rowsPerPage: validatedQuery.rowsPerPage,
|
|
||||||
continent: validatedQuery.continent,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
|
|||||||
@@ -1,37 +1,35 @@
|
|||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { requireTeamId, requireUserId, requireUserEmail } from "./controllerUtils.js";
|
import { requireTeamId, requireUserId, requireUserEmail } from "./controllerUtils.js";
|
||||||
import { IIncidentService } from "@/service/index.js";
|
|
||||||
import { getIncidentsByTeamQueryValidation, getIncidentSummaryQueryValidation } from "@/validation/incidentValidation.js";
|
|
||||||
|
|
||||||
const SERVICE_NAME = "IncidentController";
|
const SERVICE_NAME = "incidentController";
|
||||||
|
|
||||||
export interface IIncidentController {
|
class IncidentController {
|
||||||
getIncidentsByTeam: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
getIncidentSummary: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
getIncidentById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
private incidentService: any;
|
||||||
resolveIncidentManually: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
constructor(incidentService: any) {
|
||||||
}
|
|
||||||
class IncidentController implements IIncidentController {
|
|
||||||
private incidentService: IIncidentService;
|
|
||||||
constructor(incidentService: IIncidentService) {
|
|
||||||
this.incidentService = incidentService;
|
this.incidentService = incidentService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get serviceName() {
|
||||||
|
return IncidentController.SERVICE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
getIncidentsByTeam = async (req: Request, res: Response, next: NextFunction) => {
|
getIncidentsByTeam = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedQuery = getIncidentsByTeamQueryValidation.parse(req.query);
|
const { sortOrder, dateRange, page, rowsPerPage, status, monitorId, resolutionType } = req.query || {};
|
||||||
|
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = requireTeamId(req.user?.teamId);
|
||||||
const result = await this.incidentService.getIncidentsByTeam(
|
const result = await this.incidentService.getIncidentsByTeam(
|
||||||
teamId,
|
teamId,
|
||||||
validatedQuery.sortOrder,
|
sortOrder,
|
||||||
validatedQuery.dateRange,
|
dateRange,
|
||||||
validatedQuery.page,
|
page,
|
||||||
validatedQuery.rowsPerPage,
|
rowsPerPage,
|
||||||
validatedQuery.status,
|
status,
|
||||||
validatedQuery.monitorId,
|
monitorId,
|
||||||
validatedQuery.resolutionType
|
resolutionType
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
@@ -47,9 +45,10 @@ class IncidentController implements IIncidentController {
|
|||||||
getIncidentSummary = async (req: Request, res: Response, next: NextFunction) => {
|
getIncidentSummary = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = requireTeamId(req.user?.teamId);
|
||||||
const validatedQuery = getIncidentSummaryQueryValidation.parse(req.query);
|
|
||||||
|
|
||||||
const summary = await this.incidentService.getIncidentSummary(teamId, validatedQuery.limit);
|
const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : 10;
|
||||||
|
|
||||||
|
const summary = await this.incidentService.getIncidentSummary(teamId, limit);
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
msg: "Incident summary retrieved successfully",
|
msg: "Incident summary retrieved successfully",
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { inviteBodyValidation, inviteVerificationBodyValidation } from "@/validation/authValidation.js";
|
import { inviteBodyValidation, inviteVerificationBodyValidation } from "@/validation/authValidation.js";
|
||||||
import { requireFirstName, requireTeamId, requireUserRoles } from "@/controllers/controllerUtils.js";
|
import { requireTeamId, requireUserRoles } from "@/controllers/controllerUtils.js";
|
||||||
import { IInviteService } from "@/service/index.js";
|
|
||||||
const SERVICE_NAME = "inviteController";
|
const SERVICE_NAME = "inviteController";
|
||||||
|
|
||||||
export interface IInviteController {
|
class InviteController {
|
||||||
getInviteToken: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
sendInviteEmail: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
verifyInviteToken: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
class InviteController implements IInviteController {
|
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
private inviteService: IInviteService;
|
private inviteService: any;
|
||||||
constructor(inviteService: IInviteService) {
|
constructor(inviteService: any) {
|
||||||
this.inviteService = inviteService;
|
this.inviteService = inviteService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,9 +34,8 @@ class InviteController implements IInviteController {
|
|||||||
|
|
||||||
sendInviteEmail = async (req: Request, res: Response, next: NextFunction) => {
|
sendInviteEmail = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = requireTeamId(req?.user?.teamId);
|
||||||
const userRoles = requireUserRoles(req.user?.role);
|
const userRoles = requireUserRoles(req?.user?.role);
|
||||||
const firstName = requireFirstName(req.user?.firstName);
|
|
||||||
|
|
||||||
const inviteRequest = req.body;
|
const inviteRequest = req.body;
|
||||||
inviteRequest.teamId = teamId;
|
inviteRequest.teamId = teamId;
|
||||||
@@ -51,7 +43,7 @@ class InviteController implements IInviteController {
|
|||||||
|
|
||||||
const inviteToken = await this.inviteService.sendInviteEmail({
|
const inviteToken = await this.inviteService.sendInviteEmail({
|
||||||
invite: inviteRequest,
|
invite: inviteRequest,
|
||||||
firstName,
|
firstName: req?.user?.firstName,
|
||||||
userRoles,
|
userRoles,
|
||||||
});
|
});
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
import { ILogger } from "@/utils/logger.js";
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
|
||||||
const SERVICE_NAME = "LogController";
|
const SERVICE_NAME = "LogController";
|
||||||
|
|
||||||
export interface ILogController {
|
|
||||||
getLogs: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
class LogController {
|
class LogController {
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
private logger: ILogger;
|
private logger: any;
|
||||||
constructor(logger: ILogger) {
|
constructor(logger: any) {
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
get serviceName() {
|
get serviceName() {
|
||||||
@@ -19,7 +14,7 @@ class LogController {
|
|||||||
|
|
||||||
getLogs = async (req: Request, res: Response, next: NextFunction) => {
|
getLogs = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const logs = this.logger.getLogs();
|
const logs = await this.logger.getLogs();
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
msg: "Logs fetched successfully",
|
msg: "Logs fetched successfully",
|
||||||
|
|||||||
@@ -9,37 +9,26 @@ import {
|
|||||||
deleteMaintenanceWindowByIdParamValidation,
|
deleteMaintenanceWindowByIdParamValidation,
|
||||||
} from "@/validation/maintenanceWindowValidation.js";
|
} from "@/validation/maintenanceWindowValidation.js";
|
||||||
import { requireTeamId } from "@/controllers/controllerUtils.js";
|
import { requireTeamId } from "@/controllers/controllerUtils.js";
|
||||||
import { IMaintenanceWindowService } from "@/service/index.js";
|
|
||||||
|
|
||||||
export interface IMaintenanceWindowController {
|
const SERVICE_NAME = "maintenanceWindowController";
|
||||||
createMaintenanceWindows: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
getMaintenanceWindowById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
class MaintenanceWindowController {
|
||||||
getMaintenanceWindowsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
getMaintenanceWindowsByMonitorId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
private maintenanceWindowService: any;
|
||||||
deleteMaintenanceWindow: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
constructor(maintenanceWindowService: any) {
|
||||||
editMaintenanceWindow: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
}
|
|
||||||
class MaintenanceWindowController implements IMaintenanceWindowController {
|
|
||||||
private maintenanceWindowService: IMaintenanceWindowService;
|
|
||||||
constructor(maintenanceWindowService: IMaintenanceWindowService) {
|
|
||||||
this.maintenanceWindowService = maintenanceWindowService;
|
this.maintenanceWindowService = maintenanceWindowService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get serviceName() {
|
||||||
|
return MaintenanceWindowController.SERVICE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
createMaintenanceWindows = async (req: Request, res: Response, next: NextFunction) => {
|
createMaintenanceWindows = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedBody = createMaintenanceWindowBodyValidation.parse(req.body);
|
createMaintenanceWindowBodyValidation.parse(req.body);
|
||||||
const teamId = requireTeamId(req?.user?.teamId);
|
const teamId = requireTeamId(req?.user?.teamId);
|
||||||
|
|
||||||
const monitorIDs = validatedBody.monitors;
|
await this.maintenanceWindowService.createMaintenanceWindow({ teamId, body: req.body });
|
||||||
const name = validatedBody.name;
|
|
||||||
const active = validatedBody.active ?? true;
|
|
||||||
const duration = validatedBody.duration;
|
|
||||||
const durationUnit = validatedBody.durationUnit;
|
|
||||||
const repeat = validatedBody.repeat;
|
|
||||||
const start = validatedBody.start;
|
|
||||||
const end = validatedBody.end;
|
|
||||||
|
|
||||||
await this.maintenanceWindowService.createMaintenanceWindow({ teamId, monitorIDs, name, active, duration, durationUnit, repeat, start, end });
|
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -51,11 +40,11 @@ class MaintenanceWindowController implements IMaintenanceWindowController {
|
|||||||
};
|
};
|
||||||
getMaintenanceWindowById = async (req: Request, res: Response, next: NextFunction) => {
|
getMaintenanceWindowById = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedParams = getMaintenanceWindowByIdParamValidation.parse(req.params);
|
getMaintenanceWindowByIdParamValidation.parse(req.params);
|
||||||
|
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = requireTeamId(req.user?.teamId);
|
||||||
|
|
||||||
const maintenanceWindow = await this.maintenanceWindowService.getMaintenanceWindowById({ id: validatedParams.id, teamId });
|
const maintenanceWindow = await this.maintenanceWindowService.getMaintenanceWindowById({ id: req.params.id as string, teamId });
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -69,18 +58,11 @@ class MaintenanceWindowController implements IMaintenanceWindowController {
|
|||||||
|
|
||||||
getMaintenanceWindowsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
getMaintenanceWindowsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedQuery = getMaintenanceWindowsByTeamIdQueryValidation.parse(req.query);
|
getMaintenanceWindowsByTeamIdQueryValidation.parse(req.query);
|
||||||
|
|
||||||
const teamId = requireTeamId(req?.user?.teamId);
|
const teamId = requireTeamId(req?.user?.teamId);
|
||||||
|
|
||||||
const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByTeamId({
|
const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByTeamId({ teamId, query: req.query });
|
||||||
teamId,
|
|
||||||
active: validatedQuery.active,
|
|
||||||
page: validatedQuery.page,
|
|
||||||
rowsPerPage: validatedQuery.rowsPerPage,
|
|
||||||
field: validatedQuery.field,
|
|
||||||
order: validatedQuery.order,
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -94,12 +76,12 @@ class MaintenanceWindowController implements IMaintenanceWindowController {
|
|||||||
|
|
||||||
getMaintenanceWindowsByMonitorId = async (req: Request, res: Response, next: NextFunction) => {
|
getMaintenanceWindowsByMonitorId = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedParams = getMaintenanceWindowsByMonitorIdParamValidation.parse(req.params);
|
getMaintenanceWindowsByMonitorIdParamValidation.parse(req.params);
|
||||||
|
|
||||||
const teamId = requireTeamId(req?.user?.teamId);
|
const teamId = requireTeamId(req?.user?.teamId);
|
||||||
|
|
||||||
const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByMonitorId({
|
const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByMonitorId({
|
||||||
monitorId: validatedParams.monitorId,
|
monitorId: req.params.monitorId as string,
|
||||||
teamId,
|
teamId,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -114,11 +96,11 @@ class MaintenanceWindowController implements IMaintenanceWindowController {
|
|||||||
};
|
};
|
||||||
deleteMaintenanceWindow = async (req: Request, res: Response, next: NextFunction) => {
|
deleteMaintenanceWindow = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedParams = deleteMaintenanceWindowByIdParamValidation.parse(req.params);
|
deleteMaintenanceWindowByIdParamValidation.parse(req.params);
|
||||||
|
|
||||||
const teamId = requireTeamId(req?.user?.teamId);
|
const teamId = requireTeamId(req?.user?.teamId);
|
||||||
|
|
||||||
await this.maintenanceWindowService.deleteMaintenanceWindow({ id: validatedParams.id, teamId });
|
await this.maintenanceWindowService.deleteMaintenanceWindow({ id: req.params.id as string, teamId });
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -131,14 +113,14 @@ class MaintenanceWindowController implements IMaintenanceWindowController {
|
|||||||
|
|
||||||
editMaintenanceWindow = async (req: Request, res: Response, next: NextFunction) => {
|
editMaintenanceWindow = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedParams = editMaintenanceWindowByIdParamValidation.parse(req.params);
|
editMaintenanceWindowByIdParamValidation.parse(req.params);
|
||||||
const validatedBody = editMaintenanceByIdWindowBodyValidation.parse(req.body);
|
editMaintenanceByIdWindowBodyValidation.parse(req.body);
|
||||||
|
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = requireTeamId(req.user?.teamId);
|
||||||
|
|
||||||
const editedMaintenanceWindow = await this.maintenanceWindowService.editMaintenanceWindow({
|
const editedMaintenanceWindow = await this.maintenanceWindowService.editMaintenanceWindow({
|
||||||
id: validatedParams.id,
|
id: req.params.id as string,
|
||||||
body: validatedBody,
|
body: req.body,
|
||||||
teamId,
|
teamId,
|
||||||
});
|
});
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { updateNotificationsValidation } from "@/validation/notificationValidation.js";
|
|
||||||
import {
|
import {
|
||||||
getMonitorByIdParamValidation,
|
getMonitorByIdParamValidation,
|
||||||
getMonitorByIdQueryValidation,
|
getMonitorByIdQueryValidation,
|
||||||
|
getMonitorsByTeamIdParamValidation,
|
||||||
getMonitorsByTeamIdQueryValidation,
|
getMonitorsByTeamIdQueryValidation,
|
||||||
getMonitorsWithChecksQueryValidation,
|
getMonitorsWithChecksQueryValidation,
|
||||||
createMonitorBodyValidation,
|
createMonitorBodyValidation,
|
||||||
@@ -11,48 +12,31 @@ import {
|
|||||||
getCertificateParamValidation,
|
getCertificateParamValidation,
|
||||||
getHardwareDetailsByIdParamValidation,
|
getHardwareDetailsByIdParamValidation,
|
||||||
getHardwareDetailsByIdQueryValidation,
|
getHardwareDetailsByIdQueryValidation,
|
||||||
getUptimeDetailsByIdParamValidation,
|
|
||||||
getUptimeDetailsByIdQueryValidation,
|
|
||||||
importMonitorsBodyValidation,
|
|
||||||
} from "@/validation/monitorValidation.js";
|
} from "@/validation/monitorValidation.js";
|
||||||
import sslChecker from "ssl-checker";
|
import sslChecker from "ssl-checker";
|
||||||
import { fetchMonitorCertificate, requireTeamId, requireUserId } from "@/controllers/controllerUtils.js";
|
import {
|
||||||
|
fetchMonitorCertificate,
|
||||||
|
requireString,
|
||||||
|
optionalString,
|
||||||
|
optionalNumber,
|
||||||
|
optionalBoolean,
|
||||||
|
parseMonitorTypeFilter,
|
||||||
|
parseSortOrder,
|
||||||
|
requireTeamId,
|
||||||
|
requireUserId,
|
||||||
|
} from "./controllerUtils.js";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import { IMonitorService, INotificationsService } from "@/service/index.js";
|
import { IMonitorService } from "@/service/index.js";
|
||||||
import { GeoContinent } from "@/types/geoCheck.js";
|
import { GeoContinent } from "@/types/geoCheck.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "monitorController";
|
const SERVICE_NAME = "monitorController";
|
||||||
|
class MonitorController {
|
||||||
export interface IMonitorController {
|
|
||||||
getMonitorCertificate: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
getUptimeDetailsById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
getHardwareDetailsById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
getPageSpeedDetailsById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
getGeoChecksByMonitorId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
getMonitorById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
createMonitor: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
importMonitorsFromJSON: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
deleteMonitor: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
deleteAllMonitors: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
editMonitor: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
pauseMonitor: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
addDemoMonitors: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
getMonitorsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
getMonitorsWithChecksByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
exportMonitorsToJSON: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
getAllGames: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
getGroupsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
updateNotifications: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
}
|
|
||||||
class MonitorController implements IMonitorController {
|
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
|
|
||||||
private monitorService: IMonitorService;
|
private monitorService: IMonitorService;
|
||||||
private notificationsService: INotificationsService;
|
|
||||||
|
|
||||||
constructor(monitorService: IMonitorService, notificationsService: INotificationsService) {
|
constructor(monitorService: IMonitorService) {
|
||||||
this.monitorService = monitorService;
|
this.monitorService = monitorService;
|
||||||
this.notificationsService = notificationsService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
get serviceName() {
|
||||||
@@ -61,9 +45,10 @@ class MonitorController implements IMonitorController {
|
|||||||
|
|
||||||
getMonitorCertificate = async (req: Request, res: Response, next: NextFunction) => {
|
getMonitorCertificate = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedParams = getCertificateParamValidation.parse(req.params);
|
getCertificateParamValidation.parse(req.params);
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = requireTeamId(req?.user?.teamId);
|
||||||
const monitor = await this.monitorService.getMonitorById({ teamId, monitorId: validatedParams.monitorId });
|
const monitorId = requireString(req.params?.monitorId, "Monitor ID");
|
||||||
|
const monitor = await this.monitorService.getMonitorById({ teamId, monitorId });
|
||||||
const certificate = await fetchMonitorCertificate(sslChecker, monitor);
|
const certificate = await fetchMonitorCertificate(sslChecker, monitor);
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
@@ -80,18 +65,16 @@ class MonitorController implements IMonitorController {
|
|||||||
|
|
||||||
getUptimeDetailsById = async (req: Request, res: Response, next: NextFunction) => {
|
getUptimeDetailsById = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedParams = getUptimeDetailsByIdParamValidation.parse(req.params);
|
const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
|
||||||
const validatedQuery = getUptimeDetailsByIdQueryValidation.parse(req.query);
|
const dateRange = requireString(req?.query?.dateRange, "dateRange");
|
||||||
|
const normalize = optionalBoolean(req?.query?.normalize, "normalize");
|
||||||
const monitorId = validatedParams.monitorId;
|
const teamId = requireTeamId(req?.user?.teamId);
|
||||||
const dateRange = validatedQuery.dateRange;
|
|
||||||
|
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
|
||||||
|
|
||||||
const data = await this.monitorService.getUptimeDetailsById({
|
const data = await this.monitorService.getUptimeDetailsById({
|
||||||
teamId,
|
teamId,
|
||||||
monitorId,
|
monitorId,
|
||||||
dateRange,
|
dateRange,
|
||||||
|
normalize,
|
||||||
});
|
});
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -105,12 +88,12 @@ class MonitorController implements IMonitorController {
|
|||||||
|
|
||||||
getHardwareDetailsById = async (req: Request, res: Response, next: NextFunction) => {
|
getHardwareDetailsById = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedParams = getHardwareDetailsByIdParamValidation.parse(req.params);
|
getHardwareDetailsByIdParamValidation.parse(req.params);
|
||||||
const validatedQuery = getHardwareDetailsByIdQueryValidation.parse(req.query);
|
getHardwareDetailsByIdQueryValidation.parse(req.query);
|
||||||
|
|
||||||
const monitorId = validatedParams.monitorId;
|
const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
|
||||||
const dateRange = validatedQuery.dateRange || "recent";
|
const dateRange = optionalString(req?.query?.dateRange, "dateRange") || "recent";
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = requireTeamId(req?.user?.teamId);
|
||||||
|
|
||||||
const data = await this.monitorService.getHardwareDetailsById({
|
const data = await this.monitorService.getHardwareDetailsById({
|
||||||
teamId,
|
teamId,
|
||||||
@@ -129,12 +112,12 @@ class MonitorController implements IMonitorController {
|
|||||||
};
|
};
|
||||||
getPageSpeedDetailsById = async (req: Request, res: Response, next: NextFunction) => {
|
getPageSpeedDetailsById = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedParams = getHardwareDetailsByIdParamValidation.parse(req.params);
|
getHardwareDetailsByIdParamValidation.parse(req.params);
|
||||||
const validatedQuery = getHardwareDetailsByIdQueryValidation.parse(req.query);
|
getHardwareDetailsByIdQueryValidation.parse(req.query);
|
||||||
|
|
||||||
const monitorId = validatedParams.monitorId;
|
const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
|
||||||
const dateRange = validatedQuery.dateRange || "recent";
|
const dateRange = requireString(req?.query?.dateRange, "dateRange");
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = requireTeamId(req?.user?.teamId);
|
||||||
|
|
||||||
const data = await this.monitorService.getPageSpeedDetailsById({
|
const data = await this.monitorService.getPageSpeedDetailsById({
|
||||||
teamId,
|
teamId,
|
||||||
@@ -154,14 +137,18 @@ class MonitorController implements IMonitorController {
|
|||||||
|
|
||||||
getGeoChecksByMonitorId = async (req: Request, res: Response, next: NextFunction) => {
|
getGeoChecksByMonitorId = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedParams = getMonitorByIdParamValidation.parse(req.params);
|
getMonitorByIdParamValidation.parse(req.params);
|
||||||
const validatedQuery = getMonitorByIdQueryValidation.parse(req.query);
|
getMonitorByIdQueryValidation.parse(req.query);
|
||||||
|
|
||||||
const monitorId = validatedParams.monitorId;
|
const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
|
||||||
const dateRange = validatedQuery.dateRange || "recent";
|
const dateRange = requireString(req?.query?.dateRange, "dateRange");
|
||||||
const continentParam = validatedQuery.continent;
|
const continentParam = req?.query?.continent;
|
||||||
const continents = continentParam ? (Array.isArray(continentParam) ? continentParam : [continentParam]) : undefined;
|
const continents = continentParam
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
? Array.isArray(continentParam)
|
||||||
|
? (continentParam as GeoContinent[])
|
||||||
|
: [continentParam as GeoContinent]
|
||||||
|
: undefined;
|
||||||
|
const teamId = requireTeamId(req?.user?.teamId);
|
||||||
|
|
||||||
const data = await this.monitorService.getGeoChecksByMonitorId({
|
const data = await this.monitorService.getGeoChecksByMonitorId({
|
||||||
teamId,
|
teamId,
|
||||||
@@ -182,10 +169,11 @@ class MonitorController implements IMonitorController {
|
|||||||
|
|
||||||
getMonitorById = async (req: Request, res: Response, next: NextFunction) => {
|
getMonitorById = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedParams = getMonitorByIdParamValidation.parse(req.params);
|
getMonitorByIdParamValidation.parse(req.params);
|
||||||
|
getMonitorByIdQueryValidation.parse(req.query);
|
||||||
|
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = requireTeamId(req?.user?.teamId);
|
||||||
const monitorId = validatedParams.monitorId;
|
const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
|
||||||
|
|
||||||
const monitor = await this.monitorService.getMonitorById({ teamId, monitorId });
|
const monitor = await this.monitorService.getMonitorById({ teamId, monitorId });
|
||||||
|
|
||||||
@@ -201,12 +189,12 @@ class MonitorController implements IMonitorController {
|
|||||||
|
|
||||||
createMonitor = async (req: Request, res: Response, next: NextFunction) => {
|
createMonitor = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedBody = createMonitorBodyValidation.parse(req.body);
|
createMonitorBodyValidation.parse(req.body);
|
||||||
|
|
||||||
const userId = requireUserId(req.user?.id);
|
const userId = requireString(req?.user?.id, "User ID");
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = requireTeamId(req?.user?.teamId);
|
||||||
|
|
||||||
const monitor = await this.monitorService.createMonitor(teamId, userId, validatedBody);
|
const monitor = await this.monitorService.createMonitor(teamId, userId, req.body);
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -220,10 +208,14 @@ class MonitorController implements IMonitorController {
|
|||||||
|
|
||||||
importMonitorsFromJSON = async (req: Request, res: Response, next: NextFunction) => {
|
importMonitorsFromJSON = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = requireTeamId(req?.user?.teamId);
|
||||||
const userId = requireUserId(req.user?.id);
|
const userId = requireUserId(req?.user?.id);
|
||||||
const validatedBody = importMonitorsBodyValidation.parse(req.body);
|
|
||||||
const monitors = validatedBody.monitors;
|
const { monitors } = req.body;
|
||||||
|
|
||||||
|
if (!monitors || !Array.isArray(monitors)) {
|
||||||
|
throw new AppError({ message: "Invalid request: monitors array is required", status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
const result = await this.monitorService.importMonitorsFromJSON({ teamId, userId, monitors });
|
const result = await this.monitorService.importMonitorsFromJSON({ teamId, userId, monitors });
|
||||||
|
|
||||||
@@ -239,9 +231,9 @@ class MonitorController implements IMonitorController {
|
|||||||
|
|
||||||
deleteMonitor = async (req: Request, res: Response, next: NextFunction) => {
|
deleteMonitor = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedParams = getMonitorByIdParamValidation.parse(req.params);
|
getMonitorByIdParamValidation.parse(req.params);
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
|
||||||
const monitorId = validatedParams.monitorId;
|
const teamId = requireTeamId(req?.user?.teamId);
|
||||||
|
|
||||||
const deletedMonitor = await this.monitorService.deleteMonitor({ teamId, monitorId });
|
const deletedMonitor = await this.monitorService.deleteMonitor({ teamId, monitorId });
|
||||||
|
|
||||||
@@ -257,7 +249,8 @@ class MonitorController implements IMonitorController {
|
|||||||
|
|
||||||
deleteAllMonitors = async (req: Request, res: Response, next: NextFunction) => {
|
deleteAllMonitors = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = requireTeamId(req?.user?.teamId);
|
||||||
|
|
||||||
const deletedCount = await this.monitorService.deleteAllMonitors({ teamId });
|
const deletedCount = await this.monitorService.deleteAllMonitors({ teamId });
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
@@ -271,12 +264,12 @@ class MonitorController implements IMonitorController {
|
|||||||
|
|
||||||
editMonitor = async (req: Request, res: Response, next: NextFunction) => {
|
editMonitor = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedParams = getMonitorByIdParamValidation.parse(req.params);
|
getMonitorByIdParamValidation.parse(req.params);
|
||||||
const validatedBody = editMonitorBodyValidation.parse(req.body);
|
editMonitorBodyValidation.parse(req.body);
|
||||||
const monitorId = validatedParams.monitorId;
|
const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = requireTeamId(req?.user?.teamId);
|
||||||
|
|
||||||
const editedMonitor = await this.monitorService.editMonitor({ teamId, monitorId, body: validatedBody });
|
const editedMonitor = await this.monitorService.editMonitor({ teamId, monitorId, body: req.body });
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -290,10 +283,10 @@ class MonitorController implements IMonitorController {
|
|||||||
|
|
||||||
pauseMonitor = async (req: Request, res: Response, next: NextFunction) => {
|
pauseMonitor = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedParams = pauseMonitorParamValidation.parse(req.params);
|
pauseMonitorParamValidation.parse(req.params);
|
||||||
|
|
||||||
const monitorId = validatedParams.monitorId;
|
const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = requireTeamId(req?.user?.teamId);
|
||||||
|
|
||||||
const monitor = await this.monitorService.pauseMonitor({ teamId, monitorId });
|
const monitor = await this.monitorService.pauseMonitor({ teamId, monitorId });
|
||||||
|
|
||||||
@@ -309,8 +302,8 @@ class MonitorController implements IMonitorController {
|
|||||||
|
|
||||||
addDemoMonitors = async (req: Request, res: Response, next: NextFunction) => {
|
addDemoMonitors = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const id = requireUserId(req.user?.id);
|
const id = requireString(req?.user?.id, "User ID");
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = requireTeamId(req?.user?.teamId);
|
||||||
const demoMonitors = await this.monitorService.addDemoMonitors({ userId: id, teamId });
|
const demoMonitors = await this.monitorService.addDemoMonitors({ userId: id, teamId });
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
@@ -323,13 +316,32 @@ class MonitorController implements IMonitorController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
sendTestEmail = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const { to } = req.body;
|
||||||
|
if (!to || typeof to !== "string") {
|
||||||
|
throw new AppError({ message: "Invalid 'to' email address", status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageId = await this.monitorService.sendTestEmail({ to });
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
msg: "Test email sent successfully",
|
||||||
|
data: { messageId },
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
getMonitorsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
getMonitorsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedQuery = getMonitorsByTeamIdQueryValidation.parse(req.query);
|
getMonitorsByTeamIdParamValidation.parse(req.params);
|
||||||
|
getMonitorsByTeamIdQueryValidation.parse(req.query);
|
||||||
|
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = requireTeamId(req?.user?.teamId);
|
||||||
const type = validatedQuery.type;
|
const type = parseMonitorTypeFilter(req.query?.type);
|
||||||
const filter = validatedQuery.filter;
|
const filter = optionalString(req.query?.filter, "filter");
|
||||||
|
|
||||||
const monitors = await this.monitorService.getMonitorsByTeamId({ teamId, type, filter });
|
const monitors = await this.monitorService.getMonitorsByTeamId({ teamId, type, filter });
|
||||||
|
|
||||||
@@ -345,16 +357,17 @@ class MonitorController implements IMonitorController {
|
|||||||
|
|
||||||
getMonitorsWithChecksByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
getMonitorsWithChecksByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedQuery = getMonitorsWithChecksQueryValidation.parse(req.query);
|
getMonitorsByTeamIdParamValidation.parse(req.params);
|
||||||
|
getMonitorsWithChecksQueryValidation.parse(req.query);
|
||||||
const limit = validatedQuery.limit;
|
const explain = optionalBoolean(req?.query?.explain, "explain");
|
||||||
const page = validatedQuery.page;
|
const limit = optionalNumber(req?.query?.limit, "limit");
|
||||||
const rowsPerPage = validatedQuery.rowsPerPage;
|
const page = optionalNumber(req?.query?.page, "page");
|
||||||
const filter = validatedQuery.filter;
|
const rowsPerPage = optionalNumber(req?.query?.rowsPerPage, "rowsPerPage");
|
||||||
const field = validatedQuery.field;
|
const filter = optionalString(req?.query?.filter, "filter");
|
||||||
const order = validatedQuery.order;
|
const field = optionalString(req?.query?.field, "field");
|
||||||
const type = validatedQuery.type;
|
const order = parseSortOrder(req?.query?.order);
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const type = parseMonitorTypeFilter(req?.query?.type);
|
||||||
|
const teamId = requireTeamId(req?.user?.teamId);
|
||||||
|
|
||||||
const monitors = await this.monitorService.getMonitorsWithChecksByTeamId({
|
const monitors = await this.monitorService.getMonitorsWithChecksByTeamId({
|
||||||
teamId,
|
teamId,
|
||||||
@@ -365,6 +378,7 @@ class MonitorController implements IMonitorController {
|
|||||||
filter,
|
filter,
|
||||||
field,
|
field,
|
||||||
order,
|
order,
|
||||||
|
explain,
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
@@ -378,7 +392,11 @@ class MonitorController implements IMonitorController {
|
|||||||
|
|
||||||
exportMonitorsToJSON = async (req: Request, res: Response, next: NextFunction) => {
|
exportMonitorsToJSON = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = req?.user?.teamId;
|
||||||
|
if (!teamId) {
|
||||||
|
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
const json = await this.monitorService.exportMonitorsToJSON({ teamId });
|
const json = await this.monitorService.exportMonitorsToJSON({ teamId });
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
@@ -406,7 +424,11 @@ class MonitorController implements IMonitorController {
|
|||||||
|
|
||||||
getGroupsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
getGroupsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = req?.user?.teamId;
|
||||||
|
if (!teamId) {
|
||||||
|
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
const groups = await this.monitorService.getGroupsByTeamId({ teamId });
|
const groups = await this.monitorService.getGroupsByTeamId({ teamId });
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
@@ -417,42 +439,6 @@ class MonitorController implements IMonitorController {
|
|||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
updateNotifications = async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
try {
|
|
||||||
updateNotificationsValidation.parse(req.body);
|
|
||||||
|
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
|
||||||
const { monitorIds, notificationIds, action } = req.body;
|
|
||||||
|
|
||||||
// Verify all requested notification IDs actually belong to this team
|
|
||||||
const teamNotifications = await this.notificationsService.findNotificationsByTeamId(teamId);
|
|
||||||
const validNotificationIds = teamNotifications.map((n) => n.id);
|
|
||||||
|
|
||||||
const invalidIds = notificationIds.filter((id: string) => !validNotificationIds.includes(id));
|
|
||||||
if (invalidIds.length > 0) {
|
|
||||||
throw new AppError({
|
|
||||||
message: `The following notification IDs are invalid or do not belong to your team: ${invalidIds.join(", ")}`,
|
|
||||||
status: 403,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const modifiedCount = await this.monitorService.updateNotifications({
|
|
||||||
teamId,
|
|
||||||
monitorIds,
|
|
||||||
notificationIds,
|
|
||||||
action,
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).json({
|
|
||||||
success: true,
|
|
||||||
msg: `Notifications updated successfully on ${modifiedCount} monitor(s)`,
|
|
||||||
data: { modifiedCount },
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MonitorController;
|
export default MonitorController;
|
||||||
@@ -1,30 +1,16 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
|
||||||
import {
|
import { Notification } from "@/types/index.js";
|
||||||
createNotificationBodyValidation,
|
import { createNotificationBodyValidation } from "@/validation/notificationValidation.js";
|
||||||
deleteNotificationParamValidation,
|
|
||||||
getNotificationByIdParamValidation,
|
|
||||||
testNotificationBodyValidation,
|
|
||||||
editNotificationParamValidation,
|
|
||||||
testAllNotificationsBodyValidation,
|
|
||||||
} from "@/validation/notificationValidation.js";
|
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import { INotificationsService } from "@/service/index.js";
|
|
||||||
import { requireTeamId, requireUserId } from "./controllerUtils.js";
|
|
||||||
import { IMonitorsRepository } from "@/repositories/index.js";
|
import { IMonitorsRepository } from "@/repositories/index.js";
|
||||||
|
import { INotificationsService } from "@/service/index.js";
|
||||||
|
import { requireTeamId } from "./controllerUtils.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "NotificationController";
|
const SERVICE_NAME = "NotificationController";
|
||||||
|
|
||||||
export interface INotificationController {
|
class NotificationController {
|
||||||
testNotification: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
createNotification: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
getNotificationsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
deleteNotification: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
getNotificationById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
editNotification: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
testAllNotifications: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
|
||||||
}
|
|
||||||
class NotificationController implements INotificationController {
|
|
||||||
private notificationsService: INotificationsService;
|
private notificationsService: INotificationsService;
|
||||||
private monitorsRepository: IMonitorsRepository;
|
private monitorsRepository: IMonitorsRepository;
|
||||||
constructor(notificationsService: INotificationsService, monitorsRepository: IMonitorsRepository) {
|
constructor(notificationsService: INotificationsService, monitorsRepository: IMonitorsRepository) {
|
||||||
@@ -32,9 +18,13 @@ class NotificationController implements INotificationController {
|
|||||||
this.monitorsRepository = monitorsRepository;
|
this.monitorsRepository = monitorsRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get serviceName() {
|
||||||
|
return NotificationController.SERVICE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
testNotification = async (req: Request, res: Response, next: NextFunction) => {
|
testNotification = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const notification = testNotificationBodyValidation.parse(req.body);
|
const notification: Notification = req.body;
|
||||||
const success = await this.notificationsService.sendTestNotification(notification);
|
const success = await this.notificationsService.sendTestNotification(notification);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
@@ -44,7 +34,6 @@ class NotificationController implements INotificationController {
|
|||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
msg: "Notification sent successfully",
|
msg: "Notification sent successfully",
|
||||||
details: { service: SERVICE_NAME },
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
@@ -53,12 +42,23 @@ class NotificationController implements INotificationController {
|
|||||||
|
|
||||||
createNotification = async (req: Request, res: Response, next: NextFunction) => {
|
createNotification = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedBody = createNotificationBodyValidation.parse(req.body);
|
createNotificationBodyValidation.parse(req.body);
|
||||||
|
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const body = req.body;
|
||||||
const userId = requireUserId(req.user?.id);
|
|
||||||
|
|
||||||
const notification = await this.notificationsService.createNotification(validatedBody, userId, teamId);
|
const teamId = req?.user?.teamId;
|
||||||
|
if (!teamId) {
|
||||||
|
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = req?.user?.id;
|
||||||
|
if (!userId) {
|
||||||
|
throw new AppError({ message: "User ID is required", status: 400 });
|
||||||
|
}
|
||||||
|
body.userId = userId;
|
||||||
|
body.teamId = teamId;
|
||||||
|
|
||||||
|
const notification = await this.notificationsService.createNotification(body);
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
msg: "Notification created successfully",
|
msg: "Notification created successfully",
|
||||||
@@ -71,7 +71,11 @@ class NotificationController implements INotificationController {
|
|||||||
|
|
||||||
getNotificationsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
getNotificationsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = req?.user?.teamId;
|
||||||
|
if (!teamId) {
|
||||||
|
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
const notifications = await this.notificationsService.findNotificationsByTeamId(teamId);
|
const notifications = await this.notificationsService.findNotificationsByTeamId(teamId);
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
@@ -86,10 +90,17 @@ class NotificationController implements INotificationController {
|
|||||||
|
|
||||||
deleteNotification = async (req: Request, res: Response, next: NextFunction) => {
|
deleteNotification = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = req?.user?.teamId;
|
||||||
const validatedParams = deleteNotificationParamValidation.parse(req.params);
|
if (!teamId) {
|
||||||
|
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
await this.notificationsService.deleteById(validatedParams.id, teamId);
|
const notificationId = req.params.id as string;
|
||||||
|
if (!notificationId) {
|
||||||
|
throw new AppError({ message: "Notification ID is required", status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.notificationsService.deleteById(notificationId, teamId);
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
msg: "Notification deleted successfully",
|
msg: "Notification deleted successfully",
|
||||||
@@ -102,9 +113,12 @@ class NotificationController implements INotificationController {
|
|||||||
getNotificationById = async (req: Request, res: Response, next: NextFunction) => {
|
getNotificationById = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = requireTeamId(req.user?.teamId);
|
||||||
const validatedParams = getNotificationByIdParamValidation.parse(req.params);
|
const notificationId = req.params.id as string;
|
||||||
|
if (!notificationId) {
|
||||||
|
throw new AppError({ message: "Notification ID is required", status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
const notification = await this.notificationsService.findById(validatedParams.id, teamId);
|
const notification = await this.notificationsService.findById(notificationId, teamId);
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -118,13 +132,14 @@ class NotificationController implements INotificationController {
|
|||||||
|
|
||||||
editNotification = async (req: Request, res: Response, next: NextFunction) => {
|
editNotification = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedBody = createNotificationBodyValidation.parse(req.body);
|
createNotificationBodyValidation.parse(req.body);
|
||||||
const validatedParams = editNotificationParamValidation.parse(req.params);
|
|
||||||
|
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = requireTeamId(req.user?.teamId);
|
||||||
const notificationId = validatedParams.id;
|
const notificationId = req.params.id as string;
|
||||||
|
if (!notificationId) {
|
||||||
const editedNotification = await this.notificationsService.updateById(notificationId, teamId, validatedBody);
|
throw new AppError({ message: "Notification ID is required", status: 400 });
|
||||||
|
}
|
||||||
|
const editedNotification = await this.notificationsService.updateById(notificationId, teamId, req.body);
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
msg: "Notification updated successfully",
|
msg: "Notification updated successfully",
|
||||||
@@ -137,13 +152,15 @@ class NotificationController implements INotificationController {
|
|||||||
|
|
||||||
testAllNotifications = async (req: Request, res: Response, next: NextFunction) => {
|
testAllNotifications = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedBody = testAllNotificationsBodyValidation.parse(req.body);
|
const monitorId = req.body.monitorId;
|
||||||
|
const teamId = req?.user?.teamId;
|
||||||
|
if (!teamId) {
|
||||||
|
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||||
|
|
||||||
const monitor = await this.monitorsRepository.findById(validatedBody.monitorId, teamId);
|
|
||||||
const notifications = monitor.notifications || [];
|
|
||||||
|
|
||||||
|
const notifications = monitor.notifications;
|
||||||
if (notifications.length === 0) {
|
if (notifications.length === 0) {
|
||||||
throw new AppError({ message: "No notifications", status: 400 });
|
throw new AppError({ message: "No notifications", status: 400 });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,11 @@
|
|||||||
import { ISuperSimpleQueue } from "@/service/index.js";
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
|
||||||
const SERVICE_NAME = "JobQueueController";
|
const SERVICE_NAME = "JobQueueController";
|
||||||
|
|
||||||
export interface IJobQueueController {
|
class JobQueueController {
|
||||||
getMetrics(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
||||||
getJobs(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
getAllMetrics(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
flushQueue(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
class JobQueueController implements IJobQueueController {
|
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
private jobQueue: ISuperSimpleQueue;
|
private jobQueue: any;
|
||||||
constructor(jobQueue: ISuperSimpleQueue) {
|
constructor(jobQueue: any) {
|
||||||
this.jobQueue = jobQueue;
|
this.jobQueue = jobQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +53,18 @@ class JobQueueController implements IJobQueueController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
addJob = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
await this.jobQueue.addJob(Math.random().toString(36).substring(7));
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
msg: "Job added to queue successfully",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
flushQueue = async (req: Request, res: Response, next: NextFunction) => {
|
flushQueue = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const result = await this.jobQueue.flushQueues();
|
const result = await this.jobQueue.flushQueues();
|
||||||
@@ -73,5 +77,18 @@ class JobQueueController implements IJobQueueController {
|
|||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
checkQueueHealth = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const stuckQueues = await this.jobQueue.checkQueueHealth();
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
msg: "Queue health checked successfully",
|
||||||
|
data: stuckQueues,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
export default JobQueueController;
|
export default JobQueueController;
|
||||||
@@ -2,23 +2,14 @@ import { Request, Response, NextFunction } from "express";
|
|||||||
import { updateAppSettingsBodyValidation } from "@/validation/settingsValidation.js";
|
import { updateAppSettingsBodyValidation } from "@/validation/settingsValidation.js";
|
||||||
import { sendTestEmailBodyValidation } from "@/validation/notificationValidation.js";
|
import { sendTestEmailBodyValidation } from "@/validation/notificationValidation.js";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import { IEmailService, ISettingsService } from "@/service/index.js";
|
|
||||||
import { Settings } from "@/types/settings.js";
|
|
||||||
|
|
||||||
const SERVICE_NAME = "SettingsController";
|
const SERVICE_NAME = "SettingsController";
|
||||||
|
|
||||||
export interface ISettingsController {
|
class SettingsController {
|
||||||
serviceName: string;
|
|
||||||
getAppSettings(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
updateAppSettings(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
sendTestEmail(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
class SettingsController implements ISettingsController {
|
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
private settingsService: ISettingsService;
|
private settingsService: any;
|
||||||
private emailService: IEmailService;
|
private emailService: any;
|
||||||
constructor(settingsService: ISettingsService, emailService: IEmailService) {
|
constructor(settingsService: any, emailService: any) {
|
||||||
this.settingsService = settingsService;
|
this.settingsService = settingsService;
|
||||||
this.emailService = emailService;
|
this.emailService = emailService;
|
||||||
}
|
}
|
||||||
@@ -27,11 +18,11 @@ class SettingsController implements ISettingsController {
|
|||||||
return SettingsController.SERVICE_NAME;
|
return SettingsController.SERVICE_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
buildAppSettings = (dbSettings: Settings) => {
|
buildAppSettings = (dbSettings: any) => {
|
||||||
const sanitizedSettings: Record<string, unknown> = { ...dbSettings };
|
const sanitizedSettings = { ...dbSettings };
|
||||||
delete sanitizedSettings.version;
|
delete sanitizedSettings.version;
|
||||||
delete sanitizedSettings.jwtSecret;
|
delete sanitizedSettings.jwtSecret;
|
||||||
const returnSettings: Record<string, unknown | null> = {
|
const returnSettings = {
|
||||||
pagespeedKeySet: false,
|
pagespeedKeySet: false,
|
||||||
emailPasswordSet: false,
|
emailPasswordSet: false,
|
||||||
settings: null,
|
settings: null,
|
||||||
@@ -66,9 +57,9 @@ class SettingsController implements ISettingsController {
|
|||||||
|
|
||||||
updateAppSettings = async (req: Request, res: Response, next: NextFunction) => {
|
updateAppSettings = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const validatedBody = updateAppSettingsBodyValidation.parse(req.body);
|
updateAppSettingsBodyValidation.parse(req.body);
|
||||||
|
|
||||||
const updatedSettings = await this.settingsService.updateDbSettings(validatedBody);
|
const updatedSettings = await this.settingsService.updateDbSettings(req.body);
|
||||||
const returnSettings = this.buildAppSettings(updatedSettings);
|
const returnSettings = this.buildAppSettings(updatedSettings);
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -104,9 +95,6 @@ class SettingsController implements ISettingsController {
|
|||||||
const context = { testName: "Monitoring System" };
|
const context = { testName: "Monitoring System" };
|
||||||
|
|
||||||
const html = await this.emailService.buildEmail("testEmailTemplate", context);
|
const html = await this.emailService.buildEmail("testEmailTemplate", context);
|
||||||
if (!html) {
|
|
||||||
throw new AppError({ message: "Failed to build email template.", status: 500 });
|
|
||||||
}
|
|
||||||
const messageId = await this.emailService.sendEmail(to, subject, html, {
|
const messageId = await this.emailService.sendEmail(to, subject, html, {
|
||||||
systemEmailHost,
|
systemEmailHost,
|
||||||
systemEmailPort,
|
systemEmailPort,
|
||||||
|
|||||||
@@ -15,16 +15,7 @@ import { NormalizeData } from "@/utils/dataUtils.js";
|
|||||||
|
|
||||||
const SERVICE_NAME = "statusPageController";
|
const SERVICE_NAME = "statusPageController";
|
||||||
|
|
||||||
export interface IStatusPageController {
|
class StatusPageController {
|
||||||
readonly serviceName: string;
|
|
||||||
createStatusPage(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
updateStatusPage(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
getStatusPageByUrl(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
getStatusPagesByTeamId(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
deleteStatusPage(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
class StatusPageController implements IStatusPageController {
|
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
private statusPageService: IStatusPageService;
|
private statusPageService: IStatusPageService;
|
||||||
private monitorsRepository: IMonitorsRepository;
|
private monitorsRepository: IMonitorsRepository;
|
||||||
@@ -75,7 +66,7 @@ class StatusPageController implements IStatusPageController {
|
|||||||
if (statusPage === null) {
|
if (statusPage === null) {
|
||||||
throw new AppError({ message: "Status page not found", status: 404 });
|
throw new AppError({ message: "Status page not found", status: 404 });
|
||||||
}
|
}
|
||||||
res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
msg: "Status page updated successfully",
|
msg: "Status page updated successfully",
|
||||||
data: statusPage,
|
data: statusPage,
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
export interface IDb {
|
|
||||||
connect(): Promise<void>;
|
|
||||||
disconnect(): Promise<void>;
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
import mongoose from "mongoose";
|
import mongoose from "mongoose";
|
||||||
import AppSettings from "@/db/models/AppSettings.js";
|
import AppSettings from "./models/AppSettings.js";
|
||||||
import { runMigrations } from "@/db/migration/index.js";
|
import { runMigrations } from "./migration/index.js";
|
||||||
import { ILogger } from "@/utils/logger.js";
|
import { ILogger } from "@/utils/logger.js";
|
||||||
import { EnvConfig } from "@/service/system/settingsService.js";
|
import { EnvConfig } from "@/service/system/settingsService.js";
|
||||||
const SERVICE_NAME = "MongoDB";
|
const SERVICE_NAME = "MongoDB";
|
||||||
import { IDb } from "@/db/IDb.js";
|
class MongoDB {
|
||||||
|
|
||||||
class MongoDB implements IDb {
|
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
|
|
||||||
private logger: ILogger;
|
private logger: ILogger;
|
||||||
@@ -48,12 +46,12 @@ class MongoDB implements IDb {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await runMigrations();
|
await runMigrations();
|
||||||
} catch (error: unknown) {
|
} catch (error: any) {
|
||||||
this.logger.error({
|
this.logger.error({
|
||||||
message: error instanceof Error ? error.message : "Unknown error",
|
message: error.message,
|
||||||
service: SERVICE_NAME,
|
service: SERVICE_NAME,
|
||||||
method: "connect",
|
method: "connect",
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
stack: error.stack,
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -65,12 +63,12 @@ class MongoDB implements IDb {
|
|||||||
await mongoose.disconnect();
|
await mongoose.disconnect();
|
||||||
this.logger.info({ message: "Disconnected from MongoDB", service: SERVICE_NAME, method: "disconnect" });
|
this.logger.info({ message: "Disconnected from MongoDB", service: SERVICE_NAME, method: "disconnect" });
|
||||||
return;
|
return;
|
||||||
} catch (error: unknown) {
|
} catch (error: any) {
|
||||||
this.logger.error({
|
this.logger.error({
|
||||||
message: error instanceof Error ? error.message : "Unknown error",
|
message: error.message,
|
||||||
service: SERVICE_NAME,
|
service: SERVICE_NAME,
|
||||||
method: "disconnect",
|
method: "disconnect",
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
stack: error.stack,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import mongoose from "mongoose";
|
import mongoose from "mongoose";
|
||||||
import type { Document, AnyBulkWriteOperation } from "mongodb";
|
|
||||||
|
|
||||||
const CHECKS_COLLECTION = "checks";
|
const CHECKS_COLLECTION = "checks";
|
||||||
const BACKUP_COLLECTION = "checks_backup";
|
const BACKUP_COLLECTION = "checks_backup";
|
||||||
@@ -86,7 +85,7 @@ const createTimeSeriesCollection = async (backedUp: boolean) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateDocument = (doc: Document): boolean => {
|
const validateDocument = (doc: any): boolean => {
|
||||||
if (!doc.createdAt) {
|
if (!doc.createdAt) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -96,7 +95,7 @@ const validateDocument = (doc: Document): boolean => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const storeFailedDocument = async (doc: Document, reason: string) => {
|
const storeFailedDocument = async (doc: any, reason: string) => {
|
||||||
const db = getDb();
|
const db = getDb();
|
||||||
await db.collection(FAILED_DOCS_COLLECTION).insertOne({
|
await db.collection(FAILED_DOCS_COLLECTION).insertOne({
|
||||||
originalDoc: doc,
|
originalDoc: doc,
|
||||||
@@ -125,8 +124,8 @@ const migrateBackupData = async (backedUp: boolean): Promise<MigrationStats> =>
|
|||||||
stats.totalSource = await source.countDocuments();
|
stats.totalSource = await source.countDocuments();
|
||||||
|
|
||||||
const cursor = source.find().addCursorFlag("noCursorTimeout", true);
|
const cursor = source.find().addCursorFlag("noCursorTimeout", true);
|
||||||
const operations: AnyBulkWriteOperation<Document>[] = [];
|
const operations: any[] = [];
|
||||||
const invalidDocs: Document[] = [];
|
const invalidDocs: any[] = [];
|
||||||
|
|
||||||
while (await cursor.hasNext()) {
|
while (await cursor.hasNext()) {
|
||||||
const doc = await cursor.next();
|
const doc = await cursor.next();
|
||||||
@@ -140,7 +139,6 @@ const migrateBackupData = async (backedUp: boolean): Promise<MigrationStats> =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { _id, monitorId, teamId, type, ...rest } = doc;
|
const { _id, monitorId, teamId, type, ...rest } = doc;
|
||||||
void _id;
|
|
||||||
const metadata = { monitorId, teamId, type };
|
const metadata = { monitorId, teamId, type };
|
||||||
operations.push({ insertOne: { document: { ...rest, metadata } } });
|
operations.push({ insertOne: { document: { ...rest, metadata } } });
|
||||||
|
|
||||||
|
|||||||
@@ -42,10 +42,10 @@ export async function cleanupDuplicateMonitorStats(): Promise<void> {
|
|||||||
const monitorId = duplicate._id;
|
const monitorId = duplicate._id;
|
||||||
const { ids, updatedAts } = duplicate;
|
const { ids, updatedAts } = duplicate;
|
||||||
|
|
||||||
type DocPair = { id: string; updatedAt: Date };
|
type DocPair = { id: any; updatedAt: Date };
|
||||||
|
|
||||||
// Create array of {id, updatedAt} pairs and sort by updatedAt descending
|
// Create array of {id, updatedAt} pairs and sort by updatedAt descending
|
||||||
const docs: DocPair[] = ids.map((id: string, index: number) => ({
|
const docs: DocPair[] = ids.map((id: any, index: number) => ({
|
||||||
id,
|
id,
|
||||||
updatedAt: updatedAts[index],
|
updatedAt: updatedAts[index],
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
import { MonitorModel } from "../models/Monitor.js";
|
|
||||||
import { logger } from "@/utils/logger.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fix infrastructure monitors that had thresholds incorrectly set to 0 or 5
|
|
||||||
* due to the old default value. Updates them to 100 (disabled).
|
|
||||||
*/
|
|
||||||
export async function fixInfrastructureThresholds(): Promise<void> {
|
|
||||||
const SERVICE_NAME = "Migration:FixInfrastructureThresholds";
|
|
||||||
|
|
||||||
try {
|
|
||||||
logger.info({ service: SERVICE_NAME, message: "Starting infrastructure threshold fix" });
|
|
||||||
|
|
||||||
const thresholdFields = ["cpuAlertThreshold", "memoryAlertThreshold", "diskAlertThreshold", "tempAlertThreshold"];
|
|
||||||
|
|
||||||
// Find hardware monitors with any threshold set to 0 or 5
|
|
||||||
const filter = {
|
|
||||||
type: "hardware",
|
|
||||||
$or: thresholdFields.flatMap((field) => [{ [field]: 0 }, { [field]: 5 }]),
|
|
||||||
};
|
|
||||||
|
|
||||||
const monitors = await MonitorModel.find(filter);
|
|
||||||
|
|
||||||
if (monitors.length === 0) {
|
|
||||||
logger.info({ service: SERVICE_NAME, message: "No monitors with threshold value of 0 or 5 found" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info({
|
|
||||||
service: SERVICE_NAME,
|
|
||||||
message: `Found ${monitors.length} monitors with threshold value of 0 or 5`,
|
|
||||||
});
|
|
||||||
|
|
||||||
let updatedCount = 0;
|
|
||||||
|
|
||||||
for (const monitor of monitors) {
|
|
||||||
const update: Record<string, number> = {};
|
|
||||||
|
|
||||||
for (const field of thresholdFields) {
|
|
||||||
const value = monitor.get(field);
|
|
||||||
if (value === 0 || value === 5) {
|
|
||||||
update[field] = 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.keys(update).length > 0) {
|
|
||||||
await MonitorModel.updateOne({ _id: monitor._id }, { $set: update });
|
|
||||||
updatedCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info({
|
|
||||||
service: SERVICE_NAME,
|
|
||||||
message: `Fixed ${updatedCount} monitors — thresholds updated from 0/5 to 100`,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
||||||
logger.error({ service: SERVICE_NAME, message: `Error fixing thresholds: ${errorMessage}` });
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import mongoose from "mongoose";
|
|
||||||
import { logger } from "@/utils/logger.js";
|
|
||||||
import StatusPageModel from "../models/StatusPage.js";
|
|
||||||
|
|
||||||
export async function migrateStatusPageTypeToArray(): Promise<void> {
|
|
||||||
const SERVICE_NAME = "Migration:MigrateStatusPageTypeToArray";
|
|
||||||
|
|
||||||
try {
|
|
||||||
logger.info({ service: SERVICE_NAME, message: "Starting migration of StatusPage type field from string to array" });
|
|
||||||
|
|
||||||
const db = mongoose.connection.db;
|
|
||||||
if (!db) {
|
|
||||||
throw new Error("Database connection is not initialized");
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await StatusPageModel.updateMany(
|
|
||||||
{
|
|
||||||
$expr: {
|
|
||||||
$eq: [{ $type: "$type" }, "string"], // only scalar string, not existing array
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[
|
|
||||||
{
|
|
||||||
$set: {
|
|
||||||
type: ["$type"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.modifiedCount === 0) {
|
|
||||||
logger.info({ service: SERVICE_NAME, message: "No StatusPage documents needed migration" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info({
|
|
||||||
service: SERVICE_NAME,
|
|
||||||
message: `Migration complete. Converted ${result.modifiedCount} StatusPage document(s) from string type to array type`,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
||||||
logger.error({ service: SERVICE_NAME, message: `Error during StatusPage type migration: ${errorMessage}` });
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshold.js";
|
import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshold.js";
|
||||||
import { convertChecksToTimeSeries } from "./0002_convertChecksToTimeSeries.js";
|
import { convertChecksToTimeSeries } from "./0002_convertChecksToTimeSeries.js";
|
||||||
import { cleanupDuplicateMonitorStats } from "./0003_cleanupDuplicateMonitorStats.js";
|
import { cleanupDuplicateMonitorStats } from "./0003_cleanupDuplicateMonitorStats.js";
|
||||||
import { fixInfrastructureThresholds } from "./0004_fixInfrastructureThresholds.js";
|
|
||||||
import MigrationModel from "../models/Migration.js";
|
import MigrationModel from "../models/Migration.js";
|
||||||
import { migrateStatusPageTypeToArray } from "./0005_migrateStatusPageTypeToArray.js";
|
|
||||||
import type { ILogger } from "@/utils/logger.js";
|
|
||||||
|
|
||||||
type MigrationEntry = {
|
type MigrationEntry = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -15,11 +12,9 @@ const migrations: MigrationEntry[] = [
|
|||||||
{ name: "0001_migrateStatusWindowThreshold", execute: migrateStatusWindowThreshold },
|
{ name: "0001_migrateStatusWindowThreshold", execute: migrateStatusWindowThreshold },
|
||||||
{ name: "0002_convertChecksToTimeSeries", execute: convertChecksToTimeSeries },
|
{ name: "0002_convertChecksToTimeSeries", execute: convertChecksToTimeSeries },
|
||||||
{ name: "0003_cleanupDuplicateMonitorStats", execute: cleanupDuplicateMonitorStats },
|
{ name: "0003_cleanupDuplicateMonitorStats", execute: cleanupDuplicateMonitorStats },
|
||||||
{ name: "0004_fixInfrastructureThresholds", execute: fixInfrastructureThresholds },
|
|
||||||
{ name: "0005_migrateStatusPageTypeToArray", execute: migrateStatusPageTypeToArray },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const runMigrations = async (logger?: ILogger) => {
|
const runMigrations = async (logger?: { info: Function; error: Function }) => {
|
||||||
try {
|
try {
|
||||||
logger?.info({ message: "Running migrations", service: "Migrations" });
|
logger?.info({ message: "Running migrations", service: "Migrations" });
|
||||||
for (const migration of migrations) {
|
for (const migration of migrations) {
|
||||||
@@ -46,7 +41,7 @@ const runMigrations = async (logger?: ILogger) => {
|
|||||||
logger?.info({ message: "Migrations completed", service: "Migrations" });
|
logger?.info({ message: "Migrations completed", service: "Migrations" });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error as Error;
|
const err = error as Error;
|
||||||
logger?.error({ message: "Migration failed", service: "Migrations", details: { error: err?.message }, stack: err?.stack });
|
logger?.error({ message: "Migration failed", service: "Migrations", details: err?.message, stack: err?.stack });
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,10 +21,13 @@ type CheckMetadataDocument = Omit<CheckMetadata, "monitorId" | "teamId"> & {
|
|||||||
type: MonitorType;
|
type: MonitorType;
|
||||||
};
|
};
|
||||||
|
|
||||||
type CheckDocumentBase = Omit<Check, "id" | "metadata" | "createdAt" | "updatedAt"> & {
|
type CheckDocumentBase = Omit<Check, "id" | "metadata" | "expiry" | "ackAt" | "createdAt" | "updatedAt"> & {
|
||||||
metadata: CheckMetadataDocument;
|
metadata: CheckMetadataDocument;
|
||||||
|
expiry: Date;
|
||||||
|
ackAt?: Date | null;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
|
__v: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface CheckDocument extends CheckDocumentBase {
|
interface CheckDocument extends CheckDocumentBase {
|
||||||
@@ -222,7 +225,18 @@ const CheckSchema = new Schema<CheckDocument>(
|
|||||||
message: {
|
message: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
|
expiry: {
|
||||||
|
type: Date,
|
||||||
|
default: Date.now,
|
||||||
|
},
|
||||||
|
ack: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
ackAt: {
|
||||||
|
type: Date,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
cpu: {
|
cpu: {
|
||||||
type: cpuSchema,
|
type: cpuSchema,
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Schema, model, type Types } from "mongoose";
|
import { Schema, model, type Types } from "mongoose";
|
||||||
import type { MaintenanceWindow } from "@/types/maintenanceWindow.js";
|
import type { MaintenanceWindow, DurationUnit } from "@/types/maintenanceWindow.js";
|
||||||
|
|
||||||
type MaintenanceWindowDocumentBase = Omit<MaintenanceWindow, "id" | "monitorId" | "teamId" | "start" | "end" | "createdAt" | "updatedAt"> & {
|
type MaintenanceWindowDocumentBase = Omit<MaintenanceWindow, "id" | "monitorId" | "teamId" | "start" | "end" | "createdAt" | "updatedAt"> & {
|
||||||
monitorId: Types.ObjectId;
|
monitorId: Types.ObjectId;
|
||||||
|
|||||||
@@ -1,18 +1,6 @@
|
|||||||
import { Schema, model, Types } from "mongoose";
|
import { Schema, model, Types, type UpdateQuery } from "mongoose";
|
||||||
import type { Monitor, MonitorMatchMethod, CheckSnapshot } from "@/types/monitor.js";
|
import type { Monitor, MonitorMatchMethod, CheckSnapshot } from "@/types/monitor.js";
|
||||||
import { MonitorTypes, MonitorStatuses } from "@/types/monitor.js";
|
import { MonitorTypes, MonitorStatuses } from "@/types/monitor.js";
|
||||||
import type {
|
|
||||||
CheckAudits,
|
|
||||||
CheckCaptureInfo,
|
|
||||||
CheckCpuInfo,
|
|
||||||
CheckDiskInfo,
|
|
||||||
CheckErrorInfo,
|
|
||||||
CheckHostInfo,
|
|
||||||
CheckMemoryInfo,
|
|
||||||
CheckNetworkInterfaceInfo,
|
|
||||||
GotTimings,
|
|
||||||
ILighthouseAudit,
|
|
||||||
} from "@/types/check.js";
|
|
||||||
|
|
||||||
type CheckSnapshotDocument = Omit<CheckSnapshot, "createdAt"> & { createdAt: Date };
|
type CheckSnapshotDocument = Omit<CheckSnapshot, "createdAt"> & { createdAt: Date };
|
||||||
|
|
||||||
@@ -35,167 +23,13 @@ interface MonitorDocument extends MonitorDocumentBase {
|
|||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
const snapshotTimingPhasesSchema = new Schema<GotTimings["phases"]>(
|
|
||||||
{
|
|
||||||
wait: { type: Number },
|
|
||||||
dns: { type: Number },
|
|
||||||
tcp: { type: Number },
|
|
||||||
tls: { type: Number },
|
|
||||||
request: { type: Number },
|
|
||||||
firstByte: { type: Number },
|
|
||||||
download: { type: Number },
|
|
||||||
total: { type: Number },
|
|
||||||
},
|
|
||||||
{ _id: false }
|
|
||||||
);
|
|
||||||
|
|
||||||
const snapshotTimingsSchema = new Schema<GotTimings>(
|
|
||||||
{
|
|
||||||
start: { type: Number },
|
|
||||||
socket: { type: Number },
|
|
||||||
lookup: { type: Number },
|
|
||||||
connect: { type: Number },
|
|
||||||
secureConnect: { type: Number },
|
|
||||||
upload: { type: Number },
|
|
||||||
response: { type: Number },
|
|
||||||
end: { type: Number },
|
|
||||||
phases: { type: snapshotTimingPhasesSchema },
|
|
||||||
},
|
|
||||||
{ _id: false }
|
|
||||||
);
|
|
||||||
|
|
||||||
const snapshotCpuSchema = new Schema<CheckCpuInfo>(
|
|
||||||
{
|
|
||||||
physical_core: { type: Number },
|
|
||||||
logical_core: { type: Number },
|
|
||||||
frequency: { type: Number },
|
|
||||||
current_frequency: { type: Number },
|
|
||||||
temperature: { type: [Number] },
|
|
||||||
free_percent: { type: Number },
|
|
||||||
usage_percent: { type: Number },
|
|
||||||
},
|
|
||||||
{ _id: false }
|
|
||||||
);
|
|
||||||
|
|
||||||
const snapshotMemorySchema = new Schema<CheckMemoryInfo>(
|
|
||||||
{
|
|
||||||
total_bytes: { type: Number },
|
|
||||||
available_bytes: { type: Number },
|
|
||||||
used_bytes: { type: Number },
|
|
||||||
usage_percent: { type: Number },
|
|
||||||
},
|
|
||||||
{ _id: false }
|
|
||||||
);
|
|
||||||
|
|
||||||
const snapshotDiskSchema = new Schema<CheckDiskInfo>(
|
|
||||||
{
|
|
||||||
device: { type: String },
|
|
||||||
mountpoint: { type: String },
|
|
||||||
total_bytes: { type: Number },
|
|
||||||
free_bytes: { type: Number },
|
|
||||||
used_bytes: { type: Number },
|
|
||||||
usage_percent: { type: Number },
|
|
||||||
total_inodes: { type: Number },
|
|
||||||
free_inodes: { type: Number },
|
|
||||||
used_inodes: { type: Number },
|
|
||||||
inodes_usage_percent: { type: Number },
|
|
||||||
read_bytes: { type: Number },
|
|
||||||
write_bytes: { type: Number },
|
|
||||||
read_time: { type: Number },
|
|
||||||
write_time: { type: Number },
|
|
||||||
},
|
|
||||||
{ _id: false }
|
|
||||||
);
|
|
||||||
|
|
||||||
const snapshotHostSchema = new Schema<CheckHostInfo>(
|
|
||||||
{
|
|
||||||
os: { type: String },
|
|
||||||
platform: { type: String },
|
|
||||||
kernel_version: { type: String },
|
|
||||||
pretty_name: { type: String },
|
|
||||||
},
|
|
||||||
{ _id: false }
|
|
||||||
);
|
|
||||||
|
|
||||||
const snapshotErrorSchema = new Schema<CheckErrorInfo>(
|
|
||||||
{
|
|
||||||
metric: { type: [String] },
|
|
||||||
err: { type: String },
|
|
||||||
},
|
|
||||||
{ _id: false }
|
|
||||||
);
|
|
||||||
|
|
||||||
const snapshotCaptureSchema = new Schema<CheckCaptureInfo>(
|
|
||||||
{
|
|
||||||
version: { type: String },
|
|
||||||
mode: { type: String },
|
|
||||||
},
|
|
||||||
{ _id: false }
|
|
||||||
);
|
|
||||||
|
|
||||||
const snapshotNetworkInterfaceSchema = new Schema<CheckNetworkInterfaceInfo>(
|
|
||||||
{
|
|
||||||
name: { type: String },
|
|
||||||
bytes_sent: { type: Number },
|
|
||||||
bytes_recv: { type: Number },
|
|
||||||
packets_sent: { type: Number },
|
|
||||||
packets_recv: { type: Number },
|
|
||||||
err_in: { type: Number },
|
|
||||||
err_out: { type: Number },
|
|
||||||
drop_in: { type: Number },
|
|
||||||
drop_out: { type: Number },
|
|
||||||
fifo_in: { type: Number },
|
|
||||||
fifo_out: { type: Number },
|
|
||||||
},
|
|
||||||
{ _id: false }
|
|
||||||
);
|
|
||||||
|
|
||||||
const snapshotLighthouseAuditSchema = new Schema<ILighthouseAudit>(
|
|
||||||
{
|
|
||||||
id: { type: String },
|
|
||||||
title: { type: String },
|
|
||||||
score: { type: Number },
|
|
||||||
displayValue: { type: String },
|
|
||||||
numericValue: { type: Number },
|
|
||||||
numericUnit: { type: String },
|
|
||||||
},
|
|
||||||
{ _id: false }
|
|
||||||
);
|
|
||||||
|
|
||||||
const snapshotAuditsSchema = new Schema<CheckAudits>(
|
|
||||||
{
|
|
||||||
cls: { type: snapshotLighthouseAuditSchema },
|
|
||||||
si: { type: snapshotLighthouseAuditSchema },
|
|
||||||
fcp: { type: snapshotLighthouseAuditSchema },
|
|
||||||
lcp: { type: snapshotLighthouseAuditSchema },
|
|
||||||
tbt: { type: snapshotLighthouseAuditSchema },
|
|
||||||
},
|
|
||||||
{ _id: false }
|
|
||||||
);
|
|
||||||
|
|
||||||
const checkSnapshotSchema = new Schema<CheckSnapshotDocument>(
|
const checkSnapshotSchema = new Schema<CheckSnapshotDocument>(
|
||||||
{
|
{
|
||||||
id: { type: String, required: true },
|
id: { type: String, required: true },
|
||||||
status: { type: Boolean, required: true },
|
status: { type: Boolean, required: true },
|
||||||
responseTime: { type: Number },
|
|
||||||
timings: { type: snapshotTimingsSchema },
|
|
||||||
statusCode: { type: Number },
|
|
||||||
message: { type: String },
|
|
||||||
cpu: { type: snapshotCpuSchema },
|
|
||||||
memory: { type: snapshotMemorySchema },
|
|
||||||
disk: { type: [snapshotDiskSchema] },
|
|
||||||
host: { type: snapshotHostSchema },
|
|
||||||
errors: { type: [snapshotErrorSchema] },
|
|
||||||
capture: { type: snapshotCaptureSchema },
|
|
||||||
net: { type: [snapshotNetworkInterfaceSchema] },
|
|
||||||
accessibility: { type: Number },
|
|
||||||
bestPractices: { type: Number },
|
|
||||||
seo: { type: Number },
|
|
||||||
performance: { type: Number },
|
|
||||||
audits: { type: snapshotAuditsSchema },
|
|
||||||
createdAt: { type: Date, required: true },
|
createdAt: { type: Date, required: true },
|
||||||
},
|
},
|
||||||
{ _id: false }
|
{ _id: false, strict: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
const MonitorSchema = new Schema<MonitorDocument>(
|
const MonitorSchema = new Schema<MonitorDocument>(
|
||||||
@@ -259,6 +93,12 @@ const MonitorSchema = new Schema<MonitorDocument>(
|
|||||||
type: String,
|
type: String,
|
||||||
enum: ["equal", "include", "regex", ""],
|
enum: ["equal", "include", "regex", ""],
|
||||||
},
|
},
|
||||||
|
basicAuthUser: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
basicAuthPassword: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
url: {
|
url: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const NotificationSchema = new Schema<NotificationDocument>(
|
|||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: ["email", "slack", "discord", "webhook", "pager_duty", "matrix", "teams"] as NotificationChannel[],
|
enum: ["email", "slack", "discord", "webhook", "pager_duty", "matrix"] as NotificationChannel[],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
notificationName: {
|
notificationName: {
|
||||||
|
|||||||
@@ -43,9 +43,9 @@ const StatusPageSchema = new Schema<StatusPageDocument>(
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
type: [String],
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
default: ["uptime"],
|
default: "uptime",
|
||||||
enum: StatusPageTypes,
|
enum: StatusPageTypes,
|
||||||
},
|
},
|
||||||
companyName: {
|
companyName: {
|
||||||
@@ -100,10 +100,6 @@ const StatusPageSchema = new Schema<StatusPageDocument>(
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
showInfrastructure: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
customCSS: {
|
customCSS: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: "",
|
||||||
|
|||||||
+4
-3
@@ -9,7 +9,7 @@ import fs from "fs";
|
|||||||
import { runMigrations } from "./db/migration/index.js";
|
import { runMigrations } from "./db/migration/index.js";
|
||||||
|
|
||||||
import Logger, { ILogger } from "@/utils/logger.js";
|
import Logger, { ILogger } from "@/utils/logger.js";
|
||||||
import { SettingsService } from "@/service/index.js";
|
import SettingsService from "@/service/system/settingsService.js";
|
||||||
import { MongoSettingsRepository } from "./repositories/index.js";
|
import { MongoSettingsRepository } from "./repositories/index.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "Server";
|
const SERVICE_NAME = "Server";
|
||||||
@@ -50,8 +50,9 @@ const startApp = async () => {
|
|||||||
openApiSpec,
|
openApiSpec,
|
||||||
});
|
});
|
||||||
|
|
||||||
const server = app.listen(env.PORT, () => {
|
const port = 52345;
|
||||||
logger.info({ message: `Server started on port:${env.PORT}` });
|
const server = app.listen(port, () => {
|
||||||
|
logger.info({ message: `Server started on port:${port}` });
|
||||||
});
|
});
|
||||||
|
|
||||||
initShutdownListener(server, services);
|
initShutdownListener(server, services);
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
import type { NextFunction, Request, Response } from "express";
|
import type { NextFunction, Request, Response } from "express";
|
||||||
import { logger } from "@/utils/logger.js";
|
import { logger } from "@/utils/logger.js";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
|
||||||
|
|
||||||
const handleErrors = (error: unknown, req: Request, res: Response, _next: NextFunction) => {
|
const handleErrors = (error: any, req: Request, res: Response, next: NextFunction) => {
|
||||||
const status = error instanceof AppError ? error.status || 500 : 500;
|
const status = error.status || 500;
|
||||||
const message = error instanceof AppError ? error.message : "Server error";
|
const message = error.message || "Server error";
|
||||||
const service = error instanceof AppError ? error.service : "unknownService";
|
const service = error.service || "unknownService";
|
||||||
const method = error instanceof AppError ? error.method : "unknownMethod";
|
|
||||||
logger.error({
|
logger.error({
|
||||||
message: message,
|
message: message,
|
||||||
service: service,
|
service: service,
|
||||||
method: method,
|
method: error.method,
|
||||||
stack: error instanceof AppError ? error.stack : undefined,
|
stack: error.stack,
|
||||||
details: error instanceof AppError ? error.details : undefined,
|
|
||||||
});
|
});
|
||||||
res.status(status).json({
|
res.status(status).json({
|
||||||
status,
|
status,
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import type { Request, Response, NextFunction } from "express";
|
import type { Request, Response, NextFunction } from "express";
|
||||||
import DOMPurify from "isomorphic-dompurify";
|
import DOMPurify from "isomorphic-dompurify";
|
||||||
|
|
||||||
export function sanitizeInput(input: string, options?: object): string;
|
export const sanitizeInput = (input: any, options = {}) => {
|
||||||
export function sanitizeInput(input: unknown, options?: object): unknown;
|
|
||||||
export function sanitizeInput(input: unknown, options = {}): unknown {
|
|
||||||
if (typeof input !== "string") {
|
if (typeof input !== "string") {
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
@@ -17,9 +15,9 @@ export function sanitizeInput(input: unknown, options = {}): unknown {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return DOMPurify.sanitize(input, defaultConfig);
|
return DOMPurify.sanitize(input, defaultConfig);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const sanitizeObject = (obj: unknown, options = {}): unknown => {
|
export const sanitizeObject = (obj: Record<string, any>, options = {}): Record<string, any> => {
|
||||||
if (typeof obj !== "object" || obj === null) {
|
if (typeof obj !== "object" || obj === null) {
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
@@ -28,7 +26,7 @@ export const sanitizeObject = (obj: unknown, options = {}): unknown => {
|
|||||||
return obj.map((item) => sanitizeObject(item, options));
|
return obj.map((item) => sanitizeObject(item, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
const sanitized: Record<string, unknown> = {};
|
const sanitized: Record<string, any> = {};
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string") {
|
||||||
sanitized[key] = sanitizeInput(value, options);
|
sanitized[key] = sanitizeInput(value, options);
|
||||||
@@ -54,12 +52,7 @@ export const sanitizeBody = (options = {}): ((req: Request, res: Response, next:
|
|||||||
export const sanitizeQuery = (options = {}): ((req: Request, res: Response, next: NextFunction) => void) => {
|
export const sanitizeQuery = (options = {}): ((req: Request, res: Response, next: NextFunction) => void) => {
|
||||||
return (req: Request, res: Response, next: NextFunction) => {
|
return (req: Request, res: Response, next: NextFunction) => {
|
||||||
if (req.query && typeof req.query === "object") {
|
if (req.query && typeof req.query === "object") {
|
||||||
for (const key of Object.keys(req.query)) {
|
req.query = sanitizeObject(req.query, options);
|
||||||
const value = req.query[key];
|
|
||||||
if (typeof value === "string") {
|
|
||||||
req.query[key] = sanitizeInput(value, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,17 +2,12 @@ import { NextFunction, Request, Response } from "express";
|
|||||||
|
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import type { ISettingsService } from "@/service/system/settingsService.js";
|
import { Settings } from "@/types/settings.js";
|
||||||
import type { User } from "@/types/user.js";
|
|
||||||
|
|
||||||
const SERVICE_NAME = "verifyJWT";
|
const SERVICE_NAME = "verifyJWT";
|
||||||
const TOKEN_PREFIX = "Bearer ";
|
const TOKEN_PREFIX = "Bearer ";
|
||||||
|
|
||||||
const isUser = (payload: unknown): payload is User => {
|
export const createVerifyJWT = (settingsService: { getSettings: () => Settings }) => {
|
||||||
return typeof payload === "object" && payload !== null && "id" in payload && "teamId" in payload && "role" in payload;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createVerifyJWT = (settingsService: ISettingsService) => {
|
|
||||||
return (req: Request, res: Response, next: NextFunction) => {
|
return (req: Request, res: Response, next: NextFunction) => {
|
||||||
const token = req.headers["authorization"];
|
const token = req.headers["authorization"];
|
||||||
// Make sure a token is provided
|
// Make sure a token is provided
|
||||||
@@ -36,22 +31,21 @@ export const createVerifyJWT = (settingsService: ISettingsService) => {
|
|||||||
next(error);
|
next(error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
jwt.verify(parsedToken, jwtSecret, (err: jwt.VerifyErrors | null, decoded: jwt.JwtPayload | string | undefined) => {
|
jwt.verify(parsedToken, jwtSecret, (err: any, decoded: any) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
const error = new AppError({
|
const error = new AppError({
|
||||||
message: err instanceof Error ? err.message : "Failed to authenticate token",
|
message: "Failed to authenticate token",
|
||||||
|
details: err,
|
||||||
status: 401,
|
status: 401,
|
||||||
service: SERVICE_NAME,
|
service: SERVICE_NAME,
|
||||||
details: err instanceof Error ? { error: err } : undefined,
|
|
||||||
method: "verifyJWT",
|
method: "verifyJWT",
|
||||||
});
|
});
|
||||||
next(error);
|
next(error);
|
||||||
return;
|
return;
|
||||||
} else if (isUser(decoded)) {
|
} else {
|
||||||
|
// Token is valid, carry on
|
||||||
req.user = decoded;
|
req.user = decoded;
|
||||||
next();
|
next();
|
||||||
} else {
|
|
||||||
next(new AppError({ message: "Invalid token payload", status: 401, service: SERVICE_NAME, method: "verifyJWT" }));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,19 +20,12 @@ export interface IChecksRepository {
|
|||||||
monitorId: string,
|
monitorId: string,
|
||||||
sortOrder: string,
|
sortOrder: string,
|
||||||
dateRange: string,
|
dateRange: string,
|
||||||
filter: string | undefined,
|
filter: string,
|
||||||
page: number,
|
page: number,
|
||||||
rowsPerPage: number,
|
rowsPerPage: number,
|
||||||
status: boolean | undefined
|
status: boolean | undefined
|
||||||
): Promise<ChecksQueryResult>;
|
): Promise<ChecksQueryResult>;
|
||||||
findByTeamId(
|
findByTeamId(sortOrder: string, dateRange: string, filter: string, page: number, rowsPerPage: number, teamId: string): Promise<ChecksQueryResult>;
|
||||||
sortOrder: string,
|
|
||||||
dateRange: string,
|
|
||||||
filter: string | undefined,
|
|
||||||
page: number,
|
|
||||||
rowsPerPage: number,
|
|
||||||
teamId: string
|
|
||||||
): Promise<ChecksQueryResult>;
|
|
||||||
findLatestByMonitorIds(monitorIds: string[], options?: { limitPerMonitor?: number }): Promise<LatestChecksMap>;
|
findLatestByMonitorIds(monitorIds: string[], options?: { limitPerMonitor?: number }): Promise<LatestChecksMap>;
|
||||||
findByDateRangeAndMonitorId(
|
findByDateRangeAndMonitorId(
|
||||||
monitorId: string,
|
monitorId: string,
|
||||||
@@ -47,5 +40,4 @@ export interface IChecksRepository {
|
|||||||
deleteByMonitorId(monitorId: string): Promise<number>;
|
deleteByMonitorId(monitorId: string): Promise<number>;
|
||||||
deleteByTeamId(teamId: string): Promise<number>;
|
deleteByTeamId(teamId: string): Promise<number>;
|
||||||
deleteByMonitorIdsNotIn(monitorIds: string[]): Promise<number>;
|
deleteByMonitorIdsNotIn(monitorIds: string[]): Promise<number>;
|
||||||
deleteOlderThan(date: Date): Promise<number>;
|
|
||||||
}
|
}
|
||||||
@@ -13,10 +13,9 @@ import type {
|
|||||||
GotTimings,
|
GotTimings,
|
||||||
MonitorType,
|
MonitorType,
|
||||||
} from "@/types/index.js";
|
} from "@/types/index.js";
|
||||||
import { CheckModel, type CheckDocument } from "@/db/models/index.js";
|
import { CheckModel, MonitorModel, type CheckDocument } from "@/db/models/index.js";
|
||||||
import mongoose from "mongoose";
|
import mongoose from "mongoose";
|
||||||
import { getDateForRange } from "@/utils/dataUtils.js";
|
import { getDateForRange } from "@/utils/dataUtils.js";
|
||||||
import { ILogger } from "@/utils/logger.js";
|
|
||||||
|
|
||||||
const SERVICE_NAME = "StatusService";
|
const SERVICE_NAME = "StatusService";
|
||||||
|
|
||||||
@@ -27,8 +26,8 @@ type HardwareUpChecks = { totalChecks: number };
|
|||||||
class MongoChecksRepository implements IChecksRepository {
|
class MongoChecksRepository implements IChecksRepository {
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
|
|
||||||
private logger: ILogger;
|
private logger: any;
|
||||||
constructor(logger: ILogger) {
|
constructor(logger: any) {
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,6 +46,13 @@ class MongoChecksRepository implements IChecksRepository {
|
|||||||
return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
|
return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toOptionalDateString = (value?: Date | string | null): string | undefined => {
|
||||||
|
if (!value) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return toDateString(value);
|
||||||
|
};
|
||||||
|
|
||||||
const mapTimings = (timings?: GotTimings): GotTimings => {
|
const mapTimings = (timings?: GotTimings): GotTimings => {
|
||||||
const phases = timings?.phases ?? {
|
const phases = timings?.phases ?? {
|
||||||
wait: 0,
|
wait: 0,
|
||||||
@@ -165,6 +171,9 @@ class MongoChecksRepository implements IChecksRepository {
|
|||||||
timings: mapTimings(doc.timings),
|
timings: mapTimings(doc.timings),
|
||||||
statusCode: doc.statusCode ?? 0,
|
statusCode: doc.statusCode ?? 0,
|
||||||
message: doc.message ?? "",
|
message: doc.message ?? "",
|
||||||
|
ack: doc.ack ?? false,
|
||||||
|
ackAt: toOptionalDateString(doc.ackAt),
|
||||||
|
expiry: toDateString(doc.expiry),
|
||||||
cpu: mapCpu(doc.cpu),
|
cpu: mapCpu(doc.cpu),
|
||||||
memory: mapMemory(doc.memory),
|
memory: mapMemory(doc.memory),
|
||||||
disk: mapDisks(doc.disk),
|
disk: mapDisks(doc.disk),
|
||||||
@@ -177,6 +186,7 @@ class MongoChecksRepository implements IChecksRepository {
|
|||||||
seo: doc.seo,
|
seo: doc.seo,
|
||||||
performance: doc.performance,
|
performance: doc.performance,
|
||||||
audits: mapAudits(doc.audits),
|
audits: mapAudits(doc.audits),
|
||||||
|
__v: doc.__v ?? 0,
|
||||||
createdAt: toDateString(doc.createdAt),
|
createdAt: toDateString(doc.createdAt),
|
||||||
updatedAt: toDateString(doc.updatedAt),
|
updatedAt: toDateString(doc.updatedAt),
|
||||||
};
|
};
|
||||||
@@ -221,13 +231,13 @@ class MongoChecksRepository implements IChecksRepository {
|
|||||||
monitorId: string,
|
monitorId: string,
|
||||||
sortOrder: string,
|
sortOrder: string,
|
||||||
dateRange: string,
|
dateRange: string,
|
||||||
filter: string | undefined,
|
filter: string,
|
||||||
page: number,
|
page: number,
|
||||||
rowsPerPage: number,
|
rowsPerPage: number,
|
||||||
status: boolean | undefined
|
status: boolean | undefined
|
||||||
) => {
|
) => {
|
||||||
// Match
|
// Match
|
||||||
const matchStage: Record<string, unknown> = {
|
const matchStage: Record<string, any> = {
|
||||||
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
||||||
...(typeof status !== "undefined" && { status }),
|
...(typeof status !== "undefined" && { status }),
|
||||||
...(getDateForRange(dateRange) && {
|
...(getDateForRange(dateRange) && {
|
||||||
@@ -279,7 +289,7 @@ class MongoChecksRepository implements IChecksRepository {
|
|||||||
};
|
};
|
||||||
|
|
||||||
findByTeamId = async (sortOrder: string, dateRange: string, filter: string, page: number, rowsPerPage: number, teamId: string) => {
|
findByTeamId = async (sortOrder: string, dateRange: string, filter: string, page: number, rowsPerPage: number, teamId: string) => {
|
||||||
const matchStage: Record<string, unknown> = {
|
const matchStage: Record<string, any> = {
|
||||||
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
|
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
|
||||||
...(getDateForRange(dateRange) && {
|
...(getDateForRange(dateRange) && {
|
||||||
createdAt: {
|
createdAt: {
|
||||||
@@ -348,7 +358,7 @@ class MongoChecksRepository implements IChecksRepository {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const mapped = results.reduce<LatestChecksMap>((acc, { monitorId, docs }) => {
|
const mapped = results.reduce<LatestChecksMap>((acc, { monitorId, docs }) => {
|
||||||
acc[monitorId] = docs.map((doc: CheckDocument) => this.toEntity(doc));
|
acc[monitorId] = docs.map((doc: any) => this.toEntity(doc as CheckDocument));
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
return mapped;
|
return mapped;
|
||||||
@@ -402,31 +412,6 @@ class MongoChecksRepository implements IChecksRepository {
|
|||||||
return result.deletedCount ?? 0;
|
return result.deletedCount ?? 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteOlderThan = async (cutoffDate: Date, batchDays: number = 30): Promise<number> => {
|
|
||||||
// Find the oldest check that is older than the cutoff
|
|
||||||
const oldest = await CheckModel.findOne({ createdAt: { $lt: cutoffDate } })
|
|
||||||
.sort({ createdAt: 1 })
|
|
||||||
.select({ createdAt: 1 });
|
|
||||||
if (!oldest?.createdAt) return 0;
|
|
||||||
|
|
||||||
let totalDeleted = 0;
|
|
||||||
let batchStart = new Date(oldest.createdAt);
|
|
||||||
const batchMs = batchDays * 24 * 60 * 60 * 1000;
|
|
||||||
|
|
||||||
// Delete in 30 day chunks until we reach the cutoff
|
|
||||||
while (batchStart < cutoffDate) {
|
|
||||||
// Advance startTime by batchMs to get to the end of last batch
|
|
||||||
const nextStart = batchStart.getTime() + batchMs;
|
|
||||||
const batchEnd = new Date(Math.min(nextStart, cutoffDate.getTime()));
|
|
||||||
const result = await CheckModel.deleteMany({
|
|
||||||
createdAt: { $gte: batchStart, $lt: batchEnd },
|
|
||||||
});
|
|
||||||
totalDeleted += result.deletedCount ?? 0;
|
|
||||||
batchStart = batchEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
return totalDeleted;
|
|
||||||
};
|
|
||||||
private findUptimeDateRangeChecks = async (
|
private findUptimeDateRangeChecks = async (
|
||||||
monitorType: Exclude<MonitorType, "hardware" | "pagespeed">,
|
monitorType: Exclude<MonitorType, "hardware" | "pagespeed">,
|
||||||
monitorObjectId: mongoose.Types.ObjectId,
|
monitorObjectId: mongoose.Types.ObjectId,
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
import { IGeoChecksRepository } from "./IGeoChecksRepository.js";
|
import { IGeoChecksRepository } from "./IGeoChecksRepository.js";
|
||||||
import type { GeoCheck, GeoCheckMetadata, GeoCheckResult, GroupedGeoCheck, GeoContinent, FlatGeoCheck } from "@/types/geoCheck.js";
|
import type { GeoCheck, GeoCheckMetadata, GeoCheckResult, GroupedGeoCheck, GeoContinent, FlatGeoCheck } from "@/types/geoCheck.js";
|
||||||
import type { FlatGeoChecksQueryResult } from "./IGeoChecksRepository.js";
|
import type { GeoChecksQueryResult, FlatGeoChecksQueryResult } from "./IGeoChecksRepository.js";
|
||||||
import { GeoCheckMetadataDocument, GeoCheckModel, type GeoCheckDocument } from "@/db/models/index.js";
|
import { GeoCheckModel, type GeoCheckDocument } from "@/db/models/index.js";
|
||||||
import mongoose, { PipelineStage } from "mongoose";
|
import mongoose from "mongoose";
|
||||||
import { getDateForRange } from "@/utils/dataUtils.js";
|
import { getDateForRange } from "@/utils/dataUtils.js";
|
||||||
import { ILogger } from "@/utils/logger.js";
|
|
||||||
|
|
||||||
const SERVICE_NAME = "GeoChecksRepository";
|
const SERVICE_NAME = "GeoChecksRepository";
|
||||||
|
|
||||||
class MongoGeoChecksRepository implements IGeoChecksRepository {
|
class MongoGeoChecksRepository implements IGeoChecksRepository {
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
|
|
||||||
private logger: ILogger;
|
private logger: any;
|
||||||
constructor(logger: ILogger) {
|
constructor(logger: any) {
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,13 +30,13 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
|
|||||||
return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
|
return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapMetadata = (metadata: GeoCheckMetadataDocument): GeoCheckMetadata => ({
|
const mapMetadata = (metadata: any): GeoCheckMetadata => ({
|
||||||
monitorId: toStringId(metadata.monitorId),
|
monitorId: toStringId(metadata.monitorId),
|
||||||
teamId: toStringId(metadata.teamId),
|
teamId: toStringId(metadata.teamId),
|
||||||
type: metadata.type,
|
type: metadata.type,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapResults = (results: GeoCheckResult[]): GeoCheckResult[] => {
|
const mapResults = (results: any[]): GeoCheckResult[] => {
|
||||||
if (!results || !Array.isArray(results)) {
|
if (!results || !Array.isArray(results)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -89,12 +88,12 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
|
|||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
return docs.map((doc) => this.toEntity(doc));
|
return docs.map((doc) => this.toEntity(doc));
|
||||||
} catch (error: unknown) {
|
} catch (error: any) {
|
||||||
this.logger.error({
|
this.logger.error({
|
||||||
message: error instanceof Error ? `Failed to createGeoChecks: ${error.message}` : "Failed to createGeoChecks",
|
message: `Failed to createGeoChecks: ${error.message}`,
|
||||||
service: SERVICE_NAME,
|
service: SERVICE_NAME,
|
||||||
method: "createGeoChecks",
|
method: "createGeoChecks",
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
stack: error.stack,
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -109,7 +108,7 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
|
|||||||
continents?: GeoContinent[]
|
continents?: GeoContinent[]
|
||||||
): Promise<FlatGeoChecksQueryResult> => {
|
): Promise<FlatGeoChecksQueryResult> => {
|
||||||
try {
|
try {
|
||||||
const matchStage: Record<string, unknown> = {
|
const matchStage: Record<string, any> = {
|
||||||
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
||||||
...(getDateForRange(dateRange) && {
|
...(getDateForRange(dateRange) && {
|
||||||
createdAt: {
|
createdAt: {
|
||||||
@@ -125,8 +124,22 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
|
|||||||
skip = page * rowsPerPage;
|
skip = page * rowsPerPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (continents && continents.length > 0) {
|
||||||
|
const pipeline: any[] = [
|
||||||
|
{ $match: matchStage },
|
||||||
|
{ $unwind: "$results" },
|
||||||
|
{
|
||||||
|
$match: {
|
||||||
|
"results.location.continent": { $in: continents },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
const pipeline: any[] = [{ $match: matchStage }, { $unwind: "$results" }];
|
||||||
|
}
|
||||||
|
|
||||||
// Common pipeline stages for both paths
|
// Common pipeline stages for both paths
|
||||||
const pipeline: PipelineStage[] = [
|
const pipeline: any[] = [
|
||||||
{ $match: matchStage },
|
{ $match: matchStage },
|
||||||
{ $unwind: "$results" },
|
{ $unwind: "$results" },
|
||||||
// Filter by continent if specified
|
// Filter by continent if specified
|
||||||
@@ -160,7 +173,7 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Count pipeline
|
// Count pipeline
|
||||||
const countPipeline: PipelineStage[] = [
|
const countPipeline: any[] = [
|
||||||
{ $match: matchStage },
|
{ $match: matchStage },
|
||||||
{ $unwind: "$results" },
|
{ $unwind: "$results" },
|
||||||
...(continents && continents.length > 0
|
...(continents && continents.length > 0
|
||||||
@@ -192,12 +205,12 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
return { geoChecksCount, geoChecks };
|
return { geoChecksCount, geoChecks };
|
||||||
} catch (error: unknown) {
|
} catch (error: any) {
|
||||||
this.logger.error({
|
this.logger.error({
|
||||||
message: error instanceof Error ? `Error finding geo checks by monitor ID: ${error.message}` : "Error finding geo checks by monitor ID",
|
message: `Error finding geo checks by monitor ID: ${error.message}`,
|
||||||
service: SERVICE_NAME,
|
service: SERVICE_NAME,
|
||||||
method: "findByMonitorId",
|
method: "findByMonitorId",
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
stack: error.stack,
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -213,15 +226,11 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
|
|||||||
},
|
},
|
||||||
}).sort({ createdAt: -1 });
|
}).sort({ createdAt: -1 });
|
||||||
return docs.map(this.toEntity);
|
return docs.map(this.toEntity);
|
||||||
} catch (error: unknown) {
|
} catch (error: any) {
|
||||||
this.logger.error({
|
this.logger.error({
|
||||||
message:
|
message: `Error finding geo checks by monitor ID and date range: ${error.message}`,
|
||||||
error instanceof Error
|
|
||||||
? `Error finding geo checks by monitor ID and date range: ${error.message}`
|
|
||||||
: "Error finding geo checks by monitor ID and date range",
|
|
||||||
service: SERVICE_NAME,
|
service: SERVICE_NAME,
|
||||||
method: "findByMonitorIdAndDateRange",
|
method: "findByMonitorIdAndDateRange",
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -235,7 +244,7 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
|
|||||||
continents?: GeoContinent[]
|
continents?: GeoContinent[]
|
||||||
): Promise<GroupedGeoCheck[]> => {
|
): Promise<GroupedGeoCheck[]> => {
|
||||||
try {
|
try {
|
||||||
const pipeline: PipelineStage[] = [
|
const pipeline: any[] = [
|
||||||
// Match geo checks for this monitor in date range
|
// Match geo checks for this monitor in date range
|
||||||
{
|
{
|
||||||
$match: {
|
$match: {
|
||||||
@@ -315,12 +324,11 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
|
|||||||
|
|
||||||
const results = await GeoCheckModel.aggregate(pipeline);
|
const results = await GeoCheckModel.aggregate(pipeline);
|
||||||
return results as GroupedGeoCheck[];
|
return results as GroupedGeoCheck[];
|
||||||
} catch (error: unknown) {
|
} catch (error: any) {
|
||||||
this.logger.error({
|
this.logger.error({
|
||||||
message: error instanceof Error ? `Error finding grouped geo checks: ${error.message}` : "Error finding grouped geo checks",
|
message: `Error finding grouped geo checks: ${error.message}`,
|
||||||
service: SERVICE_NAME,
|
service: SERVICE_NAME,
|
||||||
method: "findGroupedByMonitorIdAndDateRange",
|
method: "findGroupedByMonitorIdAndDateRange",
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -332,12 +340,11 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
|
|||||||
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
||||||
});
|
});
|
||||||
return result.deletedCount || 0;
|
return result.deletedCount || 0;
|
||||||
} catch (error: unknown) {
|
} catch (error: any) {
|
||||||
this.logger.error({
|
this.logger.error({
|
||||||
message: error instanceof Error ? `Error deleting geo checks by monitor ID: ${error.message}` : "Error deleting geo checks by monitor ID",
|
message: `Error deleting geo checks by monitor ID: ${error.message}`,
|
||||||
service: SERVICE_NAME,
|
service: SERVICE_NAME,
|
||||||
method: "deleteByMonitorId",
|
method: "deleteByMonitorId",
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -349,12 +356,11 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
|
|||||||
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
|
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
|
||||||
});
|
});
|
||||||
return result.deletedCount || 0;
|
return result.deletedCount || 0;
|
||||||
} catch (error: unknown) {
|
} catch (error: any) {
|
||||||
this.logger.error({
|
this.logger.error({
|
||||||
message: error instanceof Error ? `Error deleting geo checks by team ID: ${error.message}` : "Error deleting geo checks by team ID",
|
message: `Error deleting geo checks by team ID: ${error.message}`,
|
||||||
service: SERVICE_NAME,
|
service: SERVICE_NAME,
|
||||||
method: "deleteByTeamId",
|
method: "deleteByTeamId",
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -365,12 +371,11 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
|
|||||||
const objectIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id));
|
const objectIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id));
|
||||||
const result = await GeoCheckModel.deleteMany({ "metadata.monitorId": { $nin: objectIds } });
|
const result = await GeoCheckModel.deleteMany({ "metadata.monitorId": { $nin: objectIds } });
|
||||||
return result.deletedCount || 0;
|
return result.deletedCount || 0;
|
||||||
} catch (error: unknown) {
|
} catch (error: any) {
|
||||||
this.logger.error({
|
this.logger.error({
|
||||||
message: error instanceof Error ? `Error deleting orphaned geo checks: ${error.message}` : "Error deleting orphaned geo checks",
|
message: `Error deleting orphaned geo checks: ${error.message}`,
|
||||||
service: SERVICE_NAME,
|
service: SERVICE_NAME,
|
||||||
method: "deleteByMonitorIdsNotIn",
|
method: "deleteByMonitorIdsNotIn",
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ class MongoIncidentRepository implements IIncidentsRepository {
|
|||||||
status?: boolean;
|
status?: boolean;
|
||||||
monitorId?: string;
|
monitorId?: string;
|
||||||
resolutionType?: string;
|
resolutionType?: string;
|
||||||
}): Record<string, unknown> {
|
}): Record<string, any> {
|
||||||
const matchStage: Record<string, unknown> = {
|
const matchStage: Record<string, any> = {
|
||||||
teamId: new mongoose.Types.ObjectId(teamId),
|
teamId: new mongoose.Types.ObjectId(teamId),
|
||||||
...(status !== undefined && { status }),
|
...(status !== undefined && { status }),
|
||||||
...(monitorId && { monitorId: new mongoose.Types.ObjectId(monitorId) }),
|
...(monitorId && { monitorId: new mongoose.Types.ObjectId(monitorId) }),
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ export interface IMaintenanceWindowsRepository {
|
|||||||
// fetch
|
// fetch
|
||||||
findById(id: string, teamId: string): Promise<MaintenanceWindow>;
|
findById(id: string, teamId: string): Promise<MaintenanceWindow>;
|
||||||
findByMonitorId(monitorId: string, teamId: string): Promise<MaintenanceWindow[]>;
|
findByMonitorId(monitorId: string, teamId: string): Promise<MaintenanceWindow[]>;
|
||||||
findByTeamId(teamId: string, page: number, rowsPerPage: number, field?: string, order?: string, active?: boolean): Promise<MaintenanceWindow[]>;
|
findByTeamId(teamId: string, active: boolean, page: number, rowsPerPage: number, field: string, order: string): Promise<MaintenanceWindow[]>;
|
||||||
|
|
||||||
// update
|
// update
|
||||||
updateById(id: string, teamId: string, data: Partial<MaintenanceWindow>): Promise<MaintenanceWindow>;
|
updateById(id: string, teamId: string, data: Partial<MaintenanceWindow>): Promise<MaintenanceWindow>;
|
||||||
// delete
|
// delete
|
||||||
deleteById(id: string, teamId: string): Promise<MaintenanceWindow>;
|
deleteById(id: string, teamId: string): Promise<MaintenanceWindow>;
|
||||||
// other
|
// other
|
||||||
countByTeamId(teamId: string, active?: boolean): Promise<number>;
|
countByTeamId(teamId: string, active: boolean): Promise<number>;
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { MaintenanceWindow } from "@/types/index.js";
|
import type { MaintenanceWindow } from "@/types/index.js";
|
||||||
import { type MaintenanceWindowDocument, MaintenanceWindowModel } from "@/db/models/index.js";
|
import { type MaintenanceWindowDocument, MaintenanceWindowModel } from "@/db/models/index.js";
|
||||||
import { IMaintenanceWindowsRepository } from "./IMaintenanceWindowsRepository.js";
|
import { IMaintenanceWindowsRepository } from "./IMaintenanceWindowsRepository.js";
|
||||||
import mongoose, { SortOrder } from "mongoose";
|
import mongoose from "mongoose";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
|
|
||||||
class MongoMaintenanceWindowsRepository implements IMaintenanceWindowsRepository {
|
class MongoMaintenanceWindowsRepository implements IMaintenanceWindowsRepository {
|
||||||
@@ -75,13 +75,13 @@ class MongoMaintenanceWindowsRepository implements IMaintenanceWindowsRepository
|
|||||||
|
|
||||||
findByTeamId = async (
|
findByTeamId = async (
|
||||||
teamId: string,
|
teamId: string,
|
||||||
|
active: boolean,
|
||||||
page: number,
|
page: number,
|
||||||
rowsPerPage: number,
|
rowsPerPage: number,
|
||||||
field?: string,
|
field: string,
|
||||||
order?: string,
|
order: string
|
||||||
active?: boolean
|
|
||||||
): Promise<MaintenanceWindow[]> => {
|
): Promise<MaintenanceWindow[]> => {
|
||||||
const maintenanceQuery: Record<string, unknown> = { teamId };
|
const maintenanceQuery: Record<string, any> = { teamId };
|
||||||
|
|
||||||
if (active !== undefined) maintenanceQuery.active = active;
|
if (active !== undefined) maintenanceQuery.active = active;
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ class MongoMaintenanceWindowsRepository implements IMaintenanceWindowsRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sorting
|
// Sorting
|
||||||
const sort: Record<string, SortOrder> = {};
|
const sort: Record<string, any> = {};
|
||||||
if (field !== undefined && order !== undefined) {
|
if (field !== undefined && order !== undefined) {
|
||||||
sort[field] = order === "asc" ? 1 : -1;
|
sort[field] = order === "asc" ? 1 : -1;
|
||||||
}
|
}
|
||||||
@@ -126,8 +126,8 @@ class MongoMaintenanceWindowsRepository implements IMaintenanceWindowsRepository
|
|||||||
}
|
}
|
||||||
return this.toEntity(deleted);
|
return this.toEntity(deleted);
|
||||||
};
|
};
|
||||||
countByTeamId = async (teamId: string, active?: boolean) => {
|
countByTeamId = async (teamId: string, active: boolean) => {
|
||||||
const maintenanceQuery: Record<string, unknown> = { teamId };
|
const maintenanceQuery: Record<string, any> = { teamId };
|
||||||
|
|
||||||
if (active !== undefined) maintenanceQuery.active = active;
|
if (active !== undefined) maintenanceQuery.active = active;
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ export interface IMonitorsRepository {
|
|||||||
findMonitorsSummaryByTeamId(teamId: string, config?: SummaryConfig): Promise<MonitorsSummary>;
|
findMonitorsSummaryByTeamId(teamId: string, config?: SummaryConfig): Promise<MonitorsSummary>;
|
||||||
findGroupsByTeamId(teamId: string): Promise<string[]>;
|
findGroupsByTeamId(teamId: string): Promise<string[]>;
|
||||||
removeNotificationFromMonitors(notificationId: string): Promise<void>;
|
removeNotificationFromMonitors(notificationId: string): Promise<void>;
|
||||||
updateNotifications(teamId: string, monitorIds: string[], notificationIds: string[], action: "add" | "remove" | "set"): Promise<number>;
|
|
||||||
deleteByTeamIdsNotIn(teamIds: string[]): Promise<number>;
|
deleteByTeamIdsNotIn(teamIds: string[]): Promise<number>;
|
||||||
findAllMonitorIds(): Promise<string[]>;
|
findAllMonitorIds(): Promise<string[]>;
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
import { MonitorModel } from "@/db/models/index.js";
|
import { MonitorModel } from "@/db/models/index.js";
|
||||||
import type { MonitorDocument, CheckSnapshotDocument } from "@/db/models/index.js";
|
import type { MonitorDocument, CheckSnapshotDocument } from "@/db/models/index.js";
|
||||||
import type { Monitor, MonitorsSummary, CheckSnapshot } from "@/types/index.js";
|
import type { Monitor, MonitorsSummary, CheckSnapshot, Check } from "@/types/index.js";
|
||||||
import mongoose, { type FilterQuery, type PipelineStage } from "mongoose";
|
import mongoose, { type FilterQuery, type PipelineStage } from "mongoose";
|
||||||
import type { IMonitorsRepository, TeamQueryConfig, SummaryConfig } from "./IMonitorsRepository.js";
|
import type { IMonitorsRepository, TeamQueryConfig, SummaryConfig } from "./IMonitorsRepository.js";
|
||||||
import { MongoBulkWriteError } from "mongodb";
|
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
|
|
||||||
class MongoMonitorsRepository implements IMonitorsRepository {
|
class MongoMonitorsRepository implements IMonitorsRepository {
|
||||||
@@ -21,9 +20,12 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
|||||||
try {
|
try {
|
||||||
const inserted = await MonitorModel.insertMany(payload, { ordered: false });
|
const inserted = await MonitorModel.insertMany(payload, { ordered: false });
|
||||||
return this.mapDocuments(inserted);
|
return this.mapDocuments(inserted);
|
||||||
} catch (error: unknown) {
|
} catch (error: any) {
|
||||||
if (error instanceof MongoBulkWriteError && "insertedDocs" in error && Array.isArray(error.insertedDocs) && error.insertedDocs.length > 0) {
|
if (error.name === "MongoBulkWriteError" || error.name === "BulkWriteError") {
|
||||||
return this.mapDocuments(error.insertedDocs);
|
const insertedDocs = error.insertedDocs || [];
|
||||||
|
if (insertedDocs.length > 0) {
|
||||||
|
return this.mapDocuments(insertedDocs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -44,7 +46,7 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
|||||||
};
|
};
|
||||||
|
|
||||||
findByTeamId = async (teamId: string, config: TeamQueryConfig): Promise<Monitor[] | null> => {
|
findByTeamId = async (teamId: string, config: TeamQueryConfig): Promise<Monitor[] | null> => {
|
||||||
const { page = 0, rowsPerPage = 0, filter, field = "createdAt", order = "desc", type } = config ?? {};
|
const { page = 0, rowsPerPage = 0, filter, field = "createdAt", order = "desc", type, limit } = config ?? {};
|
||||||
|
|
||||||
const query: Record<string, unknown> = {
|
const query: Record<string, unknown> = {
|
||||||
teamId: new mongoose.Types.ObjectId(teamId),
|
teamId: new mongoose.Types.ObjectId(teamId),
|
||||||
@@ -296,41 +298,6 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
|||||||
await MonitorModel.updateMany({ notifications: notificationId }, { $pull: { notifications: notificationId } });
|
await MonitorModel.updateMany({ notifications: notificationId }, { $pull: { notifications: notificationId } });
|
||||||
};
|
};
|
||||||
|
|
||||||
updateNotifications = async (
|
|
||||||
teamId: string,
|
|
||||||
monitorIds: string[],
|
|
||||||
notificationIds: string[],
|
|
||||||
action: "add" | "remove" | "set"
|
|
||||||
): Promise<number> => {
|
|
||||||
let objectIds;
|
|
||||||
let notificationObjectIds;
|
|
||||||
try {
|
|
||||||
objectIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id));
|
|
||||||
notificationObjectIds = notificationIds.map((id) => new mongoose.Types.ObjectId(id));
|
|
||||||
} catch {
|
|
||||||
throw new AppError({ message: "One or more monitor or notification IDs are invalid", status: 400 });
|
|
||||||
}
|
|
||||||
const filter = { _id: { $in: objectIds }, teamId: new mongoose.Types.ObjectId(teamId) };
|
|
||||||
|
|
||||||
let update;
|
|
||||||
switch (action) {
|
|
||||||
case "add":
|
|
||||||
update = { $addToSet: { notifications: { $each: notificationObjectIds } } };
|
|
||||||
break;
|
|
||||||
case "remove":
|
|
||||||
update = { $pull: { notifications: { $in: notificationObjectIds } } };
|
|
||||||
break;
|
|
||||||
case "set":
|
|
||||||
update = { $set: { notifications: notificationObjectIds } };
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new AppError({ message: `Invalid action: ${action}`, status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await MonitorModel.updateMany(filter, update);
|
|
||||||
return result.modifiedCount;
|
|
||||||
};
|
|
||||||
|
|
||||||
private mapDocuments = (documents: MonitorDocument[]): Monitor[] => {
|
private mapDocuments = (documents: MonitorDocument[]): Monitor[] => {
|
||||||
if (!documents?.length) {
|
if (!documents?.length) {
|
||||||
return [];
|
return [];
|
||||||
@@ -387,7 +354,7 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
|||||||
gameId: doc.gameId ?? undefined,
|
gameId: doc.gameId ?? undefined,
|
||||||
grpcServiceName: doc.grpcServiceName ?? undefined,
|
grpcServiceName: doc.grpcServiceName ?? undefined,
|
||||||
group: doc.group ?? null,
|
group: doc.group ?? null,
|
||||||
recentChecks: (doc.recentChecks ?? []).map((check: CheckSnapshotDocument) => this.toCheckSnapshot(check)),
|
recentChecks: (doc.recentChecks ?? []).map((check: any) => this.toCheckSnapshot(check)),
|
||||||
geoCheckEnabled: doc.geoCheckEnabled ?? false,
|
geoCheckEnabled: doc.geoCheckEnabled ?? false,
|
||||||
geoCheckLocations: doc.geoCheckLocations ?? [],
|
geoCheckLocations: doc.geoCheckLocations ?? [],
|
||||||
geoCheckInterval: doc.geoCheckInterval ?? 300000,
|
geoCheckInterval: doc.geoCheckInterval ?? 300000,
|
||||||
@@ -396,7 +363,7 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
private toEntityWithChecks = (doc: MonitorDocument): Monitor => {
|
private toEntityWithChecks = (doc: any): Monitor => {
|
||||||
const toStringId = (value: unknown): string => {
|
const toStringId = (value: unknown): string => {
|
||||||
if (value instanceof mongoose.Types.ObjectId) {
|
if (value instanceof mongoose.Types.ObjectId) {
|
||||||
return value.toString();
|
return value.toString();
|
||||||
@@ -446,7 +413,7 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
|||||||
gameId: doc.gameId ?? undefined,
|
gameId: doc.gameId ?? undefined,
|
||||||
grpcServiceName: doc.grpcServiceName ?? undefined,
|
grpcServiceName: doc.grpcServiceName ?? undefined,
|
||||||
group: doc.group ?? null,
|
group: doc.group ?? null,
|
||||||
recentChecks: (doc.recentChecks ?? []).map((check: CheckSnapshotDocument) => this.toCheckSnapshot(check)),
|
recentChecks: (doc.recentChecks ?? []).map((check: any) => this.toCheckSnapshot(check)),
|
||||||
geoCheckEnabled: doc.geoCheckEnabled ?? false,
|
geoCheckEnabled: doc.geoCheckEnabled ?? false,
|
||||||
geoCheckLocations: doc.geoCheckLocations ?? [],
|
geoCheckLocations: doc.geoCheckLocations ?? [],
|
||||||
geoCheckInterval: doc.geoCheckInterval ?? 300000,
|
geoCheckInterval: doc.geoCheckInterval ?? 300000,
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { Settings, SettingsUpdate } from "@/types/index.js";
|
import type { Settings } from "@/types/index.js";
|
||||||
export interface ISettingsRepository {
|
export interface ISettingsRepository {
|
||||||
// create
|
// create
|
||||||
create(settings: Partial<Settings>): Promise<Settings>;
|
create(settings: Partial<Settings>): Promise<Settings>;
|
||||||
// fetch
|
// fetch
|
||||||
findSingleton(): Promise<Settings | null>;
|
findSingleton(): Promise<Settings | null>;
|
||||||
// update
|
// update
|
||||||
update(settings: SettingsUpdate): Promise<Settings>;
|
update(settings: Partial<Settings>): Promise<Settings>;
|
||||||
// delete
|
// delete
|
||||||
deleteLegacy: () => Promise<boolean>;
|
deleteLegacy: () => Promise<boolean>;
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import mongoose, { type UpdateQuery } from "mongoose";
|
import mongoose from "mongoose";
|
||||||
import { ISettingsRepository } from "@/repositories/settings/ISettingsRepository.js";
|
import { ISettingsRepository } from "@/repositories/settings/ISettingsRepository.js";
|
||||||
import type { Settings, SettingsUpdate } from "@/types/index.js";
|
import type { Settings } from "@/types/index.js";
|
||||||
import { AppSettingsModel, type AppSettingsDocument } from "@/db/models/index.js";
|
import { AppSettingsModel, type AppSettingsDocument } from "@/db/models/index.js";
|
||||||
|
|
||||||
class MongoSettingsRepository implements ISettingsRepository {
|
class MongoSettingsRepository implements ISettingsRepository {
|
||||||
@@ -59,23 +59,27 @@ class MongoSettingsRepository implements ISettingsRepository {
|
|||||||
return this.toEntity(settings);
|
return this.toEntity(settings);
|
||||||
};
|
};
|
||||||
|
|
||||||
update = async (settings: SettingsUpdate) => {
|
update = async (settings: Partial<Settings>) => {
|
||||||
const $set: Record<string, unknown> = {};
|
const update: Record<string, any> = { $set: {}, $unset: {} };
|
||||||
const $unset: Record<string, string> = {};
|
|
||||||
|
|
||||||
// Iterate through settings and separate into $set and $unset
|
// Iterate through settings and separate into $set and $unset
|
||||||
Object.entries(settings).forEach(([key, value]) => {
|
Object.entries(settings).forEach(([key, value]) => {
|
||||||
if (value === undefined || value === null) {
|
if (value === undefined || value === "") {
|
||||||
$unset[key] = "";
|
// Unset undefined or empty string values
|
||||||
|
update.$unset[key] = "";
|
||||||
} else {
|
} else {
|
||||||
$set[key] = value;
|
// Set defined values
|
||||||
|
update.$set[key] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const update: UpdateQuery<AppSettingsDocument> = {
|
// Clean up empty operators
|
||||||
...(Object.keys($set).length > 0 && { $set }),
|
if (Object.keys(update.$set).length === 0) {
|
||||||
...(Object.keys($unset).length > 0 && { $unset }),
|
delete update.$set;
|
||||||
};
|
}
|
||||||
|
if (Object.keys(update.$unset).length === 0) {
|
||||||
|
delete update.$unset;
|
||||||
|
}
|
||||||
|
|
||||||
await AppSettingsModel.findOneAndUpdate({}, update, {
|
await AppSettingsModel.findOneAndUpdate({}, update, {
|
||||||
upsert: true,
|
upsert: true,
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ class MongoStatusPagesRepository implements IStatusPagesRepository {
|
|||||||
showCharts: doc.showCharts,
|
showCharts: doc.showCharts,
|
||||||
showUptimePercentage: doc.showUptimePercentage,
|
showUptimePercentage: doc.showUptimePercentage,
|
||||||
showAdminLoginLink: doc.showAdminLoginLink,
|
showAdminLoginLink: doc.showAdminLoginLink,
|
||||||
showInfrastructure: doc.showInfrastructure,
|
|
||||||
customCSS: doc.customCSS,
|
customCSS: doc.customCSS,
|
||||||
createdAt: this.toDateString(doc.createdAt),
|
createdAt: this.toDateString(doc.createdAt),
|
||||||
updatedAt: this.toDateString(doc.updatedAt),
|
updatedAt: this.toDateString(doc.updatedAt),
|
||||||
@@ -72,8 +71,7 @@ class MongoStatusPagesRepository implements IStatusPagesRepository {
|
|||||||
};
|
};
|
||||||
|
|
||||||
create = async (userId: string, teamId: string, image: Express.Multer.File | undefined, data: Partial<StatusPage>): Promise<StatusPage> => {
|
create = async (userId: string, teamId: string, image: Express.Multer.File | undefined, data: Partial<StatusPage>): Promise<StatusPage> => {
|
||||||
const { logo, ...restData } = data;
|
const { logo: _logo, ...restData } = data;
|
||||||
void logo;
|
|
||||||
const statusPage = new StatusPageModel({
|
const statusPage = new StatusPageModel({
|
||||||
...restData,
|
...restData,
|
||||||
userId,
|
userId,
|
||||||
@@ -111,8 +109,7 @@ class MongoStatusPagesRepository implements IStatusPagesRepository {
|
|||||||
image: Express.Multer.File | undefined,
|
image: Express.Multer.File | undefined,
|
||||||
patch: Partial<StatusPage> & { removeLogo?: string }
|
patch: Partial<StatusPage> & { removeLogo?: string }
|
||||||
): Promise<StatusPage> => {
|
): Promise<StatusPage> => {
|
||||||
const { logo, removeLogo, ...restPatch } = patch;
|
const { logo: _logo, removeLogo, ...restPatch } = patch;
|
||||||
void logo;
|
|
||||||
const updateData: StatusPageUpdateData = { ...restPatch };
|
const updateData: StatusPageUpdateData = { ...restPatch };
|
||||||
if (image) {
|
if (image) {
|
||||||
updateData.logo = {
|
updateData.logo = {
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ class MongoUsersRepository implements IUsersRepository {
|
|||||||
|
|
||||||
delete candidateUser.deleteProfileImage;
|
delete candidateUser.deleteProfileImage;
|
||||||
|
|
||||||
const updateQuery: Record<string, unknown> = { $set: candidateUser };
|
const updateQuery: Record<string, any> = { $set: candidateUser };
|
||||||
if (unsetFields) {
|
if (unsetFields) {
|
||||||
updateQuery.$unset = unsetFields;
|
updateQuery.$unset = unsetFields;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import { Router, RequestHandler } from "express";
|
import { Router, RequestHandler } from "express";
|
||||||
import { isAllowed } from "../middleware/isAllowed.js";
|
import { isAllowed } from "../middleware/isAllowed.js";
|
||||||
import multer from "multer";
|
import multer from "multer";
|
||||||
import { IAuthController } from "@/controllers/authController.js";
|
|
||||||
|
|
||||||
const upload = multer();
|
const upload = multer();
|
||||||
|
|
||||||
class AuthRoutes {
|
class AuthRoutes {
|
||||||
private router: Router;
|
private router: Router;
|
||||||
private authController: IAuthController;
|
private authController: any;
|
||||||
|
|
||||||
constructor(authController: IAuthController, verifyJWT: RequestHandler) {
|
constructor(authController: any, verifyJWT: RequestHandler) {
|
||||||
this.router = Router();
|
this.router = Router();
|
||||||
this.authController = authController;
|
this.authController = authController;
|
||||||
this.initRoutes(verifyJWT);
|
this.initRoutes(verifyJWT);
|
||||||
@@ -27,10 +26,9 @@ class AuthRoutes {
|
|||||||
|
|
||||||
this.router.get("/users", verifyJWT, isAllowed(["admin", "superadmin"]), this.authController.getAllUsers);
|
this.router.get("/users", verifyJWT, isAllowed(["admin", "superadmin"]), this.authController.getAllUsers);
|
||||||
this.router.post("/users", verifyJWT, isAllowed(["superadmin"]), upload.single("profileImage"), this.authController.createUser);
|
this.router.post("/users", verifyJWT, isAllowed(["superadmin"]), upload.single("profileImage"), this.authController.createUser);
|
||||||
this.router.get("/users/:userId", verifyJWT, isAllowed(["admin", "superadmin"]), this.authController.getUserById);
|
this.router.get("/users/:userId", verifyJWT, isAllowed(["superadmin"]), this.authController.getUserById);
|
||||||
this.router.patch("/users/:userId", verifyJWT, isAllowed(["superadmin"]), this.authController.editUserById);
|
this.router.patch("/users/:userId", verifyJWT, isAllowed(["superadmin"]), this.authController.editUserById);
|
||||||
this.router.patch("/users/:userId/password", verifyJWT, isAllowed(["superadmin"]), this.authController.editUserPasswordById);
|
this.router.patch("/users/:userId/password", verifyJWT, isAllowed(["superadmin"]), this.authController.editUserPasswordById);
|
||||||
this.router.delete("/users/:userId", verifyJWT, isAllowed(["admin", "superadmin"]), this.authController.deleteUserById);
|
|
||||||
|
|
||||||
this.router.patch("/user", verifyJWT, upload.single("profileImage"), this.authController.editUser);
|
this.router.patch("/user", verifyJWT, upload.single("profileImage"), this.authController.editUser);
|
||||||
this.router.delete("/user", verifyJWT, this.authController.deleteUser);
|
this.router.delete("/user", verifyJWT, this.authController.deleteUser);
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
|
|
||||||
import { isAllowed } from "../middleware/isAllowed.js";
|
import { isAllowed } from "../middleware/isAllowed.js";
|
||||||
import { ICheckController } from "@/controllers/checkController.js";
|
|
||||||
|
|
||||||
class CheckRoutes {
|
class CheckRoutes {
|
||||||
private router: Router;
|
private router: Router;
|
||||||
private checkController: ICheckController;
|
private checkController: any;
|
||||||
|
|
||||||
constructor(checkController: ICheckController) {
|
constructor(checkController: any) {
|
||||||
this.router = Router();
|
this.router = Router();
|
||||||
this.checkController = checkController;
|
this.checkController = checkController;
|
||||||
this.initRoutes();
|
this.initRoutes();
|
||||||
@@ -16,6 +15,7 @@ class CheckRoutes {
|
|||||||
initRoutes() {
|
initRoutes() {
|
||||||
this.router.get("/team/summary", this.checkController.getChecksSummaryByTeamId);
|
this.router.get("/team/summary", this.checkController.getChecksSummaryByTeamId);
|
||||||
this.router.get("/team", this.checkController.getChecksByTeam);
|
this.router.get("/team", this.checkController.getChecksByTeam);
|
||||||
|
this.router.put("/team/ttl", isAllowed(["admin", "superadmin"]), this.checkController.updateChecksTTL);
|
||||||
this.router.delete("/team", isAllowed(["admin", "superadmin"]), this.checkController.deleteChecksByTeamId);
|
this.router.delete("/team", isAllowed(["admin", "superadmin"]), this.checkController.deleteChecksByTeamId);
|
||||||
this.router.get("/:monitorId", this.checkController.getChecksByMonitor);
|
this.router.get("/:monitorId", this.checkController.getChecksByMonitor);
|
||||||
this.router.delete("/:monitorId", this.checkController.deleteChecks);
|
this.router.delete("/:monitorId", this.checkController.deleteChecks);
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { RequestHandler, Router } from "express";
|
import { RequestHandler, Router } from "express";
|
||||||
import { isAllowed } from "../middleware/isAllowed.js";
|
import { isAllowed } from "../middleware/isAllowed.js";
|
||||||
import { IDiagnosticController } from "@/controllers/diagnosticController.js";
|
|
||||||
|
|
||||||
class DiagnosticRoutes {
|
class DiagnosticRoutes {
|
||||||
private router: Router;
|
private router: Router;
|
||||||
private diagnosticController: IDiagnosticController;
|
private diagnosticController: any;
|
||||||
|
|
||||||
constructor(diagnosticController: IDiagnosticController, verifyJWT: RequestHandler) {
|
constructor(diagnosticController: any, verifyJWT: RequestHandler) {
|
||||||
this.router = Router();
|
this.router = Router();
|
||||||
this.diagnosticController = diagnosticController;
|
this.diagnosticController = diagnosticController;
|
||||||
this.initRoutes(verifyJWT);
|
this.initRoutes(verifyJWT);
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { IGeoCheckController } from "@/controllers/geoCheckController.js";
|
|
||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
|
|
||||||
class GeoCheckRoutes {
|
class GeoCheckRoutes {
|
||||||
private router: Router;
|
private router: Router;
|
||||||
private geoCheckController: IGeoCheckController;
|
private geoCheckController: any;
|
||||||
|
|
||||||
constructor(geoCheckController: IGeoCheckController) {
|
constructor(geoCheckController: any) {
|
||||||
this.router = Router();
|
this.router = Router();
|
||||||
this.geoCheckController = geoCheckController;
|
this.geoCheckController = geoCheckController;
|
||||||
this.initRoutes();
|
this.initRoutes();
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import { isAllowed } from "../middleware/isAllowed.js";
|
import { isAllowed } from "../middleware/isAllowed.js";
|
||||||
import { IIncidentController } from "@/controllers/incidentController.js";
|
|
||||||
|
|
||||||
class IncidentRoutes {
|
class IncidentRoutes {
|
||||||
private router: Router;
|
private router: Router;
|
||||||
private incidentController: IIncidentController;
|
private incidentController: any;
|
||||||
|
|
||||||
constructor(incidentController: IIncidentController) {
|
constructor(incidentController: any) {
|
||||||
this.router = Router();
|
this.router = Router();
|
||||||
this.incidentController = incidentController;
|
this.incidentController = incidentController;
|
||||||
this.initRoutes();
|
this.initRoutes();
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { RequestHandler, Router } from "express";
|
import { RequestHandler, Router } from "express";
|
||||||
import { isAllowed } from "../middleware/isAllowed.js";
|
import { isAllowed } from "../middleware/isAllowed.js";
|
||||||
import { IInviteController } from "@/controllers/inviteController.js";
|
|
||||||
|
|
||||||
class InviteRoutes {
|
class InviteRoutes {
|
||||||
private router: Router;
|
private router: Router;
|
||||||
private inviteController: IInviteController;
|
private inviteController: any;
|
||||||
|
|
||||||
constructor(inviteController: IInviteController, verifyJWT: RequestHandler) {
|
constructor(inviteController: any, verifyJWT: RequestHandler) {
|
||||||
this.router = Router();
|
this.router = Router();
|
||||||
this.inviteController = inviteController;
|
this.inviteController = inviteController;
|
||||||
this.initRoutes(verifyJWT);
|
this.initRoutes(verifyJWT);
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import { isAllowed } from "@/middleware/isAllowed.js";
|
import { isAllowed } from "../middleware/isAllowed.js";
|
||||||
import { ILogController } from "@/controllers/logController.js";
|
|
||||||
class LogRoutes {
|
class LogRoutes {
|
||||||
private router: Router;
|
private router: Router;
|
||||||
private logController: ILogController;
|
private logController: any;
|
||||||
|
|
||||||
constructor(logController: ILogController) {
|
constructor(logController: any) {
|
||||||
this.router = Router();
|
this.router = Router();
|
||||||
this.logController = logController;
|
this.logController = logController;
|
||||||
this.initRoutes();
|
this.initRoutes();
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { IMaintenanceWindowController } from "@/controllers/maintenanceWindowController.js";
|
|
||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
|
|
||||||
class MaintenanceWindowRoutes {
|
class MaintenanceWindowRoutes {
|
||||||
private router: Router;
|
private router: Router;
|
||||||
private mwController: IMaintenanceWindowController;
|
private mwController: any;
|
||||||
|
|
||||||
constructor(maintenanceWindowController: IMaintenanceWindowController) {
|
constructor(maintenanceWindowController: any) {
|
||||||
this.router = Router();
|
this.router = Router();
|
||||||
this.mwController = maintenanceWindowController;
|
this.mwController = maintenanceWindowController;
|
||||||
this.initRoutes();
|
this.initRoutes();
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import { isAllowed } from "@/middleware/isAllowed.js";
|
import { isAllowed } from "@/middleware/isAllowed.js";
|
||||||
import { IMonitorController } from "@/controllers/monitorController.js";
|
import multer from "multer";
|
||||||
|
import { fetchMonitorCertificate } from "@/controllers/controllerUtils.js";
|
||||||
|
const upload = multer({
|
||||||
|
storage: multer.memoryStorage(), // Store file in memory as Buffer
|
||||||
|
});
|
||||||
|
|
||||||
class MonitorRoutes {
|
class MonitorRoutes {
|
||||||
private router: Router;
|
private router: Router;
|
||||||
private monitorController: IMonitorController;
|
private monitorController: any;
|
||||||
constructor(monitorController: IMonitorController) {
|
constructor(monitorController: any) {
|
||||||
this.router = Router();
|
this.router = Router();
|
||||||
this.monitorController = monitorController;
|
this.monitorController = monitorController;
|
||||||
this.initRoutes();
|
this.initRoutes();
|
||||||
@@ -34,11 +38,10 @@ class MonitorRoutes {
|
|||||||
|
|
||||||
// Util routes
|
// Util routes
|
||||||
this.router.get("/certificate/:monitorId", (req, res, next) => {
|
this.router.get("/certificate/:monitorId", (req, res, next) => {
|
||||||
this.monitorController.getMonitorCertificate(req, res, next);
|
this.monitorController.getMonitorCertificate(req, res, next, fetchMonitorCertificate);
|
||||||
});
|
});
|
||||||
|
|
||||||
// General monitor CRUD routes
|
// General monitor CRUD routes
|
||||||
this.router.patch("/notifications", isAllowed(["admin", "superadmin"]), this.monitorController.updateNotifications);
|
|
||||||
this.router.post("/", isAllowed(["admin", "superadmin"]), this.monitorController.createMonitor);
|
this.router.post("/", isAllowed(["admin", "superadmin"]), this.monitorController.createMonitor);
|
||||||
this.router.delete("/", isAllowed(["superadmin"]), this.monitorController.deleteAllMonitors);
|
this.router.delete("/", isAllowed(["superadmin"]), this.monitorController.deleteAllMonitors);
|
||||||
|
|
||||||
@@ -47,6 +50,7 @@ class MonitorRoutes {
|
|||||||
this.router.get("/export/json", isAllowed(["admin", "superadmin"]), this.monitorController.exportMonitorsToJSON);
|
this.router.get("/export/json", isAllowed(["admin", "superadmin"]), this.monitorController.exportMonitorsToJSON);
|
||||||
this.router.post("/import/json", isAllowed(["admin", "superadmin"]), this.monitorController.importMonitorsFromJSON);
|
this.router.post("/import/json", isAllowed(["admin", "superadmin"]), this.monitorController.importMonitorsFromJSON);
|
||||||
|
|
||||||
|
this.router.post("/test-email", isAllowed(["admin", "superadmin"]), this.monitorController.sendTestEmail);
|
||||||
this.router.get("/games", this.monitorController.getAllGames);
|
this.router.get("/games", this.monitorController.getAllGames);
|
||||||
|
|
||||||
// Individual monitor CRUD routes
|
// Individual monitor CRUD routes
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { INotificationController } from "@/controllers/notificationController.js";
|
|
||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
class NotificationRoutes {
|
class NotificationRoutes {
|
||||||
private router: Router;
|
private router: Router;
|
||||||
private notificationController: INotificationController;
|
private notificationController: any;
|
||||||
|
|
||||||
constructor(notificationController: INotificationController) {
|
constructor(notificationController: any) {
|
||||||
this.router = Router();
|
this.router = Router();
|
||||||
this.notificationController = notificationController;
|
this.notificationController = notificationController;
|
||||||
this.initializeRoutes();
|
this.initializeRoutes();
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import { isAllowed } from "@/middleware/isAllowed.js";
|
import { isAllowed } from "../middleware/isAllowed.js";
|
||||||
import { IJobQueueController } from "@/controllers/queueController.js";
|
|
||||||
class QueueRoutes {
|
class QueueRoutes {
|
||||||
private router: Router;
|
private router: Router;
|
||||||
private queueController: IJobQueueController;
|
private queueController: any;
|
||||||
|
|
||||||
constructor(queueController: IJobQueueController) {
|
constructor(queueController: any) {
|
||||||
this.router = Router();
|
this.router = Router();
|
||||||
this.queueController = queueController;
|
this.queueController = queueController;
|
||||||
this.initRoutes();
|
this.initRoutes();
|
||||||
}
|
}
|
||||||
initRoutes() {
|
initRoutes() {
|
||||||
this.router.get("/jobs", isAllowed(["admin", "superadmin"]), this.queueController.getJobs);
|
this.router.get("/jobs", isAllowed(["admin", "superadmin"]), this.queueController.getJobs);
|
||||||
|
this.router.post("/jobs", isAllowed(["admin", "superadmin"]), this.queueController.addJob);
|
||||||
|
|
||||||
this.router.get("/metrics", isAllowed(["admin", "superadmin"]), this.queueController.getMetrics);
|
this.router.get("/metrics", isAllowed(["admin", "superadmin"]), this.queueController.getMetrics);
|
||||||
|
this.router.get("/health", isAllowed(["admin", "superadmin"]), this.queueController.checkQueueHealth);
|
||||||
this.router.get("/all-metrics", isAllowed(["admin", "superadmin"]), this.queueController.getAllMetrics);
|
this.router.get("/all-metrics", isAllowed(["admin", "superadmin"]), this.queueController.getAllMetrics);
|
||||||
this.router.post("/flush", isAllowed(["admin", "superadmin"]), this.queueController.flushQueue);
|
this.router.post("/flush", isAllowed(["admin", "superadmin"]), this.queueController.flushQueue);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import { isAllowed } from "../middleware/isAllowed.js";
|
import { isAllowed } from "../middleware/isAllowed.js";
|
||||||
import { ISettingsController } from "@/controllers/settingsController.js";
|
|
||||||
|
|
||||||
class SettingsRoutes {
|
class SettingsRoutes {
|
||||||
private router: Router;
|
private router: Router;
|
||||||
private settingsController: ISettingsController;
|
private settingsController: any;
|
||||||
|
|
||||||
constructor(settingsController: ISettingsController) {
|
constructor(settingsController: any) {
|
||||||
this.router = Router();
|
this.router = Router();
|
||||||
this.settingsController = settingsController;
|
this.settingsController = settingsController;
|
||||||
this.initRoutes();
|
this.initRoutes();
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { IStatusPageController } from "@/controllers/statusPageController.js";
|
|
||||||
import { RequestHandler, Router } from "express";
|
import { RequestHandler, Router } from "express";
|
||||||
import multer from "multer";
|
import multer from "multer";
|
||||||
const upload = multer();
|
const upload = multer();
|
||||||
|
|
||||||
class StatusPageRoutes {
|
class StatusPageRoutes {
|
||||||
private router: Router;
|
private router: Router;
|
||||||
private statusPageController: IStatusPageController;
|
private statusPageController: any;
|
||||||
|
|
||||||
constructor(statusPageController: IStatusPageController, verifyJWT: RequestHandler, verifyStatusPageAccess: RequestHandler) {
|
constructor(statusPageController: any, verifyJWT: RequestHandler, verifyStatusPageAccess: RequestHandler) {
|
||||||
this.router = Router();
|
this.router = Router();
|
||||||
this.statusPageController = statusPageController;
|
this.statusPageController = statusPageController;
|
||||||
this.initRoutes(verifyJWT, verifyStatusPageAccess);
|
this.initRoutes(verifyJWT, verifyStatusPageAccess);
|
||||||
|
|||||||
@@ -1,48 +1,25 @@
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { IChecksRepository, IMonitorsRepository } from "@/repositories/index.js";
|
import { IChecksRepository, IMonitorsRepository } from "@/repositories/index.js";
|
||||||
import type {
|
import type { MonitorStatusResponse, CheckErrorInfo, Check, ILighthouseAudit, ChecksQueryResult, ChecksSummary } from "@/types/index.js";
|
||||||
MonitorStatusResponse,
|
|
||||||
CheckErrorInfo,
|
|
||||||
Check,
|
|
||||||
ILighthouseAudit,
|
|
||||||
ChecksQueryResult,
|
|
||||||
ChecksSummary,
|
|
||||||
MonitorPayloadMap,
|
|
||||||
} from "@/types/index.js";
|
|
||||||
import type { HardwareStatusPayload, PageSpeedStatusPayload } from "@/types/network.js";
|
import type { HardwareStatusPayload, PageSpeedStatusPayload } from "@/types/network.js";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
|
import { ParseBoolean } from "@/utils/utils.js";
|
||||||
import { ILogger } from "@/utils/logger.js";
|
import { ILogger } from "@/utils/logger.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "checkService";
|
const SERVICE_NAME = "checkService";
|
||||||
|
|
||||||
export interface ICheckService {
|
export interface ICheckService {
|
||||||
createChecks(checks: Check[]): Promise<Check[]>;
|
createChecks(checks: Check[]): Promise<Check[]>;
|
||||||
buildCheck(statusResponse: MonitorStatusResponse<MonitorPayloadMap[keyof MonitorPayloadMap]>): Check | undefined;
|
buildCheck(statusResponse: MonitorStatusResponse<PageSpeedStatusPayload | HardwareStatusPayload | undefined>): Partial<Check> | undefined;
|
||||||
getChecksByMonitor(params: {
|
getChecksByMonitor(params: { monitorId: string; query: Record<string, string>; teamId: string }): Promise<ChecksQueryResult>;
|
||||||
monitorId: string;
|
getChecksByTeam(params: { teamId: string; query: Record<string, string> }): Promise<ChecksQueryResult>;
|
||||||
teamId: string;
|
|
||||||
sortOrder: string;
|
|
||||||
dateRange: string;
|
|
||||||
page: number;
|
|
||||||
rowsPerPage: number;
|
|
||||||
filter?: string;
|
|
||||||
status?: boolean;
|
|
||||||
}): Promise<ChecksQueryResult>;
|
|
||||||
getChecksByTeam(params: {
|
|
||||||
teamId: string;
|
|
||||||
sortOrder: string;
|
|
||||||
dateRange: string;
|
|
||||||
page: number;
|
|
||||||
rowsPerPage: number;
|
|
||||||
filter?: string;
|
|
||||||
}): Promise<ChecksQueryResult>;
|
|
||||||
getChecksSummaryByTeamId(params: { teamId: string; dateRange: string }): Promise<ChecksSummary>;
|
getChecksSummaryByTeamId(params: { teamId: string; dateRange: string }): Promise<ChecksSummary>;
|
||||||
deleteChecks(params: { monitorId: string; teamId: string }): Promise<number>;
|
deleteChecks(params: { monitorId: string; teamId: string }): Promise<number>;
|
||||||
deleteChecksByTeamId(params: { teamId: string }): Promise<number>;
|
deleteChecksByTeamId(params: { teamId: string }): Promise<number>;
|
||||||
deleteOlderThan(date: Date): Promise<number>;
|
updateChecksTTL(params: { teamId: string; ttl: string }): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CheckService implements ICheckService {
|
class CheckService implements ICheckService {
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
|
|
||||||
private monitorsRepository: IMonitorsRepository;
|
private monitorsRepository: IMonitorsRepository;
|
||||||
@@ -62,14 +39,12 @@ export class CheckService implements ICheckService {
|
|||||||
return this.checksRepository.createChecks(checks);
|
return this.checksRepository.createChecks(checks);
|
||||||
};
|
};
|
||||||
|
|
||||||
buildCheck = (statusResponse: MonitorStatusResponse<MonitorPayloadMap[keyof MonitorPayloadMap]>): Check | undefined => {
|
buildCheck = (statusResponse: MonitorStatusResponse<PageSpeedStatusPayload | HardwareStatusPayload | undefined>) => {
|
||||||
const { monitorId, teamId, type, status, responseTime, code, message, payload, timings } = statusResponse;
|
const { monitorId, teamId, type, status, responseTime, code, message, payload, timings } = statusResponse;
|
||||||
|
|
||||||
const now = new Date().toISOString();
|
const check: Partial<Check> = {
|
||||||
const check: Check = {
|
|
||||||
id: new Types.ObjectId().toString(),
|
id: new Types.ObjectId().toString(),
|
||||||
createdAt: now,
|
createdAt: new Date().toISOString(),
|
||||||
updatedAt: now,
|
|
||||||
metadata: {
|
metadata: {
|
||||||
monitorId,
|
monitorId,
|
||||||
teamId,
|
teamId,
|
||||||
@@ -137,25 +112,7 @@ export class CheckService implements ICheckService {
|
|||||||
return check;
|
return check;
|
||||||
};
|
};
|
||||||
|
|
||||||
getChecksByMonitor = async ({
|
getChecksByMonitor = async ({ monitorId, query, teamId }: { monitorId: string; query: Record<string, string>; teamId: string }) => {
|
||||||
monitorId,
|
|
||||||
teamId,
|
|
||||||
sortOrder,
|
|
||||||
dateRange,
|
|
||||||
filter,
|
|
||||||
page,
|
|
||||||
rowsPerPage,
|
|
||||||
status,
|
|
||||||
}: {
|
|
||||||
monitorId: string;
|
|
||||||
teamId: string;
|
|
||||||
sortOrder: string;
|
|
||||||
dateRange: string;
|
|
||||||
page: number;
|
|
||||||
rowsPerPage: number;
|
|
||||||
status?: boolean;
|
|
||||||
filter?: string;
|
|
||||||
}) => {
|
|
||||||
if (!monitorId) {
|
if (!monitorId) {
|
||||||
throw new AppError({ message: "No monitor ID in request", service: SERVICE_NAME, method: "getChecksByMonitor", status: 400 });
|
throw new AppError({ message: "No monitor ID in request", service: SERVICE_NAME, method: "getChecksByMonitor", status: 400 });
|
||||||
}
|
}
|
||||||
@@ -166,54 +123,71 @@ export class CheckService implements ICheckService {
|
|||||||
// For verification, throws an error if monitor doesn't belong to team
|
// For verification, throws an error if monitor doesn't belong to team
|
||||||
await this.monitorsRepository.findById(monitorId, teamId);
|
await this.monitorsRepository.findById(monitorId, teamId);
|
||||||
|
|
||||||
const parsedPage = page ?? 0;
|
const { sortOrder, dateRange, filter, page, rowsPerPage, status } = query;
|
||||||
const parsedRowsPerPage = rowsPerPage ?? 5;
|
if (!sortOrder || !dateRange || !filter || !page || !rowsPerPage) {
|
||||||
|
throw new AppError({ message: "Missing required query parameters", service: SERVICE_NAME, method: "getChecksByMonitor", status: 400 });
|
||||||
|
}
|
||||||
|
const parsedStatus = typeof status === "undefined" ? status : ParseBoolean(status);
|
||||||
|
const parsedPage = page ? parseInt(page) : 0;
|
||||||
|
const parsedRowsPerPage = rowsPerPage ? parseInt(rowsPerPage) : 5;
|
||||||
|
|
||||||
const result = await this.checksRepository.findByMonitorId(monitorId, sortOrder, dateRange, filter, parsedPage, parsedRowsPerPage, status);
|
const result = await this.checksRepository.findByMonitorId(monitorId, sortOrder, dateRange, filter, parsedPage, parsedRowsPerPage, parsedStatus);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
getChecksByTeam = async ({
|
getChecksByTeam = async ({ teamId, query }: { teamId: string; query: Record<string, string> }) => {
|
||||||
teamId,
|
const { sortOrder, dateRange, filter, page, rowsPerPage } = query;
|
||||||
sortOrder,
|
if (!sortOrder || !dateRange || !filter || !page || !rowsPerPage) {
|
||||||
dateRange,
|
throw new AppError({ message: "Missing required query parameters", service: SERVICE_NAME, method: "getChecksByTeam", status: 400 });
|
||||||
filter,
|
}
|
||||||
page,
|
|
||||||
rowsPerPage,
|
if (!teamId) {
|
||||||
}: {
|
throw new AppError({ message: "No team ID in request", service: SERVICE_NAME, method: "getChecksByTeam", status: 400 });
|
||||||
teamId: string;
|
}
|
||||||
sortOrder: string;
|
|
||||||
dateRange: string;
|
const parsedPage = page ? parseInt(page) : 0;
|
||||||
page: number;
|
const parsedRowsPerPage = rowsPerPage ? parseInt(rowsPerPage) : 5;
|
||||||
rowsPerPage: number;
|
|
||||||
filter?: string;
|
|
||||||
}) => {
|
|
||||||
const parsedPage = page ?? 0;
|
|
||||||
const parsedRowsPerPage = rowsPerPage ?? 5;
|
|
||||||
|
|
||||||
const checkData = await this.checksRepository.findByTeamId(sortOrder, dateRange, filter, parsedPage, parsedRowsPerPage, teamId);
|
const checkData = await this.checksRepository.findByTeamId(sortOrder, dateRange, filter, parsedPage, parsedRowsPerPage, teamId);
|
||||||
return checkData;
|
return checkData;
|
||||||
};
|
};
|
||||||
|
|
||||||
getChecksSummaryByTeamId = async ({ teamId, dateRange }: { teamId: string; dateRange: string }) => {
|
getChecksSummaryByTeamId = async ({ teamId, dateRange }: { teamId: string; dateRange: string }) => {
|
||||||
|
if (!teamId) {
|
||||||
|
throw new AppError({ message: "No team ID in request", service: SERVICE_NAME, method: "getChecksSummaryByTeamId", status: 400 });
|
||||||
|
}
|
||||||
const summary = await this.checksRepository.findSummaryByTeamId(teamId, dateRange);
|
const summary = await this.checksRepository.findSummaryByTeamId(teamId, dateRange);
|
||||||
return summary;
|
return summary;
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteChecks = async ({ monitorId, teamId }: { monitorId: string; teamId: string }) => {
|
deleteChecks = async ({ monitorId, teamId }: { monitorId: string; teamId: string }) => {
|
||||||
|
if (!monitorId) {
|
||||||
|
throw new AppError({ message: "No monitor ID in request", service: SERVICE_NAME, method: "deleteChecks", status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!teamId) {
|
||||||
|
throw new AppError({ message: "No team ID in request", service: SERVICE_NAME, method: "deleteChecks", status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// For verification, throws an error if monitor doesn't belong to team
|
||||||
await this.monitorsRepository.findById(monitorId, teamId);
|
await this.monitorsRepository.findById(monitorId, teamId);
|
||||||
|
|
||||||
const deletedCount = await this.checksRepository.deleteByMonitorId(monitorId);
|
const deletedCount = await this.checksRepository.deleteByMonitorId(monitorId);
|
||||||
return deletedCount;
|
return deletedCount;
|
||||||
};
|
};
|
||||||
deleteChecksByTeamId = async ({ teamId }: { teamId: string }) => {
|
deleteChecksByTeamId = async ({ teamId }: { teamId: string }) => {
|
||||||
|
if (!teamId) {
|
||||||
|
throw new AppError({ message: "No team ID in request", service: SERVICE_NAME, method: "deleteChecksByTeamId", status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
const deletedCount = await this.checksRepository.deleteByTeamId(teamId);
|
const deletedCount = await this.checksRepository.deleteByTeamId(teamId);
|
||||||
return deletedCount;
|
return deletedCount;
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteOlderThan = async (date: Date) => {
|
updateChecksTTL = async ({ teamId, ttl }: { teamId: string; ttl: string }) => {
|
||||||
const deletedCount = await this.checksRepository.deleteOlderThan(date);
|
throw new AppError({ message: "Not implemented", service: SERVICE_NAME, method: "updateChecksTTL", status: 500, details: { teamId, ttl } });
|
||||||
return deletedCount;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default CheckService;
|
||||||
@@ -3,19 +3,15 @@ import os from "os";
|
|||||||
|
|
||||||
const SERVICE_NAME = "diagnosticService";
|
const SERVICE_NAME = "diagnosticService";
|
||||||
|
|
||||||
export interface IDiagnosticService {
|
interface MemoryUsageMetrics {
|
||||||
getCPUUsage(): Promise<{ userUsageMs: number; systemUsageMs: number; usagePercentage: number }>;
|
rss: number;
|
||||||
getSystemStats(): Promise<{
|
heapTotal: number;
|
||||||
osStats: { freeMemoryBytes: number; totalMemoryBytes: number };
|
heapUsed: number;
|
||||||
memoryUsage: Record<keyof NodeJS.MemoryUsage, number>;
|
external: number;
|
||||||
cpuUsage: { userUsageMs: number; systemUsageMs: number; usagePercentage: number };
|
arrayBuffers: number;
|
||||||
v8HeapStats: { totalHeapSizeBytes: number; usedHeapSizeBytes: number; heapSizeLimitBytes: number };
|
|
||||||
eventLoopDelayMs: number;
|
|
||||||
uptimeMs: number;
|
|
||||||
}>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DiagnosticService implements IDiagnosticService {
|
class DiagnosticService {
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -24,7 +20,8 @@ export class DiagnosticService implements IDiagnosticService {
|
|||||||
* Clears performance marks after each measurement to prevent memory leaks.
|
* Clears performance marks after each measurement to prevent memory leaks.
|
||||||
*/
|
*/
|
||||||
const obs = new PerformanceObserver((items) => {
|
const obs = new PerformanceObserver((items) => {
|
||||||
items.getEntries();
|
// Get the first entry but we don't need to store it
|
||||||
|
items.getEntries()[0];
|
||||||
performance.clearMarks();
|
performance.clearMarks();
|
||||||
});
|
});
|
||||||
obs.observe({ entryTypes: ["measure"] });
|
obs.observe({ entryTypes: ["measure"] });
|
||||||
@@ -106,3 +103,5 @@ export class DiagnosticService implements IDiagnosticService {
|
|||||||
return diagnostics;
|
return diagnostics;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default DiagnosticService;
|
||||||
Loaded 100 of 157 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user