diff --git a/.coderabbit.yml b/.coderabbit.yml
new file mode 100644
index 000000000..61b2e6361
--- /dev/null
+++ b/.coderabbit.yml
@@ -0,0 +1 @@
+release_notes: false
diff --git a/.github/scripts/download-translations.js b/.github/scripts/download-translations.js
index 7a8125ab8..185992fd5 100644
--- a/.github/scripts/download-translations.js
+++ b/.github/scripts/download-translations.js
@@ -7,7 +7,7 @@ import { URLSearchParams } from "url";
const API_TOKEN = process.env.POEDITOR_API;
const PROJECT_ID = process.env.POEDITOR_PROJECT_ID;
const LANGUAGES = (
- process.env.LANGUAGES || "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja"
+ process.env.LANGUAGES || "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn"
).split(",");
const EXPORT_FORMAT = process.env.EXPORT_FORMAT || "key_value_json";
diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml
index d3f34635c..056e2c586 100644
--- a/.github/workflows/check-format.yml
+++ b/.github/workflows/check-format.yml
@@ -16,7 +16,7 @@ jobs:
- name: Install client dependencies
working-directory: client
- run: npm ci
+ run: npm install
- name: Check client formatting
working-directory: client
@@ -34,7 +34,7 @@ jobs:
- name: Install server dependencies
working-directory: server
- run: npm ci
+ run: npm install
- name: Check server formatting
working-directory: server
diff --git a/.github/workflows/deploy-images-on-release.yml b/.github/workflows/deploy-images-on-release.yml
index cf06f7840..a9b2dfdb8 100644
--- a/.github/workflows/deploy-images-on-release.yml
+++ b/.github/workflows/deploy-images-on-release.yml
@@ -79,18 +79,6 @@ jobs:
run: |
docker push ghcr.io/bluewave-labs/checkmate-mongo:${{ steps.extract_tag.outputs.version }}
- - name: Build Redis Docker image
- run: |
- docker build \
- -t ghcr.io/bluewave-labs/checkmate-redis:${{ steps.extract_tag.outputs.version }} \
- -f ./docker/dist/redis.Dockerfile \
- --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \
- .
-
- - name: Push Redis Docker image
- run: |
- docker push ghcr.io/bluewave-labs/checkmate-redis:${{ steps.extract_tag.outputs.version }}
-
docker-build-and-push-server-mono-multiarch:
runs-on: ubuntu-latest
steps:
diff --git a/.github/workflows/deploy-images.yml b/.github/workflows/deploy-images.yml
index fa39fed85..53d7ab368 100644
--- a/.github/workflows/deploy-images.yml
+++ b/.github/workflows/deploy-images.yml
@@ -74,18 +74,6 @@ jobs:
run: |
docker push ghcr.io/bluewave-labs/checkmate-mongo:latest
- - name: Build Redis Docker image
- run: |
- docker build \
- -t ghcr.io/bluewave-labs/checkmate-redis:latest \
- -f ./docker/dist/redis.Dockerfile \
- --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \
- .
-
- - name: Push Redis Docker image
- run: |
- docker push ghcr.io/bluewave-labs/checkmate-redis:latest
-
docker-build-and-push-server-mono-multiarch:
runs-on: ubuntu-latest
steps:
diff --git a/.github/workflows/poeditor-sync.yml b/.github/workflows/poeditor-sync.yml
index a8b330290..87396d3d0 100644
--- a/.github/workflows/poeditor-sync.yml
+++ b/.github/workflows/poeditor-sync.yml
@@ -7,7 +7,7 @@ on:
languages:
description: "Languages to synchronize (comma separated, e.g.: tr,en,es)"
required: false
- default: "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja"
+ default: "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn"
format:
description: "Export format (key_value_json or json)"
required: false
diff --git a/.github/workflows/production-deploy.yml b/.github/workflows/production-deploy.yml
index 7764c488b..532197710 100644
--- a/.github/workflows/production-deploy.yml
+++ b/.github/workflows/production-deploy.yml
@@ -71,17 +71,6 @@ jobs:
- name: Push MongoDB Docker image
run: docker push ghcr.io/bluewave-labs/checkmate:mongo-demo
- - name: Build Redis Docker image
- run: |
- docker build \
- -t ghcr.io/bluewave-labs/checkmate:redis-demo \
- -f ./docker/prod/redis.Dockerfile \
- --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \
- .
-
- - name: Push Redis Docker image
- run: docker push ghcr.io/bluewave-labs/checkmate:redis-demo
-
deploy-to-demo:
needs: docker-build-and-push-server
runs-on: ubuntu-latest
diff --git a/.github/workflows/staging-deploy.yml b/.github/workflows/staging-deploy.yml
index ea5477815..27b7e8690 100644
--- a/.github/workflows/staging-deploy.yml
+++ b/.github/workflows/staging-deploy.yml
@@ -71,17 +71,6 @@ jobs:
- name: Push MongoDB Docker image
run: docker push ghcr.io/bluewave-labs/checkmate:mongo-staging
- - name: Build Redis Docker image
- run: |
- docker build \
- -t ghcr.io/bluewave-labs/checkmate:redis-staging \
- -f ./docker/staging/redis.Dockerfile \
- --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \
- .
-
- - name: Push Redis Docker image
- run: docker push ghcr.io/bluewave-labs/checkmate:redis-staging
-
deploy-to-staging:
needs: docker-build-and-push-server
runs-on: ubuntu-latest
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..600d2d33b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.vscode
\ No newline at end of file
diff --git a/README.md b/README.md
index cc1b10e0e..048cf0706 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
This repository contains both the frontend and the backend of Checkmate, an open-source, self-hosted monitoring tool for tracking server hardware, uptime, response times, and incidents in real-time with beautiful visualizations. Checkmate regularly checks whether a server/website is accessible and performs optimally, providing real-time alerts and reports on the monitored services' availability, downtime, and response time.
-Checkmate also has an agent, called [Capture](https://github.com/bluewave-labs/capture), to retrieve data from remote servers. While Capture is not required to run Checkmate, it provides additional insights about your servers' CPU, RAM, disk, and temperature status.
+Checkmate also has an agent, called [Capture](https://github.com/bluewave-labs/capture), to retrieve data from remote servers. While Capture is not required to run Checkmate, it provides additional insights about your servers' CPU, RAM, disk, and temperature status. Capture can run on Linux, Windows, Mac, Raspberry Pi, or any device that can run Go.
Checkmate has been stress-tested with 1000+ active monitors without any particular issues or performance bottlenecks.
@@ -54,6 +54,12 @@ See installation instructions in [Checkmate documentation portal](https://docs.c
Alternatively, you can also use [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md) 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.
+### Using a Custom CA
+
+If you need to monitor internal HTTPS endpoints with certificates from private Certificate Authorities (like Smallstep), see our [Custom CA Trust Guide](./docs/custom-ca-trust.md) for Docker configuration options.
+
+For more documentation, see the [docs directory](./docs/).
+
## Translations
@@ -84,20 +90,22 @@ Feel free to ask questions or share your ideas - we'd love to hear from you!
- Completely open source, deployable on your servers or home devices (e.g Raspberry Pi 4 or 5)
- Website monitoring
- Page speed monitoring
-- Infrastructure monitoring (memory, disk usage, CPU performance etc) - requires [Capture](https://github.com/bluewave-labs/capture) agent
+- Infrastructure monitoring (memory, disk usage, CPU performance, network etc) - requires [Capture](https://github.com/bluewave-labs/capture) agent
- Docker monitoring
- Ping monitoring
- SSL monitoring
- Port monitoring
+- Game server monitoring (3.0)
- Incidents at a glance
- Status pages
-- E-mail, Webhooks, Discord, Telegram, Slack notifications
+- E-mail, Webhooks, Discord and Slack notifications
- Scheduled maintenance
- JSON query monitoring
- Multi-language support for English, German, Japanese, Portuguese (Brazil), Russian, Turkish, Ukrainian, Vietnamese, Chinese (Traditional, Taiwan)
**Short term roadmap:**
+- Plugins that will help Checkmate get any information from a remote service (e.g database, etc)
- Better notifications
- Network monitoring
- ..and a few more features
@@ -139,7 +147,7 @@ If you would like to sponsor an additional feature, [see this page](https://chec
## Contributing
-We are [Alex](http://github.com/ajhollid) (team lead), [Mohadeseh](http://github.com/mohicody), [Gorkem](http://github.com/gorkem-bwl/), [Owaise](http://github.com/Owaiseimdad), [Aryaman](https://github.com/Br0wnHammer), [Mert](https://github.com/mertssmnoglu) and [Karen](https://github.com/karenvicent) helping individuals and businesses monitor their infra and servers.
+We are [Alex](http://github.com/ajhollid) (team lead), [Gorkem](http://github.com/gorkem-bwl/), [Owaise](http://github.com/Owaiseimdad), [Aryaman](https://github.com/Br0wnHammer), [Mert](https://github.com/mertssmnoglu) and [Karen](https://github.com/karenvicent) helping individuals and businesses monitor their infra and servers.
We pride ourselves on building strong connections with contributors at every level. Despite being a young project, Checkmate has already earned 7000+ stars and attracted 90+ contributors from around the globe.
diff --git a/client/package-lock.json b/client/package-lock.json
index f58f56f44..32446c5d0 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -21,7 +21,7 @@
"dayjs": "1.11.13",
"flag-icons": "7.3.2",
"html2canvas": "^1.4.1",
- "i18next": "^24.2.2",
+ "i18next": "25.4.2",
"joi": "17.13.3",
"mui-color-input": "^6.0.0",
"react": "18.3.1",
@@ -46,9 +46,6 @@
"eslint-plugin-react-refresh": "^0.4.6",
"prettier": "^3.3.3",
"vite": "6.3.5"
- },
- "optionalDependencies": {
- "@rollup/rollup-linux-arm64-musl": "4.41.0"
}
},
"node_modules/@ampproject/remapping": {
@@ -283,9 +280,9 @@
}
},
"node_modules/@babel/runtime": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz",
- "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==",
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz",
+ "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -491,262 +488,6 @@
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
"license": "MIT"
},
- "node_modules/@esbuild/aix-ppc64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
- "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
- "cpu": [
- "ppc64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
- "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz",
- "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-x64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz",
- "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
- "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz",
- "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz",
- "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz",
- "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
- "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz",
- "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz",
- "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
- "cpu": [
- "ia32"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
- "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
- "cpu": [
- "loong64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz",
- "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
- "cpu": [
- "mips64el"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz",
- "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
- "cpu": [
- "ppc64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz",
- "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
- "cpu": [
- "riscv64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
- "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
- "cpu": [
- "s390x"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
"node_modules/@esbuild/linux-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
@@ -763,134 +504,6 @@
"node": ">=18"
}
},
- "node_modules/@esbuild/netbsd-arm64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz",
- "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz",
- "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-arm64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz",
- "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz",
- "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz",
- "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz",
- "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
- "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
- "cpu": [
- "ia32"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-x64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz",
- "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
@@ -2063,123 +1676,6 @@
}
}
},
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz",
- "integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz",
- "integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz",
- "integrity": "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz",
- "integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz",
- "integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz",
- "integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz",
- "integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz",
- "integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz",
- "integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz",
@@ -2193,71 +1689,6 @@
"linux"
]
},
- "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz",
- "integrity": "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==",
- "cpu": [
- "loong64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz",
- "integrity": "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==",
- "cpu": [
- "ppc64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz",
- "integrity": "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==",
- "cpu": [
- "riscv64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz",
- "integrity": "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==",
- "cpu": [
- "riscv64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz",
- "integrity": "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==",
- "cpu": [
- "s390x"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz",
@@ -2284,45 +1715,6 @@
"linux"
]
},
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz",
- "integrity": "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz",
- "integrity": "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==",
- "cpu": [
- "ia32"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz",
- "integrity": "sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
"node_modules/@sideway/address": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
@@ -4342,14 +3734,15 @@
}
},
"node_modules/form-data": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
- "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+ "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
@@ -4363,20 +3756,6 @@
"dev": true,
"license": "ISC"
},
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -4690,9 +4069,9 @@
}
},
"node_modules/i18next": {
- "version": "24.2.3",
- "resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.3.tgz",
- "integrity": "sha512-lfbf80OzkocvX7nmZtu7nSTNbrTYR52sLWxPtlXX1zAhVw8WEnFk4puUkCR4B1dNQwbSpEHHHemcZu//7EcB7A==",
+ "version": "25.4.2",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.4.2.tgz",
+ "integrity": "sha512-gD4T25a6ovNXsfXY1TwHXXXLnD/K2t99jyYMCSimSCBnBRJVQr5j+VAaU83RJCPzrTGhVQ6dqIga66xO2rtd5g==",
"funding": [
{
"type": "individual",
@@ -4709,7 +4088,7 @@
],
"license": "MIT",
"dependencies": {
- "@babel/runtime": "^7.26.10"
+ "@babel/runtime": "^7.27.6"
},
"peerDependencies": {
"typescript": "^5"
diff --git a/client/package.json b/client/package.json
index 611ba5093..92da6fd13 100644
--- a/client/package.json
+++ b/client/package.json
@@ -26,7 +26,7 @@
"dayjs": "1.11.13",
"flag-icons": "7.3.2",
"html2canvas": "^1.4.1",
- "i18next": "^24.2.2",
+ "i18next": "25.4.2",
"joi": "17.13.3",
"mui-color-input": "^6.0.0",
"react": "18.3.1",
@@ -59,8 +59,5 @@
"eslint-plugin-react-refresh": "^0.4.6",
"prettier": "^3.3.3",
"vite": "6.3.5"
- },
- "optionalDependencies": {
- "@rollup/rollup-linux-arm64-musl": "4.41.0"
}
}
diff --git a/client/src/Components/Charts/StatusPageBarChart/index.jsx b/client/src/Components/Charts/StatusPageBarChart/index.jsx
index e912934a0..eab0c7d1d 100644
--- a/client/src/Components/Charts/StatusPageBarChart/index.jsx
+++ b/client/src/Components/Charts/StatusPageBarChart/index.jsx
@@ -1,7 +1,7 @@
import { useTheme } from "@emotion/react";
import { Box, Stack, Tooltip, Typography } from "@mui/material";
import { formatDateWithTz } from "../../../Utils/timeUtils";
-import { useEffect, useState } from "react";
+import { useEffect, useState, forwardRef } from "react";
import { useSelector } from "react-redux";
import PropTypes from "prop-types";
@@ -17,26 +17,32 @@ import PropTypes from "prop-types";
* @returns {JSX.Element} The Bar component.
*/
-const Bar = ({ width, height, backgroundColor, borderRadius, children }) => {
- const theme = useTheme();
+const Bar = forwardRef(
+ ({ width, height, backgroundColor, borderRadius, children, ...otherProps }, ref) => {
+ const theme = useTheme();
- return (
-
- {children}
-
- );
-};
+ return (
+
+ {children}
+
+ );
+ }
+);
+
+Bar.displayName = "Bar";
Bar.propTypes = {
- width: PropTypes.string.isRequired,
+ width: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
height: PropTypes.string.isRequired,
backgroundColor: PropTypes.string.isRequired,
borderRadius: PropTypes.string,
diff --git a/client/src/Components/Charts/Utils/chartUtilFunctions.js b/client/src/Components/Charts/Utils/chartUtilFunctions.js
index d8ec6cdb6..2851af93e 100644
--- a/client/src/Components/Charts/Utils/chartUtilFunctions.js
+++ b/client/src/Components/Charts/Utils/chartUtilFunctions.js
@@ -1,5 +1,6 @@
export const tooltipDateFormatLookup = (dateRange) => {
const dateFormatLookup = {
+ recent: "ddd. MMMM D, YYYY, hh:mm A",
day: "ddd. MMMM D, YYYY, hh:mm A",
week: "ddd. MMMM D, YYYY, hh:mm A",
month: "ddd. MMMM D, YYYY",
diff --git a/client/src/Components/Charts/Utils/chartUtils.jsx b/client/src/Components/Charts/Utils/chartUtils.jsx
index 414e1ff9c..e24881c1c 100644
--- a/client/src/Components/Charts/Utils/chartUtils.jsx
+++ b/client/src/Components/Charts/Utils/chartUtils.jsx
@@ -88,7 +88,51 @@ PercentTick.propTypes = {
*/
const getFormattedPercentage = (value) => {
if (typeof value !== "number") return value;
- return `${(value * 100).toFixed(2)}.%`;
+ return `${(value * 100).toFixed(2)}%`;
+};
+
+/**
+ * Custom tick component for rendering network bytes per second.
+ *
+ * @param {Object} props - The properties object.
+ * @param {number} props.x - The x-coordinate for the tick.
+ * @param {number} props.y - The y-coordinate for the tick.
+ * @param {Object} props.payload - The payload object containing tick data.
+ * @param {number} props.index - The index of the tick.
+ * @returns {JSX.Element|null} The rendered tick component or null for the first tick.
+ */
+export const NetworkTick = ({ x, y, payload, index, formatter }) => {
+ const theme = useTheme();
+ if (index === 0) return null;
+
+ if (formatter === undefined) {
+ formatter = (value, space = false) => {
+ if (typeof value !== "number") return value;
+ // need to add space between value and unit
+ return `${(value / 1024).toFixed(1)}${space ? " " : ""}Kbps`;
+ };
+ }
+
+ return (
+
+ {formatter(payload?.value, true)}
+
+ );
+};
+
+NetworkTick.propTypes = {
+ x: PropTypes.number,
+ y: PropTypes.number,
+ payload: PropTypes.object,
+ index: PropTypes.number,
+ formatter: PropTypes.func,
};
/**
@@ -112,6 +156,7 @@ export const InfrastructureTooltip = ({
yLabel,
dotColor,
dateRange,
+ formatter = getFormattedPercentage,
}) => {
const uiTimezone = useSelector((state) => state.ui.timezone);
const theme = useTheme();
@@ -166,8 +211,8 @@ export const InfrastructureTooltip = ({
sx={{ opacity: 0.8 }}
>
{yIdx >= 0
- ? `${yLabel} ${getFormattedPercentage(payload[0].payload[hardwareType][yIdx][metric])}`
- : `${yLabel} ${getFormattedPercentage(payload[0].payload[yKey])}`}
+ ? `${yLabel} ${formatter(payload[0].payload[hardwareType][yIdx][metric])}`
+ : `${yLabel} ${formatter(payload[0].payload[yKey])}`}
diff --git a/client/src/Components/Check/Check.jsx b/client/src/Components/Check/Check.jsx
index 7f37a4dc1..02b9fd78a 100644
--- a/client/src/Components/Check/Check.jsx
+++ b/client/src/Components/Check/Check.jsx
@@ -65,7 +65,7 @@ const Check = ({ text, noHighlightText, variant = "info", outlined = false }) =>
};
Check.propTypes = {
- text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
+ text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
noHighlightText: PropTypes.string,
variant: PropTypes.oneOf(["info", "error", "success"]),
outlined: PropTypes.bool,
diff --git a/client/src/Components/Dialog/genericDialog.jsx b/client/src/Components/Dialog/genericDialog.jsx
index b2cbe1299..a13eba849 100644
--- a/client/src/Components/Dialog/genericDialog.jsx
+++ b/client/src/Components/Dialog/genericDialog.jsx
@@ -2,7 +2,7 @@ import { useId } from "react";
import PropTypes from "prop-types";
import { Modal, Stack, Typography } from "@mui/material";
-const GenericDialog = ({ title, description, open, onClose, theme, children }) => {
+const GenericDialog = ({ title, description, open, onClose, theme, children, width }) => {
const titleId = useId();
const descriptionId = useId();
const ariaDescribedBy = description?.length > 0 ? descriptionId : "";
@@ -16,6 +16,7 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) =
>
{title}
@@ -46,6 +48,7 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) =
{description}
@@ -64,6 +67,7 @@ GenericDialog.propTypes = {
theme: PropTypes.object.isRequired,
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node])
.isRequired,
+ width: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
};
export { GenericDialog };
diff --git a/client/src/Components/Fallback/FallbackBackground.jsx b/client/src/Components/Fallback/FallbackBackground.jsx
index 00ecafb4e..fe46f4064 100644
--- a/client/src/Components/Fallback/FallbackBackground.jsx
+++ b/client/src/Components/Fallback/FallbackBackground.jsx
@@ -1,7 +1,6 @@
import { useTheme } from "@emotion/react";
import Box from "@mui/material/Box";
import Background from "../../assets/Images/background-grid.svg?react";
-import SkeletonDark from "../../assets/Images/create-placeholder-dark.svg?react";
import OutputAnimation from "../../assets/Animations/output.gif";
import DarkmodeOutput from "../../assets/Animations/darkmodeOutput.gif";
import { useSelector } from "react-redux";
@@ -13,7 +12,7 @@ const FallbackBackground = () => {
{
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const isAdmin = useIsAdmin();
+ const isEmpty = items === null || (Array.isArray(items) && items.length === 0);
+ const hasInitialized = items !== undefined && !(isLoading && isEmpty);
+ const showEmpty = !isLoading && isEmpty;
+ if (networkError) {
+ return (
+
+
+ {t("common.toasts.networkError")}
+
+ {t("common.toasts.checkConnection")}
+
+ );
+ }
+ if (!hasInitialized) {
+ return null;
+ }
+ if (showEmpty) {
+ return (
+
+ {fallbackChildren}
+
+ );
+ }
+ return <>{children}>;
+};
+
+PageStateWrapper.propTypes = {
+ networkError: PropTypes.any,
+ isLoading: PropTypes.bool,
+ items: PropTypes.oneOfType([PropTypes.array, PropTypes.oneOf([null])]),
+ type: PropTypes.string,
+ fallbackLink: PropTypes.string,
+ fallbackChildren: PropTypes.node,
+ children: PropTypes.node,
+};
+
+export default PageStateWrapper;
diff --git a/client/src/Components/StarPrompt/index.jsx b/client/src/Components/StarPrompt/index.jsx
index 64fce1d3f..f5f130387 100644
--- a/client/src/Components/StarPrompt/index.jsx
+++ b/client/src/Components/StarPrompt/index.jsx
@@ -74,7 +74,7 @@ const StarPrompt = ({ repoUrl = "https://github.com/bluewave-labs/checkmate" })
{},
},
}) => {
@@ -101,24 +103,54 @@ const DataTable = ({
data.map((row) => {
const key = row.id || row._id || Math.random();
return (
- config.onRowClick(row) : null}
+ followCursor
+ enterDelay={500}
+ enterNextDelay={500}
+ title={
+ typeof config.tooltipContent === "function"
+ ? config.tooltipContent(row)
+ : config.tooltipContent
+ }
+ slotProps={{
+ tooltip: {
+ sx: {
+ background: "unset",
+ },
+ },
+ popper: {
+ modifiers: [
+ {
+ name: "offset",
+ options: {
+ offset: ({ popper }) => {
+ return [popper.width / 2 + 20, -popper.height / 8];
+ },
+ },
+ },
+ ],
+ },
+ }}
>
- {headers.map((header, index) => {
- return (
- header.onClick(e, row) : null}
- sx={header.getCellSx ? header.getCellSx(row) : {}}
- >
- {header.render(row)}
-
- );
- })}
-
+ config.onRowClick(row) : null}
+ >
+ {headers.map((header, index) => {
+ return (
+ header.onClick(e, row) : null}
+ sx={header.getCellSx ? header.getCellSx(row) : {}}
+ >
+ {header.render(row)}
+
+ );
+ })}
+
+
);
})
)}
diff --git a/client/src/Hooks/inviteHooks.js b/client/src/Hooks/inviteHooks.js
index 705906efa..fb577272a 100644
--- a/client/src/Hooks/inviteHooks.js
+++ b/client/src/Hooks/inviteHooks.js
@@ -14,25 +14,25 @@ const useGetInviteToken = () => {
const clearToken = () => {
setToken(undefined);
};
-
+ const fetchToken = async (email, role) => {
+ const response = await networkService.requestInvitationToken({ email, role });
+ const token = response?.data?.data?.token;
+ if (typeof token === "undefined") {
+ throw new Error(t("inviteNoTokenFound"));
+ }
+ return token;
+ };
const getInviteToken = async ({ email, role }) => {
try {
- const response = await networkService.requestInvitationToken({
- email,
- role,
- });
- const token = response?.data?.data?.token;
- if (typeof token === "undefined") {
- throw new Error(t("inviteNoTokenFound"));
- }
-
+ setIsLoading(true);
+ const token = await fetchToken(email, role);
let inviteLink = token;
if (typeof CLIENT_HOST !== "undefined") {
inviteLink = `${CLIENT_HOST}/register/${token}`;
}
-
setToken(inviteLink);
+ return token;
} catch (error) {
setError(error);
} finally {
@@ -40,7 +40,26 @@ const useGetInviteToken = () => {
}
};
- return [getInviteToken, clearToken, isLoading, error, token];
+ const addTeamMember = async (formData, role) => {
+ try {
+ setIsLoading(true);
+ const token = await fetchToken(formData.email, role);
+ const toSubmit = {
+ ...formData,
+ inviteToken: token,
+ };
+ delete toSubmit.confirm;
+ const responseRegister = await networkService.registerUser(toSubmit);
+ return responseRegister;
+ } catch (error) {
+ setError(error);
+ throw error;
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return [getInviteToken, clearToken, isLoading, error, token, addTeamMember];
};
export { useGetInviteToken };
diff --git a/client/src/Hooks/logHooks.js b/client/src/Hooks/logHooks.js
index b954efb4b..989870eea 100644
--- a/client/src/Hooks/logHooks.js
+++ b/client/src/Hooks/logHooks.js
@@ -15,9 +15,6 @@ const useFetchLogs = () => {
setIsLoading(true);
const response = await networkService.getLogs();
setLogs(response.data.data);
- createToast({
- body: t("logsPage.toast.fetchLogsSuccess"),
- });
} catch (error) {
setError(error);
createToast({
diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js
index 970b5d97f..eb1e7acdc 100644
--- a/client/src/Hooks/monitorHooks.js
+++ b/client/src/Hooks/monitorHooks.js
@@ -369,6 +369,8 @@ const useUpdateMonitor = () => {
setIsLoading(true);
const updatedFields = {
name: monitor.name,
+ statusWindowSize: monitor.statusWindowSize,
+ statusWindowThreshold: monitor.statusWindowThreshold,
description: monitor.description,
interval: monitor.interval,
notifications: monitor.notifications,
diff --git a/client/src/Hooks/useNotifications.js b/client/src/Hooks/useNotifications.js
index 7feebd44c..539725958 100644
--- a/client/src/Hooks/useNotifications.js
+++ b/client/src/Hooks/useNotifications.js
@@ -34,7 +34,7 @@ const useCreateNotification = () => {
const useGetNotificationsByTeamId = (updateTrigger) => {
const [notifications, setNotifications] = useState([]);
- const [isLoading, setIsLoading] = useState(false);
+ const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
const { t } = useTranslation();
diff --git a/client/src/Pages/Account/components/AddMemberMenu/index.jsx b/client/src/Pages/Account/components/AddMemberMenu/index.jsx
new file mode 100644
index 000000000..ab1ba599d
--- /dev/null
+++ b/client/src/Pages/Account/components/AddMemberMenu/index.jsx
@@ -0,0 +1,68 @@
+import { useState } from "react";
+import Button from "@mui/material/Button";
+import Menu from "@mui/material/Menu";
+import MenuItem from "@mui/material/MenuItem";
+import { useTheme } from "@emotion/react";
+import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
+import { useTranslation } from "react-i18next";
+import Proptypes from "prop-types";
+
+const AddMemberMenu = ({ handleInviteOpen, handleIsRegisterOpen }) => {
+ const [anchorEl, setAnchorEl] = useState(null);
+ const open = Boolean(anchorEl);
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const handleClick = (event) => {
+ setAnchorEl(event.currentTarget);
+ };
+ const handleClose = () => {
+ setAnchorEl(null);
+ };
+
+ return (
+ <>
+ }
+ onClick={handleClick}
+ >
+ {t("teamPanel.addTeamMember.addMemberMenu")}
+
+
+ >
+ );
+};
+
+AddMemberMenu.propTypes = {
+ handleInviteOpen: Proptypes.func.isRequired,
+ handleIsRegisterOpen: Proptypes.func.isRequired,
+};
+
+export default AddMemberMenu;
diff --git a/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx b/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx
new file mode 100644
index 000000000..e42c504b2
--- /dev/null
+++ b/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx
@@ -0,0 +1,48 @@
+import { useState } from "react";
+import { newOrChangedCredentials } from "../../../../../Validation/validation";
+import { useTranslation } from "react-i18next";
+const useAddTeamMember = () => {
+ const { t } = useTranslation();
+ const [errors, setErrors] = useState({});
+
+ const clearErrors = () => setErrors({});
+
+ const validateFields = (name, value, formData) => {
+ const { error } = newOrChangedCredentials.validate(
+ { [name]: value },
+ { abortEarly: false, context: { password: formData.password } }
+ );
+
+ setErrors((prev) => ({
+ ...prev,
+ [name]: error?.details?.[0]?.message || "",
+ }));
+ };
+
+ const validateForm = (formData, role) => {
+ const { error } = newOrChangedCredentials.validate(formData, {
+ abortEarly: false,
+ context: { password: formData.password },
+ });
+ const formErrors = {};
+ if (error) {
+ for (const err of error.details) {
+ formErrors[err.path[0]] = err.message;
+ }
+ }
+ if (!role[0] || role.length === 0) {
+ formErrors.role = t(
+ "teamPanel.registerTeamMember.auth.common.inputs.role.errors.empty"
+ );
+ }
+ if (Object.keys(formErrors).length > 0) {
+ setErrors(formErrors);
+ return false;
+ }
+ setErrors({});
+ return true;
+ };
+
+ return { errors, setErrors, clearErrors, validateFields, validateForm };
+};
+export default useAddTeamMember;
diff --git a/client/src/Pages/Account/components/AddTeamMember/index.jsx b/client/src/Pages/Account/components/AddTeamMember/index.jsx
new file mode 100644
index 000000000..2df8fb226
--- /dev/null
+++ b/client/src/Pages/Account/components/AddTeamMember/index.jsx
@@ -0,0 +1,213 @@
+import { Button, Stack } from "@mui/material";
+import { GenericDialog } from "../../../../Components/Dialog/genericDialog";
+import TextInput from "../../../../Components/Inputs/TextInput";
+import Select from "../../../../Components/Inputs/Select";
+import { useGetInviteToken } from "../../../../Hooks/inviteHooks";
+import { useTheme } from "@emotion/react";
+import { useTranslation } from "react-i18next";
+import { createToast } from "../../../../Utils/toastUtils";
+import { useState } from "react";
+import PasswordTooltip from "../../../Auth/components/PasswordTooltip";
+import useAddTeamMember from "./hooks/useAddTeamMember";
+import usePasswordFeedback from "../../../Auth/hooks/usePasswordFeedback";
+import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments";
+import PropTypes from "prop-types";
+
+const INITIAL_FORM_STATE = {
+ firstName: "",
+ lastName: "",
+ email: "",
+ password: "",
+ confirm: "",
+ teamId: "",
+};
+
+const INITIAL_ROLE_STATE = ["user"];
+const AddTeamMember = ({ handleIsRegisterOpen, isRegisterOpen, onMemberAdded }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const { errors, setErrors, clearErrors, validateFields, validateForm } =
+ useAddTeamMember();
+ const { feedback, handlePasswordFeedback } = usePasswordFeedback();
+ const [getInviteToken, clearToken, isLoading, error, token, addTeamMember] =
+ useGetInviteToken();
+ const [form, setForm] = useState(INITIAL_FORM_STATE);
+ const [role, setRole] = useState(INITIAL_ROLE_STATE);
+ const [isLoadingSubmit, setIsLoadingSubmit] = useState(false);
+ const closeAddMemberModal = () => {
+ handleIsRegisterOpen(false);
+ setForm(INITIAL_FORM_STATE);
+ setRole(INITIAL_ROLE_STATE);
+ clearErrors();
+ clearToken();
+ };
+
+ const onChange = (e) => {
+ let { name, value } = e.target;
+ if (name === "email") value = value.toLowerCase();
+ const updatedForm = { ...form, [name]: value };
+ validateFields(name, value, updatedForm);
+ setForm(updatedForm);
+
+ if (name === "password" || name === "confirm") {
+ handlePasswordFeedback(updatedForm, name, value, form, errors, setErrors);
+ }
+ };
+
+ const onsubmitAddMember = async (event) => {
+ event.preventDefault();
+ if (!validateForm(form, role)) return;
+ try {
+ setIsLoadingSubmit(true);
+ await addTeamMember(form, role);
+ createToast({
+ body: t("teamPanel.registerToast.success"),
+ });
+ onMemberAdded();
+ closeAddMemberModal();
+ } catch (error) {
+ const errorMsg = error.response?.data?.msg || error.message || "unknownError";
+ createToast({
+ type: "error",
+ body: t(errorMsg),
+ });
+ } finally {
+ setIsLoadingSubmit(false);
+ }
+ };
+ const tErr = (key) => (key ? t([`teamPanel.registerTeamMember.${key}`, key]) : "");
+ return (
+ <>
+
+
+
+
+
+
+
+
+ >
+ );
+};
+AddTeamMember.propTypes = {
+ handleIsRegisterOpen: PropTypes.func.isRequired,
+ isRegisterOpen: PropTypes.bool.isRequired,
+ onMemberAdded: PropTypes.func.isRequired,
+};
+
+export default AddTeamMember;
diff --git a/client/src/Pages/Account/components/TeamPanel.jsx b/client/src/Pages/Account/components/TeamPanel.jsx
index 95ece219f..fc1768642 100644
--- a/client/src/Pages/Account/components/TeamPanel.jsx
+++ b/client/src/Pages/Account/components/TeamPanel.jsx
@@ -9,10 +9,12 @@ import { networkService } from "../../../main";
import { createToast } from "../../../Utils/toastUtils";
import Select from "../../../Components/Inputs/Select";
import { GenericDialog } from "../../../Components/Dialog/genericDialog";
+import AddTeamMember from "../components/AddTeamMember";
import DataTable from "../../../Components/Table";
import { useGetInviteToken } from "../../../Hooks/inviteHooks";
import { useNavigate } from "react-router-dom";
import { useIsSuperAdmin } from "../../../Hooks/useIsAdmin";
+import AddMemberMenu from "./AddMemberMenu";
/**
* TeamPanel component manages the organization and team members,
* providing functionalities like renaming the organization, managing team members,
@@ -65,7 +67,10 @@ const TeamPanel = () => {
render: (row) => row.role,
},
];
-
+ const [refreshTrigger, setRefreshTrigger] = useState(false);
+ const refreshTeamList = () => {
+ setRefreshTrigger((prev) => !prev);
+ };
useEffect(() => {
const fetchTeam = async () => {
try {
@@ -79,7 +84,7 @@ const TeamPanel = () => {
};
fetchTeam();
- }, []);
+ }, [refreshTrigger]);
useEffect(() => {
const ROLE_MAP = {
@@ -109,6 +114,10 @@ const TeamPanel = () => {
setIsDisabled(Object.keys(errors).length !== 0 || toInvite.email === "");
}, [errors, toInvite.email]);
const [isOpen, setIsOpen] = useState(false);
+ const [isRegisterOpen, setIsRegisterOpen] = useState(false);
+ const handleIsRegisterOpen = (open) => {
+ setIsRegisterOpen(open);
+ };
const handleChange = (event) => {
const { value } = event.target;
@@ -138,7 +147,6 @@ const TeamPanel = () => {
const handleGetToken = async () => {
await getInviteToken({ email: toInvite.email, role: toInvite.role });
};
-
const handleInviteMember = async () => {
if (!toInvite.email) {
setErrors((prev) => ({ ...prev, email: "Email is required." }));
@@ -239,13 +247,16 @@ const TeamPanel = () => {
-
+
+
+ setIsOpen(true)}
+ handleIsRegisterOpen={handleIsRegisterOpen}
+ />
{
}}
/>
-
{
+ const [submitting, setSubmitting] = useState(false);
+ const executeSubmit = useCallback(
+ async (submitFunction) => {
+ if (submitting) return;
+ setSubmitting(true);
+ try {
+ return await submitFunction();
+ } finally {
+ setSubmitting(false);
+ }
+ },
+ [submitting]
+ );
+
+ return { submitting, executeSubmit };
+};
+
+export default useLoadingSubmit;
diff --git a/client/src/Pages/Auth/Login/hooks/useLoginForm.jsx b/client/src/Pages/Auth/Login/hooks/useLoginForm.jsx
new file mode 100644
index 000000000..61814d723
--- /dev/null
+++ b/client/src/Pages/Auth/Login/hooks/useLoginForm.jsx
@@ -0,0 +1,19 @@
+import { useState } from "react";
+
+const useLoginForm = () => {
+ const [form, setForm] = useState({
+ email: "",
+ password: "",
+ });
+ const onChange = (e) => {
+ let { name, value } = e.target;
+ if (name === "email") {
+ value = value.toLowerCase();
+ }
+ const updatedForm = { ...form, [name]: value };
+ setForm(updatedForm);
+ };
+ return [form, onChange];
+};
+
+export default useLoginForm;
diff --git a/client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx b/client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx
new file mode 100644
index 000000000..6bc5716ff
--- /dev/null
+++ b/client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx
@@ -0,0 +1,40 @@
+import { useDispatch } from "react-redux";
+import { login } from "../../../../Features/Auth/authSlice";
+import { useNavigate } from "react-router-dom";
+import { createToast } from "../../../../Utils/toastUtils";
+import { useTranslation } from "react-i18next";
+
+const useLoginSubmit = () => {
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+ const { t } = useTranslation();
+
+ const handleLoginSubmit = async (form, setErrors) => {
+ const action = await dispatch(login(form));
+ if (action.payload.success) {
+ navigate("/uptime");
+ createToast({
+ body: t("auth.login.toasts.success"),
+ });
+ } else {
+ if (action.payload) {
+ if (action.payload.msg === "Incorrect password")
+ setErrors({
+ password: t("auth.login.errors.password.incorrect"),
+ });
+ // dispatch errors
+ createToast({
+ body: t("auth.login.toasts.incorrectPassword"),
+ });
+ } else {
+ // unknown errors
+ createToast({
+ body: t("common.toasts.unknownError"),
+ });
+ }
+ }
+ };
+ return [handleLoginSubmit];
+};
+
+export default useLoginSubmit;
diff --git a/client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx b/client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx
new file mode 100644
index 000000000..e5d045be8
--- /dev/null
+++ b/client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx
@@ -0,0 +1,31 @@
+import { useState } from "react";
+import { loginCredentials } from "../../../../Validation/validation";
+
+const useValidateLoginForm = () => {
+ const [errors, setErrors] = useState({
+ email: "",
+ password: "",
+ });
+ const validateField = (name, value) => {
+ const { error } = loginCredentials.validate({ [name]: value });
+ setErrors((prev) => ({
+ ...prev,
+ [name]: error?.details?.[0]?.message || "",
+ }));
+ };
+ const validateForm = (form) => {
+ const { error } = loginCredentials.validate(form, { abortEarly: false });
+ if (error) {
+ const formErrors = {};
+ for (const err of error.details) {
+ formErrors[err.path[0]] = err.message;
+ }
+ setErrors(formErrors);
+ return false;
+ }
+ return true;
+ };
+ return [errors, setErrors, validateField, validateForm];
+};
+
+export default useValidateLoginForm;
diff --git a/client/src/Pages/Auth/Login/index.jsx b/client/src/Pages/Auth/Login/index.jsx
index d6b281c27..d06ecef6d 100644
--- a/client/src/Pages/Auth/Login/index.jsx
+++ b/client/src/Pages/Auth/Login/index.jsx
@@ -3,84 +3,35 @@ import Stack from "@mui/material/Stack";
import Button from "@mui/material/Button";
import TextInput from "../../../Components/Inputs/TextInput";
import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adornments";
-import { loginCredentials } from "../../../Validation/validation";
import TextLink from "../../../Components/TextLink";
import AuthPageWrapper from "../components/AuthPageWrapper";
// Utils
-import { login } from "../../../Features/Auth/authSlice";
-import { useNavigate } from "react-router-dom";
-import { useDispatch } from "react-redux";
-import { useState } from "react";
import { useTheme } from "@mui/material/styles";
import { useTranslation } from "react-i18next";
-import { createToast } from "../../../Utils/toastUtils";
+import useLoginForm from "./hooks/useLoginForm";
+import useValidateLoginForm from "./hooks/useValidateLoginForm";
+import useLoginSubmit from "./hooks/useLoginSubmit";
+import useLoadingSubmit from "./hooks/useLoadingSubmit";
const Login = () => {
- // Local state
- const [form, setForm] = useState({
- email: "",
- password: "",
- });
- const [errors, setErrors] = useState({
- email: "",
- password: "",
- });
-
// Hooks
const { t } = useTranslation();
const theme = useTheme();
- const dispatch = useDispatch();
- const navigate = useNavigate();
+ const [form, onChange] = useLoginForm();
+ const [errors, setErrors, validateField, validateForm] = useValidateLoginForm();
+ const [handleLoginSubmit] = useLoginSubmit();
+ const { submitting, executeSubmit } = useLoadingSubmit();
// Handlers
- const onChange = (e) => {
- let { name, value } = e.target;
- if (name === "email") {
- value = value.toLowerCase();
- }
- const updatedForm = { ...form, [name]: value };
- const { error } = loginCredentials.validate({ [name]: value });
- setForm(updatedForm);
- setErrors((prev) => ({
- ...prev,
- [name]: error?.details?.[0]?.message || "",
- }));
+ const handleChange = (e) => {
+ onChange(e);
+ validateField(e.target.name, e.target.value);
};
const onSubmit = async (e) => {
e.preventDefault();
- const toSubmit = { ...form };
- const { error } = loginCredentials.validate(toSubmit, { abortEarly: false });
- if (error) {
- const formErrors = {};
- for (const err of error.details) {
- formErrors[err.path[0]] = err.message;
- }
- setErrors(formErrors);
- return;
- }
- const action = await dispatch(login(form));
- if (action.payload.success) {
- navigate("/uptime");
- createToast({
- body: t("auth.login.toasts.success"),
- });
- } else {
- if (action.payload) {
- if (action.payload.msg === "Incorrect password")
- setErrors({
- password: t("auth.login.errors.password.incorrect"),
- });
- // dispatch errors
- createToast({
- body: t("auth.login.toasts.incorrectPassword"),
- });
- } else {
- // unknown errors
- createToast({
- body: t("common.toasts.unknownError"),
- });
- }
- }
+ const isValid = validateForm(form);
+ if (!isValid) return;
+ await executeSubmit(() => handleLoginSubmit(form, setErrors));
};
return (
{
placeholder={t("auth.common.inputs.email.placeholder")}
autoComplete="email"
value={form.email}
- onChange={onChange}
+ onChange={handleChange}
error={errors.email ? true : false}
helperText={errors.email ? t(errors.email) : ""} // Localization keys are in validation.js
/>
@@ -120,7 +71,7 @@ const Login = () => {
placeholder="••••••••••"
autoComplete="current-password"
value={form.password}
- onChange={onChange}
+ onChange={handleChange}
error={errors.password ? true : false}
helperText={errors.password ? t(errors.password) : ""} // Localization keys are in validation.js
endAdornment={}
@@ -130,6 +81,7 @@ const Login = () => {
color="accent"
type="submit"
sx={{ width: "100%", alignSelf: "center", fontWeight: 700 }}
+ loading={submitting}
>
Login
@@ -147,5 +99,4 @@ const Login = () => {
);
};
-
export default Login;
diff --git a/client/src/Pages/Auth/components/PasswordTooltip.jsx b/client/src/Pages/Auth/components/PasswordTooltip.jsx
index 93a74403a..bc2f28ec7 100644
--- a/client/src/Pages/Auth/components/PasswordTooltip.jsx
+++ b/client/src/Pages/Auth/components/PasswordTooltip.jsx
@@ -93,14 +93,14 @@ const PasswordTooltip = ({ feedback, form, children }) => {
},
}}
>
- {children}
+ {children}
);
};
PasswordTooltip.propTypes = {
feedback: PropTypes.shape({
- length: PropTypes.string.isRequired,
+ length: PropTypes.string,
special: PropTypes.string,
number: PropTypes.string,
uppercase: PropTypes.string,
diff --git a/client/src/Pages/Auth/hooks/usePasswordFeedback.jsx b/client/src/Pages/Auth/hooks/usePasswordFeedback.jsx
new file mode 100644
index 000000000..4159e6166
--- /dev/null
+++ b/client/src/Pages/Auth/hooks/usePasswordFeedback.jsx
@@ -0,0 +1,71 @@
+import { useState } from "react";
+import { newOrChangedCredentials } from "../../../Validation/validation";
+
+const usePasswordFeedback = () => {
+ const [feedback, setFeedback] = useState({});
+ const getFeedbackStatus = (form, errors, field, criteria) => {
+ const fieldErrors = errors?.[field];
+ const isFieldEmpty = form?.[field]?.length === 0;
+ const hasError = fieldErrors?.includes(criteria) || fieldErrors?.includes("empty");
+ const isCorrect = !isFieldEmpty && !hasError;
+
+ if (isCorrect) {
+ return "success";
+ } else if (hasError) {
+ return "error";
+ } else {
+ return "info";
+ }
+ };
+
+ const handlePasswordFeedback = (updatedForm, name, value, form, errors, setErrors) => {
+ const validateValue = { [name]: value };
+ const validateOptions = { abortEarly: false, context: { password: form.password } };
+ if (name === "password" && form.confirm.length > 0) {
+ validateValue.confirm = form.confirm;
+ validateOptions.context = { password: value };
+ } else if (name === "confirm") {
+ validateValue.password = form.password;
+ }
+ const { error } = newOrChangedCredentials.validate(validateValue, validateOptions);
+
+ const pwdErrors = error?.details.map((error) => ({
+ path: error.path[0],
+ type: error.type,
+ }));
+
+ const errorsByPath =
+ pwdErrors &&
+ pwdErrors.reduce((acc, { path, type }) => {
+ if (!acc[path]) {
+ acc[path] = [];
+ }
+ acc[path].push(type);
+ return acc;
+ }, {});
+
+ const oldErrors = { ...errors };
+ if (name === "password") {
+ oldErrors.password = undefined;
+ } else if (name === "confirm") {
+ oldErrors.confirm = undefined;
+ }
+ const newErrors = { ...oldErrors, ...errorsByPath };
+
+ setErrors(newErrors);
+
+ const newFeedback = {
+ length: getFeedbackStatus(updatedForm, errorsByPath, "password", "string.min"),
+ special: getFeedbackStatus(updatedForm, errorsByPath, "password", "special"),
+ number: getFeedbackStatus(updatedForm, errorsByPath, "password", "number"),
+ uppercase: getFeedbackStatus(updatedForm, errorsByPath, "password", "uppercase"),
+ lowercase: getFeedbackStatus(updatedForm, errorsByPath, "password", "lowercase"),
+ confirm: getFeedbackStatus(updatedForm, errorsByPath, "confirm", "different"),
+ };
+
+ setFeedback(newFeedback);
+ };
+ return { feedback, handlePasswordFeedback, getFeedbackStatus };
+};
+
+export default usePasswordFeedback;
diff --git a/client/src/Pages/Incidents/Components/IncidentTable/index.jsx b/client/src/Pages/Incidents/Components/IncidentTable/index.jsx
index e8929d9ed..df9174290 100644
--- a/client/src/Pages/Incidents/Components/IncidentTable/index.jsx
+++ b/client/src/Pages/Incidents/Components/IncidentTable/index.jsx
@@ -1,5 +1,10 @@
//Components
-import Table from "../../../../Components/Table";
+import Stack from "@mui/material/Stack";
+import DataTable from "../../../../Components/Table";
+import Table from "@mui/material/Table";
+import TableBody from "@mui/material/TableBody";
+import TableRow from "@mui/material/TableRow";
+import TableCell from "@mui/material/TableCell";
import TableSkeleton from "../../../../Components/Table/skeleton";
import Pagination from "../../../../Components/Table/TablePagination";
import { StatusLabel } from "../../../../Components/Label";
@@ -16,7 +21,68 @@ import { useTranslation } from "react-i18next";
import { useFetchChecksTeam } from "../../../../Hooks/checkHooks";
import { useFetchChecksByMonitor } from "../../../../Hooks/checkHooks";
import { useResolveIncident } from "../../../../Hooks/checkHooks";
-import { Button, Typography } from "@mui/material";
+import { Button, Typography, useTheme } from "@mui/material";
+import { lighten } from "@mui/material/styles";
+
+const GetTooltip = (row) => {
+ const theme = useTheme();
+ const phases = row?.timings?.phases;
+
+ const phaseKeyFormattingMap = {
+ firstByte: "first byte",
+ };
+ return (
+
+ {`Status code: ${row?.statusCode}`}
+ {`Response time: ${row?.responseTime} ms`}
+ {phases && (
+ <>
+ {`Request timing: `}
+
+
+ {Object.keys(phases)?.map((phaseKey) => (
+
+
+
+ {`${phaseKeyFormattingMap[phaseKey] || phaseKey}:`}
+
+
+
+ {`${phases[phaseKey]} ms`}
+
+
+ ))}
+
+
+ >
+ )}
+
+ );
+};
const IncidentTable = ({
isLoading,
@@ -167,9 +233,10 @@ const IncidentTable = ({
return (
<>
-
{
+ const theme = useTheme();
+ const { t } = useTranslation();
+
+ const METRICS = ["cpu", "memory", "disk", "temperature"];
+ const METRIC_PREFIX = "usage_";
+ const hasAlertError = (errors) => {
+ return Object.keys(errors).filter((k) => k.startsWith(METRIC_PREFIX)).length > 0;
+ };
+ const getAlertError = (errors) => {
+ const errorKey = Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX));
+ return errorKey ? errors[errorKey] : null;
+ };
+ return (
+
+
+
+ {t("infrastructureCustomizeAlerts")}
+
+
+ {t("infrastructureAlertNotificationDescription")}
+
+
+
+ {METRICS.map((metric) => {
+ return (
+
+ );
+ })}
+
+ {hasAlertError(errors) && (
+
+ {getAlertError(errors)}
+
+ )}
+
+
+ );
+};
+
+CustomAlertsSection.propTypes = {
+ errors: PropTypes.object.isRequired,
+ onChange: PropTypes.func.isRequired,
+ infrastructureMonitor: PropTypes.object.isRequired,
+ handleCheckboxChange: PropTypes.func.isRequired,
+};
+
+export default CustomAlertsSection;
diff --git a/client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx b/client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx
new file mode 100644
index 000000000..becda3e3a
--- /dev/null
+++ b/client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx
@@ -0,0 +1,78 @@
+import { useState } from "react";
+import { Box, Button } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import { useTranslation } from "react-i18next";
+import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline";
+import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded";
+import Dialog from "../../../../Components/Dialog";
+import PropTypes from "prop-types";
+
+const MonitorActionButtons = ({ monitor, isBusy, handlePause, handleRemove }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const [isOpen, setIsOpen] = useState(false);
+
+ return (
+
+
+
+
+ );
+};
+
+MonitorActionButtons.propTypes = {
+ monitor: PropTypes.object.isRequired,
+ isBusy: PropTypes.bool.isRequired,
+ handlePause: PropTypes.func.isRequired,
+ handleRemove: PropTypes.func.isRequired,
+};
+
+export default MonitorActionButtons;
diff --git a/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx b/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx
new file mode 100644
index 000000000..2ed48bf64
--- /dev/null
+++ b/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx
@@ -0,0 +1,73 @@
+import { Box, Stack, Tooltip, Typography } from "@mui/material";
+import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils";
+import { useTheme } from "@emotion/react";
+import { useTranslation } from "react-i18next";
+import PulseDot from "../../../../Components/Animated/PulseDot";
+import PropTypes from "prop-types";
+const MonitorStatusHeader = ({ monitor, infrastructureMonitor }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const { statusColor, pagespeedStatusMsg, determineState } = useMonitorUtils();
+ return (
+
+
+
+
+
+
+
+ {infrastructureMonitor.url?.replace(/^https?:\/\//, "") || "..."}
+
+
+ {t("editing")}
+
+
+ );
+};
+
+MonitorStatusHeader.propTypes = {
+ monitor: PropTypes.object.isRequired,
+ infrastructureMonitor: PropTypes.object.isRequired,
+};
+
+export default MonitorStatusHeader;
diff --git a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx
new file mode 100644
index 000000000..fa8a6ebe9
--- /dev/null
+++ b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx
@@ -0,0 +1,90 @@
+import { useState } from "react";
+const useInfrastructureMonitorForm = () => {
+ const [infrastructureMonitor, setInfrastructureMonitor] = useState({
+ url: "",
+ name: "",
+ notifications: [],
+ notify_email: false,
+ interval: 0.25,
+ cpu: false,
+ usage_cpu: "",
+ memory: false,
+ usage_memory: "",
+ disk: false,
+ usage_disk: "",
+ temperature: false,
+ usage_temperature: "",
+ secret: "",
+ });
+
+ const onChangeForm = (name, value) => {
+ setInfrastructureMonitor({
+ ...infrastructureMonitor,
+ [name]: value,
+ });
+ };
+ const handleCheckboxChange = (event) => {
+ setInfrastructureMonitor({
+ ...infrastructureMonitor,
+ [event.target.name]: event.target.checked,
+ });
+ };
+ const initializeInfrastructureMonitorForCreate = (globalSettings) => {
+ const gt = globalSettings?.data?.settings?.globalThresholds || {};
+ setInfrastructureMonitor({
+ url: "",
+ name: "",
+ notifications: [],
+ interval: 0.25,
+ cpu: gt.cpu !== undefined,
+ usage_cpu: gt.cpu !== undefined ? gt.cpu.toString() : "",
+ memory: gt.memory !== undefined,
+ usage_memory: gt.memory !== undefined ? gt.memory.toString() : "",
+ disk: gt.disk !== undefined,
+ usage_disk: gt.disk !== undefined ? gt.disk.toString() : "",
+ temperature: gt.temperature !== undefined,
+ usage_temperature: gt.temperature !== undefined ? gt.temperature.toString() : "",
+ secret: "",
+ });
+ };
+
+ const initializeInfrastructureMonitorForUpdate = (monitor) => {
+ const MS_PER_MINUTE = 60000;
+ const { thresholds = {} } = monitor;
+ setInfrastructureMonitor({
+ url: monitor.url.replace(/^https?:\/\//, ""),
+ name: monitor.name || "",
+ notifications: monitor.notifications || [],
+ interval: monitor.interval / MS_PER_MINUTE,
+ cpu: thresholds.usage_cpu !== undefined,
+ usage_cpu:
+ thresholds.usage_cpu !== undefined ? (thresholds.usage_cpu * 100).toString() : "",
+ memory: thresholds.usage_memory !== undefined,
+ usage_memory:
+ thresholds.usage_memory !== undefined
+ ? (thresholds.usage_memory * 100).toString()
+ : "",
+ disk: thresholds.usage_disk !== undefined,
+ usage_disk:
+ thresholds.usage_disk !== undefined
+ ? (thresholds.usage_disk * 100).toString()
+ : "",
+ temperature: thresholds.usage_temperature !== undefined,
+ usage_temperature:
+ thresholds.usage_temperature !== undefined
+ ? (thresholds.usage_temperature * 100).toString()
+ : "",
+ secret: monitor.secret || "",
+ });
+ };
+ return {
+ infrastructureMonitor,
+ setInfrastructureMonitor,
+ onChangeForm,
+ handleCheckboxChange,
+ initializeInfrastructureMonitorForCreate,
+ initializeInfrastructureMonitorForUpdate,
+ };
+};
+
+export default useInfrastructureMonitorForm;
diff --git a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx
new file mode 100644
index 000000000..da564ae7c
--- /dev/null
+++ b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx
@@ -0,0 +1,80 @@
+import { useCreateMonitor, useUpdateMonitor } from "../../../../Hooks/monitorHooks";
+const useInfrastructureSubmit = () => {
+ const [createMonitor, isCreating] = useCreateMonitor();
+ const [updateMonitor, isUpdating] = useUpdateMonitor();
+ const buildForm = (infrastructureMonitor, https) => {
+ const MS_PER_MINUTE = 60000;
+
+ let form = {
+ url: `http${https ? "s" : ""}://` + infrastructureMonitor.url,
+ name:
+ infrastructureMonitor.name === ""
+ ? infrastructureMonitor.url
+ : infrastructureMonitor.name,
+ interval: infrastructureMonitor.interval * MS_PER_MINUTE,
+ cpu: infrastructureMonitor.cpu,
+ ...(infrastructureMonitor.cpu
+ ? { usage_cpu: infrastructureMonitor.usage_cpu }
+ : {}),
+ memory: infrastructureMonitor.memory,
+ ...(infrastructureMonitor.memory
+ ? { usage_memory: infrastructureMonitor.usage_memory }
+ : {}),
+ disk: infrastructureMonitor.disk,
+ ...(infrastructureMonitor.disk
+ ? { usage_disk: infrastructureMonitor.usage_disk }
+ : {}),
+ temperature: infrastructureMonitor.temperature,
+ ...(infrastructureMonitor.temperature
+ ? { usage_temperature: infrastructureMonitor.usage_temperature }
+ : {}),
+ secret: infrastructureMonitor.secret,
+ };
+ return form;
+ };
+ const submitInfrastructureForm = async (
+ infrastructureMonitor,
+ form,
+ isCreate,
+ monitorId
+ ) => {
+ const {
+ cpu,
+ usage_cpu,
+ memory,
+ usage_memory,
+ disk,
+ usage_disk,
+ temperature,
+ usage_temperature,
+ ...rest
+ } = form;
+
+ const thresholds = {
+ ...(cpu ? { usage_cpu: usage_cpu / 100 } : {}),
+ ...(memory ? { usage_memory: usage_memory / 100 } : {}),
+ ...(disk ? { usage_disk: usage_disk / 100 } : {}),
+ ...(temperature ? { usage_temperature: usage_temperature / 100 } : {}),
+ };
+
+ const finalForm = {
+ ...(isCreate ? {} : { _id: monitorId }),
+ ...rest,
+ description: form.name,
+ type: "hardware",
+ notifications: infrastructureMonitor.notifications,
+ thresholds,
+ };
+ // Handle create or update
+ isCreate
+ ? await createMonitor({ monitor: finalForm, redirect: "/infrastructure" })
+ : await updateMonitor({ monitor: finalForm, redirect: "/infrastructure" });
+ };
+ return {
+ buildForm,
+ submitInfrastructureForm,
+ isCreating,
+ isUpdating,
+ };
+};
+export default useInfrastructureSubmit;
diff --git a/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx b/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx
new file mode 100644
index 000000000..080f441c4
--- /dev/null
+++ b/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx
@@ -0,0 +1,37 @@
+import { useState } from "react";
+import { infrastructureMonitorValidation } from "../../../../Validation/validation";
+import { createToast } from "../../../../Utils/toastUtils";
+const useValidateInfrastructureForm = () => {
+ const [errors, setErrors] = useState({});
+
+ const validateField = (name, value) => {
+ const { error } = infrastructureMonitorValidation.validate(
+ { [name]: value },
+ { abortEarly: false }
+ );
+ setErrors((prev) => ({
+ ...prev,
+ ...(error ? { [name]: error.details[0].message } : { [name]: undefined }),
+ }));
+ };
+
+ const validateForm = (form) => {
+ const { error } = infrastructureMonitorValidation.validate(form, {
+ abortEarly: false,
+ });
+
+ if (error) {
+ const newErrors = {};
+ error.details.forEach((err) => {
+ newErrors[err.path[0]] = err.message;
+ });
+ console.log(newErrors);
+ setErrors(newErrors);
+ createToast({ body: "Please check the form for errors." });
+ return error;
+ }
+ return null;
+ };
+ return { errors, validateField, validateForm };
+};
+export default useValidateInfrastructureForm;
diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx
index ae95cce33..28007c538 100644
--- a/client/src/Pages/Infrastructure/Create/index.jsx
+++ b/client/src/Pages/Infrastructure/Create/index.jsx
@@ -1,40 +1,33 @@
//Components
import Breadcrumbs from "../../../Components/Breadcrumbs";
import ConfigBox from "../../../Components/ConfigBox";
-import Dialog from "../../../Components/Dialog";
import FieldWrapper from "../../../Components/Inputs/FieldWrapper";
import Link from "../../../Components/Link";
-import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline";
-import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded";
-import PulseDot from "../../../Components/Animated/PulseDot";
import Select from "../../../Components/Inputs/Select";
import TextInput from "../../../Components/Inputs/TextInput";
-import { Box, Stack, Tooltip, Typography, Button, ButtonGroup } from "@mui/material";
-import { CustomThreshold } from "./Components/CustomThreshold";
+import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material";
import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments";
-import { createToast } from "../../../Utils/toastUtils";
+import MonitorStatusHeader from "./Components/MonitorStatusHeader";
+import MonitorActionButtons from "./Components/MonitorActionButtons";
+import CustomAlertsSection from "./Components/CustomAlertsSection";
// Utils
import NotificationsConfig from "../../../Components/NotificationConfig";
-import { capitalizeFirstLetter } from "../../../Utils/stringUtils";
-import { infrastructureMonitorValidation } from "../../../Validation/validation";
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
-import { useMonitorUtils } from "../../../Hooks/useMonitorUtils";
import { useParams } from "react-router-dom";
-import { useSelector } from "react-redux";
import { useState, useEffect } from "react";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
import {
- useCreateMonitor,
useDeleteMonitor,
useFetchGlobalSettings,
useFetchHardwareMonitorById,
usePauseMonitor,
- useUpdateMonitor,
} from "../../../Hooks/monitorHooks";
+import useInfrastructureMonitorForm from "./hooks/useInfrastructureMonitorForm";
+import useValidateInfrastructureForm from "./hooks/useValidateInfrastructureForm";
+import useInfrastructureSubmit from "./hooks/useInfrastructureSubmit";
const CreateInfrastructureMonitor = () => {
- const { user } = useSelector((state) => state.auth);
const { monitorId } = useParams();
const isCreate = typeof monitorId === "undefined";
@@ -42,39 +35,29 @@ const CreateInfrastructureMonitor = () => {
const { t } = useTranslation();
// State
- const [errors, setErrors] = useState({});
const [https, setHttps] = useState(false);
- const [isOpen, setIsOpen] = useState(false);
const [updateTrigger, setUpdateTrigger] = useState(false);
- const [infrastructureMonitor, setInfrastructureMonitor] = useState({
- url: "",
- name: "",
- notifications: [],
- notify_email: false,
- interval: 0.25,
- cpu: false,
- usage_cpu: "",
- memory: false,
- usage_memory: "",
- disk: false,
- usage_disk: "",
- temperature: false,
- usage_temperature: "",
- secret: "",
- });
// Fetch monitor details if editing
- const { statusColor, pagespeedStatusMsg, determineState } = useMonitorUtils();
const [monitor, isLoading] = useFetchHardwareMonitorById({
monitorId,
updateTrigger,
});
- const [createMonitor, isCreating] = useCreateMonitor();
const [deleteMonitor, isDeleting] = useDeleteMonitor();
const [globalSettings, globalSettingsLoading] = useFetchGlobalSettings();
const [notifications, notificationsAreLoading] = useGetNotificationsByTeamId();
const [pauseMonitor, isPausing] = usePauseMonitor();
- const [updateMonitor, isUpdating] = useUpdateMonitor();
+ const {
+ infrastructureMonitor,
+ setInfrastructureMonitor,
+ onChangeForm,
+ handleCheckboxChange,
+ initializeInfrastructureMonitorForCreate,
+ initializeInfrastructureMonitorForUpdate,
+ } = useInfrastructureMonitorForm();
+ const { errors, validateField, validateForm } = useValidateInfrastructureForm();
+ const { buildForm, submitInfrastructureForm, isCreating, isUpdating } =
+ useInfrastructureSubmit();
const FREQUENCIES = [
{ _id: 0.25, name: t("time.fifteenSeconds") },
@@ -93,157 +76,27 @@ const CreateInfrastructureMonitor = () => {
{ name: "Configure", path: `/infrastructure/configure/${monitorId}` },
]),
];
- const METRICS = ["cpu", "memory", "disk", "temperature"];
- const METRIC_PREFIX = "usage_";
- const MS_PER_MINUTE = 60000;
-
- const hasAlertError = (errors) => {
- return Object.keys(errors).filter((k) => k.startsWith(METRIC_PREFIX)).length > 0;
- };
-
- const getAlertError = (errors) => {
- const errorKey = Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX));
- return errorKey ? errors[errorKey] : null;
- };
-
// Populate form fields if editing
useEffect(() => {
if (isCreate) {
if (globalSettingsLoading) return;
-
- const gt = globalSettings?.data?.settings?.globalThresholds || {};
-
setHttps(false);
-
- setInfrastructureMonitor({
- url: "",
- name: "",
- notifications: [],
- interval: 0.25,
- cpu: gt.cpu !== undefined,
- usage_cpu: gt.cpu !== undefined ? gt.cpu.toString() : "",
- memory: gt.memory !== undefined,
- usage_memory: gt.memory !== undefined ? gt.memory.toString() : "",
- disk: gt.disk !== undefined,
- usage_disk: gt.disk !== undefined ? gt.disk.toString() : "",
- temperature: gt.temperature !== undefined,
- usage_temperature: gt.temperature !== undefined ? gt.temperature.toString() : "",
- secret: "",
- });
+ initializeInfrastructureMonitorForCreate(globalSettings);
} else if (monitor) {
- const { thresholds = {} } = monitor;
-
setHttps(monitor.url.startsWith("https"));
-
- setInfrastructureMonitor({
- url: monitor.url.replace(/^https?:\/\//, ""),
- name: monitor.name || "",
- notifications: monitor.notifications || [],
- interval: monitor.interval / MS_PER_MINUTE,
- cpu: thresholds.usage_cpu !== undefined,
- usage_cpu:
- thresholds.usage_cpu !== undefined
- ? (thresholds.usage_cpu * 100).toString()
- : "",
- memory: thresholds.usage_memory !== undefined,
- usage_memory:
- thresholds.usage_memory !== undefined
- ? (thresholds.usage_memory * 100).toString()
- : "",
- disk: thresholds.usage_disk !== undefined,
- usage_disk:
- thresholds.usage_disk !== undefined
- ? (thresholds.usage_disk * 100).toString()
- : "",
- temperature: thresholds.usage_temperature !== undefined,
- usage_temperature:
- thresholds.usage_temperature !== undefined
- ? (thresholds.usage_temperature * 100).toString()
- : "",
- secret: monitor.secret || "",
- });
+ initializeInfrastructureMonitorForUpdate(monitor);
}
}, [isCreate, monitor, globalSettings, globalSettingsLoading]);
// Handlers
const onSubmit = async (event) => {
event.preventDefault();
-
- // Build the form
- let form = {
- url: `http${https ? "s" : ""}://` + infrastructureMonitor.url,
- name:
- infrastructureMonitor.name === ""
- ? infrastructureMonitor.url
- : infrastructureMonitor.name,
- interval: infrastructureMonitor.interval * MS_PER_MINUTE,
- cpu: infrastructureMonitor.cpu,
- ...(infrastructureMonitor.cpu
- ? { usage_cpu: infrastructureMonitor.usage_cpu }
- : {}),
- memory: infrastructureMonitor.memory,
- ...(infrastructureMonitor.memory
- ? { usage_memory: infrastructureMonitor.usage_memory }
- : {}),
- disk: infrastructureMonitor.disk,
- ...(infrastructureMonitor.disk
- ? { usage_disk: infrastructureMonitor.usage_disk }
- : {}),
- temperature: infrastructureMonitor.temperature,
- ...(infrastructureMonitor.temperature
- ? { usage_temperature: infrastructureMonitor.usage_temperature }
- : {}),
- secret: infrastructureMonitor.secret,
- };
-
- const { error } = infrastructureMonitorValidation.validate(form, {
- abortEarly: false,
- });
-
+ const form = buildForm(infrastructureMonitor, https);
+ const error = validateForm(form);
if (error) {
- const newErrors = {};
- error.details.forEach((err) => {
- newErrors[err.path[0]] = err.message;
- });
- console.log(newErrors);
- setErrors(newErrors);
- createToast({ body: "Please check the form for errors." });
return;
}
-
- // Build the thresholds for the form
- const {
- cpu,
- usage_cpu,
- memory,
- usage_memory,
- disk,
- usage_disk,
- temperature,
- usage_temperature,
- ...rest
- } = form;
-
- const thresholds = {
- ...(cpu ? { usage_cpu: usage_cpu / 100 } : {}),
- ...(memory ? { usage_memory: usage_memory / 100 } : {}),
- ...(disk ? { usage_disk: usage_disk / 100 } : {}),
- ...(temperature ? { usage_temperature: usage_temperature / 100 } : {}),
- };
-
- form = {
- ...(isCreate ? {} : { _id: monitorId }),
- ...rest,
- description: form.name,
- type: "hardware",
- notifications: infrastructureMonitor.notifications,
- thresholds,
- };
-
- // Handle create or update
- isCreate
- ? await createMonitor({ monitor: form, redirect: "/infrastructure" })
- : await updateMonitor({ monitor: form, redirect: "/infrastructure" });
+ submitInfrastructureForm(infrastructureMonitor, form, isCreate, monitorId);
};
const triggerUpdate = () => {
@@ -252,28 +105,8 @@ const CreateInfrastructureMonitor = () => {
const onChange = (event) => {
const { value, name } = event.target;
- setInfrastructureMonitor({
- ...infrastructureMonitor,
- [name]: value,
- });
-
- const { error } = infrastructureMonitorValidation.validate(
- { [name]: value },
- { abortEarly: false }
- );
- setErrors((prev) => ({
- ...prev,
- ...(error ? { [name]: error.details[0].message } : { [name]: undefined }),
- }));
- };
-
- const handleCheckboxChange = (event) => {
- const { name } = event.target;
- const { checked } = event.target;
- setInfrastructureMonitor({
- ...infrastructureMonitor,
- [name]: checked,
- });
+ onChangeForm(name, value);
+ validateField(name, value);
};
const handlePause = async () => {
@@ -336,105 +169,19 @@ const CreateInfrastructureMonitor = () => {
)}
{!isCreate && (
-
-
-
-
-
-
-
- {infrastructureMonitor.url?.replace(/^https?:\/\//, "") || "..."}
-
-
- {t("editing")}
-
-
+
)}
{!isCreate && (
-
-
-
-
+
)}
@@ -534,58 +281,12 @@ const CreateInfrastructureMonitor = () => {
setNotifications={infrastructureMonitor.notifications}
/>
-
-
-
- {t("infrastructureCustomizeAlerts")}
-
-
- {t("infrastructureAlertNotificationDescription")}
-
-
-
- {METRICS.map((metric) => {
- return (
-
- );
- })}
- {/* Error text */}
- {hasAlertError(errors) && (
-
- {getAlertError(errors)}
-
- )}
-
-
+
{
- {!isCreate && (
-
);
};
diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx
new file mode 100644
index 000000000..9157a3432
--- /dev/null
+++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx
@@ -0,0 +1,132 @@
+import PropTypes from "prop-types";
+import { Stack, Typography } from "@mui/material";
+import InfraAreaChart from "../../../../../Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart";
+
+import {
+ TzTick,
+ InfrastructureTooltip,
+ NetworkTick,
+} from "../../../../../Components/Charts/Utils/chartUtils";
+import { useTheme } from "@emotion/react";
+import { useTranslation } from "react-i18next";
+import { useHardwareUtils } from "../../Hooks/useHardwareUtils";
+
+const NetworkCharts = ({ ethernetData, dateRange }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+
+ const { formatBytesPerSecondString, formatPacketsPerSecondString } = useHardwareUtils();
+
+ if (!ethernetData?.length) {
+ return {t("noNetworkStatsAvailable")};
+ }
+
+ const configs = [
+ {
+ type: "network-bytes",
+ data: ethernetData,
+ dataKeys: ["bytesPerSec"],
+ heading: t("dataReceived"),
+ strokeColor: theme.palette.info.main,
+ gradientStartColor: theme.palette.info.main,
+ yLabel: t("rate"),
+ xTick: ,
+ yTick: ,
+ toolTip: (
+
+ ),
+ },
+ {
+ type: "network-packets",
+ data: ethernetData,
+ dataKeys: ["packetsPerSec"],
+ heading: t("packetsReceivedRate"),
+ strokeColor: theme.palette.success.main,
+ gradientStartColor: theme.palette.success.main,
+ yLabel: t("rate"),
+ xTick: ,
+ yTick: ,
+ toolTip: (
+
+ ),
+ },
+ {
+ type: "network-errors",
+ data: ethernetData,
+ dataKeys: ["errors"],
+ heading: t("networkErrors"),
+ strokeColor: theme.palette.error.main,
+ gradientStartColor: theme.palette.error.main,
+ yLabel: t("errors"),
+ xTick: ,
+ toolTip: (
+ Math.round(value).toLocaleString()}
+ />
+ ),
+ },
+ {
+ type: "network-drops",
+ data: ethernetData,
+ dataKeys: ["drops"],
+ heading: t("networkDrops"),
+ strokeColor: theme.palette.warning.main,
+ gradientStartColor: theme.palette.warning.main,
+ yLabel: t("drops"),
+ xTick: ,
+ toolTip: (
+ Math.round(value).toLocaleString()}
+ />
+ ),
+ },
+ ];
+
+ return (
+ *": {
+ flexBasis: `calc(50% - ${theme.spacing(8)})`,
+ maxWidth: `calc(50% - ${theme.spacing(8)})`,
+ },
+ }}
+ >
+ {configs.map((config) => (
+
+ ))}
+
+ );
+};
+
+NetworkCharts.propTypes = {
+ ethernetData: PropTypes.array.isRequired,
+ dateRange: PropTypes.string.isRequired,
+};
+
+export default NetworkCharts;
diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx
new file mode 100644
index 000000000..98ba90dae
--- /dev/null
+++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx
@@ -0,0 +1,81 @@
+import PropTypes from "prop-types";
+import StatusBoxes from "../../../../../Components/StatusBoxes";
+import StatBox from "../../../../../Components/StatBox";
+import { Typography } from "@mui/material";
+import { useTranslation } from "react-i18next";
+import { useHardwareUtils } from "../../Hooks/useHardwareUtils";
+
+function formatNumber(num) {
+ return num != null ? num.toLocaleString() : "0";
+}
+
+const NetworkStatBoxes = ({ shouldRender, net, ifaceName }) => {
+ const { t } = useTranslation();
+ const { formatBytes } = useHardwareUtils();
+
+ const filtered = net?.filter((iface) => iface.name === ifaceName) || [];
+
+ if (!net?.length) {
+ return {t("noNetworkStatsAvailable")};
+ }
+
+ return (
+
+ {filtered
+ .map((iface) => [
+ ,
+ ,
+ ,
+ ,
+ ,
+ ,
+ ])
+ .flat()}
+
+ );
+};
+
+NetworkStatBoxes.propTypes = {
+ shouldRender: PropTypes.bool.isRequired,
+ net: PropTypes.arrayOf(
+ PropTypes.shape({
+ name: PropTypes.string.isRequired,
+ bytes_sent: PropTypes.number,
+ bytes_recv: PropTypes.number,
+ packets_sent: PropTypes.number,
+ packets_recv: PropTypes.number,
+ err_in: PropTypes.number,
+ err_out: PropTypes.number,
+ })
+ ),
+ ifaceName: PropTypes.string.isRequired,
+};
+
+export default NetworkStatBoxes;
diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx
new file mode 100644
index 000000000..41d2b0a87
--- /dev/null
+++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx
@@ -0,0 +1,101 @@
+import PropTypes from "prop-types";
+import { useState, useEffect } from "react";
+import { Box } from "@mui/material";
+import { useTranslation } from "react-i18next";
+import { useTheme } from "@emotion/react";
+import Select from "../../../../../Components/Inputs/Select";
+import NetworkStatBoxes from "./NetworkStatBoxes";
+import NetworkCharts from "./NetworkCharts";
+import MonitorTimeFrameHeader from "../../../../../Components/MonitorTimeFrameHeader";
+
+const getAvailableInterfaces = (net) => {
+ return (net || []).map((iface) => iface.name).filter(Boolean);
+};
+
+const getNetworkInterfaceData = (checks, ifaceName) => {
+ if (!ifaceName) return [];
+
+ // Transform backend data structure for the selected interface
+ // Backend already calculates deltas, we just reshape the data
+ return (checks || [])
+ .map((check) => {
+ const networkInterface = (check.net || []).find(
+ (iface) => iface.name === ifaceName
+ );
+ if (!networkInterface) return null;
+ return {
+ _id: check._id,
+ bytesPerSec: networkInterface.deltaBytesRecv,
+ packetsPerSec: networkInterface.deltaPacketsRecv,
+ errors: networkInterface.deltaErrOut ?? 0,
+ drops: networkInterface.deltaDropOut ?? 0,
+ };
+ })
+ .filter(Boolean);
+};
+
+const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+
+ const availableInterfaces = getAvailableInterfaces(net);
+ const [selectedInterface, setSelectedInterface] = useState("");
+
+ // Set default interface when data loads
+ useEffect(() => {
+ if (availableInterfaces.length > 0 && !selectedInterface) {
+ setSelectedInterface(availableInterfaces[0]);
+ }
+ }, [availableInterfaces, selectedInterface]);
+
+ const ethernetData = getNetworkInterfaceData(checks, selectedInterface);
+
+ return (
+ <>
+
+
+ {availableInterfaces.length > 0 && (
+
+
+ >
+ );
+};
+
+Network.propTypes = {
+ net: PropTypes.array,
+ checks: PropTypes.array,
+ isLoading: PropTypes.bool.isRequired,
+ dateRange: PropTypes.string.isRequired,
+ setDateRange: PropTypes.func.isRequired,
+};
+
+export default Network;
diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/skeleton.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/skeleton.jsx
new file mode 100644
index 000000000..52fcfad87
--- /dev/null
+++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/skeleton.jsx
@@ -0,0 +1,56 @@
+import {
+ Card,
+ CardContent,
+ Skeleton,
+ Table,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableBody,
+} from "@mui/material";
+
+const SkeletonLayout = () => {
+ return (
+
+
+
+
+
+
+ Name
+ Bytes Sent
+ Bytes Received
+ Packets Sent
+ Packets Received
+ Errors In
+ Errors Out
+ Drops In
+ Drops Out
+
+
+
+ {Array.from({ length: 5 }).map((_, idx) => (
+
+ {Array.from({ length: 9 }).map((__, colIdx) => (
+
+
+
+ ))}
+
+ ))}
+
+
+
+
+ );
+};
+
+export default SkeletonLayout;
diff --git a/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx b/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx
index a2e4c5153..8edb6fb87 100644
--- a/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx
+++ b/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx
@@ -57,7 +57,7 @@ const useHardwareUtils = () => {
if (GB >= 1) {
return (
<>
- {Number(GB.toFixed(0))}
+ {Number(GB.toFixed(2))}
{space ? " " : ""}
{t("gb")}
>
@@ -65,7 +65,7 @@ const useHardwareUtils = () => {
} else {
return (
<>
- {Number(MB.toFixed(0))}
+ {Number(MB.toFixed(2))}
{space ? " " : ""}
{t("mb")}
>
@@ -73,6 +73,53 @@ const useHardwareUtils = () => {
}
};
+ const formatBytesPerSecondString = (bytesPerSec, space = false) => {
+ if (
+ bytesPerSec === undefined ||
+ bytesPerSec === null ||
+ typeof bytesPerSec !== "number" ||
+ bytesPerSec === 0
+ ) {
+ return `0${space ? " " : ""}B/s`;
+ }
+
+ const GB = bytesPerSec / (1024 * 1024 * 1024);
+ const MB = bytesPerSec / (1024 * 1024);
+ const KB = bytesPerSec / 1024;
+
+ if (GB >= 1) {
+ return `${Number(GB.toFixed(1))}${space ? " " : ""}GB/s`;
+ } else if (MB >= 1) {
+ return `${Number(MB.toFixed(1))}${space ? " " : ""}MB/s`;
+ } else if (KB >= 1) {
+ return `${Number(KB.toFixed(1))}${space ? " " : ""}KB/s`;
+ } else {
+ return `${Number(bytesPerSec.toFixed(1))}${space ? " " : ""}B/s`;
+ }
+ };
+
+ const formatPacketsPerSecondString = (packetsPerSec, space = false) => {
+ if (
+ packetsPerSec === undefined ||
+ packetsPerSec === null ||
+ typeof packetsPerSec !== "number" ||
+ packetsPerSec === 0
+ ) {
+ return `0${space ? " " : ""}pps`;
+ }
+
+ const M = packetsPerSec / (1000 * 1000);
+ const K = packetsPerSec / 1000;
+
+ if (M >= 1) {
+ return `${Number(M.toFixed(1))}${space ? " " : ""}Mpps`;
+ } else if (K >= 1) {
+ return `${Number(K.toFixed(1))}${space ? " " : ""}Kpps`;
+ } else {
+ return `${Math.round(packetsPerSec)}${space ? " " : ""}pps`;
+ }
+ };
+
/**
* Converts a decimal value to a percentage
*
@@ -134,6 +181,8 @@ const useHardwareUtils = () => {
decimalToPercentage,
buildTemps,
getDimensions,
+ formatBytesPerSecondString,
+ formatPacketsPerSecondString,
};
};
diff --git a/client/src/Pages/Infrastructure/Details/index.jsx b/client/src/Pages/Infrastructure/Details/index.jsx
index 30840851f..a16f3906b 100644
--- a/client/src/Pages/Infrastructure/Details/index.jsx
+++ b/client/src/Pages/Infrastructure/Details/index.jsx
@@ -1,5 +1,5 @@
// Components
-import { Stack, Typography } from "@mui/material";
+import { Stack, Typography, Tab } from "@mui/material";
import Breadcrumbs from "../../../Components/Breadcrumbs";
import MonitorDetailsControlHeader from "../../../Components/MonitorDetailsControlHeader";
import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader";
@@ -7,6 +7,9 @@ import StatusBoxes from "./Components/StatusBoxes";
import GaugeBoxes from "./Components/GaugeBoxes";
import AreaChartBoxes from "./Components/AreaChartBoxes";
import GenericFallback from "../../../Components/GenericFallback";
+import NetworkStats from "./Components/NetworkStats";
+import CustomTabList from "../../../Components/Tab";
+import TabContext from "@mui/lab/TabContext";
// Utils
import { useTheme } from "@emotion/react";
@@ -22,11 +25,10 @@ const BREADCRUMBS = [
{ name: "details", path: "" },
];
const InfrastructureDetails = () => {
- // Redux state
-
// Local state
const [dateRange, setDateRange] = useState("recent");
const [trigger, setTrigger] = useState(false);
+ const [tab, setTab] = useState("details");
// Utils
const theme = useTheme();
@@ -87,23 +89,52 @@ const InfrastructureDetails = () => {
monitor={monitor}
triggerUpdate={triggerUpdate}
/>
-
-
-
-
+
+ setTab(v)}
+ >
+
+
+
+ {tab === "details" && (
+ <>
+
+
+
+
+ >
+ )}
+ {tab === "network" && (
+
+ )}
+
);
};
diff --git a/client/src/Pages/Infrastructure/Monitors/index.jsx b/client/src/Pages/Infrastructure/Monitors/index.jsx
index f7ca892c2..bfd2270fe 100644
--- a/client/src/Pages/Infrastructure/Monitors/index.jsx
+++ b/client/src/Pages/Infrastructure/Monitors/index.jsx
@@ -1,12 +1,11 @@
// Components
-import { Stack, Typography } from "@mui/material";
+import { Stack } from "@mui/material";
import Breadcrumbs from "../../../Components/Breadcrumbs";
import MonitorCountHeader from "../../../Components/MonitorCountHeader";
import MonitorCreateHeader from "../../../Components/MonitorCreateHeader";
import MonitorsTable from "./Components/MonitorsTable";
import Pagination from "../../..//Components/Table/TablePagination";
-import GenericFallback from "../../../Components/GenericFallback";
-import Fallback from "../../../Components/Fallback";
+import PageStateWrapper from "../../../Components/PageStateWrapper";
import Filter from "./Components/Filters";
import SearchComponent from "../../Uptime/Monitors/Components/SearchComponent";
// Utils
@@ -81,75 +80,58 @@ const InfrastructureMonitors = () => {
updateTrigger,
});
- if (networkError === true) {
- return (
-
-
- {t("common.toasts.networkError")}
-
- {t("common.toasts.checkConnection")}
-
- );
- }
-
- if (!isLoading && typeof summary?.totalMonitors === "undefined") {
- return (
-
- );
- }
-
return (
-
-
-
+
-
-
-
-
-
+ items={monitors}
+ type="infrastructureMonitor"
+ fallbackLink="/infrastructure/create"
+ >
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+ >
);
};
diff --git a/client/src/Pages/Logs/Logs/index.jsx b/client/src/Pages/Logs/Logs/index.jsx
index b65f9c0fd..b44b3fd1a 100644
--- a/client/src/Pages/Logs/Logs/index.jsx
+++ b/client/src/Pages/Logs/Logs/index.jsx
@@ -51,16 +51,18 @@ const Logs = () => {
];
return (
-
- {t("logsPage.description")}
-
-
{t("logsPage.logLevelSelect.title")}
+
+
+
+ {t("createMonitorPage.incidentConfigTitle")}
+
+
+ {t("createMonitorPage.incidentConfigDescription")}
+
+
+
+
+
+
+
{
}, [isSearching]);
const isLoading = monitorsWithSummaryIsLoading || monitorsWithChecksIsLoading;
- if (networkError) {
- return (
-
-
- {t("common.toasts.networkError")}
-
- {t("common.toasts.checkConnection")}
-
- );
- }
- if (
- !isLoading &&
- (monitorsSummary?.totalMonitors === 0 ||
- typeof monitorsSummary?.totalMonitors === "undefined")
- ) {
- return (
-
- );
- }
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
);
};
diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx
index 350d2f82d..7ddcd7e3c 100644
--- a/client/src/Routes/index.jsx
+++ b/client/src/Routes/index.jsx
@@ -223,7 +223,7 @@ const Routes = () => {
}
+ element={}
/>
({
"& .MuiSelect-icon": {
color: theme.palette.primary.contrastTextSecondary, // Dropdown + color
},
+ "& .MuiSelect-select": {
+ display: "flex",
+ alignItems: "center",
+ },
"&:hover": {
backgroundColor: theme.palette.primary.main, // Background on hover
},
diff --git a/client/src/Validation/validation.js b/client/src/Validation/validation.js
index de248f87b..55141be3e 100644
--- a/client/src/Validation/validation.js
+++ b/client/src/Validation/validation.js
@@ -115,15 +115,27 @@ const monitorValidation = joi.object({
_id: joi.string(),
userId: joi.string(),
teamId: joi.string(),
+ statusWindowSize: joi.number().min(1).max(20).default(5).messages({
+ "number.base": "Status window size must be a number.",
+ "number.min": "Status window size must be at least 1.",
+ "number.max": "Status window size must be at most 20.",
+ }),
+ statusWindowThreshold: joi.number().min(1).max(100).default(60).messages({
+ "number.base": "Incident percentage must be a number.",
+ "number.min": "Incident percentage must be at least 1.",
+ "number.max": "Incident percentage must be at most 100.",
+ }),
url: joi.when("type", {
is: "docker",
then: joi
.string()
.trim()
- .regex(/^[a-z0-9]{64}$/)
+ .regex(
+ /^(\/+)?([a-zA-Z0-9][a-zA-Z0-9_.-]*[a-zA-Z0-9]|[a-zA-Z0-9]+|[a-f0-9]{12,64})$/
+ )
.messages({
"string.empty": "This field is required.",
- "string.pattern.base": "Please enter a valid 64-character Docker container ID.",
+ "string.pattern.base": "Please enter a valid container name or ID.",
}),
otherwise: joi
.string()
@@ -150,6 +162,11 @@ const monitorValidation = joi.object({
// can be replaced by a shortest alternative
// (?![-_])(?:[-\\w\\u00a1-\\uffff]{0,63}[^-_]\\.)+
"(?:" +
+ // Single hostname without dots (like localhost)
+ "[a-z0-9\\u00a1-\\uffff][a-z0-9\\u00a1-\\uffff_-]{0,62}" +
+ "|" +
+ // Domain with dots
+ "(?:" +
"(?:" +
"[a-z0-9\\u00a1-\\uffff]" +
"[a-z0-9\\u00a1-\\uffff_-]{0,62}" +
@@ -159,6 +176,7 @@ const monitorValidation = joi.object({
// TLD identifier name, may end with dot
"(?:[a-z\\u00a1-\\uffff]{2,}\\.?)" +
")" +
+ ")" +
// port number (optional)
"(?::\\d{2,5})?" +
// resource path (optional)
@@ -439,21 +457,35 @@ const notificationValidation = joi.object({
}),
address: joi.when("type", {
- is: "email",
- then: joi
- .string()
- .email({ tlds: { allow: false } })
- .required()
- .messages({
- "string.empty": "E-mail address cannot be empty",
- "any.required": "E-mail address is required",
- "string.email": "Please enter a valid e-mail address",
- }),
- otherwise: joi.string().uri().required().messages({
- "string.empty": "Webhook URL cannot be empty",
- "any.required": "Webhook URL is required",
- "string.uri": "Please enter a valid Webhook URL",
- }),
+ switch: [
+ {
+ is: "email",
+ then: joi
+ .string()
+ .email({ tlds: { allow: false } })
+ .required()
+ .messages({
+ "string.empty": "E-mail address cannot be empty",
+ "any.required": "E-mail address is required",
+ "string.email": "Please enter a valid e-mail address",
+ }),
+ },
+ {
+ is: "pager_duty",
+ then: joi.string().required().messages({
+ "string.empty": "PagerDuty routing key cannot be empty",
+ "any.required": "PagerDuty routing key is required",
+ }),
+ },
+ {
+ is: joi.valid("webhook", "slack", "discord"),
+ then: joi.string().uri().required().messages({
+ "string.empty": "Webhook URL cannot be empty",
+ "any.required": "Webhook URL is required",
+ "string.uri": "Please enter a valid Webhook URL",
+ }),
+ },
+ ],
}),
});
diff --git a/client/src/locales/ar.json b/client/src/locales/ar.json
index 33f245fd5..60a2db4a4 100644
--- a/client/src/locales/ar.json
+++ b/client/src/locales/ar.json
@@ -260,7 +260,6 @@
"notifyEmails": "",
"seperateEmails": "",
"checkFrequency": "",
- "chooseGame": "",
"matchMethod": "",
"expectedValue": "",
"deleteDialogTitle": "",
@@ -569,7 +568,8 @@
"password": {
"incorrect": ""
}
- }
+ },
+ "welcome": ""
},
"registration": {
"heading": {
@@ -595,7 +595,8 @@
},
"toasts": {
"success": ""
- }
+ },
+ "welcome": ""
},
"forgotPassword": {
"heading": "",
@@ -847,6 +848,10 @@
"selectDisabled": "",
"selectEnabled": "",
"title": ""
+ },
+ "globalThresholds": {
+ "title": "",
+ "description": ""
}
},
"statusPageCreate": {
@@ -915,7 +920,12 @@
"twentyMinutes": "",
"oneHour": "",
"oneDay": "",
- "oneWeek": ""
+ "oneWeek": "",
+ "fourMinutes": "",
+ "oneMinute": "",
+ "twoMinutes": "",
+ "fifteenSeconds": "",
+ "thirtySeconds": ""
},
"general": {
"noOptionsFound": ""
@@ -967,5 +977,75 @@
}
},
"incidentsPageActionResolveMonitor": "",
- "incidentsPageActionResolveAll": ""
+ "incidentsPageActionResolveAll": "",
+ "matchMethodOptions": {
+ "equal": "",
+ "equalPlaceholder": "",
+ "include": "",
+ "includePlaceholder": "",
+ "regex": "",
+ "regexPlaceholder": "",
+ "text": ""
+ },
+ "monitorType": {
+ "docker": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "http": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "ping": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "port": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "game": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ }
+ },
+ "uptimeAdvancedMatching": {
+ "jsonPath": ""
+ },
+ "bytesPerSecond": "",
+ "bytesReceived": "",
+ "bytesSent": "",
+ "chooseGame": "",
+ "createMonitorPage": {
+ "incidentConfigDescription": "",
+ "incidentConfigStatusWindowLabel": "",
+ "incidentConfigStatusWindowThresholdLabel": "",
+ "incidentConfigTitle": ""
+ },
+ "dataRate": "",
+ "dataReceived": "",
+ "dataSent": "",
+ "details": "",
+ "drops": "",
+ "errors": "",
+ "errorsIn": "",
+ "errorsOut": "",
+ "gameServerMonitoring": "",
+ "gameServerMonitoringDescription": "",
+ "network": "",
+ "networkDrops": "",
+ "networkErrors": "",
+ "networkInterface": "",
+ "noNetworkStatsAvailable": "",
+ "packetsPerSecond": "",
+ "packetsReceived": "",
+ "packetsReceivedRate": "",
+ "packetsSent": "",
+ "rate": "",
+ "selectInterface": ""
}
diff --git a/client/src/locales/cs.json b/client/src/locales/cs.json
index 1fb579270..10322c471 100644
--- a/client/src/locales/cs.json
+++ b/client/src/locales/cs.json
@@ -260,7 +260,6 @@
"notifyEmails": "",
"seperateEmails": "",
"checkFrequency": "",
- "chooseGame": "",
"matchMethod": "",
"expectedValue": "",
"deleteDialogTitle": "",
@@ -569,7 +568,8 @@
"password": {
"incorrect": ""
}
- }
+ },
+ "welcome": ""
},
"registration": {
"heading": {
@@ -595,7 +595,8 @@
},
"toasts": {
"success": "Vítejte! Váš účet byl úspěšně vytvořen."
- }
+ },
+ "welcome": ""
},
"forgotPassword": {
"heading": "Zapomenuté heslo",
@@ -847,6 +848,10 @@
"selectDisabled": "",
"selectEnabled": "",
"title": ""
+ },
+ "globalThresholds": {
+ "title": "",
+ "description": ""
}
},
"statusPageCreate": {
@@ -915,7 +920,12 @@
"twentyMinutes": "",
"oneHour": "",
"oneDay": "",
- "oneWeek": ""
+ "oneWeek": "",
+ "fourMinutes": "",
+ "oneMinute": "",
+ "twoMinutes": "",
+ "fifteenSeconds": "",
+ "thirtySeconds": ""
},
"general": {
"noOptionsFound": ""
@@ -967,5 +977,75 @@
}
},
"incidentsPageActionResolveMonitor": "",
- "incidentsPageActionResolveAll": ""
+ "incidentsPageActionResolveAll": "",
+ "matchMethodOptions": {
+ "equal": "",
+ "equalPlaceholder": "",
+ "include": "",
+ "includePlaceholder": "",
+ "regex": "",
+ "regexPlaceholder": "",
+ "text": ""
+ },
+ "monitorType": {
+ "docker": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "http": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "ping": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "port": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "game": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ }
+ },
+ "uptimeAdvancedMatching": {
+ "jsonPath": ""
+ },
+ "bytesPerSecond": "",
+ "bytesReceived": "",
+ "bytesSent": "",
+ "chooseGame": "",
+ "createMonitorPage": {
+ "incidentConfigDescription": "",
+ "incidentConfigStatusWindowLabel": "",
+ "incidentConfigStatusWindowThresholdLabel": "",
+ "incidentConfigTitle": ""
+ },
+ "dataRate": "",
+ "dataReceived": "",
+ "dataSent": "",
+ "details": "",
+ "drops": "",
+ "errors": "",
+ "errorsIn": "",
+ "errorsOut": "",
+ "gameServerMonitoring": "",
+ "gameServerMonitoringDescription": "",
+ "network": "",
+ "networkDrops": "",
+ "networkErrors": "",
+ "networkInterface": "",
+ "noNetworkStatsAvailable": "",
+ "packetsPerSecond": "",
+ "packetsReceived": "",
+ "packetsReceivedRate": "",
+ "packetsSent": "",
+ "rate": "",
+ "selectInterface": ""
}
diff --git a/client/src/locales/de.json b/client/src/locales/de.json
index 4ac77ef29..115d9733f 100644
--- a/client/src/locales/de.json
+++ b/client/src/locales/de.json
@@ -51,14 +51,14 @@
"label": "Slack",
"description": "Um Slack-Benachrichtigungen zu aktivieren, erstelle eine Slack-App und aktiviere eingehende Webhooks. Danach gib einfach die Webhook-URL hier ein.",
"webhookLabel": "Webhook URL",
- "webhookPlaceholder": "",
+ "webhookPlaceholder": "https://hooks.slack.com/services/...",
"webhookRequired": "Slack webhook URL wird benötigt"
},
"discord": {
"label": "Discord",
"description": "Um Daten von Checkmate an einen Discord-Kanal über Discord-Benachrichtigungen mithilfe von Webhooks zu senden, kannst Du die eingehenden Webhooks von Discord verwenden.",
"webhookLabel": "Discord Webhook URL",
- "webhookPlaceholder": "",
+ "webhookPlaceholder": "https://discord.com/api/webhooks/...",
"webhookRequired": "Discord webhook URL wird benötigt"
},
"telegram": {
@@ -78,33 +78,37 @@
"urlRequired": "Webhook URL wird benötigt"
},
"testNotificationDevelop": "Testbenachrichtigung 2",
- "integrationButton": "",
- "testSuccess": "",
- "testFailed": "",
- "unsupportedType": "",
- "networkError": "",
+ "integrationButton": "Benachrichtigungsintegration",
+ "testSuccess": "Testbenachrichtigung erfolgreich gesendet!",
+ "testFailed": "Senden der Testbenachrichtigung fehlgeschlagen",
+ "unsupportedType": "Nicht unterstützter Benachrichtigungstyp",
+ "networkError": "Netzwerkfehler aufgetreten",
"fallback": {
- "title": "",
- "checks": [""],
- "actionButton": ""
+ "title": "Ein Benachrichtigungskanal wird verwendet, um:",
+ "checks": [
+ "Teams über Ausfallzeiten oder Leistungsprobleme informieren",
+ "Informieren Sie Techniker über Vorfälle",
+ "Informieren Sie Administratoren über Systemänderungen"
+ ],
+ "actionButton": "Lassen Sie uns Ihren ersten Benachrichtigungskanal erstellen!"
},
- "createButton": "",
- "createTitle": "",
+ "createButton": "Benachrichtigungskanal erstellen",
+ "createTitle": "Benachrichtigungskanal",
"create": {
- "success": "",
- "failed": ""
+ "success": "Benachrichtigung erfolgreich erstellt",
+ "failed": "Benachrichtigung konnte nicht erstellt werden"
},
"fetch": {
- "success": "",
- "failed": ""
+ "success": "Benachrichtigungen erfolgreich abgerufen",
+ "failed": "Abrufen der Benachrichtigungen fehlgeschlagen"
},
"delete": {
- "success": "",
- "failed": ""
+ "success": "Benachrichtigung erfolgreich gelöscht",
+ "failed": "Benachrichtigung konnte nicht gelöscht werden"
},
"edit": {
- "success": "",
- "failed": ""
+ "success": "Benachrichtigung erfolgreich aktualisiert",
+ "failed": "Benachrichtigung konnte nicht aktualisiert werden"
},
"test": {
"success": "Test Benachrichtigung erfolgreich gesendet",
@@ -227,7 +231,7 @@
"createMaintenance": "Erstelle Wartungsfenster",
"editMaintenance": "Ändere Wartungsfenster",
"maintenanceWindowName": "Name des Wartungsfensters",
- "friendlyNameInput": "",
+ "friendlyNameInput": "Freundlicher Name",
"friendlyNamePlaceholder": "Wartung um __ : __ für ___ Minuten",
"maintenanceRepeat": "Wartungsfenster wiederholen",
"maintenance": "Wartungsfenster",
@@ -247,7 +251,7 @@
"edit": "Ändern",
"createA": "Erstelle ein",
"remove": "Löschen",
- "maintenanceWindowDescription": "",
+ "maintenanceWindowDescription": "Pings werden während dieses Zeitraums nicht gesendet",
"startTime": "Anfangszeit",
"timeZoneInfo": "Alle Daten und Zeiten sind in der Zeitzone GMT+0.",
"monitorsToApply": "Monitore, auf die das Wartungsfenster angewendet werden soll",
@@ -260,79 +264,78 @@
"notifyEmails": "Benachrichtige auch per E-Mail an mehrere Adressen (kommt bald)",
"seperateEmails": "Du kannst mehrere E-Mails mit einem Komma trennen",
"checkFrequency": "Überprüfungsfrequenz",
- "chooseGame": "",
- "matchMethod": "",
+ "matchMethod": "Übereinstimmungsmethode",
"expectedValue": "Erwarteter Wert",
"deleteDialogTitle": "Möchtest du diesen Monitor wirklich löschen?",
"deleteDialogDescription": "Sobald dieser Monitor gelöscht ist, kann er nicht wiederhergestellt werden.",
- "pageSpeedMonitor": "",
- "shown": "",
+ "pageSpeedMonitor": "PageSpeed-Monitor",
+ "shown": "Gezeigt",
"ago": "vor",
- "companyName": "",
- "pageSpeedDetailsPerformanceReport": "",
- "pageSpeedDetailsPerformanceReportCalculator": "",
- "checkingEvery": "",
- "statusPageCreateSettings": "",
- "basicInformation": "",
- "statusPageCreateBasicInfoDescription": "",
- "statusPageCreateSelectTimeZoneDescription": "",
- "statusPageCreateAppearanceDescription": "",
- "statusPageCreateSettingsCheckboxLabel": "",
- "statusPageCreateBasicInfoStatusPageAddress": "",
- "statusPageCreateTabsContent": "",
- "statusPageCreateTabsContentDescription": "",
- "statusPageCreateTabsContentFeaturesDescription": "",
- "showCharts": "",
- "showUptimePercentage": "",
+ "companyName": "Name der Firma",
+ "pageSpeedDetailsPerformanceReport": "Die Werte sind geschätzt und können variieren.",
+ "pageSpeedDetailsPerformanceReportCalculator": "Rechner öffnen",
+ "checkingEvery": "Überprüfen alle",
+ "statusPageCreateSettings": "Wenn Ihre Statusseite fertig ist, können Sie sie als veröffentlicht markieren.",
+ "basicInformation": "Grundlegende Informationen",
+ "statusPageCreateBasicInfoDescription": "Definieren Sie den Firmennamen und die Subdomäne, auf die Ihre Statusseite verweist.",
+ "statusPageCreateSelectTimeZoneDescription": "Wählen Sie die Zeitzone aus, in der Ihre Statusseite angezeigt wird.",
+ "statusPageCreateAppearanceDescription": "Definieren Sie das Standard-Erscheinungsbild Ihrer öffentlichen Statusseite.",
+ "statusPageCreateSettingsCheckboxLabel": "Veröffentlicht und für die Öffentlichkeit sichtbar",
+ "statusPageCreateBasicInfoStatusPageAddress": "Ihre Statusseitenadresse",
+ "statusPageCreateTabsContent": "Statusseitenserver",
+ "statusPageCreateTabsContentDescription": "Sie können Ihrer Statusseite beliebig viele Server hinzufügen, die Sie überwachen. Sie die Reihenfolge der Server ändern.",
+ "statusPageCreateTabsContentFeaturesDescription": "Mehr Details auf der Statusseite anzeigen",
+ "showCharts": "Diagramme anzeigen",
+ "showUptimePercentage": "Verfügbarkeitsprozentsatz anzeigen",
"removeLogo": "Entferne Logo",
- "statusPageStatus": "",
- "statusPageStatusContactAdmin": "",
- "statusPageStatusNotPublic": "",
- "statusPageStatusNoPage": "",
- "statusPageStatusServiceStatus": "",
- "deleteStatusPage": "",
- "deleteStatusPageConfirm": "",
- "deleteStatusPageDescription": "",
- "uptimeCreate": "",
- "uptimeCreateJsonPath": "",
- "uptimeCreateJsonPathQuery": "",
- "maintenanceTableActionMenuDialogTitle": "",
- "infrastructureEditYour": "",
- "infrastructureEditMonitor": "",
- "infrastructureMonitorCreated": "",
- "infrastructureMonitorUpdated": "",
- "errorInvalidTypeId": "",
- "errorInvalidFieldId": "",
- "inviteNoTokenFound": "",
- "pageSpeedWarning": "",
+ "statusPageStatus": "Eine öffentliche Statusseite ist nicht eingerichtet.",
+ "statusPageStatusContactAdmin": "Bitte wenden Sie sich an Ihren Administrator",
+ "statusPageStatusNotPublic": "Diese Statusseite ist nicht öffentlich.",
+ "statusPageStatusNoPage": "Hier gibt es keine Statusseite.",
+ "statusPageStatusServiceStatus": "Dienststatus",
+ "deleteStatusPage": "Möchten Sie diese Statusseite löschen?",
+ "deleteStatusPageConfirm": "Ja, Statusseite löschen",
+ "deleteStatusPageDescription": "Nach dem Löschen kann Ihre Statusseite nicht mehr abgerufen werden.",
+ "uptimeCreate": "Der erwartete Wert wird zum Abgleich mit dem Antwortergebnis verwendet und die Übereinstimmung bestimmt den Status.",
+ "uptimeCreateJsonPath": "Dieser Ausdruck wird anhand der JSON-Antwortdaten ausgewertet und das Ergebnis wird zum Abgleich mit dem erwarteten Wert verwendet. Siehe",
+ "uptimeCreateJsonPathQuery": "für die Dokumentation der Abfragesprache.",
+ "maintenanceTableActionMenuDialogTitle": "Möchten Sie dieses Wartungsfenster wirklich entfernen?",
+ "infrastructureEditYour": "Bearbeiten Sie Ihre",
+ "infrastructureEditMonitor": "Infrastrukturmonitor speichern",
+ "infrastructureMonitorCreated": "Infrastrukturmonitor erfolgreich erstellt!",
+ "infrastructureMonitorUpdated": "Infrastrukturmonitor erfolgreich aktualisiert!",
+ "errorInvalidTypeId": "Ungültiger Benachrichtigungstyp angegeben",
+ "errorInvalidFieldId": "Ungültige Feld-ID angegeben",
+ "inviteNoTokenFound": "Kein Einladungstoken gefunden",
+ "pageSpeedWarning": "Warnung: Sie haben noch keinen Google PageSpeed API-Schlüssel hinzugefügt. Ohne diesen funktioniert der PageSpeed-Monitor nicht.",
"pageSpeedLearnMoreLink": "Hier klicken",
"pageSpeedAddApiKey": "um einen API key einzutragen",
"update": "Update",
"invalidFileFormat": "Nicht unterstützes Dateiformat",
"invalidFileSize": "Dateigröße zu groß!",
- "ClickUpload": "",
+ "ClickUpload": "Zum Hochladen klicken",
"DragandDrop": "drag and drop",
"MaxSize": "Maximalgröße",
"SupportedFormats": "Unterstützte Formate",
"FirstName": "Vorname",
"LastName": "Zuname",
- "EmailDescriptionText": "",
+ "EmailDescriptionText": "Dies ist Ihre aktuelle E-Mail-Adresse – sie kann nicht geändert werden.",
"YourPhoto": "Profilphoto",
- "PhotoDescriptionText": "",
+ "PhotoDescriptionText": "Dieses Foto wird auf Ihrer Profilseite angezeigt.",
"save": "Speichern",
- "DeleteDescriptionText": "",
- "DeleteAccountWarning": "",
+ "DeleteDescriptionText": "Dadurch werden das Konto und alle zugehörigen Daten vom Server entfernt. Dieser Vorgang kann nicht rückgängig gemacht werden.",
+ "DeleteAccountWarning": "Wenn Sie Ihr Konto löschen, können Sie sich nicht mehr anmelden und alle Ihre Daten werden gelöscht. Dieser Vorgang kann nicht rückgängig gemacht werden.",
"DeleteWarningTitle": "Account wirklich löschen?",
"bulkImport": {
- "title": "",
- "selectFileTips": "",
- "selectFileDescription": "",
+ "title": "Massenimport",
+ "selectFileTips": "Wählen Sie die CSV-Datei zum Hochladen aus",
+ "selectFileDescription": "Sie können unsere Vorlage oder Beispiel herunterladen.",
"selectFile": "Datei auswählen",
- "parsingFailed": "",
+ "parsingFailed": "Parsen fehlgeschlagen",
"uploadSuccess": "Monitore erfolgreich erstellt!",
"validationFailed": "Prüfung fehlgeschlagen",
"noFileSelected": "Keine Datei ausgewählt",
- "fallbackPage": "",
+ "fallbackPage": "Importieren Sie eine Datei, um eine Liste von Servern gleichzeitig hochzuladen",
"invalidFileType": "Ungültiger Dateityp",
"uploadFailed": "Upload fehlgeschlagen"
},
@@ -347,8 +350,8 @@
"createNew": "Neu anlegen",
"greeting": {
"prepend": "Hallo",
- "append": "",
- "overview": ""
+ "append": "Der Nachmittag gehört Ihnen – machen wir ihn episch!",
+ "overview": "Hier ist eine Übersicht Ihrer {{type}}-Monitore."
},
"roles": {
"superAdmin": "Super admin",
@@ -364,7 +367,7 @@
},
"inviteTeamMember": "Team Mirglied einladen",
"inviteNewTeamMember": "Neues Team Mirglied einladen",
- "inviteDescription": "",
+ "inviteDescription": "Wenn Sie ein neues Teammitglied hinzufügen, erhält dieses Zugriff auf alle Monitore.",
"email": "Email",
"selectRole": "Rolle auswählen",
"inviteLink": "Einladungslink",
@@ -381,17 +384,17 @@
},
"monitorState": {
"paused": "Pausiert",
- "resumed": "",
+ "resumed": "Wiederaufgenommen",
"active": "Aktiv"
},
"menu": {
"uptime": "Uptime",
- "pagespeed": "",
- "infrastructure": "",
- "incidents": "",
+ "pagespeed": "Pagespeed",
+ "infrastructure": "Infrastruktur",
+ "incidents": "Vorfälle",
"statusPages": "Statusseite",
"maintenance": "Wartung",
- "integrations": "",
+ "integrations": "Integrationen",
"settings": "Einstellungen",
"support": "Support",
"discussions": "Diskussionen",
@@ -404,12 +407,12 @@
"notifications": "Benachrichtigungen",
"logs": "Logs"
},
- "settingsEmailUser": "",
- "state": "",
+ "settingsEmailUser": "E-Mail-Benutzer – Benutzername zur Authentifizierung, überschreibt die E-Mail-Adresse, falls angegeben",
+ "state": "Zustand",
"statusBreadCrumbsStatusPages": "Statusseite",
"statusBreadCrumbsDetails": "Details",
"commonSaving": "Speichere...",
- "navControls": "",
+ "navControls": "Steuerungen",
"incidentsPageTitle": "Ereignisse",
"passwordPanel": {
"passwordChangedSuccess": "Dein Passwort wurde erfolgreich geändert.",
@@ -430,22 +433,22 @@
"settingsTestEmailUnknownError": "Unbekannter Fehler",
"statusMsg": {
"paused": "Monitoring pausiert",
- "up": "",
- "down": "",
- "pending": ""
+ "up": "Ihre Site ist online.",
+ "down": "Ihre Site ist nicht erreichbar.",
+ "pending": "Ausstehend..."
},
"uptimeGeneralInstructions": {
- "http": "",
- "ping": "",
- "docker": "",
- "port": "",
+ "http": "Geben Sie die zu überwachende URL oder IP ein (z. B. https://example.com/ oder 192.168.1.100) und fügen Sie einen eindeutigen Anzeigenamen hinzu, der auf dem Dashboard angezeigt wird.",
+ "ping": "Geben Sie die IP-Adresse oder den Hostnamen ein, der angepingt werden soll (z. B. 192.168.1.100 oder example.com), und fügen Sie einen eindeutigen Anzeigenamen hinzu, der auf dem Dashboard angezeigt wird.",
+ "docker": "Geben Sie die Docker-ID Ihres Containers ein. Docker-IDs müssen die vollständige 64-Zeichen-ID sein. Sie können docker inspect ausführen, um die vollständige Container-ID abzurufen.",
+ "port": "Geben Sie die URL oder IP des Servers, die Portnummer und einen eindeutigen Anzeigenamen ein, der auf dem Dashboard angezeigt wird.",
"game": ""
},
"common": {
"appName": "Checkmate",
"monitoringAgentName": "Capture",
"buttons": {
- "toggleTheme": ""
+ "toggleTheme": "Schaltet hell und dunkel um"
},
"toasts": {
"networkError": "Netzwerkfehler",
@@ -465,7 +468,7 @@
"placeholder": "jordan.ellis@domain.com",
"errors": {
"empty": "Email adresse eingeben um fortzufahren",
- "invalid": ""
+ "invalid": "Bitte überprüfen Sie die Gültigkeit der eingegebenen E-Mail-Adresse erneut"
}
},
"password": {
@@ -492,7 +495,7 @@
"highlighted": "einen Kleinbuchstaben enthalten"
},
"match": {
- "beginning": "",
+ "beginning": "Passwort und Kennwort bestätigen",
"highlighted": "muß übereinstimmen"
}
},
@@ -544,7 +547,7 @@
},
"role": {
"errors": {
- "min": ""
+ "min": "Mindestens eine Rolle ist erforderlich"
}
}
}
@@ -569,7 +572,8 @@
"password": {
"incorrect": "Das Passwort stimmt nicht mit dem gespeicherten überein"
}
- }
+ },
+ "welcome": ""
},
"registration": {
"heading": {
@@ -577,25 +581,26 @@
"user": "Anmelden"
},
"subheadings": {
- "stepOne": "",
+ "stepOne": "Geben Sie Ihre persönlichen Daten ein",
"stepTwo": "Email eingeben",
"stepThree": "Passwort eingeben"
},
"description": {
"superAdmin": "Super admin account anlegen um zu beginnen",
- "user": ""
+ "user": "Melden Sie sich als Benutzer an und bitten Sie den Superadministrator um Zugriff auf Ihre Monitore"
},
"gettingStartedButton": {
"superAdmin": "Super admin account anlegen",
"user": "Normalen Benutzer anlegen"
},
- "termsAndPolicies": "",
+ "termsAndPolicies": "Indem Sie ein Konto erstellen, stimmen Sie unseren Servicebedingungen und Datenschutzrichtlinien zu.",
"links": {
"login": "Du hast bereits einen Benutzer? Anmelden"
},
"toasts": {
"success": "Willkommen! Benutzer erfolgreich angelegt"
- }
+ },
+ "welcome": ""
},
"forgotPassword": {
"heading": "Passwort vergessen?",
@@ -624,7 +629,7 @@
"emailNotFound": "Email nicht gefunden.",
"redirect": "Umleitung in ...",
"success": "Passwort erfolgreich zurückgesetzt.",
- "error": ""
+ "error": "Kennwort konnte nicht zurückgesetzt werden. Bitte versuchen Sie es später erneut oder wenden Sie sich an den Support."
}
}
},
@@ -635,7 +640,7 @@
"stillUnreachable": "Server immer noch nicht erreichbar. Bitte später erneut versuchen."
},
"alertBox": "Server Verbindungsfehler",
- "description": "",
+ "description": "Wir können keine Verbindung zum Server herstellen. Bitte überprüfen Sie Ihre Internetverbindung oder Ihre Bereitstellungskonfiguration, falls das Problem weiterhin besteht.",
"retryButton": {
"default": "Verbindung erneut versuchen",
"processing": "Verbinde..."
@@ -643,16 +648,16 @@
}
},
"createNotifications": {
- "title": "",
+ "title": "Benachrichtigungskanal erstellen",
"nameSettings": {
"title": "Name",
- "description": "",
+ "description": "Ein beschreibender Name für Ihre Integration.",
"nameLabel": "Name",
"namePlaceholder": "z.B. Slack Benachrichtigung"
},
"typeSettings": {
"title": "Typ",
- "description": "",
+ "description": "Wählen Sie den Typ des Benachrichtigungskanals aus, den Sie erstellen möchten.",
"typeLabel": "Typ"
},
"emailSettings": {
@@ -665,7 +670,7 @@
"title": "Slack",
"description": "Slack webhook hier konfigurieren",
"webhookLabel": "Slack webhook URL",
- "webhookPlaceholder": ""
+ "webhookPlaceholder": "https://hooks.slack.com/services/..."
},
"pagerdutySettings": {
"title": "PagerDuty",
@@ -685,38 +690,38 @@
"webhookLabel": "Webhook URL",
"webhookPlaceholder": "https://your-server.com/webhook"
},
- "testNotification": "",
- "dialogDeleteTitle": "",
- "dialogDeleteConfirm": ""
+ "testNotification": "Testbenachrichtigung",
+ "dialogDeleteTitle": "Möchten Sie diese Benachrichtigung wirklich löschen?",
+ "dialogDeleteConfirm": "Löschen"
},
"notificationConfig": {
"title": "Benachrichtigungen",
- "description": ""
+ "description": "Wählen Sie die Benachrichtigungskanäle aus, die Sie verwenden möchten"
},
"monitorStatus": {
- "checkingEvery": "",
- "withCaptureAgent": "",
+ "checkingEvery": "Überprüfung alle {{interval}}",
+ "withCaptureAgent": "mit Capture-Agent {{version}}",
"up": "hoch",
"down": "runter",
"paused": "pausiert"
},
- "advancedMatching": "",
+ "advancedMatching": "Erweiterter Abgleich",
"sendTestNotifications": "Test Benachrichtigung senden",
"selectAll": "Alle auswählen",
- "showAdminLoginLink": "",
+ "showAdminLoginLink": "Link „Administrator? Hier anmelden“ auf der Statusseite anzeigen",
"logsPage": {
"title": "Logs",
- "description": "",
+ "description": "Systemprotokolle – letzte 1000 Zeilen",
"tabs": {
"queue": "Warteschlange",
"logs": "Server logs",
- "diagnostics": ""
+ "diagnostics": "Diagnose"
},
"toast": {
- "fetchLogsSuccess": ""
+ "fetchLogsSuccess": "Protokolle erfolgreich abgerufen"
},
"logLevelSelect": {
- "title": "",
+ "title": "Protokollebene",
"values": {
"all": "Alle",
"info": "Info",
@@ -736,7 +741,7 @@
"urlHeader": "URL",
"typeHeader": "Typ",
"activeHeader": "Aktiv",
- "lockedAtHeader": "",
+ "lockedAtHeader": "Gesperrt bei",
"runCountHeader": "Anzahl Läufe",
"failCountHeader": "Anzahl Fehler",
"lastRunHeader": "Letzter Lauf um",
@@ -744,8 +749,8 @@
"lastRunTookHeader": "Letzter Lauf dauerte"
},
"metricsTable": {
- "title": "",
- "metricHeader": "",
+ "title": "Warteschlangenmetriken",
+ "metricHeader": "Metrisch",
"valueHeader": "Wert"
},
"failedJobTable": {
@@ -766,9 +771,9 @@
"title": "Export/Import",
"import": "Monitore importieren",
"export": "Monitore exportieren",
- "deleteSuccess": "",
- "deleteFailed": "",
- "details": ""
+ "deleteSuccess": "Monitor erfolgreich gelöscht",
+ "deleteFailed": "Monitor konnte nicht gelöscht werden",
+ "details": "Details"
},
"settingsPage": {
"aboutSettings": {
@@ -783,43 +788,43 @@
},
"emailSettings": {
"buttonSendTestEmail": "Test-Email senden",
- "description": "",
- "descriptionTransport": "",
- "labelAddress": "",
- "labelConnectionHost": "",
- "labelHost": "",
+ "description": "Konfigurieren Sie die E-Mail-Einstellungen für Ihr System. Diese werden zum Senden von Benachrichtigungen und Warnungen verwendet.",
+ "descriptionTransport": "Dies erstellt einen SMTP-Transport für NodeMailer",
+ "labelAddress": "E-Mail-Adresse - Wird zur Authentifizierung verwendet",
+ "labelConnectionHost": "E-Mail-Verbindungshost – Hostname zur Verwendung in der HELO/EHLO-Begrüßung",
+ "labelHost": "E-Mail-Host – Hostname oder IP-Adresse, mit der eine Verbindung hergestellt werden soll",
"labelIgnoreTLS": "STARTTLS abschalten: TLS nicht nutzen, selbst wenn der Server es unterstützt",
"labelPassword": "Email password - Password für Anmeldung",
"labelPasswordSet": "Passwort erstellt. Reset drücken um es zu ändern.",
- "labelPool": "",
+ "labelPool": "Verbindungspooling aktivieren: Vorhandene Verbindungen wiederverwenden, um die Leistung zu verbessern",
"labelPort": "Email port - Port zum verbinden",
"labelRejectUnauthorized": "Ungültige Zertifikate ablehnen. Verbindungen mit self-signed oder nicht vertrauenswürdigen Zertifikaten ablehnen.",
- "labelRequireTLS": "",
+ "labelRequireTLS": "STARTTLS erzwingen: TLS-Upgrade erforderlich, schlägt fehl, wenn nicht unterstützt",
"labelSecure": "SSL verwenden (empfohlen): Verbindung mit SSL/TLS verschlüsseln",
- "labelTLSServername": "",
- "labelUser": "",
- "linkTransport": "",
+ "labelTLSServername": "TLS-Servername – Optionaler Hostname für die TLS-Validierung, wenn der Host eine IP ist",
+ "labelUser": "E-Mail-Benutzer – Benutzername zur Authentifizierung, überschreibt die E-Mail-Adresse, falls angegeben",
+ "linkTransport": "Spezifikationen finden Sie hier",
"placeholderUser": "Leer lassen, wenn nicht benötigt",
"title": "Email",
- "toastEmailRequiredFieldsError": ""
+ "toastEmailRequiredFieldsError": "E-Mail-Adresse, Host, Port und Passwort sind erforderlich"
},
"pageSpeedSettings": {
- "description": "",
+ "description": "Geben Sie Ihren Google PageSpeed API-Schlüssel ein, um die Google PageSpeed-Überwachung zu aktivieren. Klicken Sie auf „Zurücksetzen“, um den Schlüssel zu aktualisieren.",
"labelApiKeySet": "API key erstellt. Reset drücken um ihn zu ändern.",
"labelApiKey": "PageSpeed API key",
"title": "Google PageSpeed API key"
},
"saveButtonLabel": "Speichern",
"statsSettings": {
- "clearAllStatsButton": "",
- "clearAllStatsDescription": "",
- "clearAllStatsDialogConfirm": "",
- "clearAllStatsDialogDescription": "",
- "clearAllStatsDialogTitle": "",
- "description": "",
- "labelTTL": "",
+ "clearAllStatsButton": "Alle Statistiken löschen",
+ "clearAllStatsDescription": "Alle Statistiken löschen. Dies ist irreversibel.",
+ "clearAllStatsDialogConfirm": "Ja, alle Statistiken löschen",
+ "clearAllStatsDialogDescription": "Nach dem Entfernen können der Überwachungsverlauf und die Statistiken nicht mehr abgerufen werden.",
+ "clearAllStatsDialogTitle": "Möchten Sie alle Statistiken löschen?",
+ "description": "Legen Sie fest, wie lange Sie historische Daten aufbewahren möchten. Sie können auch alle vorhandenen Daten löschen.",
+ "labelTTL": "Die Tage, an denen Sie den Überwachungsverlauf beibehalten möchten.",
"labelTTLOptional": "0 für unendlich",
- "title": ""
+ "title": "Monitorverlauf"
},
"systemResetSettings": {
"buttonRemoveAllMonitors": "Alle Monitore löschen",
@@ -830,142 +835,242 @@
"title": "System zurücksetzen"
},
"timezoneSettings": {
- "description": "",
+ "description": "Wählen Sie die Zeitzone aus, die zum Anzeigen von Datum und Uhrzeit in der gesamten Anwendung verwendet wird.",
"label": "Zeitzone",
"title": "Zeitzone"
},
"title": "Einstellungen",
"uiSettings": {
- "description": "",
+ "description": "Wechseln Sie zwischen hellem und dunklem Modus oder ändern Sie die Sprache der Benutzeroberfläche.",
"labelLanguage": "Sprache",
- "labelTheme": "",
- "title": ""
+ "labelTheme": "Themenmodus",
+ "title": "Aussehen"
},
"urlSettings": {
- "description": "",
- "label": "",
+ "description": "Zeigt die IP-Adresse oder URL des Monitors auf der öffentlichen Statusseite an. Wenn diese Option deaktiviert ist, wird zum Schutz vertraulicher Informationen nur der Monitorname angezeigt.",
+ "label": "IP/URL auf der Statusseite anzeigen",
"selectDisabled": "Inaktiv",
"selectEnabled": "Aktiv",
- "title": ""
+ "title": "Überwachen Sie IP/URL auf der Statusseite"
+ },
+ "globalThresholds": {
+ "title": "",
+ "description": ""
}
},
"statusPageCreate": {
"buttonSave": "Speichern"
},
- "incidentsOptionsHeaderFilterResolved": "",
+ "incidentsOptionsHeaderFilterResolved": "Gelöst",
"settingsSave": "Speichern",
- "statusPageCreateAppearanceTitle": "",
+ "statusPageCreateAppearanceTitle": "Aussehen",
"confirmPassword": "Passwort bestätigen",
"monitorHooks": {
"failureAddDemoMonitors": "Demo Monitor konnte nicht hinzugefügt werden",
"successAddDemoMonitors": "Demo Monitor erfolgreich hinzugefügt"
},
- "settingsAppearance": "",
- "settingsDisplayTimezone": "",
- "settingsGeneralSettings": "",
- "incidentsOptionsHeaderTotalIncidents": "",
+ "settingsAppearance": "Aussehen",
+ "settingsDisplayTimezone": "Zeitzone anzeigen",
+ "settingsGeneralSettings": "Allgemeine Einstellungen",
+ "incidentsOptionsHeaderTotalIncidents": "Gesamtanzahl der Vorfälle",
"statusPage": {
- "deleteSuccess": "",
- "deleteFailed": "",
- "createSuccess": "",
- "updateSuccess": "",
- "generalSettings": "",
- "contents": "",
+ "deleteSuccess": "Statusseite erfolgreich gelöscht",
+ "deleteFailed": "Löschen der Statusseite fehlgeschlagen",
+ "createSuccess": "Statusseite erfolgreich erstellt",
+ "updateSuccess": "Statusseite erfolgreich aktualisiert",
+ "generalSettings": "Allgemeine Einstellungen",
+ "contents": "Inhalt",
"fallback": {
- "checks": [""],
- "title": "",
- "actionButton": ""
+ "checks": [
+ "Überwachen und zeigen Sie den Zustand Ihrer Dienste in Echtzeit an.",
+ "Verfolgen Sie mehrere Dienste und teilen Sie deren Status.",
+ "Informieren Sie Nutzer über Ausfälle und Leistung."
+ ],
+ "title": "Eine Statusseite wird verwendet, um:",
+ "actionButton": "Lassen Sie uns Ihre erste Statusseite erstellen!"
}
},
- "testNotificationsDisabled": "",
- "incidentsTableResolvedAt": "",
- "incidentsTableActionResolve": "",
+ "testNotificationsDisabled": "Für diesen Monitor sind keine Benachrichtigungen eingerichtet. Sie müssen eine hinzufügen, indem Sie auf die Schaltfläche „Konfigurieren“ klicken.",
+ "incidentsTableResolvedAt": "Gelöst am",
+ "incidentsTableActionResolve": "Lösen",
"checkHooks": {
- "failureResolveOne": "",
- "failureResolveAll": "",
- "failureResolveMonitor": ""
+ "failureResolveOne": "Der Vorfall konnte nicht gelöst werden.",
+ "failureResolveAll": "Es konnten nicht alle Vorfälle gelöst werden.",
+ "failureResolveMonitor": "Monitorvorfälle konnten nicht behoben werden."
},
- "checkFormError": "",
+ "checkFormError": "Bitte überprüfen Sie das Formular auf Fehler.",
"diagnosticsPage": {
- "diagnosticDescription": "",
- "statsDescription": "",
+ "diagnosticDescription": "Systemdiagnose",
+ "statsDescription": "Systemstatistiken",
"gauges": {
- "heapAllocationTitle": "",
- "heapAllocationSubtitle": "",
- "heapUsageTitle": "",
- "heapUsageSubtitle": "",
- "heapUtilizationTitle": "",
- "heapUtilizationSubtitle": "",
- "instantCpuUsageTitle": "",
- "instantCpuUsageSubtitle": ""
+ "heapAllocationTitle": "Heap-Zuweisung",
+ "heapAllocationSubtitle": "% des verfügbaren Speichers",
+ "heapUsageTitle": "Heap-Nutzung",
+ "heapUsageSubtitle": "% des verfügbaren Speichers",
+ "heapUtilizationTitle": "Heap-Auslastung",
+ "heapUtilizationSubtitle": "% der zugewiesenen",
+ "instantCpuUsageTitle": "Sofortige CPU-Auslastung",
+ "instantCpuUsageSubtitle": "% der von der CPU genutzten 1s"
},
"stats": {
- "eventLoopDelayTitle": "",
- "uptimeTitle": "",
- "usedHeapSizeTitle": "",
- "totalHeapSizeTitle": "",
- "osMemoryLimitTitle": ""
+ "eventLoopDelayTitle": "Ereignisschleifenverzögerung",
+ "uptimeTitle": "Betriebszeit",
+ "usedHeapSizeTitle": "Verwendete Heap-Größe",
+ "totalHeapSizeTitle": "Gesamtgröße des Heapspeichers",
+ "osMemoryLimitTitle": "Betriebssystem-Speicherlimit"
}
},
- "pageSpeedLighthouseAPI": "",
+ "pageSpeedLighthouseAPI": "Verwenden Sie die Lighthouse PageSpeed API, um Ihre Website zu überwachen",
"time": {
- "threeMinutes": "",
- "fiveMinutes": "",
- "tenMinutes": "",
- "twentyMinutes": "",
- "oneHour": "",
- "oneDay": "",
- "oneWeek": ""
+ "threeMinutes": "3 Minuten",
+ "fiveMinutes": "5 Minuten",
+ "tenMinutes": "10 Minuten",
+ "twentyMinutes": "20 Minuten",
+ "oneHour": "1 Stunde",
+ "oneDay": "1 Tag",
+ "oneWeek": "1 Woche",
+ "fourMinutes": "",
+ "oneMinute": "",
+ "twoMinutes": "",
+ "fifteenSeconds": "",
+ "thirtySeconds": ""
},
"general": {
- "noOptionsFound": ""
+ "noOptionsFound": "Keine {{unit}} gefunden"
},
"infrastructureMonitor": {
"fallback": {
- "checks": [""],
- "title": "",
- "actionButton": ""
+ "checks": [
+ "Verfolgen Sie die Leistung Ihrer Server",
+ "Identifizieren Sie Engpässe und optimieren Sie die Nutzung",
+ "Sorgen Sie für Zuverlässigkeit durch Echtzeitüberwachung"
+ ],
+ "title": "Ein Infrastrukturmonitor wird verwendet, um:",
+ "actionButton": "Lassen Sie uns Ihren ersten Infrastrukturmonitor erstellen!"
}
},
"maintenanceWindow": {
"fallback": {
- "checks": [""],
- "title": "",
- "actionButton": ""
+ "checks": [
+ "Markieren Sie Ihre Wartungszeiträume.",
+ "Beseitigen Sie Missverständnisse.",
+ "Beenden Sie das Senden von Warnmeldungen in Wartungsfenstern."
+ ],
+ "title": "Ein Wartungsfenster wird verwendet, um:",
+ "actionButton": "Lassen Sie uns Ihr erstes Wartungsfenster erstellen!"
}
},
"pageSpeed": {
"fallback": {
- "checks": [""],
- "title": "",
- "actionButton": ""
+ "checks": [
+ "Berichten Sie über die Benutzerfreundlichkeit einer Seite.",
+ "Helfen Sie bei der Analyse der Webseitengeschwindigkeit.",
+ "Geben Sie Verbesserungsvorschläge für die Seite."
+ ],
+ "title": "Ein PageSpeed-Monitor wird verwendet, um:",
+ "actionButton": "Lassen Sie uns Ihren ersten PageSpeed-Monitor erstellen!"
}
},
"uptimeMonitor": {
"fallback": {
- "checks": [""],
- "title": "",
- "actionButton": ""
+ "checks": [
+ "Prüfen Sie, ob Websites oder Server online und reaktionsfähig sind.",
+ "Benachrichtigen Sie Ihre Teams über Ausfallzeiten oder Leistungsprobleme.",
+ "Überwachen Sie HTTP-Endpunkte, Pings, Container und Ports.",
+ "Verfolgen Sie historische Verfügbarkeits- und Zuverlässigkeitstrends."
+ ],
+ "title": "Ein Uptime-Monitor wird verwendet, um:",
+ "actionButton": "Lassen Sie uns Ihren ersten Uptime-Monitor erstellen!"
}
},
"editUserPage": {
"form": {
- "email": "",
- "firstName": "",
- "lastName": "",
- "role": "",
- "save": ""
+ "email": "E-Mail",
+ "firstName": "Vorname",
+ "lastName": "Nachname",
+ "role": "Rollen",
+ "save": "Speichern"
},
"table": {
- "actionHeader": "",
- "roleHeader": ""
+ "actionHeader": "Aktion",
+ "roleHeader": "Rolle"
},
- "title": "",
+ "title": "Benutzer bearbeiten",
"toast": {
- "successUserUpdate": "",
- "validationErrors": ""
+ "successUserUpdate": "Benutzer erfolgreich aktualisiert",
+ "validationErrors": "Validierungsfehler"
}
},
- "incidentsPageActionResolveMonitor": "",
- "incidentsPageActionResolveAll": ""
+ "incidentsPageActionResolveMonitor": "Monitor-Vorfälle beheben",
+ "incidentsPageActionResolveAll": "Alle Vorfälle lösen",
+ "matchMethodOptions": {
+ "equal": "",
+ "equalPlaceholder": "",
+ "include": "",
+ "includePlaceholder": "",
+ "regex": "",
+ "regexPlaceholder": "",
+ "text": ""
+ },
+ "monitorType": {
+ "docker": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "http": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "ping": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "port": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "game": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ }
+ },
+ "uptimeAdvancedMatching": {
+ "jsonPath": ""
+ },
+ "bytesPerSecond": "",
+ "bytesReceived": "",
+ "bytesSent": "",
+ "chooseGame": "",
+ "createMonitorPage": {
+ "incidentConfigDescription": "",
+ "incidentConfigStatusWindowLabel": "",
+ "incidentConfigStatusWindowThresholdLabel": "",
+ "incidentConfigTitle": ""
+ },
+ "dataRate": "",
+ "dataReceived": "",
+ "dataSent": "",
+ "details": "",
+ "drops": "",
+ "errors": "",
+ "errorsIn": "",
+ "errorsOut": "",
+ "gameServerMonitoring": "",
+ "gameServerMonitoringDescription": "",
+ "network": "",
+ "networkDrops": "",
+ "networkErrors": "",
+ "networkInterface": "",
+ "noNetworkStatsAvailable": "",
+ "packetsPerSecond": "",
+ "packetsReceived": "",
+ "packetsReceivedRate": "",
+ "packetsSent": "",
+ "rate": "",
+ "selectInterface": ""
}
diff --git a/client/src/locales/en.json b/client/src/locales/en.json
index ecb080357..1aeb4b6a2 100644
--- a/client/src/locales/en.json
+++ b/client/src/locales/en.json
@@ -1,29 +1,586 @@
{
+ "submit": "Submit",
+ "title": "Title",
+ "distributedStatusHeaderText": "Real-time, real-device coverage",
+ "distributedStatusSubHeaderText": "Powered by millions devices worldwide, view a system performance by global region, country or city",
+ "settingsDisabled": "Disabled",
+ "settingsSuccessSaved": "Settings saved successfully",
+ "settingsFailedToSave": "Failed to save settings",
+ "settingsStatsCleared": "Stats cleared successfully",
+ "settingsFailedToClearStats": "Failed to clear stats",
+ "settingsMonitorsDeleted": "Successfully deleted all monitors",
+ "settingsFailedToDeleteMonitors": "Failed to delete all monitors",
+ "starPromptTitle": "Star Checkmate",
+ "starPromptDescription": "See the latest releases and help grow the community on GitHub",
+ "https": "HTTPS",
+ "http": "HTTP",
+ "monitor": "monitor",
+ "aboutus": "About Us",
+ "now": "Now",
+ "delete": "Delete",
+ "configure": "Configure",
+ "responseTime": "Response time",
+ "ms": "ms",
+ "bar": "Bar",
+ "area": "Area",
+ "country": "COUNTRY",
+ "city": "CITY",
+ "response": "RESPONSE",
+ "monitorStatusUp": "Monitor {name} ({url}) is now UP and responding",
+ "monitorStatusDown": "Monitor {name} ({url}) is DOWN and not responding",
+ "webhookSendSuccess": "Webhook notification sent successfully",
+ "webhookSendError": "Error sending webhook notification to {platform}",
+ "webhookUnsupportedPlatform": "Unsupported platform: {platform}",
+ "distributedRightCategoryTitle": "Monitor",
+ "distributedStatusServerMonitors": "Server Monitors",
+ "distributedStatusServerMonitorsDescription": "Monitor status of related servers",
+ "distributedUptimeCreateSelectURL": "Here you can select the URL of the host, together with the type of monitor.",
+ "distributedUptimeCreateChecks": "Checks to perform",
+ "distributedUptimeCreateChecksDescription": "You can always add or remove checks after adding your site.",
+ "distributedUptimeCreateIncidentNotification": "Incident notifications",
+ "distributedUptimeCreateIncidentDescription": "When there is an incident, notify users.",
+ "distributedUptimeCreateAdvancedSettings": "Advanced settings",
+ "distributedUptimeDetailsNoMonitorHistory": "There is no check history for this monitor yet.",
+ "distributedUptimeDetailsStatusHeaderUptime": "Uptime:",
+ "distributedUptimeDetailsStatusHeaderLastUpdate": "Last updated",
+ "notifications": {
+ "enableNotifications": "Enable {{platform}} notifications",
+ "testNotification": "Test notification",
+ "addOrEditNotifications": "Add or edit notifications",
+ "slack": {
+ "label": "Slack",
+ "description": "To enable Slack notifications, create a Slack app and enable incoming webhooks. After that, simply provide the webhook URL here.",
+ "webhookLabel": "Webhook URL",
+ "webhookPlaceholder": "https://hooks.slack.com/services/...",
+ "webhookRequired": "Slack webhook URL is required"
+ },
+ "discord": {
+ "label": "Discord",
+ "description": "To send data to a Discord channel from Checkmate via Discord notifications using webhooks, you can use Discord's incoming Webhooks feature.",
+ "webhookLabel": "Discord Webhook URL",
+ "webhookPlaceholder": "https://discord.com/api/webhooks/...",
+ "webhookRequired": "Discord webhook URL is required"
+ },
+ "telegram": {
+ "label": "Telegram",
+ "description": "To enable Telegram notifications, create a Telegram bot using BotFather, an official bot for creating and managing Telegram bots. Then, get the API token and chat ID and write them down here.",
+ "tokenLabel": "Your bot token",
+ "tokenPlaceholder": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
+ "chatIdLabel": "Your Chat ID",
+ "chatIdPlaceholder": "-1001234567890",
+ "fieldsRequired": "Telegram token and chat ID are required"
+ },
+ "webhook": {
+ "label": "Webhooks",
+ "description": "You can set up a custom webhook to receive notifications when incidents occur.",
+ "urlLabel": "Webhook URL",
+ "urlPlaceholder": "https://your-server.com/webhook",
+ "urlRequired": "Webhook URL is required"
+ },
+ "testNotificationDevelop": "Test notification 2",
+ "integrationButton": "Notification Integration",
+ "testSuccess": "Test notification sent successfully!",
+ "testFailed": "Failed to send test notification",
+ "unsupportedType": "Unsupported notification type",
+ "networkError": "Network error occurred",
+ "fallback": {
+ "title": "A notification channel is used to:",
+ "checks": [
+ "Alert teams about downtime or performance issues",
+ "Let engineers know when incidents happen",
+ "Keep administrators informed of system changes"
+ ],
+ "actionButton": "Let's create your first notification channel!"
+ },
+ "createButton": "Create notification channel",
+ "createTitle": "Notification channel",
+ "create": {
+ "success": "Notification created successfully",
+ "failed": "Failed to create notification"
+ },
+ "fetch": {
+ "success": "Notifications fetched successfully",
+ "failed": "Failed to fetch notifications"
+ },
+ "delete": {
+ "success": "Notification deleted successfully",
+ "failed": "Failed to delete notification"
+ },
+ "edit": {
+ "success": "Notification updated successfully",
+ "failed": "Failed to update notification"
+ },
+ "test": {
+ "success": "Test notification sent successfully",
+ "failed": "Failed to send test notification"
+ }
+ },
+ "testLocale": "testLocale",
+ "add": "Add",
+ "monitors": "monitors",
+ "distributedUptimeStatusCreateStatusPage": "status page",
+ "distributedUptimeStatusCreateStatusPageAccess": "Access",
+ "distributedUptimeStatusCreateStatusPageReady": "If your status page is ready, you can mark it as published.",
+ "distributedUptimeStatusBasicInfoHeader": "Basic Information",
+ "distributedUptimeStatusBasicInfoDescription": "Define company name and the subdomain that your status page points to.",
+ "distributedUptimeStatusLogoHeader": "Logo",
+ "distributedUptimeStatusLogoDescription": "Upload a logo for your status page",
+ "distributedUptimeStatusLogoUploadButton": "Upload logo",
+ "distributedUptimeStatusStandardMonitorsHeader": "Standard Monitors",
+ "distributedUptimeStatusStandardMonitorsDescription": "Attach standard monitors to your status page.",
+ "distributedUptimeStatusCreateYour": "Create your",
+ "distributedUptimeStatusEditYour": "Edit your",
+ "distributedUptimeStatusPublishedLabel": "Published and visible to the public",
+ "distributedUptimeStatusCompanyNameLabel": "Company name",
+ "distributedUptimeStatusPageAddressLabel": "Your status page address",
+ "distributedUptimeStatus30Days": "30 days",
+ "distributedUptimeStatus60Days": "60 days",
+ "distributedUptimeStatus90Days": "90 days",
+ "distributedUptimeStatusPageNotSetUp": "A status page is not set up.",
+ "distributedUptimeStatusContactAdmin": "Please contact your administrator",
+ "distributedUptimeStatusPageNotPublic": "This status page is not public.",
+ "distributedUptimeStatusPageDeleteDialog": "Do you want to delete this status page?",
+ "distributedUptimeStatusPageDeleteConfirm": "Yes, delete status page",
+ "distributedUptimeStatusPageDeleteDescription": "Once deleted, your status page cannot be retrieved.",
+ "distributedUptimeStatusDevices": "Devices",
+ "distributedUptimeStatusUpt": "UPT",
+ "distributedUptimeStatusUptBurned": "UPT Burned",
+ "distributedUptimeStatusUptLogo": "Upt Logo",
+ "incidentsTableNoIncidents": "No incidents recorded",
+ "incidentsTablePaginationLabel": "incidents",
+ "incidentsTableMonitorName": "Monitor Name",
+ "incidentsTableStatus": "Status",
+ "incidentsTableDateTime": "Date & Time",
+ "incidentsTableStatusCode": "Status Code",
+ "incidentsTableMessage": "Message",
+ "incidentsOptionsHeader": "Incidents for:",
+ "incidentsOptionsHeaderFilterBy": "Filter by:",
+ "incidentsOptionsHeaderFilterAll": "All",
+ "incidentsOptionsHeaderFilterDown": "Down",
+ "incidentsOptionsHeaderFilterCannotResolve": "Cannot Resolve",
+ "incidentsOptionsHeaderShow": "Show:",
+ "incidentsOptionsHeaderLastHour": "Last hour",
+ "incidentsOptionsHeaderLastDay": "Last day",
+ "incidentsOptionsHeaderLastWeek": "Last week",
+ "incidentsOptionsPlaceholderAllServers": "All servers",
+ "infrastructureCreateYour": "Create your",
+ "infrastructureCreateGeneralSettingsDescription": "Here you can select the URL of the host, together with the friendly name and authorization secret to connect to the server agent.",
+ "infrastructureServerRequirement": "The server you are monitoring must be running the",
+ "infrastructureCustomizeAlerts": "Customize alerts",
+ "infrastructureAlertNotificationDescription": "Send a notification to user(s) when thresholds exceed a specified percentage.",
+ "infrastructureCreateMonitor": "Create Infrastructure Monitor",
+ "infrastructureProtocol": "Protocol",
+ "infrastructureServerUrlLabel": "Server URL",
+ "infrastructureDisplayNameLabel": "Display name",
+ "infrastructureAuthorizationSecretLabel": "Authorization secret",
+ "gb": "GB",
+ "mb": "MB",
+ "mem": "Mem",
+ "memoryUsage": "Memory usage",
+ "cpu": "CPU",
+ "cpuUsage": "CPU usage",
+ "cpuTemperature": "CPU Temperature",
+ "diskUsage": "Disk Usage",
+ "used": "Used",
+ "total": "Total",
+ "cores": "Cores",
+ "frequency": "Frequency",
+ "status": "Status",
+ "cpuPhysical": "CPU (Physical)",
+ "cpuLogical": "CPU (Logical)",
+ "cpuFrequency": "CPU Frequency",
+ "avgCpuTemperature": "Average CPU Temperature",
+ "memory": "Memory",
+ "disk": "Disk",
+ "uptime": "Uptime",
+ "os": "OS",
+ "host": "Host",
+ "actions": "Actions",
+ "integrations": "Integrations",
+ "integrationsPrism": "Connect Prism to your favorite service.",
+ "integrationsSlack": "Slack",
+ "integrationsSlackInfo": "Connect with Slack and see incidents in a channel",
+ "integrationsDiscord": "Discord",
+ "integrationsDiscordInfo": "Connect with Discord and view incidents directly in a channel",
+ "integrationsZapier": "Zapier",
+ "integrationsZapierInfo": "Send all incidents to Zapier, and then see them everywhere",
+ "commonSave": "Save",
+ "createYour": "Create your",
+ "createMonitor": "Create monitor",
+ "pause": "Pause",
+ "resume": "Resume",
+ "editing": "Editing...",
+ "url": "URL",
+ "access": "Access",
+ "timezone": "Timezone",
+ "features": "Features",
+ "administrator": "Administrator?",
+ "loginHere": "Login here",
+ "displayName": "Display name",
+ "urlMonitor": "URL to monitor",
+ "portToMonitor": "Port to monitor",
+ "websiteMonitoring": "Website monitoring",
+ "websiteMonitoringDescription": "Use HTTP(s) to monitor your website or API endpoint.",
+ "pingMonitoring": "Ping monitoring",
+ "pingMonitoringDescription": "Check whether your server is available or not.",
+ "dockerContainerMonitoring": "Docker container monitoring",
+ "dockerContainerMonitoringDescription": "Check whether your Docker container is running or not.",
+ "portMonitoring": "Port monitoring",
+ "portMonitoringDescription": "Check whether your port is open or not.",
+ "createMaintenanceWindow": "Create maintenance window",
+ "createMaintenance": "Create maintenance",
+ "editMaintenance": "Edit maintenance",
+ "maintenanceWindowName": "Maintenance Window Name",
+ "friendlyNameInput": "Friendly name",
+ "friendlyNamePlaceholder": "Maintenance at __ : __ for ___ minutes",
+ "maintenanceRepeat": "Maintenance Repeat",
+ "maintenance": "maintenance",
+ "duration": "Duration",
+ "addMonitors": "Add monitors",
+ "window": "window",
+ "cancel": "Cancel",
+ "message": "Message",
+ "low": "low",
+ "high": "high",
+ "statusCode": "Status code",
+ "date&Time": "Date & Time",
+ "type": "Type",
+ "statusPageName": "Status page name",
+ "publicURL": "Public URL",
+ "repeat": "Repeat",
+ "edit": "Edit",
+ "createA": "Create a",
+ "remove": "Remove",
+ "maintenanceWindowDescription": "During maintenance windows, all monitoring is suspended for selected monitors. No network checks will be performed, preventing any status updates or notifications from being triggered. Your monitors will appear frozen at their last known status, and status pages will display a maintenance indicator. Once the maintenance window ends, monitoring automatically resumes, and alerts will trigger if issues are detected. Maintenance periods do not count against uptime calculations.",
+ "startTime": "Start time",
+ "timeZoneInfo": "All dates and times are in GMT+0 time zone.",
+ "monitorsToApply": "Monitors to apply maintenance window to",
+ "nextWindow": "Next window",
+ "notFoundButton": "Go to the main dashboard",
+ "pageSpeedConfigureSettingsDescription": "Here you can select the URL of the host, together with the type of monitor.",
+ "monitorDisplayName": "Monitor display name",
+ "whenNewIncident": "When there is a new incident,",
+ "notifySMS": "Notify via SMS (coming soon)",
+ "notifyEmails": "Also notify via email to multiple addresses (coming soon)",
+ "seperateEmails": "You can separate multiple emails with a comma",
+ "checkFrequency": "Check frequency",
+ "matchMethod": "Match Method",
+ "expectedValue": "Expected value",
+ "deleteDialogTitle": "Do you really want to delete this monitor?",
+ "deleteDialogDescription": "Once deleted, this monitor cannot be retrieved.",
+ "pageSpeedMonitor": "PageSpeed monitor",
+ "shown": "Shown",
+ "ago": "ago",
+ "companyName": "Company name",
+ "pageSpeedDetailsPerformanceReport": "Values are estimated and may vary.",
+ "pageSpeedDetailsPerformanceReportCalculator": "See calculator",
+ "checkingEvery": "Checking every",
+ "statusPageCreateSettings": "If your status page is ready, you can mark it as published.",
+ "basicInformation": "Basic Information",
+ "statusPageCreateBasicInfoDescription": "Define company name and the subdomain that your status page points to.",
+ "statusPageCreateSelectTimeZoneDescription": "Select the timezone that your status page will be displayed in.",
+ "statusPageCreateAppearanceDescription": "Define the default look and feel of your public status page.",
+ "statusPageCreateSettingsCheckboxLabel": "Published and visible to the public",
+ "statusPageCreateBasicInfoStatusPageAddress": "Your status page address",
+ "statusPageCreateTabsContent": "Status page servers",
+ "statusPageCreateTabsContentDescription": "You can add any number of servers that you monitor to your status page. You can also reorder them for the best viewing experience.",
+ "statusPageCreateTabsContentFeaturesDescription": "Show more details on the status page",
+ "showCharts": "Show charts",
+ "showUptimePercentage": "Show uptime percentage",
+ "removeLogo": "Remove Logo",
+ "statusPageStatus": "A public status page is not set up.",
+ "statusPageStatusContactAdmin": "Please contact to your administrator",
+ "statusPageStatusNotPublic": "This status page is not public.",
+ "statusPageStatusNoPage": "There's no status page here.",
+ "statusPageStatusServiceStatus": "Service status",
+ "deleteStatusPage": "Do you want to delete this status page?",
+ "deleteStatusPageConfirm": "Yes, delete status page",
+ "deleteStatusPageDescription": "Once deleted, your status page cannot be retrieved.",
+ "uptimeCreate": "The expected value is used to match against response result, and the match determines the status.",
+ "uptimeCreateJsonPath": "This expression will be evaluated against the reponse JSON data and the result will be used to match against the expected value. See",
+ "uptimeCreateJsonPathQuery": "for query language documentation.",
+ "maintenanceTableActionMenuDialogTitle": "Do you really want to remove this maintenance window?",
+ "infrastructureEditYour": "Edit your",
+ "infrastructureEditMonitor": "Save Infrastructure Monitor",
+ "infrastructureMonitorCreated": "Infrastructure monitor created successfully!",
+ "infrastructureMonitorUpdated": "Infrastructure monitor updated successfully!",
+ "errorInvalidTypeId": "Invalid notification type provided",
+ "errorInvalidFieldId": "Invalid field ID provided",
+ "inviteNoTokenFound": "No invite token found",
+ "pageSpeedWarning": "Warning: You haven't added a Google PageSpeed API key yet. Without it, the PageSpeed monitor won't function.",
+ "pageSpeedLearnMoreLink": "Click here",
+ "pageSpeedAddApiKey": "to add your API key.",
+ "update": "Update",
+ "invalidFileFormat": "Unsupported file format!",
+ "invalidFileSize": "File size is too large!",
"ClickUpload": "Click to upload",
- "DeleteAccountButton": "Remove account",
- "DeleteAccountTitle": "Remove account",
- "DeleteAccountWarning": "Removing your account means you won't be able to sign in again and all your data will be removed. This isn't reversible.",
- "DeleteDescriptionText": "This will remove the account and all associated data from the server. This isn't reversible.",
- "DeleteWarningTitle": "Really remove this account?",
"DragandDrop": "drag and drop",
- "EmailDescriptionText": "This is your current email address — it cannot be changed.",
+ "MaxSize": "Maximum Size",
+ "SupportedFormats": "Supported formats",
"FirstName": "First name",
"LastName": "Last name",
- "MaxSize": "Maximum Size",
- "PhotoDescriptionText": "This photo will be displayed in your profile page.",
- "SupportedFormats": "Supported formats",
+ "EmailDescriptionText": "This is your current email address — it cannot be changed.",
"YourPhoto": "Profile photo",
- "aboutus": "About Us",
- "access": "Access",
- "actions": "Actions",
- "add": "Add",
- "addMonitors": "Add monitors",
- "administrator": "Administrator?",
- "advancedMatching": "Advanced matching",
- "ago": "ago",
- "area": "Area",
+ "PhotoDescriptionText": "This photo will be displayed in your profile page.",
+ "save": "Save",
+ "DeleteDescriptionText": "This will remove the account and all associated data from the server. This isn't reversible.",
+ "DeleteAccountWarning": "Removing your account means you won't be able to sign in again and all your data will be removed. This isn't reversible.",
+ "DeleteWarningTitle": "Really remove this account?",
+ "bulkImport": {
+ "title": "Bulk Import",
+ "selectFileTips": "Select CSV file to upload",
+ "selectFileDescription": "You can download our template or sample",
+ "selectFile": "Select File",
+ "parsingFailed": "Parsing failed",
+ "uploadSuccess": "Monitors created successfully!",
+ "validationFailed": "Validation failed",
+ "noFileSelected": "No file selected",
+ "fallbackPage": "Import a file to upload a list of servers in bulk",
+ "invalidFileType": "Invalid file type",
+ "uploadFailed": "Upload failed"
+ },
+ "DeleteAccountTitle": "Remove account",
+ "DeleteAccountButton": "Remove account",
+ "publicLink": "Public link",
+ "maskedPageSpeedKeyPlaceholder": "*************************************",
+ "reset": "Reset",
+ "ignoreTLSError": "Ignore TLS/SSL error",
+ "tlsErrorIgnored": "TLS/SSL errors ignored",
+ "ignoreTLSErrorDescription": "Ignore TLS/SSL errors and continue checking the website's availability",
+ "createNew": "Create new",
+ "greeting": {
+ "prepend": "Hey there",
+ "append": "The afternoon is your playground—let's make it epic!",
+ "overview": "Here's an overview of your {{type}} monitors."
+ },
+ "roles": {
+ "superAdmin": "Super admin",
+ "admin": "Admin",
+ "teamMember": "Team member",
+ "demoUser": "Demo user"
+ },
+ "teamPanel": {
+ "addTeamMember": {
+ "addMemberMenu": "Add Team Member",
+ "title": "Register new team member",
+ "description": "Create a new user and share the credentials with them. This method gives the member immediate access to all monitors.",
+ "addButton": "Add Member"
+ },
+ "teamMembers": "Team members",
+ "filter": {
+ "all": "All",
+ "member": "Member"
+ },
+ "inviteTeamMember": "Invite a team member",
+ "inviteNewTeamMember": "Invite new team member",
+ "inviteDescription": "When you add a new team member, they will get access to all monitors.",
+ "email": "Email",
+ "selectRole": "Select role",
+ "inviteLink": "Invite link",
+ "cancel": "Cancel",
+ "noMembers": "There are no team members with this role",
+ "getToken": "Get token",
+ "emailToken": "E-mail token",
+ "register": "Register a team member",
+ "registerToast": {
+ "success": "User created, share credentials with the member securely.",
+ "dbUserExists": "User already exists.",
+ "unknownError": "Unknown error occurred."
+ },
+ "registerTeamMember": {
+ "title": "Register team member",
+ "auth": {
+ "common": {
+ "inputs": {
+ "firstName": {
+ "errors": {
+ "empty": "Please enter a name",
+ "pattern": "Name must contain only letters, spaces, apostrophes, or hyphens"
+ }
+ },
+ "lastName": {
+ "errors": {
+ "empty": "Please enter a surname",
+ "pattern": "Surname must contain only letters, spaces, apostrophes, or hyphens"
+ }
+ },
+ "email": {
+ "errors": {
+ "empty": "To continue, please enter an email address",
+ "invalid": "Please recheck validity of entered email address"
+ }
+ },
+ "role": {
+ "errors": {
+ "empty": "Role is required"
+ }
+ }
+ }
+ }
+ }
+ },
+ "role": "Role",
+ "table": {
+ "name": "Name",
+ "email": "Email",
+ "role": "Role",
+ "created": "Created"
+ }
+ },
+ "monitorState": {
+ "paused": "Paused",
+ "resumed": "Resumed",
+ "active": "Active"
+ },
+ "menu": {
+ "uptime": "Uptime",
+ "pagespeed": "Pagespeed",
+ "infrastructure": "Infrastructure",
+ "incidents": "Incidents",
+ "statusPages": "Status pages",
+ "maintenance": "Maintenance",
+ "integrations": "Integrations",
+ "settings": "Settings",
+ "support": "Support",
+ "discussions": "Discussions",
+ "docs": "Docs",
+ "changelog": "Changelog",
+ "profile": "Profile",
+ "password": "Password",
+ "team": "Team",
+ "logOut": "Log out",
+ "notifications": "Notifications",
+ "logs": "Logs"
+ },
+ "settingsEmailUser": "Email user - Username for authentication, overrides email address if specified",
+ "state": "State",
+ "statusBreadCrumbsStatusPages": "Status Pages",
+ "statusBreadCrumbsDetails": "Details",
+ "commonSaving": "Saving...",
+ "navControls": "Controls",
+ "incidentsPageTitle": "Incidents",
+ "passwordPanel": {
+ "passwordChangedSuccess": "Your password was changed successfully.",
+ "passwordInputIncorrect": "Your password input was incorrect.",
+ "currentPassword": "Current password",
+ "enterCurrentPassword": "Enter your current password",
+ "newPassword": "New password",
+ "enterNewPassword": "Enter your new password",
+ "confirmNewPassword": "Confirm new password",
+ "passwordRequirements": "New password must contain at least 8 characters and must have at least one uppercase letter, one lowercase letter, one number and one special character.",
+ "saving": "Saving..."
+ },
+ "emailSent": "Email sent successfully",
+ "failedToSendEmail": "Failed to send email",
+ "settingsTestEmailSuccess": "Test email sent successfully",
+ "settingsTestEmailFailed": "Failed to send test email",
+ "settingsTestEmailFailedWithReason": "Failed to send test email: {{reason}}",
+ "settingsTestEmailUnknownError": "Unknown error",
+ "statusMsg": {
+ "paused": "Monitoring is paused.",
+ "up": "Your site is up.",
+ "down": "Your site is down.",
+ "pending": "Pending..."
+ },
+ "uptimeGeneralInstructions": {
+ "http": "Enter the URL or IP to monitor (e.g., https://example.com/ or 192.168.1.100) and add a clear display name that appears on the dashboard.",
+ "ping": "Enter the IP address or hostname to ping (e.g., 192.168.1.100 or example.com) and add a clear display name that appears on the dashboard.",
+ "docker": "Enter the Docker container name or ID. You can use either the container name (e.g., my-app) or the container ID (full 64-char ID or short ID).",
+ "port": "Enter the URL or IP of the server, the port number and a clear display name that appears on the dashboard.",
+ "game": "Enter the IP address or hostname and the port number to ping (e.g., 192.168.1.100 or example.com) and choose game type."
+ },
+ "common": {
+ "appName": "Checkmate",
+ "monitoringAgentName": "Capture",
+ "buttons": {
+ "toggleTheme": "Toggles light & dark"
+ },
+ "toasts": {
+ "networkError": "Network error",
+ "checkConnection": "Please check your connection",
+ "unknownError": "Unknown error"
+ }
+ },
"auth": {
"common": {
+ "navigation": {
+ "continue": "Continue",
+ "back": "Back"
+ },
+ "inputs": {
+ "email": {
+ "label": "Email",
+ "placeholder": "jordan.ellis@domain.com",
+ "errors": {
+ "empty": "To continue, please enter your email address",
+ "invalid": "Please recheck validity of entered email address"
+ }
+ },
+ "password": {
+ "label": "Password",
+ "rules": {
+ "length": {
+ "beginning": "Must be at least",
+ "highlighted": "8 characters long"
+ },
+ "special": {
+ "beginning": "Must contain at least",
+ "highlighted": "one special character"
+ },
+ "number": {
+ "beginning": "Must contain at least",
+ "highlighted": "one number"
+ },
+ "uppercase": {
+ "beginning": "Must contain at least",
+ "highlighted": "one upper character"
+ },
+ "lowercase": {
+ "beginning": "Must contain at least",
+ "highlighted": "one lower character"
+ },
+ "match": {
+ "beginning": "Passwords",
+ "highlighted": "must match"
+ }
+ },
+ "errors": {
+ "empty": "Please enter your password",
+ "length": "Password must be at least 8 characters long",
+ "uppercase": "Password must contain at least 1 uppercase letter",
+ "lowercase": "Password must contain at least 1 lowercase letter",
+ "number": "Password must contain at least 1 number",
+ "special": "Password must contain at least 1 special character",
+ "incorrect": "The password you provided does not match our records"
+ }
+ },
+ "passwordConfirm": {
+ "label": "Confirm password",
+ "placeholder": "Re-enter password to confirm",
+ "errors": {
+ "empty": "Please enter your password again for confirmation (helps with typos)",
+ "different": "Entered passwords don't match, so one of them is probably mistyped"
+ }
+ },
+ "firstName": {
+ "label": "Name",
+ "placeholder": "Jordan",
+ "errors": {
+ "empty": "Please enter your name",
+ "length": "Name must be less than 50 characters",
+ "pattern": "Name must contain only letters, spaces, apostrophes, or hyphens"
+ }
+ },
+ "lastName": {
+ "label": "Surname",
+ "placeholder": "Ellis",
+ "errors": {
+ "empty": "Please enter your surname",
+ "length": "Surname must be less than 50 characters",
+ "pattern": "Surname must contain only letters, spaces, apostrophes, or hyphens"
+ }
+ }
+ },
"errors": {
"validation": "Error validating data."
},
@@ -38,140 +595,41 @@
"min": "At least one role is required"
}
}
- },
- "inputs": {
- "email": {
- "errors": {
- "empty": "To continue, please enter your email address",
- "invalid": "Please recheck validity of entered email address"
- },
- "label": "Email",
- "placeholder": "jordan.ellis@domain.com"
- },
- "firstName": {
- "errors": {
- "empty": "Please enter your name",
- "length": "Name must be less than 50 characters",
- "pattern": "Name must contain only letters, spaces, apostrophes, or hyphens"
- },
- "label": "Name",
- "placeholder": "Jordan"
- },
- "lastName": {
- "errors": {
- "empty": "Please enter your surname",
- "length": "Surname must be less than 50 characters",
- "pattern": "Surname must contain only letters, spaces, apostrophes, or hyphens"
- },
- "label": "Surname",
- "placeholder": "Ellis"
- },
- "password": {
- "errors": {
- "empty": "Please enter your password",
- "incorrect": "The password you provided does not match our records",
- "length": "Password must be at least 8 characters long",
- "lowercase": "Password must contain at least 1 lowercase letter",
- "number": "Password must contain at least 1 number",
- "special": "Password must contain at least 1 special character",
- "uppercase": "Password must contain at least 1 uppercase letter"
- },
- "label": "Password",
- "rules": {
- "length": {
- "beginning": "Must be at least",
- "highlighted": "8 characters long"
- },
- "lowercase": {
- "beginning": "Must contain at least",
- "highlighted": "one lower character"
- },
- "match": {
- "beginning": "Passwords",
- "highlighted": "must match"
- },
- "number": {
- "beginning": "Must contain at least",
- "highlighted": "one number"
- },
- "special": {
- "beginning": "Must contain at least",
- "highlighted": "one special character"
- },
- "uppercase": {
- "beginning": "Must contain at least",
- "highlighted": "one upper character"
- }
- }
- },
- "passwordConfirm": {
- "errors": {
- "different": "Entered passwords don't match, so one of them is probably mistyped",
- "empty": "Please enter your password again for confirmation (helps with typos)"
- },
- "label": "Confirm password",
- "placeholder": "Re-enter password to confirm"
- }
- },
- "navigation": {
- "back": "Back",
- "continue": "Continue"
- }
- },
- "forgotPassword": {
- "buttons": {
- "openEmail": "Open email app",
- "resetPassword": "Reset password"
- },
- "heading": "Forgot password?",
- "imageAlts": {
- "email": "Email icon",
- "lock": "Lock icon",
- "passwordConfirm": "Password confirm icon",
- "passwordKey": "Password key icon"
- },
- "links": {
- "login": "Go back to Log In",
- "resend": "Didn't receive the email? Click to resend"
- },
- "subheadings": {
- "stepFour": "Your password has been successfully reset. Click below to log in magically.",
- "stepOne": "No worries, we'll send you reset instructions.",
- "stepThree": "Your new password must be different from previously used passwords.",
- "stepTwo": "We sent a password reset link to "
- },
- "toasts": {
- "emailNotFound": "Email not found.",
- "error": "Unable to reset password. Please try again later or contact support.",
- "redirect": "Redirecting in ...",
- "sent": "Instructions sent to .",
- "success": "Your password was reset successfully."
}
},
"login": {
+ "heading": "Log in to continue",
+ "subheadings": {
+ "stepOne": "Enter your email",
+ "stepTwo": "Enter your password"
+ },
+ "links": {
+ "forgotPassword": "Forgot password?",
+ "register": "Do not have an account?",
+ "forgotPasswordLink": "Reset password",
+ "registerLink": "Register here"
+ },
+ "toasts": {
+ "success": "Welcome back! You're successfully logged in.",
+ "incorrectPassword": "Incorrect password"
+ },
"errors": {
"password": {
"incorrect": "The password you provided does not match our records"
}
},
- "heading": "Log in to continue",
- "links": {
- "forgotPassword": "Forgot password?",
- "forgotPasswordLink": "Reset password",
- "register": "Do not have an account?",
- "registerLink": "Register here"
- },
- "subheadings": {
- "stepOne": "Enter your email",
- "stepTwo": "Enter your password"
- },
- "toasts": {
- "incorrectPassword": "Incorrect password",
- "success": "Welcome back! You're successfully logged in."
- },
"welcome": "Welcome back to Checkmate!"
},
"registration": {
+ "heading": {
+ "superAdmin": "Create super admin",
+ "user": "Sign Up"
+ },
+ "subheadings": {
+ "stepOne": "Enter your personal details",
+ "stepTwo": "Enter your email",
+ "stepThree": "Create your password"
+ },
"description": {
"superAdmin": "Create your super admin account to get started",
"user": "Sign up as a user and ask super admin for access to your monitors"
@@ -180,644 +638,189 @@
"superAdmin": "Create super admin account",
"user": "Sign up regular user"
},
- "heading": {
- "superAdmin": "Create super admin",
- "user": "Sign Up"
- },
+ "termsAndPolicies": "By creating an account, you agree to our Terms of Service and Privacy Policy.",
"links": {
"login": "Already have an account? Log In"
},
- "subheadings": {
- "stepOne": "Enter your personal details",
- "stepThree": "Create your password",
- "stepTwo": "Enter your email"
- },
- "termsAndPolicies": "By creating an account, you agree to our Terms of Service and Privacy Policy.",
"toasts": {
"success": "Welcome! Your account was created successfully."
},
"welcome": "Welcome to Checkmate!"
+ },
+ "forgotPassword": {
+ "heading": "Forgot password?",
+ "subheadings": {
+ "stepOne": "No worries, we'll send you reset instructions.",
+ "stepTwo": "We sent a password reset link to ",
+ "stepThree": "Your new password must be different from previously used passwords.",
+ "stepFour": "Your password has been successfully reset. Click below to log in magically."
+ },
+ "buttons": {
+ "openEmail": "Open email app",
+ "resetPassword": "Reset password"
+ },
+ "imageAlts": {
+ "passwordKey": "Password key icon",
+ "email": "Email icon",
+ "lock": "Lock icon",
+ "passwordConfirm": "Password confirm icon"
+ },
+ "links": {
+ "login": "Go back to Log In",
+ "resend": "Didn't receive the email? Click to resend"
+ },
+ "toasts": {
+ "sent": "Instructions sent to .",
+ "emailNotFound": "Email not found.",
+ "redirect": "Redirecting in ...",
+ "success": "Your password was reset successfully.",
+ "error": "Unable to reset password. Please try again later or contact support."
+ }
}
},
- "avgCpuTemperature": "Average CPU Temperature",
- "bar": "Bar",
- "basicInformation": "Basic Information",
- "bulkImport": {
- "fallbackPage": "Import a file to upload a list of servers in bulk",
- "invalidFileType": "Invalid file type",
- "noFileSelected": "No file selected",
- "parsingFailed": "Parsing failed",
- "selectFile": "Select File",
- "selectFileDescription": "You can download our template or sample",
- "selectFileTips": "Select CSV file to upload",
- "title": "Bulk Import",
- "uploadFailed": "Upload failed",
- "uploadSuccess": "Monitors created successfully!",
- "validationFailed": "Validation failed"
- },
- "cancel": "Cancel",
- "checkFormError": "Please check the form for errors.",
- "checkFrequency": "Check frequency",
- "chooseGame": "Choose game",
- "checkHooks": {
- "failureResolveAll": "Failed to resolve all incidents.",
- "failureResolveMonitor": "Failed to resolve monitor incidents.",
- "failureResolveOne": "Failed to resolve incident."
- },
- "checkingEvery": "Checking every",
- "city": "CITY",
- "common": {
- "appName": "Checkmate",
- "buttons": {
- "toggleTheme": "Toggles light & dark"
- },
- "monitoringAgentName": "Capture",
- "toasts": {
- "checkConnection": "Please check your connection",
- "networkError": "Network error",
- "unknownError": "Unknown error"
- }
- },
- "commonSave": "Save",
- "commonSaving": "Saving...",
- "companyName": "Company name",
- "configure": "Configure",
- "confirmPassword": "Confirm password",
- "cores": "Cores",
- "country": "COUNTRY",
- "cpu": "CPU",
- "cpuFrequency": "CPU Frequency",
- "cpuLogical": "CPU (Logical)",
- "cpuPhysical": "CPU (Physical)",
- "cpuTemperature": "CPU Temperature",
- "cpuUsage": "CPU usage",
- "createA": "Create a",
- "createMaintenance": "Create maintenance",
- "createMaintenanceWindow": "Create maintenance window",
- "createMonitor": "Create monitor",
- "createNew": "Create new",
- "createNotifications": {
- "dialogDeleteConfirm": "Delete",
- "dialogDeleteTitle": "Are you sure you want to delete this notification?",
- "discordSettings": {
- "description": "Configure your Discord webhook here",
- "title": "Discord",
- "webhookLabel": "Discord Webhook URL",
- "webhookPlaceholder": "https://your-server.com/webhook"
- },
- "emailSettings": {
- "description": "Destination email addresses.",
- "emailLabel": "Email address",
- "emailPlaceholder": "e.g. john@example.com",
- "title": "Email"
- },
- "nameSettings": {
- "description": "A descriptive name for your integration.",
- "nameLabel": "Name",
- "namePlaceholder": "e.g. Slack notifications",
- "title": "Name"
- },
- "pagerdutySettings": {
- "description": "Configure your PagerDuty integration here",
- "integrationKeyLabel": "Integration key",
- "integrationKeyPlaceholder": "1234567890",
- "title": "PagerDuty"
- },
- "slackSettings": {
- "description": "Configure your Slack webhook here",
- "title": "Slack",
- "webhookLabel": "Slack webhook URL",
- "webhookPlaceholder": "https://hooks.slack.com/services/..."
- },
- "testNotification": "Test notification",
- "title": "Create notification channel",
- "typeSettings": {
- "description": "Select the type of notification channel you want to create.",
- "title": "Type",
- "typeLabel": "Type"
- },
- "webhookSettings": {
- "description": "Configure your webhook here",
- "title": "Webhook",
- "webhookLabel": "Webhook URL",
- "webhookPlaceholder": "https://your-server.com/webhook"
- }
- },
- "createYour": "Create your",
- "date&Time": "Date & Time",
- "delete": "Delete",
- "deleteDialogDescription": "Once deleted, this monitor cannot be retrieved.",
- "deleteDialogTitle": "Do you really want to delete this monitor?",
- "deleteStatusPage": "Do you want to delete this status page?",
- "deleteStatusPageConfirm": "Yes, delete status page",
- "deleteStatusPageDescription": "Once deleted, your status page cannot be retrieved.",
- "diagnosticsPage": {
- "diagnosticDescription": "System diagnostics",
- "gauges": {
- "heapAllocationSubtitle": "% of available memory",
- "heapAllocationTitle": "Heap allocation",
- "heapUsageSubtitle": "% of available memory",
- "heapUsageTitle": "Heap usage",
- "heapUtilizationSubtitle": "% of allocated",
- "heapUtilizationTitle": "Heap utilization",
- "instantCpuUsageSubtitle": "% of 1s used by CPU",
- "instantCpuUsageTitle": "Instant CPU usage"
- },
- "stats": {
- "eventLoopDelayTitle": "Event loop delay",
- "osMemoryLimitTitle": "OS Memory Limit",
- "totalHeapSizeTitle": "Total heap size",
- "uptimeTitle": "Uptime",
- "usedHeapSizeTitle": "Used heap size"
- },
- "statsDescription": "System statistics"
- },
- "disk": "Disk",
- "diskUsage": "Disk Usage",
- "displayName": "Display name",
- "distributedRightCategoryTitle": "Monitor",
- "distributedStatusHeaderText": "Real-time, real-device coverage",
- "distributedStatusServerMonitors": "Server Monitors",
- "distributedStatusServerMonitorsDescription": "Monitor status of related servers",
- "distributedStatusSubHeaderText": "Powered by millions devices worldwide, view a system performance by global region, country or city",
- "distributedUptimeCreateAdvancedSettings": "Advanced settings",
- "distributedUptimeCreateChecks": "Checks to perform",
- "distributedUptimeCreateChecksDescription": "You can always add or remove checks after adding your site.",
- "distributedUptimeCreateIncidentDescription": "When there is an incident, notify users.",
- "distributedUptimeCreateIncidentNotification": "Incident notifications",
- "distributedUptimeCreateSelectURL": "Here you can select the URL of the host, together with the type of monitor.",
- "distributedUptimeDetailsNoMonitorHistory": "There is no check history for this monitor yet.",
- "distributedUptimeDetailsStatusHeaderLastUpdate": "Last updated",
- "distributedUptimeDetailsStatusHeaderUptime": "Uptime:",
- "distributedUptimeStatus30Days": "30 days",
- "distributedUptimeStatus60Days": "60 days",
- "distributedUptimeStatus90Days": "90 days",
- "distributedUptimeStatusBasicInfoDescription": "Define company name and the subdomain that your status page points to.",
- "distributedUptimeStatusBasicInfoHeader": "Basic Information",
- "distributedUptimeStatusCompanyNameLabel": "Company name",
- "distributedUptimeStatusContactAdmin": "Please contact your administrator",
- "distributedUptimeStatusCreateStatusPage": "status page",
- "distributedUptimeStatusCreateStatusPageAccess": "Access",
- "distributedUptimeStatusCreateStatusPageReady": "If your status page is ready, you can mark it as published.",
- "distributedUptimeStatusCreateYour": "Create your",
- "distributedUptimeStatusDevices": "Devices",
- "distributedUptimeStatusEditYour": "Edit your",
- "distributedUptimeStatusLogoDescription": "Upload a logo for your status page",
- "distributedUptimeStatusLogoHeader": "Logo",
- "distributedUptimeStatusLogoUploadButton": "Upload logo",
- "distributedUptimeStatusPageAddressLabel": "Your status page address",
- "distributedUptimeStatusPageDeleteConfirm": "Yes, delete status page",
- "distributedUptimeStatusPageDeleteDescription": "Once deleted, your status page cannot be retrieved.",
- "distributedUptimeStatusPageDeleteDialog": "Do you want to delete this status page?",
- "distributedUptimeStatusPageNotPublic": "This status page is not public.",
- "distributedUptimeStatusPageNotSetUp": "A status page is not set up.",
- "distributedUptimeStatusPublishedLabel": "Published and visible to the public",
- "distributedUptimeStatusStandardMonitorsDescription": "Attach standard monitors to your status page.",
- "distributedUptimeStatusStandardMonitorsHeader": "Standard Monitors",
- "distributedUptimeStatusUpt": "UPT",
- "distributedUptimeStatusUptBurned": "UPT Burned",
- "distributedUptimeStatusUptLogo": "Upt Logo",
- "dockerContainerMonitoring": "Docker container monitoring",
- "dockerContainerMonitoringDescription": "Check whether your Docker container is running or not.",
- "duration": "Duration",
- "edit": "Edit",
- "editing": "Editing...",
- "editMaintenance": "Edit maintenance",
- "editUserPage": {
- "form": {
- "email": "Email",
- "firstName": "First name",
- "lastName": "Last name",
- "role": "Roles",
- "save": "Save"
- },
- "table": {
- "actionHeader": "Action",
- "roleHeader": "Role"
- },
- "title": "Edit user",
- "toast": {
- "successUserUpdate": "User updated successfully",
- "validationErrors": "Validation errors"
- }
- },
- "emailSent": "Email sent successfully",
- "errorInvalidFieldId": "Invalid field ID provided",
- "errorInvalidTypeId": "Invalid notification type provided",
"errorPages": {
"serverUnreachable": {
+ "toasts": {
+ "reconnected": "Successfully reconnected to the server.",
+ "stillUnreachable": "Server is still unreachable. Please try again later."
+ },
"alertBox": "Server Connection Error",
"description": "We're unable to connect to the server. Please check your internet connection or verify your deployment configuration if the problem persists.",
"retryButton": {
"default": "Retry connection",
"processing": "Connecting..."
- },
- "toasts": {
- "reconnected": "Successfully reconnected to the server.",
- "stillUnreachable": "Server is still unreachable. Please try again later."
}
}
},
- "expectedValue": "Expected value",
- "export": {
- "failed": "Failed to export monitors",
- "success": "Monitors exported successfully!",
- "title": "Export Monitors"
+ "createNotifications": {
+ "title": "Create notification channel",
+ "nameSettings": {
+ "title": "Name",
+ "description": "A descriptive name for your integration.",
+ "nameLabel": "Name",
+ "namePlaceholder": "e.g. Slack notifications"
+ },
+ "typeSettings": {
+ "title": "Type",
+ "description": "Select the type of notification channel you want to create.",
+ "typeLabel": "Type"
+ },
+ "emailSettings": {
+ "title": "Email",
+ "description": "Destination email addresses.",
+ "emailLabel": "Email address",
+ "emailPlaceholder": "e.g. john@example.com"
+ },
+ "slackSettings": {
+ "title": "Slack",
+ "description": "Configure your Slack webhook here",
+ "webhookLabel": "Slack webhook URL",
+ "webhookPlaceholder": "https://hooks.slack.com/services/..."
+ },
+ "pagerdutySettings": {
+ "title": "PagerDuty",
+ "description": "Configure your PagerDuty integration here",
+ "integrationKeyLabel": "Integration key",
+ "integrationKeyPlaceholder": "1234567890"
+ },
+ "discordSettings": {
+ "title": "Discord",
+ "description": "Configure your Discord webhook here",
+ "webhookLabel": "Discord Webhook URL",
+ "webhookPlaceholder": "https://your-server.com/webhook"
+ },
+ "webhookSettings": {
+ "title": "Webhook",
+ "description": "Configure your webhook here",
+ "webhookLabel": "Webhook URL",
+ "webhookPlaceholder": "https://your-server.com/webhook"
+ },
+ "testNotification": "Test notification",
+ "dialogDeleteTitle": "Are you sure you want to delete this notification?",
+ "dialogDeleteConfirm": "Delete"
},
- "failedToSendEmail": "Failed to send email",
- "features": "Features",
- "frequency": "Frequency",
- "friendlyNameInput": "Friendly name",
- "friendlyNamePlaceholder": "Maintenance at __ : __ for ___ minutes",
- "gb": "GB",
- "general": {
- "noOptionsFound": "No {{unit}} found"
+ "notificationConfig": {
+ "title": "Notifications",
+ "description": "Select the notifications channels you want to use"
},
- "greeting": {
- "append": "The afternoon is your playground—let's make it epic!",
- "overview": "Here's an overview of your {{type}} monitors.",
- "prepend": "Hey there"
+ "monitorStatus": {
+ "checkingEvery": "Checking every {{interval}}",
+ "withCaptureAgent": "with Capture agent {{version}}",
+ "up": "up",
+ "down": "down",
+ "paused": "paused"
},
- "high": "high",
- "host": "Host",
- "http": "HTTP",
- "https": "HTTPS",
- "ignoreTLSError": "Ignore TLS/SSL error",
- "ignoreTLSErrorDescription": "Ignore TLS/SSL errors and continue checking the website's availability",
- "incidentsOptionsHeader": "Incidents for:",
- "incidentsOptionsHeaderFilterAll": "All",
- "incidentsOptionsHeaderFilterBy": "Filter by:",
- "incidentsOptionsHeaderFilterCannotResolve": "Cannot Resolve",
- "incidentsOptionsHeaderFilterDown": "Down",
- "incidentsOptionsHeaderFilterResolved": "Resolved",
- "incidentsOptionsHeaderLastDay": "Last day",
- "incidentsOptionsHeaderLastHour": "Last hour",
- "incidentsOptionsHeaderLastWeek": "Last week",
- "incidentsOptionsHeaderShow": "Show:",
- "incidentsOptionsHeaderTotalIncidents": "Total Incidents",
- "incidentsOptionsPlaceholderAllServers": "All servers",
- "incidentsPageActionResolveAll": "Resolve all incidents",
- "incidentsPageActionResolveMonitor": "Resolve monitor incidents",
- "incidentsPageTitle": "Incidents",
- "incidentsTableActionResolve": "Resolve",
- "incidentsTableDateTime": "Date & Time",
- "incidentsTableMessage": "Message",
- "incidentsTableMonitorName": "Monitor Name",
- "incidentsTableNoIncidents": "No incidents recorded",
- "incidentsTablePaginationLabel": "incidents",
- "incidentsTableResolvedAt": "Resolved at",
- "incidentsTableStatus": "Status",
- "incidentsTableStatusCode": "Status Code",
- "infrastructureAlertNotificationDescription": "Send a notification to user(s) when thresholds exceed a specified percentage.",
- "infrastructureAuthorizationSecretLabel": "Authorization secret",
- "infrastructureCreateGeneralSettingsDescription": "Here you can select the URL of the host, together with the friendly name and authorization secret to connect to the server agent.",
- "infrastructureCreateMonitor": "Create Infrastructure Monitor",
- "infrastructureCreateYour": "Create your",
- "infrastructureCustomizeAlerts": "Customize alerts",
- "infrastructureDisplayNameLabel": "Display name",
- "infrastructureEditMonitor": "Save Infrastructure Monitor",
- "infrastructureEditYour": "Edit your",
- "infrastructureMonitor": {
- "fallback": {
- "actionButton": "Let's create your first infrastructure monitor!",
- "checks": [
- "Track the performance of your servers",
- "Identify bottlenecks and optimize usage",
- "Ensure reliability with real-time monitoring"
- ],
- "title": "An infrastructure monitor is used to:"
- }
- },
- "infrastructureMonitorCreated": "Infrastructure monitor created successfully!",
- "infrastructureMonitorUpdated": "Infrastructure monitor updated successfully!",
- "infrastructureProtocol": "Protocol",
- "infrastructureServerRequirement": "The server you are monitoring must be running the",
- "infrastructureServerUrlLabel": "Server URL",
- "integrations": "Integrations",
- "integrationsDiscord": "Discord",
- "integrationsDiscordInfo": "Connect with Discord and view incidents directly in a channel",
- "integrationsPrism": "Connect Prism to your favorite service.",
- "integrationsSlack": "Slack",
- "integrationsSlackInfo": "Connect with Slack and see incidents in a channel",
- "integrationsZapier": "Zapier",
- "integrationsZapierInfo": "Send all incidents to Zapier, and then see them everywhere",
- "invalidFileFormat": "Unsupported file format!",
- "invalidFileSize": "File size is too large!",
- "inviteNoTokenFound": "No invite token found",
- "loginHere": "Login here",
+ "advancedMatching": "Advanced matching",
+ "sendTestNotifications": "Send test notifications",
+ "selectAll": "Select all",
+ "showAdminLoginLink": "Show \"Administrator? Login Here\" link on the status page",
"logsPage": {
+ "title": "Logs",
"description": "System logs - last 1000 lines",
+ "tabs": {
+ "queue": "Job queue",
+ "logs": "Server logs",
+ "diagnostics": "Diagnostics"
+ },
+ "toast": {
+ "fetchLogsSuccess": "Logs fetched successfully"
+ },
"logLevelSelect": {
"title": "Log level",
"values": {
"all": "All",
- "debug": "Debug",
- "error": "Error",
"info": "Info",
- "warn": "Warn"
+ "warn": "Warn",
+ "error": "Error",
+ "debug": "Debug"
}
- },
- "tabs": {
- "diagnostics": "Diagnostics",
- "logs": "Server logs",
- "queue": "Job queue"
- },
- "title": "Logs",
- "toast": {
- "fetchLogsSuccess": "Logs fetched successfully"
}
},
- "low": "low",
- "maintenance": "maintenance",
- "maintenanceRepeat": "Maintenance Repeat",
- "maintenanceTableActionMenuDialogTitle": "Do you really want to remove this maintenance window?",
- "maintenanceWindow": {
- "fallback": {
- "actionButton": "Let's create your first maintenance window!",
- "checks": [
- "Mark your maintenance periods",
- "Eliminate any misunderstandings",
- "Stop sending alerts in maintenance windows"
- ],
- "title": "A maintenance window is used to:"
- }
- },
- "maintenanceWindowDescription": "Your pings won't be sent during this time frame",
- "maintenanceWindowName": "Maintenance Window Name",
- "maskedPageSpeedKeyPlaceholder": "*************************************",
- "matchMethod": "Match Method",
- "matchMethodOptions": {
- "equal": "Equal",
- "equalPlaceholder": "success",
- "include": "Include",
- "includePlaceholder": "ok",
- "regex": "Regex",
- "regexPlaceholder": "^(success|ok)$",
- "text": "Match Method"
- },
- "mb": "MB",
- "mem": "Mem",
- "memory": "Memory",
- "memoryUsage": "Memory usage",
- "menu": {
- "changelog": "Changelog",
- "discussions": "Discussions",
- "docs": "Docs",
- "incidents": "Incidents",
- "infrastructure": "Infrastructure",
- "integrations": "Integrations",
- "logOut": "Log out",
- "logs": "Logs",
- "maintenance": "Maintenance",
- "notifications": "Notifications",
- "pagespeed": "Pagespeed",
- "password": "Password",
- "profile": "Profile",
- "settings": "Settings",
- "statusPages": "Status pages",
- "support": "Support",
- "team": "Team",
- "uptime": "Uptime"
- },
- "message": "Message",
- "monitor": "monitor",
- "monitorActions": {
- "deleteFailed": "Failed to delete monitor",
- "deleteSuccess": "Monitor deleted successfully",
- "details": "Details",
- "export": "Export Monitors",
- "import": "Import Monitors",
- "title": "Export/Import"
- },
- "monitorDisplayName": "Monitor display name",
- "monitorHooks": {
- "failureAddDemoMonitors": "Failed to add demo monitors",
- "successAddDemoMonitors": "Successfully added demo monitors"
- },
- "monitors": "monitors",
- "monitorState": {
- "active": "Active",
- "paused": "Paused",
- "resumed": "Resumed"
- },
- "monitorStatus": {
- "checkingEvery": "Checking every {{interval}}",
- "down": "down",
- "paused": "paused",
- "up": "up",
- "withCaptureAgent": "with Capture agent {{version}}"
- },
- "monitorStatusDown": "Monitor {name} ({url}) is DOWN and not responding",
- "monitorStatusUp": "Monitor {name} ({url}) is now UP and responding",
- "monitorsToApply": "Monitors to apply maintenance window to",
- "monitorType": {
- "docker": {
- "label": "Container ID",
- "namePlaceholder": "My Container",
- "placeholder": "abcd1234"
- },
- "http": {
- "label": "URL to monitor",
- "namePlaceholder": "Google",
- "placeholder": "google.com"
- },
- "ping": {
- "label": "IP address to monitor",
- "namePlaceholder": "Google",
- "placeholder": "1.1.1.1"
- },
- "port": {
- "label": "URL to monitor",
- "namePlaceholder": "Localhost:5173",
- "placeholder": "localhost"
- },
- "game": {
- "label": "URL to monitor",
- "namePlaceholder": "localhost:5173",
- "placeholder": "localhost"
- }
- },
- "ms": "ms",
- "navControls": "Controls",
- "nextWindow": "Next window",
- "notFoundButton": "Go to the main dashboard",
- "notificationConfig": {
- "description": "Select the notifications channels you want to use",
- "title": "Notifications"
- },
- "notifications": {
- "addOrEditNotifications": "Add or edit notifications",
- "create": {
- "failed": "Failed to create notification",
- "success": "Notification created successfully"
- },
- "createButton": "Create notification channel",
- "createTitle": "Notification channel",
- "delete": {
- "failed": "Failed to delete notification",
- "success": "Notification deleted successfully"
- },
- "discord": {
- "description": "To send data to a Discord channel from Checkmate via Discord notifications using webhooks, you can use Discord's incoming Webhooks feature.",
- "label": "Discord",
- "webhookLabel": "Discord Webhook URL",
- "webhookPlaceholder": "https://discord.com/api/webhooks/...",
- "webhookRequired": "Discord webhook URL is required"
- },
- "edit": {
- "failed": "Failed to update notification",
- "success": "Notification updated successfully"
- },
- "enableNotifications": "Enable {{platform}} notifications",
- "fallback": {
- "actionButton": "Let's create your first notification channel!",
- "checks": [
- "Alert teams about downtime or performance issues",
- "Let engineers know when incidents happen",
- "Keep administrators informed of system changes"
- ],
- "title": "A notification channel is used to:"
- },
- "fetch": {
- "failed": "Failed to fetch notifications",
- "success": "Notifications fetched successfully"
- },
- "integrationButton": "Notification Integration",
- "networkError": "Network error occurred",
- "slack": {
- "description": "To enable Slack notifications, create a Slack app and enable incoming webhooks. After that, simply provide the webhook URL here.",
- "label": "Slack",
- "webhookLabel": "Webhook URL",
- "webhookPlaceholder": "https://hooks.slack.com/services/...",
- "webhookRequired": "Slack webhook URL is required"
- },
- "telegram": {
- "chatIdLabel": "Your Chat ID",
- "chatIdPlaceholder": "-1001234567890",
- "description": "To enable Telegram notifications, create a Telegram bot using BotFather, an official bot for creating and managing Telegram bots. Then, get the API token and chat ID and write them down here.",
- "fieldsRequired": "Telegram token and chat ID are required",
- "label": "Telegram",
- "tokenLabel": "Your bot token",
- "tokenPlaceholder": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
- },
- "test": {
- "failed": "Failed to send test notification",
- "success": "Test notification sent successfully"
- },
- "testFailed": "Failed to send test notification",
- "testNotification": "Test notification",
- "testNotificationDevelop": "Test notification 2",
- "testSuccess": "Test notification sent successfully!",
- "unsupportedType": "Unsupported notification type",
- "webhook": {
- "description": "You can set up a custom webhook to receive notifications when incidents occur.",
- "label": "Webhooks",
- "urlLabel": "Webhook URL",
- "urlPlaceholder": "https://your-server.com/webhook",
- "urlRequired": "Webhook URL is required"
- }
- },
- "notifyEmails": "Also notify via email to multiple addresses (coming soon)",
- "notifySMS": "Notify via SMS (coming soon)",
- "now": "Now",
- "os": "OS",
- "pageSpeed": {
- "fallback": {
- "actionButton": "Let's create your first PageSpeed monitor!",
- "checks": [
- "Report on the user experience of a page",
- "Help analyze webpage speed",
- "Give suggestions on how the page can be improved"
- ],
- "title": "A PageSpeed monitor is used to:"
- }
- },
- "pageSpeedAddApiKey": "to add your API key.",
- "pageSpeedConfigureSettingsDescription": "Here you can select the URL of the host, together with the type of monitor.",
- "pageSpeedDetailsPerformanceReport": "Values are estimated and may vary.",
- "pageSpeedDetailsPerformanceReportCalculator": "See calculator",
- "pageSpeedLearnMoreLink": "Click here",
- "pageSpeedLighthouseAPI": "Use the Lighthouse PageSpeed API to monitor your website",
- "pageSpeedMonitor": "PageSpeed monitor",
- "pageSpeedWarning": "Warning: You haven't added a Google PageSpeed API key yet. Without it, the PageSpeed monitor won't function.",
- "passwordPanel": {
- "confirmNewPassword": "Confirm new password",
- "currentPassword": "Current password",
- "enterCurrentPassword": "Enter your current password",
- "enterNewPassword": "Enter your new password",
- "newPassword": "New password",
- "passwordChangedSuccess": "Your password was changed successfully.",
- "passwordInputIncorrect": "Your password input was incorrect.",
- "passwordRequirements": "New password must contain at least 8 characters and must have at least one uppercase letter, one lowercase letter, one number and one special character.",
- "saving": "Saving..."
- },
- "pause": "Pause",
- "pingMonitoring": "Ping monitoring",
- "pingMonitoringDescription": "Check whether your server is available or not.",
- "portMonitoring": "Port monitoring",
- "portMonitoringDescription": "Check whether your port is open or not.",
- "gameServerMonitoring": "Game Server Monitoring",
- "gameServerMonitoringDescription": "Check whether your game server is running or not",
- "portToMonitor": "Port to monitor",
- "publicLink": "Public link",
- "publicURL": "Public URL",
"queuePage": {
- "failedJobTable": {
- "failCountHeader": "Fail count",
- "failedAtHeader": "Last failed at",
- "failReasonHeader": "Fail reason",
- "monitorIdHeader": "Monitor ID",
- "monitorUrlHeader": "Monitor URL",
- "title": "Failed jobs"
- },
+ "title": "Queue",
+ "refreshButton": "Refresh",
"flushButton": "Flush queue",
"jobTable": {
- "activeHeader": "Active",
- "failCountHeader": "Fail count",
+ "title": "Jobs currently in queue",
"idHeader": "Monitor ID",
- "lastFinishedAtHeader": "Last finished at",
- "lastRunHeader": "Last run at",
- "lastRunTookHeader": "Last run took",
+ "urlHeader": "URL",
+ "typeHeader": "Type",
+ "intervalHeader": "Interval",
+ "activeHeader": "Active",
"lockedAtHeader": "Locked at",
"runCountHeader": "Run count",
- "title": "Jobs currently in queue",
- "typeHeader": "Type",
- "urlHeader": "URL"
+ "failCountHeader": "Fail count",
+ "lastRunHeader": "Last run at",
+ "lastFinishedAtHeader": "Last finished at",
+ "lastRunTookHeader": "Last run took"
},
"metricsTable": {
- "metricHeader": "Metric",
"title": "Queue metrics",
+ "metricHeader": "Metric",
"valueHeader": "Value"
},
- "refreshButton": "Refresh",
- "title": "Queue"
+ "failedJobTable": {
+ "title": "Failed jobs",
+ "monitorIdHeader": "Monitor ID",
+ "monitorUrlHeader": "Monitor URL",
+ "failCountHeader": "Fail count",
+ "failedAtHeader": "Last failed at",
+ "failReasonHeader": "Fail reason"
+ }
},
- "remove": "Remove",
- "removeLogo": "Remove Logo",
- "repeat": "Repeat",
- "reset": "Reset",
- "response": "RESPONSE",
- "responseTime": "Response time",
- "resume": "Resume",
- "roles": {
- "admin": "Admin",
- "demoUser": "Demo user",
- "superAdmin": "Super admin",
- "teamMember": "Team member"
+ "export": {
+ "title": "Export Monitors",
+ "success": "Monitors exported successfully!",
+ "failed": "Failed to export monitors"
+ },
+ "monitorActions": {
+ "title": "Export/Import",
+ "import": "Import Monitors",
+ "export": "Export Monitors",
+ "deleteSuccess": "Monitor deleted successfully",
+ "deleteFailed": "Failed to delete monitor",
+ "details": "Details"
},
- "save": "Save",
- "selectAll": "Select all",
- "sendTestNotifications": "Send test notifications",
- "seperateEmails": "You can separate multiple emails with a comma",
- "settingsAppearance": "Appearance",
- "settingsDisabled": "Disabled",
- "settingsDisplayTimezone": "Display timezone",
- "settingsEmailUser": "Email user - Username for authentication, overrides email address if specified",
- "settingsFailedToClearStats": "Failed to clear stats",
- "settingsFailedToDeleteMonitors": "Failed to delete all monitors",
- "settingsFailedToSave": "Failed to save settings",
- "settingsGeneralSettings": "General settings",
- "settingsMonitorsDeleted": "Successfully deleted all monitors",
"settingsPage": {
"aboutSettings": {
"labelDevelopedBy": "Developed by Bluewave Labs",
@@ -853,8 +856,8 @@
},
"pageSpeedSettings": {
"description": "Enter your Google PageSpeed API key to enable Google PageSpeed monitoring. Click Reset to update the key.",
- "labelApiKey": "PageSpeed API key",
"labelApiKeySet": "API key is set. Click Reset to change it.",
+ "labelApiKey": "PageSpeed API key",
"title": "Google PageSpeed API key"
},
"saveButtonLabel": "Save",
@@ -883,10 +886,6 @@
"title": "Display timezone"
},
"title": "Settings",
- "globalThresholds": {
- "title": "Global Thresholds",
- "description": "Configure global CPU, Memory, Disk, and Temperature thresholds. If a value is provided, it will automatically be enabled for monitoring."
- },
"uiSettings": {
"description": "Switch between light and dark mode, or change user interface language.",
"labelLanguage": "Language",
@@ -899,150 +898,225 @@
"selectDisabled": "Disabled",
"selectEnabled": "Enabled",
"title": "Monitor IP/URL on Status Page"
+ },
+ "globalThresholds": {
+ "title": "Global Thresholds",
+ "description": "Configure global CPU, Memory, Disk, and Temperature thresholds. If a value is provided, it will automatically be enabled for monitoring."
}
},
- "settingsSave": "Save",
- "settingsStatsCleared": "Stats cleared successfully",
- "settingsSuccessSaved": "Settings saved successfully",
- "settingsTestEmailFailed": "Failed to send test email",
- "settingsTestEmailFailedWithReason": "Failed to send test email: {{reason}}",
- "settingsTestEmailSuccess": "Test email sent successfully",
- "settingsTestEmailUnknownError": "Unknown error",
- "showAdminLoginLink": "Show \"Administrator? Login Here\" link on the status page",
- "showCharts": "Show charts",
- "shown": "Shown",
- "showUptimePercentage": "Show uptime percentage",
- "starPromptDescription": "See the latest releases and help grow the community on GitHub",
- "starPromptTitle": "Star Checkmate",
- "startTime": "Start time",
- "state": "State",
- "status": "Status",
- "statusBreadCrumbsDetails": "Details",
- "statusBreadCrumbsStatusPages": "Status Pages",
- "statusCode": "Status code",
- "statusMsg": {
- "down": "Your site is down.",
- "paused": "Monitoring is paused.",
- "pending": "Pending...",
- "up": "Your site is up."
+ "statusPageCreate": {
+ "buttonSave": "Save"
},
+ "incidentsOptionsHeaderFilterResolved": "Resolved",
+ "settingsSave": "Save",
+ "statusPageCreateAppearanceTitle": "Appearance",
+ "confirmPassword": "Confirm password",
+ "monitorHooks": {
+ "failureAddDemoMonitors": "Failed to add demo monitors",
+ "successAddDemoMonitors": "Successfully added demo monitors"
+ },
+ "settingsAppearance": "Appearance",
+ "settingsDisplayTimezone": "Display timezone",
+ "settingsGeneralSettings": "General settings",
+ "incidentsOptionsHeaderTotalIncidents": "Total Incidents",
"statusPage": {
- "contents": "Contents",
- "createSuccess": "Status page created successfully",
- "deleteFailed": "Failed to delete status page",
"deleteSuccess": "Status page deleted successfully",
+ "deleteFailed": "Failed to delete status page",
+ "createSuccess": "Status page created successfully",
+ "updateSuccess": "Status page updated successfully",
+ "generalSettings": "General settings",
+ "contents": "Contents",
"fallback": {
- "actionButton": "Let's create your first status page!",
"checks": [
"Monitor and display the health of your services in real time",
"Track multiple services and share their status",
"Keep users informed about outages and performance"
],
- "title": "A status page is used to:"
- },
- "generalSettings": "General settings",
- "updateSuccess": "Status page updated successfully"
+ "title": "A status page is used to:",
+ "actionButton": "Let's create your first status page!"
+ }
},
- "statusPageCreate": {
- "buttonSave": "Save"
- },
- "statusPageCreateAppearanceDescription": "Define the default look and feel of your public status page.",
- "statusPageCreateAppearanceTitle": "Appearance",
- "statusPageCreateBasicInfoDescription": "Define company name and the subdomain that your status page points to.",
- "statusPageCreateBasicInfoStatusPageAddress": "Your status page address",
- "statusPageCreateSelectTimeZoneDescription": "Select the timezone that your status page will be displayed in.",
- "statusPageCreateSettings": "If your status page is ready, you can mark it as published.",
- "statusPageCreateSettingsCheckboxLabel": "Published and visible to the public",
- "statusPageCreateTabsContent": "Status page servers",
- "statusPageCreateTabsContentDescription": "You can add any number of servers that you monitor to your status page. You can also reorder them for the best viewing experience.",
- "statusPageCreateTabsContentFeaturesDescription": "Show more details on the status page",
- "statusPageName": "Status page name",
- "statusPageStatus": "A public status page is not set up.",
- "statusPageStatusContactAdmin": "Please contact to your administrator",
- "statusPageStatusNoPage": "There's no status page here.",
- "statusPageStatusNotPublic": "This status page is not public.",
- "statusPageStatusServiceStatus": "Service status",
- "submit": "Submit",
- "teamPanel": {
- "cancel": "Cancel",
- "email": "Email",
- "emailToken": "E-mail token",
- "filter": {
- "all": "All",
- "member": "Member"
- },
- "getToken": "Get token",
- "inviteDescription": "When you add a new team member, they will get access to all monitors.",
- "inviteLink": "Invite link",
- "inviteNewTeamMember": "Invite new team member",
- "inviteTeamMember": "Invite a team member",
- "noMembers": "There are no team members with this role",
- "selectRole": "Select role",
- "table": {
- "created": "Created",
- "email": "Email",
- "name": "Name",
- "role": "Role"
- },
- "teamMembers": "Team members"
- },
- "testLocale": "testLocale",
"testNotificationsDisabled": "There are no notifications setup for this monitor. You need to add one by clicking 'Configure' button",
+ "incidentsTableResolvedAt": "Resolved at",
+ "incidentsTableActionResolve": "Resolve",
+ "checkHooks": {
+ "failureResolveOne": "Failed to resolve incident.",
+ "failureResolveAll": "Failed to resolve all incidents.",
+ "failureResolveMonitor": "Failed to resolve monitor incidents."
+ },
+ "checkFormError": "Please check the form for errors.",
+ "diagnosticsPage": {
+ "diagnosticDescription": "System diagnostics",
+ "statsDescription": "System statistics",
+ "gauges": {
+ "heapAllocationTitle": "Heap allocation",
+ "heapAllocationSubtitle": "% of available memory",
+ "heapUsageTitle": "Heap usage",
+ "heapUsageSubtitle": "% of available memory",
+ "heapUtilizationTitle": "Heap utilization",
+ "heapUtilizationSubtitle": "% of allocated",
+ "instantCpuUsageTitle": "Instant CPU usage",
+ "instantCpuUsageSubtitle": "% of 1s used by CPU"
+ },
+ "stats": {
+ "eventLoopDelayTitle": "Event loop delay",
+ "uptimeTitle": "Uptime",
+ "usedHeapSizeTitle": "Used heap size",
+ "totalHeapSizeTitle": "Total heap size",
+ "osMemoryLimitTitle": "OS Memory Limit"
+ }
+ },
+ "pageSpeedLighthouseAPI": "Use the Lighthouse PageSpeed API to monitor your website",
"time": {
- "fifteenSeconds": "15 seconds",
- "fiveMinutes": "5 minutes",
- "fourMinutes": "4 minutes",
- "oneDay": "1 day",
- "oneHour": "1 hour",
- "oneMinute": "1 minute",
- "oneWeek": "1 week",
- "tenMinutes": "10 minutes",
- "thirtySeconds": "30 seconds",
"threeMinutes": "3 minutes",
+ "fiveMinutes": "5 minutes",
+ "tenMinutes": "10 minutes",
"twentyMinutes": "20 minutes",
- "twoMinutes": "2 minutes"
+ "oneHour": "1 hour",
+ "oneDay": "1 day",
+ "oneWeek": "1 week",
+ "fourMinutes": "4 minutes",
+ "oneMinute": "1 minute",
+ "twoMinutes": "2 minutes",
+ "fifteenSeconds": "15 seconds",
+ "thirtySeconds": "30 seconds"
},
- "timezone": "Timezone",
- "timeZoneInfo": "All dates and times are in GMT+0 time zone.",
- "title": "Title",
- "tlsErrorIgnored": "TLS/SSL errors ignored",
- "total": "Total",
- "type": "Type",
- "update": "Update",
- "uptime": "Uptime",
- "uptimeAdvancedMatching": {
- "jsonPath": "JSON Path"
+ "general": {
+ "noOptionsFound": "No {{unit}} found"
},
- "uptimeCreate": "The expected value is used to match against response result, and the match determines the status.",
- "uptimeCreateJsonPath": "This expression will be evaluated against the reponse JSON data and the result will be used to match against the expected value. See",
- "uptimeCreateJsonPathQuery": "for query language documentation.",
- "uptimeGeneralInstructions": {
- "docker": "Enter the Docker ID of your container. Docker IDs must be the full 64 char Docker ID. You can run docker inspect to get the full container ID.",
- "http": "Enter the URL or IP to monitor (e.g., https://example.com/ or 192.168.1.100) and add a clear display name that appears on the dashboard.",
- "ping": "Enter the IP address or hostname to ping (e.g., 192.168.1.100 or example.com) and add a clear display name that appears on the dashboard.",
- "port": "Enter the URL or IP of the server, the port number and a clear display name that appears on the dashboard.",
- "game": "Enter the IP address or hostname and the port number to ping (e.g., 192.168.1.100 or example.com) and choose game type."
+ "infrastructureMonitor": {
+ "fallback": {
+ "checks": [
+ "Track the performance of your servers",
+ "Identify bottlenecks and optimize usage",
+ "Ensure reliability with real-time monitoring"
+ ],
+ "title": "An infrastructure monitor is used to:",
+ "actionButton": "Let's create your first infrastructure monitor!"
+ }
+ },
+ "maintenanceWindow": {
+ "fallback": {
+ "checks": [
+ "Mark your maintenance periods",
+ "Eliminate any misunderstandings",
+ "Stop sending alerts in maintenance windows"
+ ],
+ "title": "A maintenance window is used to:",
+ "actionButton": "Let's create your first maintenance window!"
+ }
+ },
+ "pageSpeed": {
+ "fallback": {
+ "checks": [
+ "Report on the user experience of a page",
+ "Help analyze webpage speed",
+ "Give suggestions on how the page can be improved"
+ ],
+ "title": "A PageSpeed monitor is used to:",
+ "actionButton": "Let's create your first PageSpeed monitor!"
+ }
},
"uptimeMonitor": {
"fallback": {
- "actionButton": "Let's create your first uptime monitor!",
"checks": [
"Check if websites or servers are online & responsive",
"Alert teams about downtime or performance issues",
"Monitor HTTP endpoints, pings, containers & ports",
"Track historical uptime and reliability trends"
],
- "title": "An uptime monitor is used to:"
+ "title": "An uptime monitor is used to:",
+ "actionButton": "Let's create your first uptime monitor!"
}
},
- "url": "URL",
- "urlMonitor": "URL to monitor",
- "used": "Used",
- "webhookSendError": "Error sending webhook notification to {platform}",
- "webhookSendSuccess": "Webhook notification sent successfully",
- "webhookUnsupportedPlatform": "Unsupported platform: {platform}",
- "websiteMonitoring": "Website monitoring",
- "websiteMonitoringDescription": "Use HTTP(s) to monitor your website or API endpoint.",
- "whenNewIncident": "When there is a new incident,",
- "window": "window"
+ "editUserPage": {
+ "form": {
+ "email": "Email",
+ "firstName": "First name",
+ "lastName": "Last name",
+ "role": "Roles",
+ "save": "Save"
+ },
+ "table": {
+ "actionHeader": "Action",
+ "roleHeader": "Role"
+ },
+ "title": "Edit user",
+ "toast": {
+ "successUserUpdate": "User updated successfully",
+ "validationErrors": "Validation errors"
+ }
+ },
+ "incidentsPageActionResolveMonitor": "Resolve monitor incidents",
+ "incidentsPageActionResolveAll": "Resolve all incidents",
+ "matchMethodOptions": {
+ "equal": "Equal",
+ "equalPlaceholder": "success",
+ "include": "Include",
+ "includePlaceholder": "ok",
+ "regex": "Regex",
+ "regexPlaceholder": "^(success|ok)$",
+ "text": "Match Method"
+ },
+ "monitorType": {
+ "docker": {
+ "label": "Container Name/ID",
+ "namePlaceholder": "My Container",
+ "placeholder": "my-app or abcd1234"
+ },
+ "http": {
+ "label": "URL to monitor",
+ "namePlaceholder": "Google",
+ "placeholder": "google.com"
+ },
+ "ping": {
+ "label": "IP address to monitor",
+ "namePlaceholder": "Google",
+ "placeholder": "1.1.1.1"
+ },
+ "port": {
+ "label": "URL to monitor",
+ "namePlaceholder": "Localhost:5173",
+ "placeholder": "localhost"
+ },
+ "game": {
+ "label": "URL to monitor",
+ "namePlaceholder": "localhost:5173",
+ "placeholder": "localhost"
+ }
+ },
+ "uptimeAdvancedMatching": {
+ "jsonPath": "JSON Path"
+ },
+ "bytesPerSecond": "Bytes per second",
+ "bytesReceived": "Bytes Received",
+ "bytesSent": "Bytes Sent",
+ "chooseGame": "Choose game",
+ "createMonitorPage": {
+ "incidentConfigDescription": "A sliding window is used to determine when a monitor goes down. The status of a monitor will only change when the percentage of checks in the sliding window meet the specified value.",
+ "incidentConfigStatusWindowLabel": "How many checks should be in the sliding window?",
+ "incidentConfigStatusWindowThresholdLabel": "What percentage of checks in the sliding window fail/succeed before monitor status changes?",
+ "incidentConfigTitle": "Incidents"
+ },
+ "dataRate": "Data Rate",
+ "dataReceived": "Data Received",
+ "dataSent": "Data Sent",
+ "details": "Details",
+ "drops": "Drops",
+ "errors": "Errors",
+ "errorsIn": "Errors In",
+ "errorsOut": "Errors Out",
+ "gameServerMonitoring": "Game Server Monitoring",
+ "gameServerMonitoringDescription": "Check whether your game server is running or not",
+ "network": "Network",
+ "networkDrops": "Network Drops",
+ "networkErrors": "Network Errors",
+ "networkInterface": "Network Interface",
+ "noNetworkStatsAvailable": "No network stats available.",
+ "packetsPerSecond": "Packets per second",
+ "packetsReceived": "Packets Received",
+ "packetsReceivedRate": "Packets Received Rate",
+ "packetsSent": "Packets Sent",
+ "rate": "Rate",
+ "selectInterface": "Select Interface"
}
diff --git a/client/src/locales/es.json b/client/src/locales/es.json
index 1e2ee3b6c..52fd8c97b 100644
--- a/client/src/locales/es.json
+++ b/client/src/locales/es.json
@@ -260,7 +260,6 @@
"notifyEmails": "",
"seperateEmails": "",
"checkFrequency": "",
- "chooseGame": "",
"matchMethod": "",
"expectedValue": "",
"deleteDialogTitle": "",
@@ -569,7 +568,8 @@
"password": {
"incorrect": ""
}
- }
+ },
+ "welcome": ""
},
"registration": {
"heading": {
@@ -595,7 +595,8 @@
},
"toasts": {
"success": ""
- }
+ },
+ "welcome": ""
},
"forgotPassword": {
"heading": "",
@@ -847,6 +848,10 @@
"selectDisabled": "",
"selectEnabled": "",
"title": ""
+ },
+ "globalThresholds": {
+ "title": "",
+ "description": ""
}
},
"statusPageCreate": {
@@ -915,7 +920,12 @@
"twentyMinutes": "",
"oneHour": "",
"oneDay": "",
- "oneWeek": ""
+ "oneWeek": "",
+ "fourMinutes": "",
+ "oneMinute": "",
+ "twoMinutes": "",
+ "fifteenSeconds": "",
+ "thirtySeconds": ""
},
"general": {
"noOptionsFound": ""
@@ -967,5 +977,75 @@
}
},
"incidentsPageActionResolveMonitor": "",
- "incidentsPageActionResolveAll": ""
+ "incidentsPageActionResolveAll": "",
+ "matchMethodOptions": {
+ "equal": "",
+ "equalPlaceholder": "",
+ "include": "",
+ "includePlaceholder": "",
+ "regex": "",
+ "regexPlaceholder": "",
+ "text": ""
+ },
+ "monitorType": {
+ "docker": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "http": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "ping": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "port": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "game": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ }
+ },
+ "uptimeAdvancedMatching": {
+ "jsonPath": ""
+ },
+ "bytesPerSecond": "",
+ "bytesReceived": "",
+ "bytesSent": "",
+ "chooseGame": "",
+ "createMonitorPage": {
+ "incidentConfigDescription": "",
+ "incidentConfigStatusWindowLabel": "",
+ "incidentConfigStatusWindowThresholdLabel": "",
+ "incidentConfigTitle": ""
+ },
+ "dataRate": "",
+ "dataReceived": "",
+ "dataSent": "",
+ "details": "",
+ "drops": "",
+ "errors": "",
+ "errorsIn": "",
+ "errorsOut": "",
+ "gameServerMonitoring": "",
+ "gameServerMonitoringDescription": "",
+ "network": "",
+ "networkDrops": "",
+ "networkErrors": "",
+ "networkInterface": "",
+ "noNetworkStatsAvailable": "",
+ "packetsPerSecond": "",
+ "packetsReceived": "",
+ "packetsReceivedRate": "",
+ "packetsSent": "",
+ "rate": "",
+ "selectInterface": ""
}
diff --git a/client/src/locales/fi.json b/client/src/locales/fi.json
index cce87a89f..d42a9f1f9 100644
--- a/client/src/locales/fi.json
+++ b/client/src/locales/fi.json
@@ -260,7 +260,6 @@
"notifyEmails": "",
"seperateEmails": "",
"checkFrequency": "",
- "chooseGame": "",
"matchMethod": "",
"expectedValue": "",
"deleteDialogTitle": "",
@@ -569,7 +568,8 @@
"password": {
"incorrect": ""
}
- }
+ },
+ "welcome": ""
},
"registration": {
"heading": {
@@ -595,7 +595,8 @@
},
"toasts": {
"success": ""
- }
+ },
+ "welcome": ""
},
"forgotPassword": {
"heading": "",
@@ -847,6 +848,10 @@
"selectDisabled": "",
"selectEnabled": "",
"title": ""
+ },
+ "globalThresholds": {
+ "title": "",
+ "description": ""
}
},
"statusPageCreate": {
@@ -915,7 +920,12 @@
"twentyMinutes": "",
"oneHour": "",
"oneDay": "",
- "oneWeek": ""
+ "oneWeek": "",
+ "fourMinutes": "",
+ "oneMinute": "",
+ "twoMinutes": "",
+ "fifteenSeconds": "",
+ "thirtySeconds": ""
},
"general": {
"noOptionsFound": ""
@@ -967,5 +977,75 @@
}
},
"incidentsPageActionResolveMonitor": "",
- "incidentsPageActionResolveAll": ""
+ "incidentsPageActionResolveAll": "",
+ "matchMethodOptions": {
+ "equal": "",
+ "equalPlaceholder": "",
+ "include": "",
+ "includePlaceholder": "",
+ "regex": "",
+ "regexPlaceholder": "",
+ "text": ""
+ },
+ "monitorType": {
+ "docker": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "http": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "ping": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "port": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "game": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ }
+ },
+ "uptimeAdvancedMatching": {
+ "jsonPath": ""
+ },
+ "bytesPerSecond": "",
+ "bytesReceived": "",
+ "bytesSent": "",
+ "chooseGame": "",
+ "createMonitorPage": {
+ "incidentConfigDescription": "",
+ "incidentConfigStatusWindowLabel": "",
+ "incidentConfigStatusWindowThresholdLabel": "",
+ "incidentConfigTitle": ""
+ },
+ "dataRate": "",
+ "dataReceived": "",
+ "dataSent": "",
+ "details": "",
+ "drops": "",
+ "errors": "",
+ "errorsIn": "",
+ "errorsOut": "",
+ "gameServerMonitoring": "",
+ "gameServerMonitoringDescription": "",
+ "network": "",
+ "networkDrops": "",
+ "networkErrors": "",
+ "networkInterface": "",
+ "noNetworkStatsAvailable": "",
+ "packetsPerSecond": "",
+ "packetsReceived": "",
+ "packetsReceivedRate": "",
+ "packetsSent": "",
+ "rate": "",
+ "selectInterface": ""
}
diff --git a/client/src/locales/fr.json b/client/src/locales/fr.json
index 52d5d8c7e..d2a5fd19d 100644
--- a/client/src/locales/fr.json
+++ b/client/src/locales/fr.json
@@ -90,7 +90,7 @@
"Informez les ingénieurs lorsque des incidents se produisent",
"Tenez les administrateurs informés des changements apportés au système"
],
- "actionButton": ""
+ "actionButton": "Créez votre premier canal de notification !"
},
"createButton": "Créer un canal de notification",
"createTitle": "Canal de notification",
@@ -264,7 +264,6 @@
"notifyEmails": "Envoyer également une notification par email à plusieurs adresses (bientôt).",
"seperateEmails": "Vous pouvez ajouter plusieurs adresses emails en les séparant par une virgule",
"checkFrequency": "Vérifier la fréquence",
- "chooseGame": "",
"matchMethod": "Méthode de rapprochement",
"expectedValue": "Valeur attendue",
"deleteDialogTitle": "Voulez-vous vraiment supprimer ce moniteur ?",
@@ -548,7 +547,7 @@
},
"role": {
"errors": {
- "min": ""
+ "min": "Au moins un rôle est requis"
}
}
}
@@ -573,7 +572,8 @@
"password": {
"incorrect": "Le mot de passe fourni est erroné"
}
- }
+ },
+ "welcome": "Bon retour sur Checkmate !"
},
"registration": {
"heading": {
@@ -599,7 +599,8 @@
},
"toasts": {
"success": "Bienvenue ! Votre compte a été créé avec succès."
- }
+ },
+ "welcome": "Bienvenue sur Checkmate !"
},
"forgotPassword": {
"heading": "Mot de passe oublié ?",
@@ -851,6 +852,10 @@
"selectDisabled": "Désactivé",
"selectEnabled": "Activé",
"title": "IP/URL du moniteur sur la page de statut"
+ },
+ "globalThresholds": {
+ "title": "Seuils globaux",
+ "description": "Configurez des seuils globaux pour le CPU, la mémoire, le disque et la température. Si une valeur est fournie, elle sera automatiquement activée pour la surveillance."
}
},
"statusPageCreate": {
@@ -876,9 +881,13 @@
"generalSettings": "Paramètres généraux",
"contents": "Contenus",
"fallback": {
- "checks": [""],
- "title": "",
- "actionButton": ""
+ "checks": [
+ "Surveillez et affichez l’état de santé de vos services en temps réel",
+ "Suivez plusieurs services et partagez leur statut",
+ "Tenez les utilisateurs informés des pannes et des performances"
+ ],
+ "title": "Une page de status est utilisée pour :",
+ "actionButton": "Créer votre première page de status !"
}
},
"testNotificationsDisabled": "Il n'y aucune notification paramétrée pour ce moniteur. Vous pouvez en ajouter une en cliquant sur le bouton 'Configurer'",
@@ -919,57 +928,149 @@
"twentyMinutes": "20 minutes",
"oneHour": "1 heure",
"oneDay": "1 jour",
- "oneWeek": "1 semaine"
+ "oneWeek": "1 semaine",
+ "fourMinutes": "4 minutes",
+ "oneMinute": "1 minute",
+ "twoMinutes": "2 minutes",
+ "fifteenSeconds": "15 secondes",
+ "thirtySeconds": "30 secondes"
},
"general": {
- "noOptionsFound": ""
+ "noOptionsFound": "Aucune {{unit}} trouvée"
},
"infrastructureMonitor": {
"fallback": {
- "checks": [""],
- "title": "",
- "actionButton": ""
+ "checks": [
+ "Suivez les performances de vos serveurs",
+ "Identifiez les points de blocage et optimisez l’utilisation",
+ "Assurez la fiabilité grâce à la surveillance en temps réel"
+ ],
+ "title": "Une surveillance d'infrastructure est utilisé pour :",
+ "actionButton": "Créer votre premier moniteur d'infrastructure !"
}
},
"maintenanceWindow": {
"fallback": {
- "checks": [""],
- "title": "",
- "actionButton": ""
+ "checks": [
+ "Marquez vos périodes de maintenance",
+ "Éliminez tout malentendu",
+ "Arrêtez d’envoyer des alertes pendant les fenêtres de maintenance"
+ ],
+ "title": "Une fenêtre de maintenance est utilisé pour :",
+ "actionButton": "Créer votre première fenêtre de maintenance !"
}
},
"pageSpeed": {
"fallback": {
- "checks": [""],
+ "checks": [
+ "Rapport sur l’expérience utilisateur d’une page",
+ "Aider à analyser la vitesse de la page web",
+ "Donner des suggestions sur la façon dont la page peut être améliorée"
+ ],
"title": "",
"actionButton": ""
}
},
"uptimeMonitor": {
"fallback": {
- "checks": [""],
+ "checks": [
+ "Vérifiez si les sites web ou serveurs sont en ligne et réactifs",
+ "Alertez les équipes en cas de panne ou de problème de performance",
+ "Surveillez les points de terminaison HTTP, les pings, les conteneurs et les ports",
+ "Suivez l’historique de la disponibilité et les tendances de fiabilité"
+ ],
"title": "",
"actionButton": ""
}
},
"editUserPage": {
"form": {
- "email": "",
- "firstName": "",
- "lastName": "",
- "role": "",
- "save": ""
+ "email": "Email",
+ "firstName": "Prénom",
+ "lastName": "Nom",
+ "role": "Rôles",
+ "save": "Enregistrer"
},
"table": {
- "actionHeader": "",
- "roleHeader": ""
+ "actionHeader": "Action",
+ "roleHeader": "Rôle"
},
- "title": "",
+ "title": "Modifier l'utilisateur",
"toast": {
- "successUserUpdate": "",
+ "successUserUpdate": "L'utilisateur à bien été modifié",
"validationErrors": ""
}
},
- "incidentsPageActionResolveMonitor": "",
- "incidentsPageActionResolveAll": ""
+ "incidentsPageActionResolveMonitor": "Résoudres les incidents de surveillance",
+ "incidentsPageActionResolveAll": "Résoudre tous les incidents",
+ "matchMethodOptions": {
+ "equal": "Égal",
+ "equalPlaceholder": "",
+ "include": "Inclure",
+ "includePlaceholder": "ok",
+ "regex": "Regex",
+ "regexPlaceholder": "",
+ "text": ""
+ },
+ "monitorType": {
+ "docker": {
+ "label": "ID du conteneur",
+ "namePlaceholder": "Mon conteneur",
+ "placeholder": "abcd1234"
+ },
+ "http": {
+ "label": "URL à surveiller",
+ "namePlaceholder": "Google",
+ "placeholder": "google.com"
+ },
+ "ping": {
+ "label": "Adresse IP à surveiller",
+ "namePlaceholder": "Google",
+ "placeholder": "1.1.1.1"
+ },
+ "port": {
+ "label": "URL à surveiller",
+ "namePlaceholder": "",
+ "placeholder": "localhost"
+ },
+ "game": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ }
+ },
+ "uptimeAdvancedMatching": {
+ "jsonPath": "Chemin JSON"
+ },
+ "bytesPerSecond": "",
+ "bytesReceived": "",
+ "bytesSent": "",
+ "chooseGame": "",
+ "createMonitorPage": {
+ "incidentConfigDescription": "",
+ "incidentConfigStatusWindowLabel": "",
+ "incidentConfigStatusWindowThresholdLabel": "",
+ "incidentConfigTitle": ""
+ },
+ "dataRate": "",
+ "dataReceived": "",
+ "dataSent": "",
+ "details": "",
+ "drops": "",
+ "errors": "",
+ "errorsIn": "",
+ "errorsOut": "",
+ "gameServerMonitoring": "",
+ "gameServerMonitoringDescription": "",
+ "network": "",
+ "networkDrops": "",
+ "networkErrors": "",
+ "networkInterface": "",
+ "noNetworkStatsAvailable": "",
+ "packetsPerSecond": "",
+ "packetsReceived": "",
+ "packetsReceivedRate": "",
+ "packetsSent": "",
+ "rate": "",
+ "selectInterface": ""
}
diff --git a/client/src/locales/ja.json b/client/src/locales/ja.json
index 80245d64b..4c90c485e 100644
--- a/client/src/locales/ja.json
+++ b/client/src/locales/ja.json
@@ -264,7 +264,6 @@
"notifyEmails": "複数のアドレスにメールでも通知(近日公開)",
"seperateEmails": "複数のメールはカンマで区切ることができます",
"checkFrequency": "チェック頻度",
- "chooseGame": "",
"matchMethod": "マッチ方法",
"expectedValue": "期待値",
"deleteDialogTitle": "本当にこのモニターを削除しますか?",
@@ -573,7 +572,8 @@
"password": {
"incorrect": "入力されたパスワードが記録と一致しません"
}
- }
+ },
+ "welcome": ""
},
"registration": {
"heading": {
@@ -599,7 +599,8 @@
},
"toasts": {
"success": "ようこそ!アカウントが正常に作成されました。"
- }
+ },
+ "welcome": ""
},
"forgotPassword": {
"heading": "パスワードを忘れましたか?",
@@ -851,6 +852,10 @@
"selectDisabled": "無効",
"selectEnabled": "有効",
"title": "ステータスページのモニターIP/URL"
+ },
+ "globalThresholds": {
+ "title": "",
+ "description": ""
}
},
"statusPageCreate": {
@@ -919,7 +924,12 @@
"twentyMinutes": "20分",
"oneHour": "1時間",
"oneDay": "1日",
- "oneWeek": "1週間"
+ "oneWeek": "1週間",
+ "fourMinutes": "",
+ "oneMinute": "",
+ "twoMinutes": "",
+ "fifteenSeconds": "",
+ "thirtySeconds": ""
},
"general": {
"noOptionsFound": ""
@@ -971,5 +981,75 @@
}
},
"incidentsPageActionResolveMonitor": "",
- "incidentsPageActionResolveAll": ""
+ "incidentsPageActionResolveAll": "",
+ "matchMethodOptions": {
+ "equal": "",
+ "equalPlaceholder": "",
+ "include": "",
+ "includePlaceholder": "",
+ "regex": "",
+ "regexPlaceholder": "",
+ "text": ""
+ },
+ "monitorType": {
+ "docker": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "http": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "ping": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "port": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "game": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ }
+ },
+ "uptimeAdvancedMatching": {
+ "jsonPath": ""
+ },
+ "bytesPerSecond": "",
+ "bytesReceived": "",
+ "bytesSent": "",
+ "chooseGame": "",
+ "createMonitorPage": {
+ "incidentConfigDescription": "",
+ "incidentConfigStatusWindowLabel": "",
+ "incidentConfigStatusWindowThresholdLabel": "",
+ "incidentConfigTitle": ""
+ },
+ "dataRate": "",
+ "dataReceived": "",
+ "dataSent": "",
+ "details": "",
+ "drops": "",
+ "errors": "",
+ "errorsIn": "",
+ "errorsOut": "",
+ "gameServerMonitoring": "",
+ "gameServerMonitoringDescription": "",
+ "network": "",
+ "networkDrops": "",
+ "networkErrors": "",
+ "networkInterface": "",
+ "noNetworkStatsAvailable": "",
+ "packetsPerSecond": "",
+ "packetsReceived": "",
+ "packetsReceivedRate": "",
+ "packetsSent": "",
+ "rate": "",
+ "selectInterface": ""
}
diff --git a/client/src/locales/pt-BR.json b/client/src/locales/pt-BR.json
index 4b4bd04a9..80fd34474 100644
--- a/client/src/locales/pt-BR.json
+++ b/client/src/locales/pt-BR.json
@@ -84,31 +84,35 @@
"unsupportedType": "Tipo de notificação não suportado",
"networkError": "Ocorreu um erro de rede",
"fallback": {
- "title": "",
- "checks": [""],
- "actionButton": ""
+ "title": "Um canal de notificação é usado para:",
+ "checks": [
+ "Alerte as equipes sobre tempo de inatividade ou problemas de desempenho",
+ "Informe os engenheiros sobre incidentes",
+ "Mantenha os administradores informados sobre as mudanças no sistema"
+ ],
+ "actionButton": "Vamos criar seu primeiro canal de notificação!"
},
- "createButton": "",
- "createTitle": "",
+ "createButton": "Criar canal de notificação",
+ "createTitle": "Canal de notificação",
"create": {
- "success": "",
- "failed": ""
+ "success": "Notificação criada com sucesso",
+ "failed": "Falha ao criar notificação"
},
"fetch": {
- "success": "",
- "failed": ""
+ "success": "Notificações obtidas com sucesso",
+ "failed": "Falha ao buscar notificações"
},
"delete": {
- "success": "",
- "failed": ""
+ "success": "Notificação excluída com sucesso",
+ "failed": "Falha ao excluir a notificação"
},
"edit": {
- "success": "",
- "failed": ""
+ "success": "Notificação atualizada com sucesso",
+ "failed": "Falha ao atualizar a notificação"
},
"test": {
- "success": "",
- "failed": ""
+ "success": "Notificação de teste enviada com sucesso",
+ "failed": "Falha ao enviar notificação de teste"
}
},
"testLocale": "testLocale",
@@ -260,7 +264,6 @@
"notifyEmails": "Também notificar por e-mail para vários endereços (em breve)",
"seperateEmails": "Você pode separar vários e-mails com uma vírgula",
"checkFrequency": "Verifique a frequência",
- "chooseGame": "",
"matchMethod": "Método de correspondência",
"expectedValue": "Valor esperado",
"deleteDialogTitle": "Você realmente deseja excluir este monitor?",
@@ -333,8 +336,8 @@
"validationFailed": "Falha na validação",
"noFileSelected": "Nenhum arquivo selecionado",
"fallbackPage": "Importe um arquivo para enviar uma lista de servidores em massa",
- "invalidFileType": "",
- "uploadFailed": ""
+ "invalidFileType": "Tipo de arquivo inválido",
+ "uploadFailed": "Falha no upload"
},
"DeleteAccountTitle": "Remover conta",
"DeleteAccountButton": "Remover conta",
@@ -401,8 +404,8 @@
"password": "Senha",
"team": "Equipe",
"logOut": "Sair",
- "notifications": "",
- "logs": ""
+ "notifications": "Notificações",
+ "logs": "Registros"
},
"settingsEmailUser": "Usuário de e-mail - Nome de usuário para autenticação, substitui o endereço de e-mail, se especificado",
"state": "Estado",
@@ -477,41 +480,41 @@
},
"special": {
"beginning": "Deve conter pelo menos",
- "highlighted": ""
+ "highlighted": "um caractere especial"
},
"number": {
- "beginning": "",
- "highlighted": ""
+ "beginning": "Deve ter pelo menos",
+ "highlighted": "um número"
},
"uppercase": {
- "beginning": "",
- "highlighted": ""
+ "beginning": "Deve ter pelo menos",
+ "highlighted": "uma letra maiúscula"
},
"lowercase": {
- "beginning": "",
- "highlighted": ""
+ "beginning": "Deve ter pelo menos",
+ "highlighted": "uma letra minúscula"
},
"match": {
- "beginning": "",
- "highlighted": ""
+ "beginning": "Senha e confirmação de senha",
+ "highlighted": "devem combinar"
}
},
"errors": {
"empty": "Por favor, digite sua senha",
"length": "A senha deve ter pelo menos 8 caracteres",
"uppercase": "A senha deve conter pelo menos 1 letra maiúscula",
- "lowercase": "",
- "number": "",
- "special": "",
- "incorrect": ""
+ "lowercase": "Senha deve ter pelo menos uma letra minúscula",
+ "number": "Senha deve ter pelo menos um número",
+ "special": "Senha deve ter pelo menos um caractere especial",
+ "incorrect": "A senha que você forneceu não está registrada"
}
},
"passwordConfirm": {
- "label": "",
- "placeholder": "",
+ "label": "Confirme a senha",
+ "placeholder": "Informe a senha novamente para confirmar",
"errors": {
- "empty": "",
- "different": ""
+ "empty": "Por favor, informe a senha novamente para confirmação (cuidado com erros de digitação)",
+ "different": "As senhas informadas não combinam, então uma deve estar errada"
}
},
"firstName": {
@@ -539,12 +542,12 @@
"fields": {
"password": {
"errors": {
- "incorrect": ""
+ "incorrect": "A senha que você forneceu não corresponde aos nossos registros"
}
},
"role": {
"errors": {
- "min": ""
+ "min": "Pelo menos uma função é necessária"
}
}
}
@@ -558,8 +561,8 @@
"links": {
"forgotPassword": "Esqueceu sua senha?",
"register": "Não tem uma conta?",
- "forgotPasswordLink": "",
- "registerLink": ""
+ "forgotPasswordLink": "Redefinir senha",
+ "registerLink": "Registre-se aqui"
},
"toasts": {
"success": "Bem-vindo de volta! Você está autenticado.",
@@ -567,9 +570,10 @@
},
"errors": {
"password": {
- "incorrect": ""
+ "incorrect": "A senha que você forneceu não corresponde aos nossos registros"
}
- }
+ },
+ "welcome": ""
},
"registration": {
"heading": {
@@ -577,42 +581,43 @@
"user": "Se inscrever"
},
"subheadings": {
- "stepOne": "",
- "stepTwo": "",
- "stepThree": ""
+ "stepOne": "Informe seus dados pessoais",
+ "stepTwo": "Informe seu email",
+ "stepThree": "Crie sua senha"
},
"description": {
- "superAdmin": "",
- "user": ""
+ "superAdmin": "Crie sua conta de superadministrador para começar",
+ "user": "Cadastre-se como usuário e peça ao superadministrador acesso aos seus monitores"
},
"gettingStartedButton": {
- "superAdmin": "",
- "user": ""
+ "superAdmin": "Criar conta de superadministrador",
+ "user": "Cadastre-se como usuário normal"
},
- "termsAndPolicies": "",
+ "termsAndPolicies": "Ao criar uma conta, você concorda com nossos Termos de Serviço e Política de Privacidade.",
"links": {
- "login": ""
+ "login": "Já tem uma conta? Entrar"
},
"toasts": {
- "success": ""
- }
+ "success": "Bem-vindo! Sua conta foi criada com sucesso."
+ },
+ "welcome": ""
},
"forgotPassword": {
- "heading": "",
+ "heading": "Esqueceu sua senha?",
"subheadings": {
- "stepOne": "",
- "stepTwo": "",
- "stepThree": "",
- "stepFour": ""
+ "stepOne": "Não se preocupe, enviaremos instruções de redefinição.",
+ "stepTwo": "Enviamos um link de redefinição de senha para ",
+ "stepThree": "Sua nova senha deve ser diferente das senhas usadas anteriormente.",
+ "stepFour": "Sua senha foi redefinida com sucesso. Clique abaixo para fazer login magicamente."
},
"buttons": {
- "openEmail": "",
- "resetPassword": ""
+ "openEmail": "Abra o aplicativo de e-mail",
+ "resetPassword": "Redefinir senha"
},
"imageAlts": {
- "passwordKey": "",
- "email": "",
- "lock": "",
+ "passwordKey": "Ícone de chave de senha",
+ "email": "Ícone de e-mail",
+ "lock": "Ícone de cadeado",
"passwordConfirm": "Ícone de confirmação de senha"
},
"links": {
@@ -620,352 +625,452 @@
"resend": "Não recebeu o email? Clique para reenviar"
},
"toasts": {
- "sent": "",
- "emailNotFound": "",
- "redirect": "",
- "success": "",
- "error": ""
+ "sent": "Instruções enviadas para .",
+ "emailNotFound": "E-mail não encontrado.",
+ "redirect": "Redirecionando em ...",
+ "success": "Sua senha foi redefinida com sucesso.",
+ "error": "Não foi possível redefinir a senha. Tente novamente mais tarde ou entre em contato com o suporte."
}
}
},
"errorPages": {
"serverUnreachable": {
"toasts": {
- "reconnected": "",
- "stillUnreachable": ""
+ "reconnected": "Reconectado ao servidor com sucesso.",
+ "stillUnreachable": "O servidor ainda está inacessível. Tente novamente mais tarde."
},
- "alertBox": "",
- "description": "",
+ "alertBox": "Erro de conexão do servidor",
+ "description": "Não conseguimos conectar ao servidor. Verifique sua conexão com a internet ou sua configuração de implantação se o problema persistir.",
"retryButton": {
- "default": "",
- "processing": ""
+ "default": "Tentar conectar novamente",
+ "processing": "Conectando..."
}
}
},
"createNotifications": {
- "title": "",
+ "title": "Criar canal de notificação",
"nameSettings": {
- "title": "",
- "description": "",
- "nameLabel": "",
- "namePlaceholder": ""
+ "title": "Nome",
+ "description": "Um nome descritivo para sua integração.",
+ "nameLabel": "Nome",
+ "namePlaceholder": "por exemplo, notificações do Slack"
},
"typeSettings": {
- "title": "",
- "description": "",
- "typeLabel": ""
+ "title": "Tipo",
+ "description": "Selecione o tipo de canal de notificação que você deseja criar.",
+ "typeLabel": "Tipo"
},
"emailSettings": {
- "title": "",
- "description": "",
- "emailLabel": "",
- "emailPlaceholder": ""
+ "title": "E-mail",
+ "description": "Endereços de e-mail de destino.",
+ "emailLabel": "Endereço de email",
+ "emailPlaceholder": "por exemplo john@example.com"
},
"slackSettings": {
- "title": "",
- "description": "",
- "webhookLabel": "",
- "webhookPlaceholder": ""
+ "title": "Folga",
+ "description": "Configure seu webhook do Slack aqui",
+ "webhookLabel": "URL do webhook do Slack",
+ "webhookPlaceholder": "https://hooks.slack.com/services/..."
},
"pagerdutySettings": {
- "title": "",
- "description": "",
- "integrationKeyLabel": "",
- "integrationKeyPlaceholder": ""
+ "title": "PagerDuty",
+ "description": "Configure sua integração com o PagerDuty aqui",
+ "integrationKeyLabel": "Chave de integração",
+ "integrationKeyPlaceholder": "1234567890"
},
"discordSettings": {
- "title": "",
- "description": "",
- "webhookLabel": "",
- "webhookPlaceholder": ""
+ "title": "Discórdia",
+ "description": "Configure seu webhook do Discord aqui",
+ "webhookLabel": "URL do webhook do Discord",
+ "webhookPlaceholder": "https://seu-servidor.com/webhook"
},
"webhookSettings": {
- "title": "",
- "description": "",
- "webhookLabel": "",
- "webhookPlaceholder": ""
+ "title": "Webhook",
+ "description": "Configure seu webhook aqui",
+ "webhookLabel": "URL do webhook",
+ "webhookPlaceholder": "https://seu-servidor.com/webhook"
},
- "testNotification": "",
- "dialogDeleteTitle": "",
- "dialogDeleteConfirm": ""
+ "testNotification": "Notificação de teste",
+ "dialogDeleteTitle": "Tem certeza de que deseja excluir esta notificação?",
+ "dialogDeleteConfirm": "Excluir"
},
"notificationConfig": {
- "title": "",
- "description": ""
+ "title": "Notificações",
+ "description": "Selecione os canais de notificação que deseja usar"
},
"monitorStatus": {
- "checkingEvery": "",
- "withCaptureAgent": "",
- "up": "",
- "down": "",
- "paused": ""
+ "checkingEvery": "Verificando a cada {{intervalo}}",
+ "withCaptureAgent": "com agente de captura {{versão}}",
+ "up": "acima",
+ "down": "abaixo",
+ "paused": "pausado"
},
- "advancedMatching": "",
- "sendTestNotifications": "",
- "selectAll": "",
- "showAdminLoginLink": "",
+ "advancedMatching": "Correspondência avançada",
+ "sendTestNotifications": "Enviar notificações de teste",
+ "selectAll": "Selecionar tudo",
+ "showAdminLoginLink": "Mostrar o link \"Administrador? Efetue login aqui\" na página de status",
"logsPage": {
- "title": "",
- "description": "",
+ "title": "Registros",
+ "description": "Logs do sistema - últimas 1000 linhas",
"tabs": {
- "queue": "",
- "logs": "",
- "diagnostics": ""
+ "queue": "Fila de tarefas",
+ "logs": "Logs do servidor",
+ "diagnostics": "Diagnóstico"
},
"toast": {
- "fetchLogsSuccess": ""
+ "fetchLogsSuccess": "Logs obtidos com sucesso"
},
"logLevelSelect": {
- "title": "",
+ "title": "Nível de log",
"values": {
- "all": "",
- "info": "",
- "warn": "",
- "error": "",
- "debug": ""
+ "all": "Todos",
+ "info": "Informações",
+ "warn": "Avisar",
+ "error": "Erro",
+ "debug": "Depurar"
}
}
},
"queuePage": {
- "title": "",
- "refreshButton": "",
- "flushButton": "",
+ "title": "Fila",
+ "refreshButton": "Atualizar",
+ "flushButton": "Fila de descarga",
"jobTable": {
- "title": "",
- "idHeader": "",
- "urlHeader": "",
- "typeHeader": "",
- "activeHeader": "",
- "lockedAtHeader": "",
- "runCountHeader": "",
- "failCountHeader": "",
- "lastRunHeader": "",
- "lastFinishedAtHeader": "",
- "lastRunTookHeader": ""
+ "title": "Empregos atualmente na fila",
+ "idHeader": "ID do monitor",
+ "urlHeader": "URL",
+ "typeHeader": "Tipo",
+ "activeHeader": "Ativo",
+ "lockedAtHeader": "Trancado em",
+ "runCountHeader": "Contagem de corridas",
+ "failCountHeader": "Contagem de falhas",
+ "lastRunHeader": "Última corrida em",
+ "lastFinishedAtHeader": "Última conclusão em",
+ "lastRunTookHeader": "A última corrida aconteceu"
},
"metricsTable": {
- "title": "",
- "metricHeader": "",
- "valueHeader": ""
+ "title": "Métricas de fila",
+ "metricHeader": "Métrica",
+ "valueHeader": "Valor"
},
"failedJobTable": {
- "title": "",
- "monitorIdHeader": "",
- "monitorUrlHeader": "",
- "failCountHeader": "",
- "failedAtHeader": "",
- "failReasonHeader": ""
+ "title": "Trabalhos com falha",
+ "monitorIdHeader": "ID do monitor",
+ "monitorUrlHeader": "URL do monitor",
+ "failCountHeader": "Contagem de falhas",
+ "failedAtHeader": "Última falha em",
+ "failReasonHeader": "Motivo da falha"
}
},
"export": {
- "title": "",
- "success": "",
- "failed": ""
+ "title": "Monitores de Exportação",
+ "success": "Monitores exportados com sucesso!",
+ "failed": "Falha ao exportar monitores"
},
"monitorActions": {
- "title": "",
- "import": "",
- "export": "",
- "deleteSuccess": "",
- "deleteFailed": "",
- "details": ""
+ "title": "Exportação/Importação",
+ "import": "Monitores de importação",
+ "export": "Monitores de Exportação",
+ "deleteSuccess": "Monitor excluído com sucesso",
+ "deleteFailed": "Falha ao excluir o monitor",
+ "details": "Detalhes"
},
"settingsPage": {
"aboutSettings": {
- "labelDevelopedBy": "",
- "labelVersion": "",
- "title": ""
+ "labelDevelopedBy": "Desenvolvido pela Bluewave Labs",
+ "labelVersion": "Versão",
+ "title": "Sobre"
},
"demoMonitorsSettings": {
- "buttonAddMonitors": "",
- "description": "",
- "title": ""
+ "buttonAddMonitors": "Adicionar monitores de demonstração",
+ "description": "Adicione monitores de amostra para fins de demonstração.",
+ "title": "Monitores de demonstração"
},
"emailSettings": {
- "buttonSendTestEmail": "",
- "description": "",
- "descriptionTransport": "",
- "labelAddress": "",
- "labelConnectionHost": "",
- "labelHost": "",
- "labelIgnoreTLS": "",
- "labelPassword": "",
- "labelPasswordSet": "",
- "labelPool": "",
- "labelPort": "",
- "labelRejectUnauthorized": "",
- "labelRequireTLS": "",
- "labelSecure": "",
- "labelTLSServername": "",
- "labelUser": "",
- "linkTransport": "",
- "placeholderUser": "",
- "title": "",
- "toastEmailRequiredFieldsError": ""
+ "buttonSendTestEmail": "Enviar e-mail de teste",
+ "description": "Configure as configurações de e-mail do seu sistema. Elas são usadas para enviar notificações e alertas.",
+ "descriptionTransport": "Isso cria um transporte SMTP para o NodeMailer",
+ "labelAddress": "Endereço de e-mail - Usado para autenticação",
+ "labelConnectionHost": "Host de conexão de e-mail - Nome do host a ser usado na saudação HELO/EHLO",
+ "labelHost": "Host de e-mail - Nome do host ou endereço IP para conectar",
+ "labelIgnoreTLS": "Desabilitar STARTTLS: Não use TLS mesmo que o servidor suporte",
+ "labelPassword": "Senha de e-mail - Senha para autenticação",
+ "labelPasswordSet": "A senha foi definida. Clique em Redefinir para alterá-la.",
+ "labelPool": "Habilitar pool de conexões: Reutilize conexões existentes para melhorar o desempenho",
+ "labelPort": "Porta de e-mail - Porta para conectar",
+ "labelRejectUnauthorized": "Rejeitar certificados inválidos: rejeitar conexões com certificados autoassinados ou não confiáveis",
+ "labelRequireTLS": "Forçar STARTTLS: requer atualização de TLS, falha se não for suportado",
+ "labelSecure": "Usar SSL (recomendado): criptografar a conexão usando SSL/TLS",
+ "labelTLSServername": "Nome do servidor TLS - Nome do host opcional para validação TLS quando o host é um IP",
+ "labelUser": "Usuário de e-mail - Nome de usuário para autenticação, substitui o endereço de e-mail, se especificado",
+ "linkTransport": "Veja as especificações aqui",
+ "placeholderUser": "Deixe em branco se não for necessário",
+ "title": "E-mail",
+ "toastEmailRequiredFieldsError": "Endereço de e-mail, host, porta e senha são obrigatórios"
},
"pageSpeedSettings": {
- "description": "",
- "labelApiKeySet": "",
- "labelApiKey": "",
- "title": ""
+ "description": "Insira sua chave da API do Google PageSpeed para ativar o monitoramento do Google PageSpeed. Clique em Redefinir para atualizar a chave.",
+ "labelApiKeySet": "A chave de API está definida. Clique em Redefinir para alterá-la.",
+ "labelApiKey": "Chave da API do PageSpeed",
+ "title": "Chave da API do Google PageSpeed"
},
- "saveButtonLabel": "",
+ "saveButtonLabel": "Salvar",
"statsSettings": {
- "clearAllStatsButton": "",
- "clearAllStatsDescription": "",
- "clearAllStatsDialogConfirm": "",
- "clearAllStatsDialogDescription": "",
- "clearAllStatsDialogTitle": "",
- "description": "",
- "labelTTL": "",
- "labelTTLOptional": "",
- "title": ""
+ "clearAllStatsButton": "Limpar todas as estatísticas",
+ "clearAllStatsDescription": "Limpe todas as estatísticas. Isso é irreversível.",
+ "clearAllStatsDialogConfirm": "Sim, limpar todas as estatísticas",
+ "clearAllStatsDialogDescription": "Uma vez removido, o histórico de monitoramento e as estatísticas não podem ser recuperados.",
+ "clearAllStatsDialogTitle": "Você quer limpar todas as estatísticas?",
+ "description": "Defina por quanto tempo você deseja manter os dados históricos. Você também pode limpar todos os dados existentes.",
+ "labelTTL": "Os dias em que você deseja continuar monitorando o histórico.",
+ "labelTTLOptional": "0 para infinito",
+ "title": "Histórico do monitor"
},
"systemResetSettings": {
- "buttonRemoveAllMonitors": "",
- "description": "",
- "dialogConfirm": "",
- "dialogDescription": "",
- "dialogTitle": "",
- "title": ""
+ "buttonRemoveAllMonitors": "Remover todos os monitores",
+ "description": "Remova todos os monitores do seu sistema.",
+ "dialogConfirm": "Sim, remova todos os monitores",
+ "dialogDescription": "Uma vez removidos, os monitores não podem ser recuperados.",
+ "dialogTitle": "Você deseja remover todos os monitores?",
+ "title": "Reinicialização do sistema"
},
"timezoneSettings": {
- "description": "",
- "label": "",
- "title": ""
+ "description": "Selecione o fuso horário usado para exibir datas e horas em todo o aplicativo.",
+ "label": "Exibir fuso horário",
+ "title": "Exibir fuso horário"
},
- "title": "",
+ "title": "Configurações",
"uiSettings": {
- "description": "",
- "labelLanguage": "",
- "labelTheme": "",
- "title": ""
+ "description": "Alterne entre o modo claro e escuro ou altere o idioma da interface do usuário.",
+ "labelLanguage": "Linguagem",
+ "labelTheme": "Modo de tema",
+ "title": "Aparência"
},
"urlSettings": {
- "description": "",
- "label": "",
- "selectDisabled": "",
- "selectEnabled": "",
- "title": ""
+ "description": "Exiba o endereço IP ou URL do monitor na página de status pública. Se estiver desabilitado, apenas o nome do monitor será exibido para proteger informações confidenciais.",
+ "label": "Exibir IP/URL na página de status",
+ "selectDisabled": "Desabilitado",
+ "selectEnabled": "Habilitado",
+ "title": "Monitorar IP/URL na página de status"
+ },
+ "globalThresholds": {
+ "title": "Limites Globais",
+ "description": "Configurar limites globais da CPU, Memória, Disco e Temperatura. Se um valor for fornecido, o monitoramento será ativado automaticamente"
}
},
"statusPageCreate": {
- "buttonSave": ""
+ "buttonSave": "Salvar"
},
- "incidentsOptionsHeaderFilterResolved": "",
- "settingsSave": "",
- "statusPageCreateAppearanceTitle": "",
- "confirmPassword": "",
+ "incidentsOptionsHeaderFilterResolved": "Resolvido",
+ "settingsSave": "Salvar",
+ "statusPageCreateAppearanceTitle": "Aparência",
+ "confirmPassword": "Confirme sua senha",
"monitorHooks": {
- "failureAddDemoMonitors": "",
- "successAddDemoMonitors": ""
+ "failureAddDemoMonitors": "Falha ao adicionar monitores de demonstração",
+ "successAddDemoMonitors": "Monitores de demonstração adicionados com sucesso"
},
- "settingsAppearance": "",
- "settingsDisplayTimezone": "",
- "settingsGeneralSettings": "",
- "incidentsOptionsHeaderTotalIncidents": "",
+ "settingsAppearance": "Aparência",
+ "settingsDisplayTimezone": "Exibir fuso horário",
+ "settingsGeneralSettings": "Configurações gerais",
+ "incidentsOptionsHeaderTotalIncidents": "Total de incidentes",
"statusPage": {
- "deleteSuccess": "",
- "deleteFailed": "",
- "createSuccess": "",
- "updateSuccess": "",
- "generalSettings": "",
- "contents": "",
+ "deleteSuccess": "Página de status excluída com sucesso",
+ "deleteFailed": "Falha ao excluir a página de status",
+ "createSuccess": "Página de status criada com sucesso",
+ "updateSuccess": "Página de status atualizada com sucesso",
+ "generalSettings": "Configurações gerais",
+ "contents": "Conteúdo",
"fallback": {
- "checks": [""],
- "title": "",
- "actionButton": ""
+ "checks": [
+ "Monitore e exiba a integridade dos seus serviços em tempo real",
+ "Acompanhe vários serviços e compartilhe seus status",
+ "Mantenha os usuários informados sobre interrupções e desempenho"
+ ],
+ "title": "Uma página de status é usada para:",
+ "actionButton": "Vamos criar sua primeira página de status!"
}
},
- "testNotificationsDisabled": "",
- "incidentsTableResolvedAt": "",
- "incidentsTableActionResolve": "",
+ "testNotificationsDisabled": "Não há notificações configuradas para este monitor. Você precisa adicionar uma clicando no botão \"Configurar\".",
+ "incidentsTableResolvedAt": "Resolvido em",
+ "incidentsTableActionResolve": "Resolver",
"checkHooks": {
- "failureResolveOne": "",
- "failureResolveAll": "",
- "failureResolveMonitor": ""
+ "failureResolveOne": "Falha ao resolver o incidente.",
+ "failureResolveAll": "Falha ao resolver todos os incidentes.",
+ "failureResolveMonitor": "Falha ao resolver incidentes do monitor."
},
- "checkFormError": "",
+ "checkFormError": "Verifique se há erros no formulário.",
"diagnosticsPage": {
- "diagnosticDescription": "",
- "statsDescription": "",
+ "diagnosticDescription": "Diagnóstico do sistema",
+ "statsDescription": "Estatísticas do sistema",
"gauges": {
- "heapAllocationTitle": "",
- "heapAllocationSubtitle": "",
- "heapUsageTitle": "",
- "heapUsageSubtitle": "",
- "heapUtilizationTitle": "",
- "heapUtilizationSubtitle": "",
- "instantCpuUsageTitle": "",
- "instantCpuUsageSubtitle": ""
+ "heapAllocationTitle": "Alocação de heap",
+ "heapAllocationSubtitle": "% de memória disponível",
+ "heapUsageTitle": "Uso de heap",
+ "heapUsageSubtitle": "% de memória disponível",
+ "heapUtilizationTitle": "Utilização de heap",
+ "heapUtilizationSubtitle": "% do alocado",
+ "instantCpuUsageTitle": "Uso instantâneo da CPU",
+ "instantCpuUsageSubtitle": "% de 1s usados pela CPU"
},
"stats": {
- "eventLoopDelayTitle": "",
- "uptimeTitle": "",
- "usedHeapSizeTitle": "",
- "totalHeapSizeTitle": "",
- "osMemoryLimitTitle": ""
+ "eventLoopDelayTitle": "Atraso do loop de eventos",
+ "uptimeTitle": "Tempo de atividade",
+ "usedHeapSizeTitle": "Tamanho de heap usado",
+ "totalHeapSizeTitle": "Tamanho total do heap",
+ "osMemoryLimitTitle": "Limite de memória do sistema operacional"
}
},
- "pageSpeedLighthouseAPI": "",
+ "pageSpeedLighthouseAPI": "Use a API Lighthouse PageSpeed para monitorar seu site",
"time": {
- "threeMinutes": "",
- "fiveMinutes": "",
- "tenMinutes": "",
- "twentyMinutes": "",
- "oneHour": "",
- "oneDay": "",
- "oneWeek": ""
+ "threeMinutes": "3 minutos",
+ "fiveMinutes": "5 minutos",
+ "tenMinutes": "10 minutos",
+ "twentyMinutes": "20 minutos",
+ "oneHour": "1 hora",
+ "oneDay": "1 dia",
+ "oneWeek": "1 semana",
+ "fourMinutes": "4 minutos",
+ "oneMinute": "1 minuto",
+ "twoMinutes": "2 minutos",
+ "fifteenSeconds": "",
+ "thirtySeconds": ""
},
"general": {
- "noOptionsFound": ""
+ "noOptionsFound": "Nenhuma {{unidade}} encontrada"
},
"infrastructureMonitor": {
"fallback": {
- "checks": [""],
- "title": "",
- "actionButton": ""
+ "checks": [
+ "Acompanhe o desempenho dos seus servidores",
+ "Identifique gargalos e otimize o uso",
+ "Garanta a confiabilidade com monitoramento em tempo real"
+ ],
+ "title": "Um monitor de infraestrutura é usado para:",
+ "actionButton": "Vamos criar seu primeiro monitor de infraestrutura!"
}
},
"maintenanceWindow": {
"fallback": {
- "checks": [""],
- "title": "",
- "actionButton": ""
+ "checks": [
+ "Marque seus períodos de manutenção",
+ "Elimine qualquer mal-entendido",
+ "Pare de enviar alertas em janelas de manutenção"
+ ],
+ "title": "Uma janela de manutenção é usada para:",
+ "actionButton": "Vamos criar sua primeira janela de manutenção!"
}
},
"pageSpeed": {
"fallback": {
- "checks": [""],
- "title": "",
- "actionButton": ""
+ "checks": [
+ "Relatar a experiência do usuário em uma página",
+ "Ajudar a analisar a velocidade da página",
+ "Dar sugestões sobre como a página pode ser melhorada"
+ ],
+ "title": "Um monitor PageSpeed é usado para:",
+ "actionButton": "Vamos criar seu primeiro monitor PageSpeed!"
}
},
"uptimeMonitor": {
"fallback": {
- "checks": [""],
- "title": "",
- "actionButton": ""
+ "checks": [
+ "Verifique se sites ou servidores estão online e responsivos",
+ "Alerte as equipes sobre tempo de inatividade ou problemas de desempenho",
+ "Monitore endpoints HTTP, pings, contêineres e portas",
+ "Acompanhe o histórico de tempo de atividade e tendências de confiabilidade"
+ ],
+ "title": "Um monitor de tempo de atividade é usado para:",
+ "actionButton": "Vamos criar seu primeiro monitor de tempo de atividade!"
}
},
"editUserPage": {
"form": {
- "email": "",
- "firstName": "",
- "lastName": "",
- "role": "",
- "save": ""
+ "email": "E-mail",
+ "firstName": "Primeiro nome",
+ "lastName": "Sobrenome",
+ "role": "Funções",
+ "save": "Salvar"
},
"table": {
- "actionHeader": "",
- "roleHeader": ""
+ "actionHeader": "Ação",
+ "roleHeader": "Papel"
},
- "title": "",
+ "title": "Editar usuário",
"toast": {
- "successUserUpdate": "",
- "validationErrors": ""
+ "successUserUpdate": "Usuário atualizado com sucesso",
+ "validationErrors": "Erros de validação"
}
},
- "incidentsPageActionResolveMonitor": "",
- "incidentsPageActionResolveAll": ""
+ "incidentsPageActionResolveMonitor": "Resolver incidentes de monitoramento",
+ "incidentsPageActionResolveAll": "Resolver todos os incidentes",
+ "matchMethodOptions": {
+ "equal": "",
+ "equalPlaceholder": "",
+ "include": "",
+ "includePlaceholder": "",
+ "regex": "Regex",
+ "regexPlaceholder": "",
+ "text": ""
+ },
+ "monitorType": {
+ "docker": {
+ "label": "",
+ "namePlaceholder": "Meu Contêiner",
+ "placeholder": "abcd1234"
+ },
+ "http": {
+ "label": "URL para monitorar",
+ "namePlaceholder": "Google",
+ "placeholder": "google.com"
+ },
+ "ping": {
+ "label": "Endereço IP para monitorar",
+ "namePlaceholder": "Google",
+ "placeholder": "1.1.1.1"
+ },
+ "port": {
+ "label": "URL para monitorar",
+ "namePlaceholder": "Localhost:5173",
+ "placeholder": "localhost"
+ },
+ "game": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ }
+ },
+ "uptimeAdvancedMatching": {
+ "jsonPath": ""
+ },
+ "bytesPerSecond": "",
+ "bytesReceived": "",
+ "bytesSent": "",
+ "chooseGame": "",
+ "createMonitorPage": {
+ "incidentConfigDescription": "",
+ "incidentConfigStatusWindowLabel": "",
+ "incidentConfigStatusWindowThresholdLabel": "",
+ "incidentConfigTitle": ""
+ },
+ "dataRate": "",
+ "dataReceived": "",
+ "dataSent": "",
+ "details": "",
+ "drops": "",
+ "errors": "",
+ "errorsIn": "",
+ "errorsOut": "",
+ "gameServerMonitoring": "",
+ "gameServerMonitoringDescription": "",
+ "network": "",
+ "networkDrops": "",
+ "networkErrors": "",
+ "networkInterface": "",
+ "noNetworkStatsAvailable": "",
+ "packetsPerSecond": "",
+ "packetsReceived": "",
+ "packetsReceivedRate": "",
+ "packetsSent": "",
+ "rate": "",
+ "selectInterface": ""
}
diff --git a/client/src/locales/ru.json b/client/src/locales/ru.json
index 25e1038aa..ecb1dd8f8 100644
--- a/client/src/locales/ru.json
+++ b/client/src/locales/ru.json
@@ -260,7 +260,6 @@
"notifyEmails": "Также уведомлять по электронной почте на несколько адресов (скоро)",
"seperateEmails": "Вы можете разделить несколько адресов электронной почты запятой.",
"checkFrequency": "Проверить частоту",
- "chooseGame": "",
"matchMethod": "Метод сопоставления",
"expectedValue": "Ожидаемое значение",
"deleteDialogTitle": "Вы действительно хотите удалить этот монитор?",
@@ -427,7 +426,7 @@
"settingsTestEmailSuccess": "",
"settingsTestEmailFailed": "",
"settingsTestEmailFailedWithReason": "",
- "settingsTestEmailUnknownError": "",
+ "settingsTestEmailUnknownError": "Неизвестная ошибка",
"statusMsg": {
"paused": "",
"up": "",
@@ -450,46 +449,46 @@
"toasts": {
"networkError": "",
"checkConnection": "",
- "unknownError": ""
+ "unknownError": "Неизвестная ошибка"
}
},
"auth": {
"common": {
"navigation": {
"continue": "",
- "back": ""
+ "back": "Назад"
},
"inputs": {
"email": {
"label": "",
"placeholder": "",
"errors": {
- "empty": "",
+ "empty": "Для продолжения введите свой адрес электронной почты",
"invalid": ""
}
},
"password": {
- "label": "",
+ "label": "Пароль",
"rules": {
"length": {
- "beginning": "",
- "highlighted": ""
+ "beginning": "Должен быть как минимум",
+ "highlighted": "8 символов в длину"
},
"special": {
- "beginning": "",
- "highlighted": ""
+ "beginning": "Должен содержать как минимум",
+ "highlighted": "один спецсимвол"
},
"number": {
- "beginning": "",
- "highlighted": ""
+ "beginning": "Должен содержать как минимум",
+ "highlighted": "одно число"
},
"uppercase": {
- "beginning": "",
- "highlighted": ""
+ "beginning": "Должен содержать как минимум",
+ "highlighted": "одну заглавную букву"
},
"lowercase": {
- "beginning": "",
- "highlighted": ""
+ "beginning": "Должен содержать как минимум",
+ "highlighted": "одну прописную букву"
},
"match": {
"beginning": "",
@@ -569,7 +568,8 @@
"password": {
"incorrect": ""
}
- }
+ },
+ "welcome": ""
},
"registration": {
"heading": {
@@ -595,7 +595,8 @@
},
"toasts": {
"success": ""
- }
+ },
+ "welcome": ""
},
"forgotPassword": {
"heading": "",
@@ -847,6 +848,10 @@
"selectDisabled": "",
"selectEnabled": "",
"title": ""
+ },
+ "globalThresholds": {
+ "title": "",
+ "description": ""
}
},
"statusPageCreate": {
@@ -915,7 +920,12 @@
"twentyMinutes": "",
"oneHour": "",
"oneDay": "",
- "oneWeek": ""
+ "oneWeek": "",
+ "fourMinutes": "",
+ "oneMinute": "",
+ "twoMinutes": "",
+ "fifteenSeconds": "",
+ "thirtySeconds": ""
},
"general": {
"noOptionsFound": ""
@@ -967,5 +977,75 @@
}
},
"incidentsPageActionResolveMonitor": "",
- "incidentsPageActionResolveAll": ""
+ "incidentsPageActionResolveAll": "",
+ "matchMethodOptions": {
+ "equal": "",
+ "equalPlaceholder": "",
+ "include": "",
+ "includePlaceholder": "",
+ "regex": "",
+ "regexPlaceholder": "",
+ "text": ""
+ },
+ "monitorType": {
+ "docker": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "http": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "ping": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "port": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "game": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ }
+ },
+ "uptimeAdvancedMatching": {
+ "jsonPath": ""
+ },
+ "bytesPerSecond": "",
+ "bytesReceived": "",
+ "bytesSent": "",
+ "chooseGame": "",
+ "createMonitorPage": {
+ "incidentConfigDescription": "",
+ "incidentConfigStatusWindowLabel": "",
+ "incidentConfigStatusWindowThresholdLabel": "",
+ "incidentConfigTitle": ""
+ },
+ "dataRate": "",
+ "dataReceived": "",
+ "dataSent": "",
+ "details": "",
+ "drops": "",
+ "errors": "",
+ "errorsIn": "",
+ "errorsOut": "",
+ "gameServerMonitoring": "",
+ "gameServerMonitoringDescription": "",
+ "network": "",
+ "networkDrops": "",
+ "networkErrors": "",
+ "networkInterface": "",
+ "noNetworkStatsAvailable": "",
+ "packetsPerSecond": "",
+ "packetsReceived": "",
+ "packetsReceivedRate": "",
+ "packetsSent": "",
+ "rate": "",
+ "selectInterface": ""
}
diff --git a/client/src/locales/tr.json b/client/src/locales/tr.json
index 9e20242fb..8271da521 100644
--- a/client/src/locales/tr.json
+++ b/client/src/locales/tr.json
@@ -260,7 +260,6 @@
"notifyEmails": "Ayrıca birden fazla eposta adresine bildirim gönderebilirsiniz (yakında geliyor)",
"seperateEmails": "Birden fazla epostayı virgülle ayırabilirsiniz",
"checkFrequency": "Frekansı denetle",
- "chooseGame": "Oyun seç",
"matchMethod": "Eşleşme yöntemi",
"expectedValue": "Beklenen değer",
"deleteDialogTitle": "Gerçekten bu monitörü silmek istiyor musunuz?",
@@ -569,7 +568,8 @@
"password": {
"incorrect": "Giriş yaptığınız parola sistemdekiyle uyuşmuyor"
}
- }
+ },
+ "welcome": ""
},
"registration": {
"heading": {
@@ -595,7 +595,8 @@
},
"toasts": {
"success": "Hoşgeldiniz! Hesabınız başarıyla oluşturuldu."
- }
+ },
+ "welcome": ""
},
"forgotPassword": {
"heading": "Parolanızı mı kaybettiniz?",
@@ -847,6 +848,10 @@
"selectDisabled": "Kapalı",
"selectEnabled": "Etkin",
"title": ""
+ },
+ "globalThresholds": {
+ "title": "",
+ "description": ""
}
},
"statusPageCreate": {
@@ -915,7 +920,12 @@
"twentyMinutes": "",
"oneHour": "1 saat",
"oneDay": "1 gün",
- "oneWeek": "1 hafta"
+ "oneWeek": "1 hafta",
+ "fourMinutes": "",
+ "oneMinute": "",
+ "twoMinutes": "",
+ "fifteenSeconds": "",
+ "thirtySeconds": ""
},
"general": {
"noOptionsFound": ""
@@ -967,5 +977,75 @@
}
},
"incidentsPageActionResolveMonitor": "",
- "incidentsPageActionResolveAll": ""
+ "incidentsPageActionResolveAll": "",
+ "matchMethodOptions": {
+ "equal": "",
+ "equalPlaceholder": "",
+ "include": "",
+ "includePlaceholder": "",
+ "regex": "",
+ "regexPlaceholder": "",
+ "text": ""
+ },
+ "monitorType": {
+ "docker": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "http": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "ping": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "port": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "game": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ }
+ },
+ "uptimeAdvancedMatching": {
+ "jsonPath": ""
+ },
+ "bytesPerSecond": "",
+ "bytesReceived": "",
+ "bytesSent": "",
+ "chooseGame": "",
+ "createMonitorPage": {
+ "incidentConfigDescription": "",
+ "incidentConfigStatusWindowLabel": "",
+ "incidentConfigStatusWindowThresholdLabel": "",
+ "incidentConfigTitle": ""
+ },
+ "dataRate": "",
+ "dataReceived": "",
+ "dataSent": "",
+ "details": "",
+ "drops": "",
+ "errors": "",
+ "errorsIn": "",
+ "errorsOut": "",
+ "gameServerMonitoring": "",
+ "gameServerMonitoringDescription": "",
+ "network": "",
+ "networkDrops": "",
+ "networkErrors": "",
+ "networkInterface": "",
+ "noNetworkStatsAvailable": "",
+ "packetsPerSecond": "",
+ "packetsReceived": "",
+ "packetsReceivedRate": "",
+ "packetsSent": "",
+ "rate": "",
+ "selectInterface": ""
}
diff --git a/client/src/locales/zh-TW.json b/client/src/locales/zh-TW.json
index 90a1aa30c..8e0dc2ed2 100644
--- a/client/src/locales/zh-TW.json
+++ b/client/src/locales/zh-TW.json
@@ -3,81 +3,81 @@
"title": "名稱",
"distributedStatusHeaderText": "實時、真實設備覆蓋",
"distributedStatusSubHeaderText": "由全球數百萬個設備提供支持,您可以按全球區域、國家或城市查看系統效能",
- "settingsDisabled": "",
- "settingsSuccessSaved": "",
- "settingsFailedToSave": "",
- "settingsStatsCleared": "",
- "settingsFailedToClearStats": "",
- "settingsMonitorsDeleted": "",
- "settingsFailedToDeleteMonitors": "",
- "starPromptTitle": "",
- "starPromptDescription": "",
- "https": "",
- "http": "",
- "monitor": "",
- "aboutus": "",
- "now": "",
- "delete": "",
- "configure": "",
- "responseTime": "",
- "ms": "",
- "bar": "",
- "area": "",
- "country": "",
- "city": "",
- "response": "",
+ "settingsDisabled": "已停用",
+ "settingsSuccessSaved": "設定已成功儲存",
+ "settingsFailedToSave": "無法儲存設定",
+ "settingsStatsCleared": "統計數據已成功清除",
+ "settingsFailedToClearStats": "無法清除統計數據",
+ "settingsMonitorsDeleted": "已成功刪除所有監視器",
+ "settingsFailedToDeleteMonitors": "無法刪除所有監視器",
+ "starPromptTitle": "星標Checkmate",
+ "starPromptDescription": "查看最新版本發布及在 GitHub 上幫助社區成長",
+ "https": "HTTPS",
+ "http": "HTTP",
+ "monitor": "監控",
+ "aboutus": "關於我們",
+ "now": "現在",
+ "delete": "刪除",
+ "configure": "設定",
+ "responseTime": "回應時間",
+ "ms": "毫秒",
+ "bar": "條",
+ "area": "區域",
+ "country": "國家",
+ "city": "城市",
+ "response": "回應",
"monitorStatusUp": "",
"monitorStatusDown": "",
- "webhookSendSuccess": "",
+ "webhookSendSuccess": "Webhook 通知發送成功",
"webhookSendError": "",
- "webhookUnsupportedPlatform": "",
- "distributedRightCategoryTitle": "",
- "distributedStatusServerMonitors": "",
- "distributedStatusServerMonitorsDescription": "",
- "distributedUptimeCreateSelectURL": "",
- "distributedUptimeCreateChecks": "",
- "distributedUptimeCreateChecksDescription": "",
- "distributedUptimeCreateIncidentNotification": "",
- "distributedUptimeCreateIncidentDescription": "",
- "distributedUptimeCreateAdvancedSettings": "",
- "distributedUptimeDetailsNoMonitorHistory": "",
- "distributedUptimeDetailsStatusHeaderUptime": "",
- "distributedUptimeDetailsStatusHeaderLastUpdate": "",
+ "webhookUnsupportedPlatform": "不支援的平台: {platform}",
+ "distributedRightCategoryTitle": "監控",
+ "distributedStatusServerMonitors": "伺服器監控",
+ "distributedStatusServerMonitorsDescription": "監控相關伺服器狀態",
+ "distributedUptimeCreateSelectURL": "你可以在這裡選擇主機的 URL,同時選擇監視器類型。",
+ "distributedUptimeCreateChecks": "要執行的檢查項目",
+ "distributedUptimeCreateChecksDescription": "你可以在新增網站後,隨時新增或移除檢查項目。",
+ "distributedUptimeCreateIncidentNotification": "事故通知",
+ "distributedUptimeCreateIncidentDescription": "事故發生時,向用戶發送通知。",
+ "distributedUptimeCreateAdvancedSettings": "進階設定",
+ "distributedUptimeDetailsNoMonitorHistory": "此監控尚無檢查記錄。",
+ "distributedUptimeDetailsStatusHeaderUptime": "正常運行時間:",
+ "distributedUptimeDetailsStatusHeaderLastUpdate": "最後更新",
"notifications": {
"enableNotifications": "",
- "testNotification": "",
- "addOrEditNotifications": "",
+ "testNotification": "測試通知",
+ "addOrEditNotifications": "新增或編輯通知",
"slack": {
- "label": "",
- "description": "",
- "webhookLabel": "",
- "webhookPlaceholder": "",
- "webhookRequired": ""
+ "label": "Slack",
+ "description": "要啟用 Slack 通知,請建立一個 Slack 應用程式並啟用傳入 Webhook。完成後,請在此提供 Webhook URL 即可。",
+ "webhookLabel": "Webhook URL",
+ "webhookPlaceholder": "https://hooks.slack.com/services/...",
+ "webhookRequired": "Slack webhook URL is required"
},
"discord": {
- "label": "",
+ "label": "Discord",
"description": "",
- "webhookLabel": "",
- "webhookPlaceholder": "",
+ "webhookLabel": "Discord Webhook URL",
+ "webhookPlaceholder": "https://discord.com/api/webhooks/...",
"webhookRequired": ""
},
"telegram": {
- "label": "",
- "description": "",
- "tokenLabel": "",
- "tokenPlaceholder": "",
- "chatIdLabel": "",
- "chatIdPlaceholder": "",
+ "label": "Telegram",
+ "description": "要啟用 Telegram 通知,請使用 BotFather 用於創建和管理 Telegram 的官方機器來建立一個 Telegram 機器人。接著,取得 API 金鑰和聊天室 ID,並在此處填寫。",
+ "tokenLabel": "你的機器人金鑰",
+ "tokenPlaceholder": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
+ "chatIdLabel": "你的聊天 ID",
+ "chatIdPlaceholder": "-1001234567890",
"fieldsRequired": ""
},
"webhook": {
- "label": "",
- "description": "",
- "urlLabel": "",
- "urlPlaceholder": "",
+ "label": "Webhooks",
+ "description": "你可以設定自訂 webhook,在事件發生時接收通知。",
+ "urlLabel": "Webhook URL",
+ "urlPlaceholder": "https://your-server.com/webhook",
"urlRequired": ""
},
- "testNotificationDevelop": "",
+ "testNotificationDevelop": "測試通知 2",
"integrationButton": "",
"testSuccess": "",
"testFailed": "",
@@ -85,8 +85,12 @@
"networkError": "",
"fallback": {
"title": "",
- "checks": [""],
- "actionButton": ""
+ "checks": [
+ "提醒團隊有關停機或性能問題",
+ "讓工程師知道事件發生時的情況",
+ "讓管理員了解系統變更情況"
+ ],
+ "actionButton": "讓我們建立您的第一個通知頻道!"
},
"createButton": "",
"createTitle": "",
@@ -111,35 +115,35 @@
"failed": ""
}
},
- "testLocale": "",
- "add": "",
+ "testLocale": "測試語言環境",
+ "add": "新增",
"monitors": "",
"distributedUptimeStatusCreateStatusPage": "",
"distributedUptimeStatusCreateStatusPageAccess": "",
"distributedUptimeStatusCreateStatusPageReady": "",
- "distributedUptimeStatusBasicInfoHeader": "",
+ "distributedUptimeStatusBasicInfoHeader": "基本資料",
"distributedUptimeStatusBasicInfoDescription": "",
- "distributedUptimeStatusLogoHeader": "",
+ "distributedUptimeStatusLogoHeader": "標誌",
"distributedUptimeStatusLogoDescription": "",
- "distributedUptimeStatusLogoUploadButton": "",
+ "distributedUptimeStatusLogoUploadButton": "上傳標誌",
"distributedUptimeStatusStandardMonitorsHeader": "",
"distributedUptimeStatusStandardMonitorsDescription": "",
"distributedUptimeStatusCreateYour": "",
"distributedUptimeStatusEditYour": "",
"distributedUptimeStatusPublishedLabel": "",
- "distributedUptimeStatusCompanyNameLabel": "",
+ "distributedUptimeStatusCompanyNameLabel": "公司名稱",
"distributedUptimeStatusPageAddressLabel": "",
- "distributedUptimeStatus30Days": "",
- "distributedUptimeStatus60Days": "",
- "distributedUptimeStatus90Days": "",
+ "distributedUptimeStatus30Days": "30日",
+ "distributedUptimeStatus60Days": "60日",
+ "distributedUptimeStatus90Days": "90日",
"distributedUptimeStatusPageNotSetUp": "",
"distributedUptimeStatusContactAdmin": "",
"distributedUptimeStatusPageNotPublic": "",
"distributedUptimeStatusPageDeleteDialog": "",
"distributedUptimeStatusPageDeleteConfirm": "",
"distributedUptimeStatusPageDeleteDescription": "",
- "distributedUptimeStatusDevices": "",
- "distributedUptimeStatusUpt": "",
+ "distributedUptimeStatusDevices": "設備",
+ "distributedUptimeStatusUpt": "UPT",
"distributedUptimeStatusUptBurned": "",
"distributedUptimeStatusUptLogo": "",
"incidentsTableNoIncidents": "",
@@ -169,53 +173,53 @@
"infrastructureServerUrlLabel": "",
"infrastructureDisplayNameLabel": "",
"infrastructureAuthorizationSecretLabel": "",
- "gb": "",
- "mb": "",
- "mem": "",
+ "gb": "GB",
+ "mb": "MB",
+ "mem": "Mem",
"memoryUsage": "",
- "cpu": "",
- "cpuUsage": "",
- "cpuTemperature": "",
+ "cpu": "CPU",
+ "cpuUsage": "CPU 使用量",
+ "cpuTemperature": "CPU 溫度",
"diskUsage": "",
"used": "",
"total": "",
- "cores": "",
+ "cores": "核",
"frequency": "",
"status": "",
- "cpuPhysical": "",
- "cpuLogical": "",
- "cpuFrequency": "",
- "avgCpuTemperature": "",
+ "cpuPhysical": "CPU (實體核心)",
+ "cpuLogical": "CPU (邏輯核心)",
+ "cpuFrequency": "CPU 頻率",
+ "avgCpuTemperature": "平均 CPU 溫度",
"memory": "",
"disk": "",
"uptime": "",
- "os": "",
+ "os": "OS",
"host": "",
- "actions": "",
+ "actions": "行動",
"integrations": "",
"integrationsPrism": "",
- "integrationsSlack": "",
+ "integrationsSlack": "Slack",
"integrationsSlackInfo": "",
"integrationsDiscord": "",
"integrationsDiscordInfo": "",
"integrationsZapier": "",
"integrationsZapierInfo": "",
- "commonSave": "",
+ "commonSave": "儲存",
"createYour": "",
"createMonitor": "",
"pause": "",
"resume": "",
"editing": "",
- "url": "",
- "access": "",
- "timezone": "",
+ "url": "UR:",
+ "access": "存取",
+ "timezone": "時區",
"features": "",
- "administrator": "",
+ "administrator": "系統管理員?",
"loginHere": "",
"displayName": "",
"urlMonitor": "",
"portToMonitor": "",
- "websiteMonitoring": "",
+ "websiteMonitoring": "網站監控",
"websiteMonitoringDescription": "",
"pingMonitoring": "",
"pingMonitoringDescription": "",
@@ -232,15 +236,15 @@
"maintenanceRepeat": "",
"maintenance": "",
"duration": "",
- "addMonitors": "",
+ "addMonitors": "新增監視器",
"window": "",
- "cancel": "",
+ "cancel": "取消",
"message": "",
- "low": "",
- "high": "",
+ "low": "低",
+ "high": "高",
"statusCode": "",
- "date&Time": "",
- "type": "",
+ "date&Time": "日期與時間",
+ "type": "類型",
"statusPageName": "",
"publicURL": "",
"repeat": "",
@@ -259,21 +263,20 @@
"notifySMS": "",
"notifyEmails": "",
"seperateEmails": "",
- "checkFrequency": "",
- "chooseGame": "",
+ "checkFrequency": "檢查頻率",
"matchMethod": "",
"expectedValue": "",
- "deleteDialogTitle": "",
- "deleteDialogDescription": "",
+ "deleteDialogTitle": "您確定要刪除此監視器嗎?",
+ "deleteDialogDescription": "一旦刪除,該監視器無法恢復。",
"pageSpeedMonitor": "",
"shown": "",
"ago": "",
- "companyName": "",
+ "companyName": "公司名稱",
"pageSpeedDetailsPerformanceReport": "",
"pageSpeedDetailsPerformanceReportCalculator": "",
"checkingEvery": "",
"statusPageCreateSettings": "",
- "basicInformation": "",
+ "basicInformation": "基本資料",
"statusPageCreateBasicInfoDescription": "",
"statusPageCreateSelectTimeZoneDescription": "",
"statusPageCreateAppearanceDescription": "",
@@ -307,24 +310,24 @@
"pageSpeedWarning": "",
"pageSpeedLearnMoreLink": "",
"pageSpeedAddApiKey": "",
- "update": "",
+ "update": "更新",
"invalidFileFormat": "",
"invalidFileSize": "",
- "ClickUpload": "",
+ "ClickUpload": "點擊上傳",
"DragandDrop": "",
"MaxSize": "",
"SupportedFormats": "",
"FirstName": "",
"LastName": "",
"EmailDescriptionText": "",
- "YourPhoto": "",
+ "YourPhoto": "頭像",
"PhotoDescriptionText": "",
"save": "",
- "DeleteDescriptionText": "",
- "DeleteAccountWarning": "",
+ "DeleteDescriptionText": "這將刪除帳號以及所有相關的伺服器資料,此操作不可逆轉。",
+ "DeleteAccountWarning": "刪除您的帳號表示您將無法再次登入,且所有資料都會被移除。此操作不可逆轉。",
"DeleteWarningTitle": "",
"bulkImport": {
- "title": "",
+ "title": "批量匯入",
"selectFileTips": "",
"selectFileDescription": "",
"selectFile": "",
@@ -336,8 +339,8 @@
"invalidFileType": "",
"uploadFailed": ""
},
- "DeleteAccountTitle": "",
- "DeleteAccountButton": "",
+ "DeleteAccountTitle": "刪除帳戶",
+ "DeleteAccountButton": "刪除帳戶",
"publicLink": "",
"maskedPageSpeedKeyPlaceholder": "",
"reset": "",
@@ -352,14 +355,14 @@
},
"roles": {
"superAdmin": "",
- "admin": "",
+ "admin": "管理員",
"teamMember": "",
- "demoUser": ""
+ "demoUser": "示範用戶"
},
"teamPanel": {
"teamMembers": "",
"filter": {
- "all": "",
+ "all": "全部",
"member": ""
},
"inviteTeamMember": "",
@@ -368,7 +371,7 @@
"email": "",
"selectRole": "",
"inviteLink": "",
- "cancel": "",
+ "cancel": "取消",
"noMembers": "",
"getToken": "",
"emailToken": "",
@@ -382,7 +385,7 @@
"monitorState": {
"paused": "",
"resumed": "",
- "active": ""
+ "active": "啟用中"
},
"menu": {
"uptime": "",
@@ -396,7 +399,7 @@
"support": "",
"discussions": "",
"docs": "",
- "changelog": "",
+ "changelog": "版本變更記錄",
"profile": "",
"password": "",
"team": "",
@@ -408,17 +411,17 @@
"state": "",
"statusBreadCrumbsStatusPages": "",
"statusBreadCrumbsDetails": "",
- "commonSaving": "",
+ "commonSaving": "儲存中...",
"navControls": "",
"incidentsPageTitle": "",
"passwordPanel": {
"passwordChangedSuccess": "",
"passwordInputIncorrect": "",
- "currentPassword": "",
+ "currentPassword": "當前密碼",
"enterCurrentPassword": "",
"newPassword": "",
"enterNewPassword": "",
- "confirmNewPassword": "",
+ "confirmNewPassword": "確認新密碼",
"passwordRequirements": "",
"saving": ""
},
@@ -442,22 +445,22 @@
"game": ""
},
"common": {
- "appName": "",
+ "appName": "Checkmate",
"monitoringAgentName": "",
"buttons": {
"toggleTheme": ""
},
"toasts": {
"networkError": "",
- "checkConnection": "",
+ "checkConnection": "請檢查您的連線",
"unknownError": ""
}
},
"auth": {
"common": {
"navigation": {
- "continue": "",
- "back": ""
+ "continue": "繼續",
+ "back": "返回"
},
"inputs": {
"email": {
@@ -472,23 +475,23 @@
"label": "",
"rules": {
"length": {
- "beginning": "",
+ "beginning": "必須至少",
"highlighted": ""
},
"special": {
- "beginning": "",
+ "beginning": "必須至少包含",
"highlighted": ""
},
"number": {
- "beginning": "",
+ "beginning": "必須至少包含",
"highlighted": ""
},
"uppercase": {
- "beginning": "",
+ "beginning": "必須至少包含",
"highlighted": ""
},
"lowercase": {
- "beginning": "",
+ "beginning": "必須至少包含",
"highlighted": ""
},
"match": {
@@ -562,14 +565,15 @@
"registerLink": ""
},
"toasts": {
- "success": "",
+ "success": "歡迎回來!您已成功登入。",
"incorrectPassword": ""
},
"errors": {
"password": {
"incorrect": ""
}
- }
+ },
+ "welcome": ""
},
"registration": {
"heading": {
@@ -594,8 +598,9 @@
"login": ""
},
"toasts": {
- "success": ""
- }
+ "success": "歡迎!您的帳號已成功建立。"
+ },
+ "welcome": ""
},
"forgotPassword": {
"heading": "",
@@ -623,7 +628,7 @@
"sent": "",
"emailNotFound": "",
"redirect": "",
- "success": "",
+ "success": "您的密碼已成功重設。",
"error": ""
}
}
@@ -634,10 +639,10 @@
"reconnected": "",
"stillUnreachable": ""
},
- "alertBox": "",
- "description": "",
+ "alertBox": "伺服器連線錯誤",
+ "description": "我們無法連接到伺服器。請檢查您的網路連線,若問題持續,請確認您的部署設定是否正確。",
"retryButton": {
- "default": "",
+ "default": "重新嘗試連線",
"processing": ""
}
}
@@ -645,42 +650,42 @@
"createNotifications": {
"title": "",
"nameSettings": {
- "title": "",
- "description": "",
+ "title": "姓名",
+ "description": "為您的整合設定描述名稱。",
"nameLabel": "",
"namePlaceholder": ""
},
"typeSettings": {
- "title": "",
- "description": "",
- "typeLabel": ""
+ "title": "類型",
+ "description": "請選擇您想要建立的通知頻道類型。",
+ "typeLabel": "類型"
},
"emailSettings": {
- "title": "",
- "description": "",
+ "title": "電郵",
+ "description": "目的地電子郵件位址。",
"emailLabel": "",
"emailPlaceholder": ""
},
"slackSettings": {
- "title": "",
- "description": "",
+ "title": "Slack",
+ "description": "在此配置您的 Slack webhook",
"webhookLabel": "",
"webhookPlaceholder": ""
},
"pagerdutySettings": {
- "title": "",
- "description": "",
+ "title": "PagerDuty",
+ "description": "在此配置您的 PagerDuty 整合",
"integrationKeyLabel": "",
"integrationKeyPlaceholder": ""
},
"discordSettings": {
- "title": "",
- "description": "",
+ "title": "Discord",
+ "description": "在此配置您的 Discord webhook",
"webhookLabel": "",
"webhookPlaceholder": ""
},
"webhookSettings": {
- "title": "",
+ "title": "Webhook",
"description": "",
"webhookLabel": "",
"webhookPlaceholder": ""
@@ -690,8 +695,8 @@
"dialogDeleteConfirm": ""
},
"notificationConfig": {
- "title": "",
- "description": ""
+ "title": "通知",
+ "description": "請選擇您想要使用的通知頻道"
},
"monitorStatus": {
"checkingEvery": "",
@@ -700,7 +705,7 @@
"down": "",
"paused": ""
},
- "advancedMatching": "",
+ "advancedMatching": "進階匹配",
"sendTestNotifications": "",
"selectAll": "",
"showAdminLoginLink": "",
@@ -718,11 +723,11 @@
"logLevelSelect": {
"title": "",
"values": {
- "all": "",
+ "all": "全部",
"info": "",
"warn": "",
"error": "",
- "debug": ""
+ "debug": "除錯"
}
}
},
@@ -734,8 +739,8 @@
"title": "",
"idHeader": "",
"urlHeader": "",
- "typeHeader": "",
- "activeHeader": "",
+ "typeHeader": "類型",
+ "activeHeader": "啟用中",
"lockedAtHeader": "",
"runCountHeader": "",
"failCountHeader": "",
@@ -774,7 +779,7 @@
"aboutSettings": {
"labelDevelopedBy": "",
"labelVersion": "",
- "title": ""
+ "title": "關於"
},
"demoMonitorsSettings": {
"buttonAddMonitors": "",
@@ -782,7 +787,7 @@
"title": ""
},
"emailSettings": {
- "buttonSendTestEmail": "",
+ "buttonSendTestEmail": "發送測試電子郵件",
"description": "",
"descriptionTransport": "",
"labelAddress": "",
@@ -800,25 +805,25 @@
"labelUser": "",
"linkTransport": "",
"placeholderUser": "",
- "title": "",
+ "title": "電郵",
"toastEmailRequiredFieldsError": ""
},
"pageSpeedSettings": {
"description": "",
"labelApiKeySet": "",
"labelApiKey": "",
- "title": ""
+ "title": "Google PageSpeed API 金鑰"
},
"saveButtonLabel": "",
"statsSettings": {
- "clearAllStatsButton": "",
- "clearAllStatsDescription": "",
- "clearAllStatsDialogConfirm": "",
- "clearAllStatsDialogDescription": "",
- "clearAllStatsDialogTitle": "",
+ "clearAllStatsButton": "清除所有統計數據",
+ "clearAllStatsDescription": "清除所有統計數據,此操作不可逆轉。",
+ "clearAllStatsDialogConfirm": "是,請清除所有統計數據",
+ "clearAllStatsDialogDescription": "一旦刪除,監控歷史和統計數據將無法復原。",
+ "clearAllStatsDialogTitle": "您是否要清除所有統計數據?",
"description": "",
"labelTTL": "",
- "labelTTLOptional": "",
+ "labelTTLOptional": "0 表示無限",
"title": ""
},
"systemResetSettings": {
@@ -827,19 +832,19 @@
"dialogConfirm": "",
"dialogDescription": "",
"dialogTitle": "",
- "title": ""
+ "title": "系統重設"
},
"timezoneSettings": {
"description": "",
"label": "",
- "title": ""
+ "title": "顯示時區"
},
- "title": "",
+ "title": "設定",
"uiSettings": {
"description": "",
"labelLanguage": "",
"labelTheme": "",
- "title": ""
+ "title": "外觀"
},
"urlSettings": {
"description": "",
@@ -847,15 +852,19 @@
"selectDisabled": "",
"selectEnabled": "",
"title": ""
+ },
+ "globalThresholds": {
+ "title": "",
+ "description": ""
}
},
"statusPageCreate": {
- "buttonSave": ""
+ "buttonSave": "儲存"
},
"incidentsOptionsHeaderFilterResolved": "",
"settingsSave": "",
"statusPageCreateAppearanceTitle": "",
- "confirmPassword": "",
+ "confirmPassword": "確認密碼",
"monitorHooks": {
"failureAddDemoMonitors": "",
"successAddDemoMonitors": ""
@@ -866,15 +875,15 @@
"incidentsOptionsHeaderTotalIncidents": "",
"statusPage": {
"deleteSuccess": "",
- "deleteFailed": "",
+ "deleteFailed": "刪除狀態頁面失敗",
"createSuccess": "",
"updateSuccess": "",
"generalSettings": "",
- "contents": "",
+ "contents": "內容",
"fallback": {
"checks": [""],
"title": "",
- "actionButton": ""
+ "actionButton": "讓我們建立您的第一個狀態頁面!"
}
},
"testNotificationsDisabled": "",
@@ -909,63 +918,138 @@
},
"pageSpeedLighthouseAPI": "",
"time": {
- "threeMinutes": "",
- "fiveMinutes": "",
- "tenMinutes": "",
- "twentyMinutes": "",
- "oneHour": "",
- "oneDay": "",
- "oneWeek": ""
+ "threeMinutes": "3分鐘",
+ "fiveMinutes": "5分鐘",
+ "tenMinutes": "10分鐘",
+ "twentyMinutes": "20分鐘",
+ "oneHour": "1小時",
+ "oneDay": "1日",
+ "oneWeek": "1星期",
+ "fourMinutes": "",
+ "oneMinute": "",
+ "twoMinutes": "",
+ "fifteenSeconds": "",
+ "thirtySeconds": ""
},
"general": {
"noOptionsFound": ""
},
"infrastructureMonitor": {
"fallback": {
- "checks": [""],
+ "checks": ["追蹤伺服器的效能", "識別瓶頸並優化資源使用", "透過即時監控確保可靠性"],
"title": "",
- "actionButton": ""
+ "actionButton": "讓我們建立您的第一個基礎設施監視器!"
}
},
"maintenanceWindow": {
"fallback": {
- "checks": [""],
+ "checks": ["標記您的維護期間", "消除任何誤解", "在維護時段停止發送警報"],
"title": "",
- "actionButton": ""
+ "actionButton": "讓我們建立您的第一個維護時段!"
}
},
"pageSpeed": {
"fallback": {
"checks": [""],
"title": "",
- "actionButton": ""
+ "actionButton": "讓我們建立您的第一個 PageSpeed 監視器!"
}
},
"uptimeMonitor": {
"fallback": {
"checks": [""],
"title": "",
- "actionButton": ""
+ "actionButton": "讓我們建立您的第一個正常運行時間監視器!"
}
},
"editUserPage": {
"form": {
- "email": "",
- "firstName": "",
- "lastName": "",
- "role": "",
- "save": ""
+ "email": "電郵",
+ "firstName": "名",
+ "lastName": "姓",
+ "role": "角色",
+ "save": "儲存"
},
"table": {
- "actionHeader": "",
- "roleHeader": ""
+ "actionHeader": "行動",
+ "roleHeader": "角色"
},
- "title": "",
+ "title": "編輯用戶",
"toast": {
"successUserUpdate": "",
"validationErrors": ""
}
},
- "incidentsPageActionResolveMonitor": "",
- "incidentsPageActionResolveAll": ""
+ "incidentsPageActionResolveMonitor": "解決監視器事故",
+ "incidentsPageActionResolveAll": "解決所有事故",
+ "matchMethodOptions": {
+ "equal": "",
+ "equalPlaceholder": "",
+ "include": "",
+ "includePlaceholder": "",
+ "regex": "",
+ "regexPlaceholder": "",
+ "text": ""
+ },
+ "monitorType": {
+ "docker": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "http": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "ping": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "port": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ },
+ "game": {
+ "label": "",
+ "namePlaceholder": "",
+ "placeholder": ""
+ }
+ },
+ "uptimeAdvancedMatching": {
+ "jsonPath": ""
+ },
+ "bytesPerSecond": "",
+ "bytesReceived": "",
+ "bytesSent": "",
+ "chooseGame": "",
+ "createMonitorPage": {
+ "incidentConfigDescription": "",
+ "incidentConfigStatusWindowLabel": "",
+ "incidentConfigStatusWindowThresholdLabel": "",
+ "incidentConfigTitle": ""
+ },
+ "dataRate": "",
+ "dataReceived": "",
+ "dataSent": "",
+ "details": "",
+ "drops": "",
+ "errors": "",
+ "errorsIn": "",
+ "errorsOut": "",
+ "gameServerMonitoring": "",
+ "gameServerMonitoringDescription": "",
+ "network": "",
+ "networkDrops": "",
+ "networkErrors": "",
+ "networkInterface": "",
+ "noNetworkStatsAvailable": "",
+ "packetsPerSecond": "",
+ "packetsReceived": "",
+ "packetsReceivedRate": "",
+ "packetsSent": "",
+ "rate": "",
+ "selectInterface": ""
}
diff --git a/client/vite.config.js b/client/vite.config.js
index 44a5905c5..158dc5e4e 100644
--- a/client/vite.config.js
+++ b/client/vite.config.js
@@ -5,7 +5,7 @@ import { execSync } from "child_process";
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "");
- let version = 2.3;
+ let version = "3.1.8";
return {
base: "/",
diff --git a/docker/.gitignore b/docker/.gitignore
index 9a6df883a..76567d514 100755
--- a/docker/.gitignore
+++ b/docker/.gitignore
@@ -12,3 +12,13 @@ dist/docker-compose-test.yaml
/dist-mono/redis/data/*
/dist-arm/mongo/data/*
*.env
+
+# Custom CA certificates and keys (should not be committed)
+dev/certs/*
+!dev/certs/.gitkeep
+
+# Certificate artifacts
+*.key
+*.csr
+*.srl
+*.log
diff --git a/docker/coolify/client.Dockerfile b/docker/coolify/client.Dockerfile
index 1384bfa2c..8c3853b99 100755
--- a/docker/coolify/client.Dockerfile
+++ b/docker/coolify/client.Dockerfile
@@ -1,18 +1,9 @@
-FROM node:20-alpine AS build
+FROM node:20-slim AS build
ENV NODE_OPTIONS="--max-old-space-size=4096"
WORKDIR /app
-RUN apk add --no-cache \
- python3 \
- make g++ \
- gcc \
- libc-dev \
- linux-headers \
- libusb-dev \
- eudev-dev
-
COPY ./client/package*.json ./
RUN npm install
diff --git a/docker/coolify/server.Dockerfile b/docker/coolify/server.Dockerfile
index 611a325a5..238bd404c 100755
--- a/docker/coolify/server.Dockerfile
+++ b/docker/coolify/server.Dockerfile
@@ -1,4 +1,9 @@
-FROM node:20-alpine
+FROM node:20-slim
+
+# Install ping
+RUN apt-get update \
+ && apt-get install -y iputils-ping \
+ && rm -rf /var/lib/apt/lists/*
WORKDIR /app
diff --git a/docker/dev/certs/.gitkeep b/docker/dev/certs/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/docker/dev/client.Dockerfile b/docker/dev/client.Dockerfile
index aa4d228fc..df3ac7439 100755
--- a/docker/dev/client.Dockerfile
+++ b/docker/dev/client.Dockerfile
@@ -1,19 +1,9 @@
-FROM node:20-alpine AS build
+FROM node:20-slim AS build
ENV NODE_OPTIONS="--max-old-space-size=4096"
WORKDIR /app
-RUN apk add --no-cache \
- python3 \
- make g++ \
- gcc \
- libc-dev \
- linux-headers \
- libusb-dev \
- eudev-dev
-
-
COPY ./client/package*.json ./
RUN npm install
diff --git a/docker/dev/docker-compose.custom-ca-example.yaml b/docker/dev/docker-compose.custom-ca-example.yaml
new file mode 100644
index 000000000..a5d7e2f43
--- /dev/null
+++ b/docker/dev/docker-compose.custom-ca-example.yaml
@@ -0,0 +1,30 @@
+# Dev/Test only: Not required in production
+# Example Docker Compose configuration for custom CA trust
+# This file demonstrates how to configure Checkmate to trust custom CAs
+#
+# Usage: docker-compose -f docker-compose.yaml -f docker-compose.custom-ca-example.yaml up
+
+services:
+ server:
+ image: uptime_server:latest
+ restart: always
+ ports:
+ - "52345:52345"
+ env_file:
+ - server.env
+ environment:
+ # Mount your custom CA certificate
+ NODE_EXTRA_CA_CERTS: /certs/custom-ca.pem
+ volumes:
+ # Mount the certs directory (read-only for security)
+ - ./certs:/certs:ro
+ depends_on:
+ - redis
+ - mongodb
+ # Optional: Add healthcheck to ensure the server starts properly
+ healthcheck:
+ test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:52345/api/v1/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
diff --git a/docker/dev/docker-compose.yaml b/docker/dev/docker-compose.yaml
index f0b193b2d..a2c66c027 100755
--- a/docker/dev/docker-compose.yaml
+++ b/docker/dev/docker-compose.yaml
@@ -19,29 +19,17 @@ services:
env_file:
- server.env
depends_on:
- - redis
- mongodb
- redis:
- image: uptime_redis:latest
- restart: always
- volumes:
- - ./redis/data:/data
- healthcheck:
- test: ["CMD", "redis-cli", "ping"]
- interval: 30s
- timeout: 10s
- retries: 5
- start_period: 5s
mongodb:
image: uptime_mongo:latest
restart: always
- command: ["mongod", "--quiet", "--replSet", "rs0", "--bind_ip_all"]
+ command: ["mongod", "--quiet", "--bind_ip_all"]
volumes:
- ./mongo/data:/data/db
healthcheck:
- test: echo "try { rs.status() } catch (err) { rs.initiate({_id:'rs0',members:[{_id:0,host:'mongodb:27017'}]}) }" | mongosh --port 27017 --quiet
+ test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')", "--quiet"]
interval: 5s
timeout: 30s
start_period: 0s
start_interval: 1s
- retries: 30
\ No newline at end of file
+ retries: 30
diff --git a/docker/dev/nginx-test/docker-compose.nginx-test.yaml b/docker/dev/nginx-test/docker-compose.nginx-test.yaml
new file mode 100644
index 000000000..766e839bd
--- /dev/null
+++ b/docker/dev/nginx-test/docker-compose.nginx-test.yaml
@@ -0,0 +1,13 @@
+# Dev/Test only: Not required in production
+# Docker Compose configuration for nginx test service
+
+services:
+ nginx-test:
+ image: nginx:alpine
+ ports:
+ - "8443:443"
+ volumes:
+ - ../certs/host-int-cert.pem:/etc/nginx/certs/server.crt:ro
+ - ../certs/host-int-key.pem:/etc/nginx/certs/server.key:ro
+ - ./nginx.conf:/etc/nginx/nginx.conf:ro
+ restart: unless-stopped
diff --git a/docker/dev/nginx-test/nginx.conf b/docker/dev/nginx-test/nginx.conf
new file mode 100644
index 000000000..81c4c9240
--- /dev/null
+++ b/docker/dev/nginx-test/nginx.conf
@@ -0,0 +1,14 @@
+# Dev/Test only: Not required in production
+# Nginx configuration for testing custom CA trust functionality
+
+events {}
+http {
+ server {
+ listen 443 ssl;
+ ssl_certificate /etc/nginx/certs/server.crt;
+ ssl_certificate_key /etc/nginx/certs/server.key;
+ location / {
+ return 200 "hello from tls\n";
+ }
+ }
+}
diff --git a/docker/dev/redis.Dockerfile b/docker/dev/redis.Dockerfile
deleted file mode 100755
index af68ec61e..000000000
--- a/docker/dev/redis.Dockerfile
+++ /dev/null
@@ -1,2 +0,0 @@
-FROM redis
-EXPOSE 6379
\ No newline at end of file
diff --git a/docker/dev/server.Dockerfile b/docker/dev/server.Dockerfile
index a3542c168..d24bff63f 100755
--- a/docker/dev/server.Dockerfile
+++ b/docker/dev/server.Dockerfile
@@ -1,4 +1,9 @@
-FROM node:20-alpine
+FROM node:20-slim
+
+# Install ping
+RUN apt-get update \
+ && apt-get install -y iputils-ping \
+ && rm -rf /var/lib/apt/lists/*
WORKDIR /app
diff --git a/docker/dist-arm/docker-compose.yaml b/docker/dist-arm/docker-compose.yaml
index 921d3b8c1..0a5825b3e 100644
--- a/docker/dist-arm/docker-compose.yaml
+++ b/docker/dist-arm/docker-compose.yaml
@@ -2,12 +2,13 @@ services:
server:
image: ghcr.io/bluewave-labs/checkmate-backend-mono-multiarch:latest
restart: always
+ pull_policy: always
ports:
- "52345:52345"
environment:
- UPTIME_APP_API_BASE_URL=http://localhost:52345/api/v1
- UPTIME_APP_CLIENT_HOST=http://localhost
- - DB_CONNECTION_STRING=mongodb://mongodb:27017/uptime_db?replicaSet=rs0
+ - DB_CONNECTION_STRING=mongodb://mongodb:27017/uptime_db
- REDIS_URL=redis://redis:6379
- CLIENT_HOST=http://localhost
- JWT_SECRET=my_secret
@@ -15,29 +16,16 @@ services:
- mongodb
mongodb:
image: mongo:4.4.18
- container_name: checkmate-mongodb
restart: always
- command: ["mongod", "--replSet", "rs0", "--bind_ip_all"]
+ command: ["mongod", "--quiet", "--bind_ip_all"]
ports:
- "27017:27017"
volumes:
- ./mongo/data:/data/db
-
- mongo-init:
- image: mongo:4.4.18
- depends_on:
- - mongodb
- entrypoint: >
- bash -c "
- echo 'Waiting for MongoDB to be ready...' &&
- until mongo --host mongodb --eval 'db.adminCommand(\"ping\")' > /dev/null 2>&1; do
- sleep 2
- done &&
- echo 'MongoDB is up. Initiating replica set...' &&
- mongo --host mongodb --eval '
- rs.initiate({
- _id: \"rs0\",
- members: [{ _id: 0, host: \"mongodb:27017\" }]
- })
- ' || echo 'Replica set may already be initialized.'
- "
+ healthcheck:
+ test: ["CMD", "mongo", "--eval", "db.runCommand({ ping: 1 })", "--quiet"]
+ interval: 5s
+ timeout: 30s
+ start_period: 0s
+ start_interval: 1s
+ retries: 30
diff --git a/docker/dist-arm/server.Dockerfile b/docker/dist-arm/server.Dockerfile
index c6d31a893..e47db9a74 100644
--- a/docker/dist-arm/server.Dockerfile
+++ b/docker/dist-arm/server.Dockerfile
@@ -1,25 +1,44 @@
-FROM node:20-alpine AS frontend-build
+# ---------------------
+# Frontend build stage
+# ---------------------
+FROM node:24-slim AS frontend-build
+
+
WORKDIR /app/client
-COPY client/package*.json ./
-RUN npm ci
+COPY client/package.json ./
+
+RUN npm install
+
+RUN npm install esbuild@0.25.5 --build-from-source
COPY client ./
+
RUN npm run build
-FROM node:20-alpine AS backend
+# ---------------------
+# Backend stage
+# ---------------------
+FROM node:24-slim AS backend
+
+# Install ping
+RUN apt-get update \
+ && apt-get install -y iputils-ping \
+ && rm -rf /var/lib/apt/lists/*
WORKDIR /app/server
+COPY server/package.json ./
+
+RUN npm install
+
COPY server ./
COPY --from=frontend-build /app/client/dist ./public
-RUN npm ci
-
RUN chmod +x ./scripts/inject-vars.sh
EXPOSE 52345
-CMD ./scripts/inject-vars.sh && node ./src/index.js
+CMD ["sh", "-c", "./scripts/inject-vars.sh && node ./src/index.js"]
diff --git a/docker/dist-mono/build_images.sh b/docker/dist-mono/build_images.sh
index b56b9052c..e3a0233d4 100755
--- a/docker/dist-mono/build_images.sh
+++ b/docker/dist-mono/build_images.sh
@@ -5,10 +5,9 @@ cd "$(dirname "$0")"
cd ../..
# Define service names and their corresponding Dockerfiles in parallel arrays
-services=("mono_mongo" "mono_redis" "mono_server")
+services=("mono_mongo" "mono_server")
dockerfiles=(
"./docker/dist-mono/mongoDB.Dockerfile"
- "./docker/dist-mono/redis.Dockerfile"
"./docker/dist-mono/server.Dockerfile"
)
diff --git a/docker/dist-mono/docker-compose.yaml b/docker/dist-mono/docker-compose.yaml
index 0dd97aafb..9553757b9 100755
--- a/docker/dist-mono/docker-compose.yaml
+++ b/docker/dist-mono/docker-compose.yaml
@@ -1,14 +1,14 @@
services:
server:
image: ghcr.io/bluewave-labs/checkmate-backend-mono:latest
+ pull_policy: always
restart: always
ports:
- "52345:52345"
environment:
- UPTIME_APP_API_BASE_URL=http://localhost:52345/api/v1
- UPTIME_APP_CLIENT_HOST=http://localhost
- - DB_CONNECTION_STRING=mongodb://mongodb:27017/uptime_db?replicaSet=rs0
- - REDIS_URL=redis://redis:6379
+ - DB_CONNECTION_STRING=mongodb://mongodb:27017/uptime_db
- CLIENT_HOST=http://localhost
- JWT_SECRET=my_secret
depends_on:
@@ -16,11 +16,11 @@ services:
mongodb:
image: ghcr.io/bluewave-labs/checkmate-mongo:latest
restart: always
- command: ["mongod", "--quiet", "--replSet", "rs0", "--bind_ip_all"]
+ command: ["mongod", "--quiet", "--bind_ip_all"]
volumes:
- ./mongo/data:/data/db
healthcheck:
- test: echo "try { rs.status() } catch (err) { rs.initiate({_id:'rs0',members:[{_id:0,host:'mongodb:27017'}]}) }" | mongosh --port 27017 --quiet
+ test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')", "--quiet"]
interval: 5s
timeout: 30s
start_period: 0s
diff --git a/docker/dist-mono/redis.Dockerfile b/docker/dist-mono/redis.Dockerfile
deleted file mode 100755
index af68ec61e..000000000
--- a/docker/dist-mono/redis.Dockerfile
+++ /dev/null
@@ -1,2 +0,0 @@
-FROM redis
-EXPOSE 6379
\ No newline at end of file
diff --git a/docker/dist-mono/server.Dockerfile b/docker/dist-mono/server.Dockerfile
index 4063e4d7a..405f9e6ac 100644
--- a/docker/dist-mono/server.Dockerfile
+++ b/docker/dist-mono/server.Dockerfile
@@ -1,4 +1,4 @@
-FROM node:20-alpine AS frontend-build
+FROM node:20-slim AS frontend-build
WORKDIR /app/client
@@ -8,7 +8,12 @@ RUN npm install
COPY client ./
RUN npm run build
-FROM node:20-alpine AS app
+FROM node:20-slim AS app
+
+# Install ping
+RUN apt-get update \
+ && apt-get install -y iputils-ping \
+ && rm -rf /var/lib/apt/lists/*
WORKDIR /app/server
diff --git a/docker/dist/build_images.sh b/docker/dist/build_images.sh
index 953682175..84fbb7d5a 100755
--- a/docker/dist/build_images.sh
+++ b/docker/dist/build_images.sh
@@ -7,7 +7,6 @@ cd ../../
declare -A services=(
["bluewaveuptime/uptime_client"]="./docker/dist/client.Dockerfile"
["bluewaveuptime/uptime_database_mongo"]="./docker/dist/mongoDB.Dockerfile"
- ["bluewaveuptime/uptime_redis"]="./docker/dist/redis.Dockerfile"
["bluewaveuptime/uptime_server"]="./docker/dist/server.Dockerfile"
)
diff --git a/docker/dist/client.Dockerfile b/docker/dist/client.Dockerfile
index 6491f4bbb..9b42654b0 100755
--- a/docker/dist/client.Dockerfile
+++ b/docker/dist/client.Dockerfile
@@ -1,18 +1,9 @@
-FROM node:20-alpine AS build
+FROM node:20-slim AS build
ENV NODE_OPTIONS="--max-old-space-size=4096"
WORKDIR /app
-RUN apk add --no-cache \
- python3 \
- make g++ \
- gcc \
- libc-dev \
- linux-headers \
- libusb-dev \
- eudev-dev
-
COPY ./client/package*.json ./
RUN npm install
diff --git a/docker/dist/docker-compose.yaml b/docker/dist/docker-compose.yaml
index ff2f1b7ea..2e845daab 100755
--- a/docker/dist/docker-compose.yaml
+++ b/docker/dist/docker-compose.yaml
@@ -18,8 +18,7 @@ services:
depends_on:
- mongodb
environment:
- - DB_CONNECTION_STRING=mongodb://mongodb:27017/uptime_db?replicaSet=rs0
- - REDIS_URL=redis://redis:6379
+ - DB_CONNECTION_STRING=mongodb://mongodb:27017/uptime_db
- CLIENT_HOST=http://localhost
- JWT_SECRET=my_secret
# volumes:
@@ -29,9 +28,9 @@ services:
restart: always
volumes:
- ./mongo/data:/data/db
- command: ["mongod", "--quiet", "--replSet", "rs0", "--bind_ip_all"]
+ command: ["mongod", "--quiet", "--bind_ip_all"]
healthcheck:
- test: echo "try { rs.status() } catch (err) { rs.initiate({_id:'rs0',members:[{_id:0,host:'mongodb:27017'}]}) }" | mongosh --port 27017 --quiet
+ test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')", "--quiet"]
interval: 5s
timeout: 30s
start_period: 0s
diff --git a/docker/dist/redis.Dockerfile b/docker/dist/redis.Dockerfile
deleted file mode 100755
index af68ec61e..000000000
--- a/docker/dist/redis.Dockerfile
+++ /dev/null
@@ -1,2 +0,0 @@
-FROM redis
-EXPOSE 6379
\ No newline at end of file
diff --git a/docker/dist/server.Dockerfile b/docker/dist/server.Dockerfile
index a3542c168..d24bff63f 100755
--- a/docker/dist/server.Dockerfile
+++ b/docker/dist/server.Dockerfile
@@ -1,4 +1,9 @@
-FROM node:20-alpine
+FROM node:20-slim
+
+# Install ping
+RUN apt-get update \
+ && apt-get install -y iputils-ping \
+ && rm -rf /var/lib/apt/lists/*
WORKDIR /app
diff --git a/docker/prod/build_images.sh b/docker/prod/build_images.sh
index 23e91355c..49f8c7841 100755
--- a/docker/prod/build_images.sh
+++ b/docker/prod/build_images.sh
@@ -8,7 +8,6 @@ cd ../../
declare -A services=(
["uptime_client"]="./docker/prod/client.Dockerfile"
["uptime_database_mongo"]="./docker/prod/mongoDB.Dockerfile"
- ["uptime_redis"]="./docker/prod/redis.Dockerfile"
["uptime_server"]="./docker/prod/server.Dockerfile"
)
diff --git a/docker/prod/client.Dockerfile b/docker/prod/client.Dockerfile
index e81a1af26..b4c685e3e 100755
--- a/docker/prod/client.Dockerfile
+++ b/docker/prod/client.Dockerfile
@@ -1,18 +1,9 @@
-FROM node:20-alpine AS build
+FROM node:20-slim AS build
ENV NODE_OPTIONS="--max-old-space-size=4096"
WORKDIR /app
-RUN apk add --no-cache \
- python3 \
- make g++ \
- gcc \
- libc-dev \
- linux-headers \
- libusb-dev \
- eudev-dev
-
COPY ./client/package*.json ./
RUN npm install
diff --git a/docker/prod/docker-compose.yaml b/docker/prod/docker-compose.yaml
index 980a0e2f5..6e29fd90a 100755
--- a/docker/prod/docker-compose.yaml
+++ b/docker/prod/docker-compose.yaml
@@ -37,16 +37,26 @@ services:
mongodb:
image: ghcr.io/bluewave-labs/checkmate:mongo-demo
restart: always
- command: ["mongod", "--quiet", "--replSet", "rs0", "--bind_ip_all"]
+ command: ["mongod", "--quiet", "--bind_ip_all"]
volumes:
- ./mongo/data:/data/db
- # - ./mongo/init/init.js:/docker-entrypoint-initdb.d/init.js // No longer needed
env_file:
- mongo.env
healthcheck:
- test: echo "try { rs.status() } catch (err) { rs.initiate({_id:'rs0',members:[{_id:0,host:'mongodb:27017'}]}) }" | mongosh --port 27017 --quiet
+ test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')", "--quiet"]
interval: 5s
timeout: 30s
start_period: 0s
start_interval: 1s
retries: 30
+ capture:
+ image: ghcr.io/bluewave-labs/capture:latest
+ container_name: capture
+ restart: unless-stopped
+ ports:
+ - "59232:59232"
+ environment:
+ - API_SECRET=my_secret
+ - GIN_MODE=release
+ volumes:
+ - /etc/os-release:/etc/os-release:ro
diff --git a/docker/prod/redis.Dockerfile b/docker/prod/redis.Dockerfile
deleted file mode 100755
index af68ec61e..000000000
--- a/docker/prod/redis.Dockerfile
+++ /dev/null
@@ -1,2 +0,0 @@
-FROM redis
-EXPOSE 6379
\ No newline at end of file
diff --git a/docker/prod/server.Dockerfile b/docker/prod/server.Dockerfile
index 94018c07d..b78de2e33 100755
--- a/docker/prod/server.Dockerfile
+++ b/docker/prod/server.Dockerfile
@@ -1,4 +1,9 @@
-FROM node:20-alpine
+FROM node:20-slim
+
+# Install ping
+RUN apt-get update \
+ && apt-get install -y iputils-ping \
+ && rm -rf /var/lib/apt/lists/*
ENV NODE_OPTIONS="--max-old-space-size=2048"
diff --git a/docker/staging/build_images.sh b/docker/staging/build_images.sh
index ccfd451ba..a11aebf7c 100755
--- a/docker/staging/build_images.sh
+++ b/docker/staging/build_images.sh
@@ -8,7 +8,6 @@ cd ../../
declare -A services=(
["uptime_client"]="./docker/staging/client.Dockerfile"
["uptime_database_mongo"]="./docker/staging/mongoDB.Dockerfile"
- ["uptime_redis"]="./docker/staging/redis.Dockerfile"
["uptime_server"]="./docker/staging/server.Dockerfile"
)
diff --git a/docker/staging/client.Dockerfile b/docker/staging/client.Dockerfile
index aa4d228fc..df3ac7439 100644
--- a/docker/staging/client.Dockerfile
+++ b/docker/staging/client.Dockerfile
@@ -1,19 +1,9 @@
-FROM node:20-alpine AS build
+FROM node:20-slim AS build
ENV NODE_OPTIONS="--max-old-space-size=4096"
WORKDIR /app
-RUN apk add --no-cache \
- python3 \
- make g++ \
- gcc \
- libc-dev \
- linux-headers \
- libusb-dev \
- eudev-dev
-
-
COPY ./client/package*.json ./
RUN npm install
diff --git a/docker/staging/docker-compose.yaml b/docker/staging/docker-compose.yaml
index b9ca52b6e..f4ed5ce6f 100755
--- a/docker/staging/docker-compose.yaml
+++ b/docker/staging/docker-compose.yaml
@@ -31,36 +31,32 @@ services:
env_file:
- server.env
depends_on:
- - redis
- mongodb
- redis:
- image: ghcr.io/bluewave-labs/checkmate:redis-staging
- restart: always
- ports:
- - "6379:6379"
- volumes:
- - ./redis/data:/data
- healthcheck:
- test: ["CMD", "redis-cli", "ping"]
- interval: 30s
- timeout: 10s
- retries: 5
- start_period: 5s
mongodb:
image: ghcr.io/bluewave-labs/checkmate:mongo-staging
restart: always
- command: ["mongod", "--quiet", "--replSet", "rs0", "--bind_ip_all"]
+ command: ["mongod", "--quiet", "--bind_ip_all"]
ports:
- "27017:27017"
volumes:
- ./mongo/data:/data/db
- # - ./mongo/init/02_create_users.js:/docker-entrypoint-initdb.d/02_create_users.js // No longer needed
env_file:
- mongo.env
healthcheck:
- test: echo "try { rs.status() } catch (err) { rs.initiate({_id:'rs0',members:[{_id:0,host:'mongodb:27017'}]}) }" | mongosh --port 27017 --quiet
+ test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')", "--quiet"]
interval: 5s
timeout: 30s
start_period: 0s
start_interval: 1s
retries: 30
+ capture:
+ image: ghcr.io/bluewave-labs/capture:latest
+ container_name: capture
+ restart: unless-stopped
+ ports:
+ - "59232:59232"
+ environment:
+ - API_SECRET=my_secret
+ - GIN_MODE=release
+ volumes:
+ - /etc/os-release:/etc/os-release:ro
diff --git a/docker/staging/redis.Dockerfile b/docker/staging/redis.Dockerfile
deleted file mode 100755
index af68ec61e..000000000
--- a/docker/staging/redis.Dockerfile
+++ /dev/null
@@ -1,2 +0,0 @@
-FROM redis
-EXPOSE 6379
\ No newline at end of file
diff --git a/docker/staging/server.Dockerfile b/docker/staging/server.Dockerfile
index 94018c07d..b78de2e33 100755
--- a/docker/staging/server.Dockerfile
+++ b/docker/staging/server.Dockerfile
@@ -1,4 +1,9 @@
-FROM node:20-alpine
+FROM node:20-slim
+
+# Install ping
+RUN apt-get update \
+ && apt-get install -y iputils-ping \
+ && rm -rf /var/lib/apt/lists/*
ENV NODE_OPTIONS="--max-old-space-size=2048"
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 000000000..dc245e21e
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,38 @@
+# Checkmate Documentation
+
+Welcome to the Checkmate documentation. This directory contains guides and references for various aspects of Checkmate deployment and configuration.
+
+## Available Documentation
+
+### Deployment & Configuration
+- **[Custom CA Trust Guide](./custom-ca-trust.md)** - Configure Checkmate to trust custom Certificate Authorities
+- **[Custom CA Quick Reference](./custom-ca-quick-reference.md)** - Quick setup guide for custom CA trust
+
+## Docker Configuration
+
+### Custom CA Trust
+If you need to monitor internal HTTPS endpoints with certificates from private Certificate Authorities (like Smallstep), see our custom CA trust documentation:
+
+- **Full Guide**: [Custom CA Trust Guide](./custom-ca-trust.md)
+- **Quick Reference**: [Custom CA Quick Reference](./custom-ca-quick-reference.md)
+
+### Example Configurations
+The `docker/dev/` directory contains example configurations:
+- `docker-compose.custom-ca-example.yaml` - Example Docker Compose with custom CA
+- `server-custom-ca.Dockerfile` - Example Dockerfile for OS-level CA trust
+- `export-smallstep-ca.sh` - Helper script for Smallstep CA export
+
+## Contributing to Documentation
+
+If you find issues or want to improve the documentation:
+
+1. Check existing issues and discussions
+2. Submit a pull request with your changes
+3. Follow the same markdown formatting style
+4. Include practical examples and code snippets
+
+## Getting Help
+
+- [GitHub Discussions](https://github.com/bluewave-labs/checkmate/discussions)
+- [Discord Channel](https://discord.gg/NAb6H3UTjK)
+- [Documentation Portal](https://docs.checkmate.so/)
diff --git a/docs/custom-ca-quick-reference.md b/docs/custom-ca-quick-reference.md
new file mode 100644
index 000000000..71ea3b954
--- /dev/null
+++ b/docs/custom-ca-quick-reference.md
@@ -0,0 +1,60 @@
+# Custom CA Trust - Quick Reference
+
+## Fast Setup (Node-level)
+
+1. **Export your CA certificate:**
+ ```bash
+ # For Smallstep
+ step certificate inspect --format pem step-ca/root_ca.crt > docker/dev/certs/custom-ca.pem
+ ```
+
+2. **Use the provided example override:**
+ ```bash
+ docker-compose -f docker-compose.yaml -f docker-compose.custom-ca-example.yaml up -d
+ ```
+
+3. **Or manually update docker-compose.yaml:**
+ ```yaml
+ services:
+ server:
+ environment:
+ NODE_EXTRA_CA_CERTS: /certs/custom-ca.pem
+ volumes:
+ - ./certs:/certs:ro
+ ```
+
+## Alternative Setup (OS-level)
+
+1. **Use custom Dockerfile:**
+ ```bash
+ docker-compose -f docker-compose.yaml -f docker-compose.custom-ca.yaml up
+ ```
+
+## File Structure
+```
+docker/dev/
+├── docker-compose.yaml
+├── docker-compose.custom-ca-example.yaml # Example config
+├── certs/
+│ ├── README.md
+│ └── custom-ca.pem # Your CA certificate
+└── export-smallstep-ca.sh # Helper script
+```
+
+## Environment Variables
+- `NODE_EXTRA_CA_CERTS=/certs/custom-ca.pem` - Node.js trust
+- Mount `./certs:/certs:ro` - Volume mount
+
+## Security
+- ✅ Only trust CAs you control
+- ✅ Use read-only volume mounts
+- ✅ Keep certificates out of version control
+- ❌ Never trust untrusted CAs
+
+## Troubleshooting
+- Check container logs: `docker-compose logs server`
+- Verify certificate: `docker exec -it ls -la /certs/`
+- Test connection: `docker exec -it wget --ca-certificate=/certs/custom-ca.pem https://your-internal-site.com`
+
+## Full Documentation
+See [Custom CA Trust Guide](./custom-ca-trust.md) for detailed instructions.
diff --git a/docs/custom-ca-trust.md b/docs/custom-ca-trust.md
new file mode 100644
index 000000000..b3a19831f
--- /dev/null
+++ b/docs/custom-ca-trust.md
@@ -0,0 +1,234 @@
+# Custom Certificate Authority Trust
+
+This guide explains how to configure Checkmate to trust custom Certificate Authorities (CAs) when running in Docker, particularly useful for internal/private CAs like Smallstep.
+
+## Understanding Certificate Authorities
+
+Certificate Authorities (CAs) are entities that issue and manage digital certificates. While public CAs (like Let's Encrypt, DigiCert) are trusted by default in most systems, private or internal CAs (like those issued by Smallstep, internal PKI systems) require explicit trust configuration.
+
+When Checkmate monitors HTTPS endpoints with certificates from private CAs, it may show them as "DOWN" due to certificate validation failures, even if the service is actually accessible.
+
+## Node-level Trust Approach
+
+The simplest approach is to mount your custom CA certificate and configure Node.js to trust it using the `NODE_EXTRA_CA_CERTS` environment variable.
+
+### Docker Compose Configuration
+
+Add a volume mount for your CA certificate and set the environment variable:
+
+```yaml
+services:
+ server:
+ image: uptime_server:latest
+ restart: always
+ ports:
+ - "52345:52345"
+ env_file:
+ - server.env
+ environment:
+ NODE_EXTRA_CA_CERTS: /certs/custom-ca.pem
+ volumes:
+ - ./certs:/certs:ro
+ depends_on:
+ - redis
+ - mongodb
+```
+
+### Directory Structure
+
+Create a `certs` directory in your Docker Compose project root:
+
+```
+docker/dev/
+├── docker-compose.yaml
+├── certs/
+│ └── custom-ca.pem
+└── ...
+```
+
+## OS-level Trust Approach (Debian-based)
+
+For more comprehensive trust configuration, you can create a derived Dockerfile that installs your CA at the OS level.
+
+### Custom Dockerfile
+
+Create `docker/dev/server-custom-ca.Dockerfile`:
+
+```dockerfile
+FROM node:20-alpine
+
+# Install ca-certificates for Alpine
+RUN apk add --no-cache ca-certificates
+
+WORKDIR /app
+
+COPY ./server/package*.json ./
+
+RUN npm install
+
+COPY ./server/ ./
+
+# Copy your custom CA certificate
+COPY ./certs/custom-ca.crt /usr/local/share/ca-certificates/
+
+# Update CA certificates
+RUN update-ca-certificates
+
+EXPOSE 52345
+
+CMD ["node", "src/index.js"]
+```
+
+### Docker Compose Override
+
+Create `docker/dev/docker-compose.custom-ca.yaml`:
+
+```yaml
+services:
+ server:
+ build:
+ context: .
+ dockerfile: server-custom-ca.Dockerfile
+ restart: always
+ ports:
+ - "52345:52345"
+ env_file:
+ - server.env
+ depends_on:
+ - redis
+ - mongodb
+```
+
+Run with: `docker-compose -f docker-compose.yaml -f docker-compose.custom-ca.yaml up`
+
+## Alpine Linux Considerations
+
+Since Checkmate uses Alpine Linux as the base image, you need to install the `ca-certificates` package:
+
+```dockerfile
+# Install ca-certificates for Alpine
+RUN apk add --no-cache ca-certificates
+
+# Copy and update CA certificates
+COPY ./certs/custom-ca.crt /usr/local/share/ca-certificates/
+RUN update-ca-certificates
+```
+
+## Smallstep CA Configuration
+
+If you're using Smallstep as your internal CA, you can export the root CA certificate:
+
+### Export Smallstep Root CA
+
+```bash
+# Export the root CA certificate
+step certificate inspect --format pem step-ca/root_ca.crt > custom-ca.pem
+
+# Or if you have the CA URL configured
+step certificate inspect --format pem $(step path)/certs/root_ca.crt > custom-ca.pem
+```
+
+### Using the Exported Certificate
+
+1. Copy the exported `custom-ca.pem` to your `docker/dev/certs/` directory
+2. Use either the Node-level or OS-level approach above
+3. Restart your Checkmate server container
+
+## Security Considerations
+
+⚠️ **Important Security Warning**: Only trust CAs that you control or explicitly trust. Adding untrusted CAs can compromise the security of your monitoring system.
+
+- **Private CAs**: Only trust CAs from your organization's PKI infrastructure
+- **Self-signed certificates**: Consider using proper CA infrastructure instead
+- **Certificate validation**: Ensure your CA certificates are valid and not expired
+- **Access control**: Limit access to the CA certificate files in production
+
+## Troubleshooting
+
+### Verify CA Trust
+
+Test if your CA is trusted by the container:
+
+```bash
+# Enter the running container
+docker exec -it sh
+
+# Check if the CA is in the trust store
+ls -la /usr/local/share/ca-certificates/
+cat /etc/ssl/certs/ca-certificates.crt | grep -A 5 -B 5 "YOUR_CA_NAME"
+```
+
+### Common Issues
+
+1. **Permission denied**: Ensure the CA certificate file has proper read permissions
+2. **Certificate format**: Use PEM format (.pem, .crt) for best compatibility
+3. **Container restart**: Always restart the container after adding new CA certificates
+4. **Path issues**: Verify the certificate path in your volume mounts
+
+## Example: Complete Working Setup
+
+Here's a complete example for a Smallstep CA setup. This demonstrates both the baseline failure (expected) and the custom CA success:
+
+### Baseline Test (Should Fail)
+1. **Start Checkmate without custom CA trust:**
+ ```bash
+ cd docker/dev
+ docker-compose up -d
+ ```
+
+2. **Test connection to internal HTTPS endpoint:**
+ ```bash
+ # This should fail with TLS error (unknown CA)
+ docker-compose exec server node -e "
+ const https = require('https');
+ https.get('https://your-internal-site.com', res => {
+ console.log('STATUS:', res.statusCode);
+ }).on('error', e => {
+ console.error('ERR:', e.message);
+ });
+ "
+ ```
+ **Expected result**: TLS error due to unknown CA
+
+### Custom CA Test (Should Succeed)
+1. **Export Smallstep Root CA:**
+ ```bash
+ step certificate inspect --format pem step-ca/root_ca.crt > docker/dev/certs/smallstep-root-ca.pem
+ ```
+
+2. **Update docker-compose.yaml with custom CA trust:**
+ ```yaml
+ services:
+ server:
+ environment:
+ NODE_EXTRA_CA_CERTS: /certs/smallstep-root-ca.pem
+ volumes:
+ - ./certs:/certs:ro
+ ```
+
+3. **Restart with custom CA:**
+ ```bash
+ docker-compose down
+ docker-compose -f docker-compose.yaml -f docker-compose.custom-ca-example.yaml up -d
+ ```
+
+4. **Test the same connection:**
+ ```bash
+ # This should now succeed
+ docker-compose exec server node -e "
+ const https = require('https');
+ https.get('https://your-internal-site.com', res => {
+ console.log('STATUS:', res.statusCode);
+ }).on('error', e => {
+ console.error('ERR:', e.message);
+ });
+ "
+ ```
+ **Expected result**: HTTP 200 OK
+
+### Verification
+- **Baseline**: TLS failure proves default behavior (unknown CA)
+- **Custom CA**: TLS success proves custom CA trust is working
+- **Both tests must pass** to confirm the feature works correctly
+
+Your Checkmate server should now trust certificates issued by your Smallstep CA, allowing you to monitor internal HTTPS endpoints without disabling SSL validation.
diff --git a/scripts/dev/setup-custom-ca.sh b/scripts/dev/setup-custom-ca.sh
new file mode 100755
index 000000000..699f70bbd
--- /dev/null
+++ b/scripts/dev/setup-custom-ca.sh
@@ -0,0 +1,143 @@
+#!/usr/bin/env bash
+
+# Dev/Test only: Not required in production
+# This script generates test certificates for development and testing purposes
+
+set -euo pipefail
+
+# Colors for output
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m' # No Color
+
+# Configuration
+REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
+CERTS_DIR="$REPO_ROOT/docker/dev/certs"
+
+# Function to print colored output
+print_status() {
+ local status=$1
+ local message=$2
+ case $status in
+ "PASS")
+ echo -e "${GREEN}[PASS]${NC} $message"
+ ;;
+ "INFO")
+ echo -e "${BLUE}[INFO]${NC} $message"
+ ;;
+ "WARN")
+ echo -e "${YELLOW}[WARN]${NC} $message"
+ ;;
+ esac
+}
+
+# Function to check if a command exists
+command_exists() {
+ command -v "$1" >/dev/null 2>&1
+}
+
+# Function to generate certificates using mkcert
+generate_certs_mkcert() {
+ print_status "INFO" "Generating certificates using mkcert..."
+
+ # Install mkcert CA (ignore if already done)
+ mkcert -install 2>/dev/null || true
+
+ # Copy root CA
+ local ca_root
+ ca_root=$(mkcert -CAROOT)
+ cp "$ca_root/rootCA.pem" "$CERTS_DIR/custom-ca.pem"
+
+ # Generate server certificate
+ mkcert -key-file "$CERTS_DIR/host-int-key.pem" \
+ -cert-file "$CERTS_DIR/host-int-cert.pem" \
+ host.docker.internal
+
+ print_status "PASS" "Certificates generated using mkcert"
+}
+
+# Function to generate certificates using OpenSSL
+generate_certs_openssl() {
+ print_status "INFO" "Generating certificates using OpenSSL..."
+
+ # Create CA private key
+ openssl genrsa -out "$CERTS_DIR/ca.key" 2048
+
+ # Create CA certificate
+ openssl req -new -x509 -days 365 -key "$CERTS_DIR/ca.key" \
+ -out "$CERTS_DIR/custom-ca.pem" \
+ -subj "/C=US/ST=Test/L=Test/O=Test CA/CN=Test Root CA"
+
+ # Create server private key
+ openssl genrsa -out "$CERTS_DIR/host-int-key.pem" 2048
+
+ # Create server certificate signing request
+ openssl req -new -key "$CERTS_DIR/host-int-key.pem" \
+ -out "$CERTS_DIR/host-int-cert.csr" \
+ -subj "/C=US/ST=Test/L=Test/O=Test/CN=host.docker.internal"
+
+ # Create extfile for SAN
+ cat > "$CERTS_DIR/san.ext" << EOF
+subjectAltName=DNS:host.docker.internal,IP:127.0.0.1
+EOF
+
+ # Sign server certificate with CA
+ openssl x509 -req -days 365 \
+ -in "$CERTS_DIR/host-int-cert.csr" \
+ -CA "$CERTS_DIR/custom-ca.pem" \
+ -CAkey "$CERTS_DIR/ca.key" \
+ -CAcreateserial \
+ -out "$CERTS_DIR/host-int-cert.pem" \
+ -extfile "$CERTS_DIR/san.ext"
+
+ # Clean up temporary files
+ rm -f "$CERTS_DIR/ca.key" "$CERTS_DIR/host-int-cert.csr" "$CERTS_DIR/san.ext" "$CERTS_DIR/.srl"
+
+ print_status "PASS" "Certificates generated using OpenSSL"
+}
+
+# Main function
+main() {
+ print_status "INFO" "Setting up custom CA certificates for Checkmate testing"
+ echo "================================================================"
+
+ # Create certs directory if missing
+ print_status "INFO" "Creating certificates directory..."
+ mkdir -p "$CERTS_DIR"
+
+ # Generate certificates
+ if command_exists mkcert; then
+ generate_certs_mkcert
+ else
+ print_status "WARN" "mkcert not found, falling back to OpenSSL"
+ generate_certs_openssl
+ fi
+
+ # Create duplicate for compatibility with existing overrides
+ print_status "INFO" "Creating duplicate CA file for compatibility..."
+ cp "$CERTS_DIR/custom-ca.pem" "$CERTS_DIR/smallstep-root-ca.pem"
+
+ # Verify certificates exist
+ if [ ! -f "$CERTS_DIR/custom-ca.pem" ] || [ ! -f "$CERTS_DIR/host-int-cert.pem" ] || [ ! -f "$CERTS_DIR/host-int-key.pem" ]; then
+ echo "Error: Failed to generate required certificates"
+ exit 1
+ fi
+
+ # Print summary
+ echo ""
+ print_status "PASS" "All required certificates generated successfully"
+ echo ""
+ echo "Certificate summary:"
+ echo "===================="
+ ls -l "$CERTS_DIR"
+ echo ""
+ echo "CA certificate preview:"
+ echo "======================"
+ head -3 "$CERTS_DIR/custom-ca.pem"
+ echo ""
+ print_status "INFO" "Certificates are ready for use with Checkmate custom CA trust"
+}
+
+# Run main function
+main "$@"
diff --git a/scripts/test-custom-ca.sh b/scripts/test-custom-ca.sh
new file mode 100755
index 000000000..bdf5b62c7
--- /dev/null
+++ b/scripts/test-custom-ca.sh
@@ -0,0 +1,454 @@
+#!/usr/bin/env bash
+
+# Dev/Test only: Not required in production
+# This script tests the custom CA trust functionality in development environment
+
+set -euo pipefail
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m' # No Color
+
+# Configuration
+ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+DEV_DIR="${ROOT_DIR}/docker/dev"
+CERT_DIR="${DEV_DIR}/certs"
+NGINX_DIR="${DEV_DIR}/nginx-test"
+COMPOSE_BASELINE="${DEV_DIR}/docker-compose.yaml"
+COMPOSE_CUSTOM_CA="${DEV_DIR}/docker-compose.custom-ca-example.yaml"
+COMPOSE_NGINX_TEST="${NGINX_DIR}/docker-compose.nginx-test.yaml"
+
+# Test configuration
+NGINX_PORT=8443
+CHECKMATE_PORT=52345
+HEALTH_ENDPOINT="http://localhost:$CHECKMATE_PORT/api/v1/health"
+TEST_URL="https://host.docker.internal:$NGINX_PORT"
+MAX_WAIT=60
+WAIT_INTERVAL=2
+
+# Global flag for cleanup behavior
+CLEAN_CERTS=false
+
+# Function to print colored output
+print_status() {
+ local status=$1
+ local message=$2
+ case $status in
+ "PASS")
+ echo -e "${GREEN}[PASS]${NC} $message"
+ ;;
+ "FAIL")
+ echo -e "${RED}[FAIL]${NC} $message"
+ ;;
+ "INFO")
+ echo -e "${BLUE}[INFO]${NC} $message"
+ ;;
+ "WARN")
+ echo -e "${YELLOW}[WARN]${NC} $message"
+ ;;
+ esac
+}
+
+# Function to check if a command exists
+command_exists() {
+ command -v "$1" >/dev/null 2>&1
+}
+
+# Function to check if certificates exist and are valid
+check_certificates() {
+ local ca_file="${CERT_DIR}/custom-ca.pem"
+ local cert_file="${CERT_DIR}/host-int-cert.pem"
+ local key_file="${CERT_DIR}/host-int-key.pem"
+
+ if [ ! -f "$ca_file" ] || [ ! -f "$cert_file" ] || [ ! -f "$key_file" ]; then
+ return 1
+ fi
+
+ # Check if files are not empty
+ if [ ! -s "$ca_file" ] || [ ! -s "$cert_file" ] || [ ! -s "$key_file" ]; then
+ return 1
+ fi
+
+ # Check if CA file starts with BEGIN CERTIFICATE
+ if ! head -1 "$ca_file" | grep -q "BEGIN CERTIFICATE"; then
+ return 1
+ fi
+
+ return 0
+}
+
+# Function to wait for service to be ready
+wait_for_service() {
+ local url=$1
+ local service_name=$2
+ local elapsed=0
+
+ print_status "INFO" "Waiting for $service_name to be ready..."
+
+ while [ $elapsed -lt $MAX_WAIT ]; do
+ if curl -s -f "$url" >/dev/null 2>&1; then
+ print_status "PASS" "$service_name is ready after ${elapsed}s"
+ return 0
+ fi
+
+ sleep $WAIT_INTERVAL
+ elapsed=$((elapsed + WAIT_INTERVAL))
+ echo -n "."
+ done
+
+ print_status "FAIL" "$service_name failed to start within ${MAX_WAIT}s"
+ return 1
+}
+
+# Function to get container name by image
+get_container_name() {
+ local image_name=$1
+ docker ps --filter "ancestor=$image_name" --format "{{.Names}}" | head -n1
+}
+
+# Function to run Node.js probe test
+run_probe_test() {
+ local container_name=$1
+ local test_name=$2
+ local expected_exit_code=$3
+
+ print_status "INFO" "Running $test_name probe test..."
+
+ local probe_script="
+const https = require('https');
+https.get('https://host.docker.internal:8443', res => {
+ console.log('STATUS', res.statusCode);
+ process.exit(res.statusCode===200?0:1);
+}).on('error', e => {
+ console.error('ERR', e.code||e.message);
+ process.exit(1);
+});
+"
+
+ local exit_code
+ if docker exec -i "$container_name" node -e "$probe_script" 2>/dev/null; then
+ exit_code=$?
+ else
+ exit_code=$?
+ fi
+
+ if [ $exit_code -eq $expected_exit_code ]; then
+ print_status "PASS" "$test_name probe test completed with expected exit code $expected_exit_code"
+ return 0
+ else
+ print_status "FAIL" "$test_name probe test failed with exit code $exit_code (expected $expected_exit_code)"
+ return 1
+ fi
+}
+
+# Function to setup certificates if needed
+setup_certificates_if_needed() {
+ if check_certificates; then
+ print_status "INFO" "Certificates already exist and are valid, skipping generation"
+ return 0
+ fi
+
+ print_status "INFO" "Certificates missing or invalid, generating new ones..."
+ if [ -x "${ROOT_DIR}/scripts/dev/setup-custom-ca.sh" ]; then
+ "${ROOT_DIR}/scripts/dev/setup-custom-ca.sh"
+ else
+ print_status "FAIL" "Certificate setup script not found at scripts/dev/setup-custom-ca.sh"
+ return 1
+ fi
+}
+
+# Function to create nginx test configuration
+create_nginx_test_config() {
+ print_status "INFO" "Setting up nginx test configuration..."
+
+ mkdir -p "$NGINX_DIR"
+
+ # Create nginx.conf if it doesn't exist
+ if [ ! -f "${NGINX_DIR}/nginx.conf" ]; then
+ cat > "${NGINX_DIR}/nginx.conf" << 'EOF'
+events {}
+http {
+ server {
+ listen 443 ssl;
+ ssl_certificate /etc/nginx/certs/server.crt;
+ ssl_certificate_key /etc/nginx/certs/server.key;
+ location / {
+ return 200 "hello from tls\n";
+ }
+ }
+}
+EOF
+ fi
+
+ # Create docker-compose.nginx-test.yaml if it doesn't exist
+ if [ ! -f "$COMPOSE_NGINX_TEST" ]; then
+ cat > "$COMPOSE_NGINX_TEST" << EOF
+services:
+ nginx-test:
+ image: nginx:alpine
+ ports:
+ - "$NGINX_PORT:443"
+ volumes:
+ - ../certs/host-int-cert.pem:/etc/nginx/certs/server.crt:ro
+ - ../certs/host-int-key.pem:/etc/nginx/certs/server.key:ro
+ - ./nginx.conf:/etc/nginx/nginx.conf:ro
+ restart: unless-stopped
+EOF
+ fi
+
+ print_status "PASS" "Nginx test configuration created"
+}
+
+# Function to start nginx test service
+start_nginx_test() {
+ print_status "INFO" "Starting nginx test service..."
+
+ cd "$NGINX_DIR"
+ docker-compose -f docker-compose.nginx-test.yaml up -d
+
+ # Wait for nginx to be ready
+ local elapsed=0
+ while [ $elapsed -lt $MAX_WAIT ]; do
+ if curl -s -f -k "https://localhost:$NGINX_PORT" >/dev/null 2>&1; then
+ print_status "PASS" "Nginx test service is ready"
+ cd "$ROOT_DIR"
+ return 0
+ fi
+
+ sleep $WAIT_INTERVAL
+ elapsed=$((elapsed + WAIT_INTERVAL))
+ echo -n "."
+ done
+
+ print_status "FAIL" "Nginx test service failed to start"
+ cd "$ROOT_DIR"
+ return 1
+}
+
+# Function to stop nginx test service
+stop_nginx_test() {
+ print_status "INFO" "Stopping nginx test service..."
+ cd "$NGINX_DIR"
+ docker-compose -f docker-compose.nginx-test.yaml down 2>/dev/null || true
+ cd "$ROOT_DIR"
+}
+
+# Function to run baseline test
+run_baseline_test() {
+ print_status "INFO" "Running baseline test (should fail due to unknown CA)..."
+
+ # Start baseline Checkmate
+ cd "$DEV_DIR"
+ docker-compose -f docker-compose.yaml up -d --build
+
+ # Wait for Checkmate to be ready
+ if ! wait_for_service "$HEALTH_ENDPOINT" "Checkmate baseline"; then
+ cd "$ROOT_DIR"
+ return 1
+ fi
+
+ # Get container name
+ local container_name
+ container_name=$(get_container_name "uptime_server")
+ if [ -z "$container_name" ]; then
+ print_status "FAIL" "Could not find Checkmate server container"
+ cd "$ROOT_DIR"
+ return 1
+ fi
+
+ # Run probe test (should fail)
+ if run_probe_test "$container_name" "baseline" 1; then
+ print_status "PASS" "Baseline test completed - TLS failure as expected"
+ cd "$ROOT_DIR"
+ return 0
+ else
+ print_status "FAIL" "Baseline test failed - unexpected behavior"
+ cd "$ROOT_DIR"
+ return 1
+ fi
+}
+
+# Function to run custom CA test
+run_custom_ca_test() {
+ print_status "INFO" "Running custom CA test (should succeed)..."
+
+ # Stop baseline Checkmate
+ cd "$DEV_DIR"
+ docker-compose -f docker-compose.yaml down
+
+ # Start Checkmate with custom CA
+ docker-compose -f docker-compose.yaml -f docker-compose.custom-ca-example.yaml up -d --build
+
+ # Wait for Checkmate to be ready
+ if ! wait_for_service "$HEALTH_ENDPOINT" "Checkmate custom CA"; then
+ cd "$ROOT_DIR"
+ return 1
+ fi
+
+ # Get container name
+ local container_name
+ container_name=$(get_container_name "uptime_server")
+ if [ -z "$container_name" ]; then
+ print_status "FAIL" "Could not find Checkmate server container"
+ cd "$ROOT_DIR"
+ return 1
+ fi
+
+ # Run probe test (should succeed)
+ if run_probe_test "$container_name" "custom CA" 0; then
+ print_status "PASS" "Custom CA test completed - TLS success as expected"
+ cd "$ROOT_DIR"
+ return 0
+ else
+ print_status "FAIL" "Custom CA test failed - unexpected behavior"
+ cd "$ROOT_DIR"
+ return 1
+ fi
+}
+
+# Function to cleanup everything
+cleanup() {
+ print_status "INFO" "Cleaning up test environment..."
+
+ # Stop all services
+ cd "$DEV_DIR"
+ docker-compose -f docker-compose.yaml down 2>/dev/null || true
+ docker-compose -f docker-compose.yaml -f docker-compose.custom-ca-example.yaml down 2>/dev/null || true
+
+ stop_nginx_test
+
+ # Only remove certificates if --clean was specified
+ if [ "$CLEAN_CERTS" = true ]; then
+ print_status "INFO" "Removing certificates as requested with --clean"
+ rm -rf "${CERT_DIR:?}"/*
+ else
+ print_status "INFO" "Preserving certificates for future test runs"
+ fi
+
+ cd "$ROOT_DIR"
+ print_status "PASS" "Cleanup completed"
+}
+
+# Function to show usage
+show_usage() {
+ echo "Usage: $0 [OPTIONS]"
+ echo ""
+ echo "Options:"
+ echo " --clean Clean up all test containers and certificates"
+ echo " --help Show this help message"
+ echo ""
+ echo "This script tests the custom CA trust functionality in Checkmate."
+}
+
+# Main function
+main() {
+ local clean_only=false
+
+ # Parse command line arguments
+ while [[ $# -gt 0 ]]; do
+ case $1 in
+ --clean)
+ clean_only=true
+ CLEAN_CERTS=true
+ shift
+ ;;
+ --help)
+ show_usage
+ exit 0
+ ;;
+ *)
+ echo "Unknown option: $1"
+ show_usage
+ exit 1
+ ;;
+ esac
+ done
+
+ print_status "INFO" "Starting Checkmate Custom CA Trust Test"
+ echo "=================================================="
+
+ if [ "$clean_only" = true ]; then
+ cleanup
+ exit 0
+ fi
+
+ # Check prerequisites
+ if ! command_exists docker; then
+ print_status "FAIL" "Docker is not installed or not in PATH"
+ exit 1
+ fi
+
+ if ! command_exists docker-compose; then
+ print_status "FAIL" "docker-compose is not installed or not in PATH"
+ exit 1
+ fi
+
+ # Check if required files exist
+ if [ ! -f "$COMPOSE_BASELINE" ]; then
+ print_status "FAIL" "Baseline docker-compose.yaml not found at $COMPOSE_BASELINE"
+ exit 1
+ fi
+
+ if [ ! -f "$COMPOSE_CUSTOM_CA" ]; then
+ print_status "FAIL" "Custom CA docker-compose override not found at $COMPOSE_CUSTOM_CA"
+ exit 1
+ fi
+
+ # Setup test environment
+ setup_certificates_if_needed
+ create_nginx_test_config
+ start_nginx_test
+
+ # Run tests
+ local baseline_result=false
+ local custom_ca_result=false
+
+ if run_baseline_test; then
+ baseline_result=true
+ fi
+
+ if run_custom_ca_test; then
+ custom_ca_result=true
+ fi
+
+ # Print summary
+ echo ""
+ echo "=================================================="
+ print_status "INFO" "Test Summary"
+ echo "=================================================="
+
+ if [ "$baseline_result" = true ]; then
+ print_status "PASS" "Baseline: TLS failure as expected"
+ else
+ print_status "FAIL" "Baseline: Unexpected behavior"
+ fi
+
+ if [ "$custom_ca_result" = true ]; then
+ print_status "PASS" "Custom CA: TLS success (STATUS 200)"
+ else
+ print_status "FAIL" "Custom CA: Unexpected behavior"
+ fi
+
+ if [ "$baseline_result" = true ] && [ "$custom_ca_result" = true ]; then
+ echo ""
+ print_status "PASS" "All tests passed! Custom CA trust is working correctly."
+ echo ""
+ print_status "INFO" "To clean up, run: $0 --clean"
+ exit 0
+ else
+ echo ""
+ print_status "FAIL" "Some tests failed. Custom CA trust may not be working correctly."
+ echo ""
+ print_status "INFO" "To clean up, run: $0 --clean"
+ exit 1
+ fi
+}
+
+# Trap cleanup on script exit
+trap cleanup EXIT
+
+# Run main function
+main "$@"
diff --git a/server/package-lock.json b/server/package-lock.json
index 3084a5361..3e1130dc4 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -21,6 +21,7 @@
"express": "^4.19.2",
"express-rate-limit": "8.0.1",
"gamedig": "^5.3.1",
+ "got": "14.4.7",
"handlebars": "^4.7.8",
"helmet": "^8.0.0",
"ioredis": "^5.4.2",
@@ -38,7 +39,7 @@
"ping": "0.4.4",
"sharp": "0.33.5",
"ssl-checker": "2.0.10",
- "super-simple-scheduler": "1.3.0",
+ "super-simple-scheduler": "1.4.1",
"swagger-ui-express": "5.0.1",
"winston": "^3.13.0"
},
@@ -93,9 +94,9 @@
}
},
"node_modules/@babel/runtime": {
- "version": "7.27.6",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
- "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz",
+ "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -127,9 +128,9 @@
}
},
"node_modules/@csstools/color-helpers": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz",
- "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
+ "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
"funding": [
{
"type": "github",
@@ -169,9 +170,9 @@
}
},
"node_modules/@csstools/css-color-parser": {
- "version": "3.0.10",
- "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz",
- "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
+ "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
"funding": [
{
"type": "github",
@@ -184,7 +185,7 @@
],
"license": "MIT",
"dependencies": {
- "@csstools/color-helpers": "^5.0.2",
+ "@csstools/color-helpers": "^5.1.0",
"@csstools/css-calc": "^2.1.4"
},
"engines": {
@@ -248,9 +249,9 @@
}
},
"node_modules/@emnapi/runtime": {
- "version": "1.4.5",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz",
- "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==",
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz",
+ "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==",
"license": "MIT",
"optional": true,
"dependencies": {
@@ -258,9 +259,9 @@
}
},
"node_modules/@eslint-community/eslint-utils": {
- "version": "4.7.0",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
- "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
+ "version": "4.8.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.8.0.tgz",
+ "integrity": "sha512-MJQFqrZgcW0UNYLGOuQpey/oTN59vyWwplvCGZztn1cKz9agZPPYpJB7h2OMmuu7VLqkvEjN8feFZJmxNF9D+Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -315,9 +316,9 @@
}
},
"node_modules/@eslint/config-helpers": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz",
- "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==",
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz",
+ "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -325,9 +326,9 @@
}
},
"node_modules/@eslint/core": {
- "version": "0.15.1",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz",
- "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
+ "version": "0.15.2",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz",
+ "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -375,9 +376,9 @@
}
},
"node_modules/@eslint/js": {
- "version": "9.31.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz",
- "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==",
+ "version": "9.34.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz",
+ "integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -398,13 +399,13 @@
}
},
"node_modules/@eslint/plugin-kit": {
- "version": "0.3.4",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz",
- "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==",
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz",
+ "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/core": "^0.15.1",
+ "@eslint/core": "^0.15.2",
"levn": "^0.4.1"
},
"engines": {
@@ -468,33 +469,19 @@
}
},
"node_modules/@humanfs/node": {
- "version": "0.16.6",
- "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
- "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@humanfs/core": "^0.19.1",
- "@humanwhocodes/retry": "^0.3.0"
+ "@humanwhocodes/retry": "^0.4.0"
},
"engines": {
"node": ">=18.18.0"
}
},
- "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
- "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
"node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
@@ -885,9 +872,9 @@
}
},
"node_modules/@ioredis/commands": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
- "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==",
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.3.1.tgz",
+ "integrity": "sha512-bYtU8avhGIcje3IhvF9aSjsa5URMZBHnwKtOvXsT4sfYy9gppW11gLPT/9oNqlJZD47yPKveQFTAFWpHjKvUoQ==",
"license": "MIT"
},
"node_modules/@isaacs/cliui": {
@@ -928,16 +915,16 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.4",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
- "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.29",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
- "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
+ "version": "0.3.30",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz",
+ "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1137,6 +1124,12 @@
"hasInstallScript": true,
"license": "Apache-2.0"
},
+ "node_modules/@sec-ant/readable-stream": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
+ "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==",
+ "license": "MIT"
+ },
"node_modules/@sideway/address": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
@@ -1159,12 +1152,12 @@
"license": "BSD-3-Clause"
},
"node_modules/@sindresorhus/is": {
- "version": "5.6.0",
- "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz",
- "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==",
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.0.2.tgz",
+ "integrity": "sha512-d9xRovfKNz1SKieM0qJdO+PQonjnnIfSNWfHYnBSJ9hkjm0ZPw6HlxscDXYstp3z+7V2GOFHc+J0CYrYTjqCJw==",
"license": "MIT",
"engines": {
- "node": ">=14.16"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sindresorhus/is?sponsor=1"
@@ -1191,14 +1184,13 @@
}
},
"node_modules/@sinonjs/samsam": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz",
- "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==",
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz",
+ "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"@sinonjs/commons": "^3.0.1",
- "lodash.get": "^4.4.2",
"type-detect": "^4.1.0"
}
},
@@ -1231,6 +1223,17 @@
"node": ">=14.16"
}
},
+ "node_modules/@trysound/sax": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
+ "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
+ "license": "ISC",
+ "optional": true,
+ "peer": true,
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -1259,14 +1262,20 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "24.1.0",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz",
- "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==",
+ "version": "24.3.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz",
+ "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==",
"license": "MIT",
"dependencies": {
- "undici-types": "~7.8.0"
+ "undici-types": "~7.10.0"
}
},
+ "node_modules/@types/relateurl": {
+ "version": "0.2.33",
+ "resolved": "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.33.tgz",
+ "integrity": "sha512-bTQCKsVbIdzLqZhLkF5fcJQreE4y1ro4DIyVrlDNSCJRRwHhB8Z+4zXXa8jN6eDvc2HbRsEYgbvrnGvi54EpSw==",
+ "license": "MIT"
+ },
"node_modules/@types/triple-beam": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
@@ -1376,9 +1385,9 @@
}
},
"node_modules/ansi-regex": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
- "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz",
+ "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -1496,12 +1505,6 @@
"readable-stream": "~1.0.2"
}
},
- "node_modules/barse/node_modules/isarray": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
- "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
- "license": "MIT"
- },
"node_modules/barse/node_modules/readable-stream": {
"version": "1.0.34",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
@@ -1657,9 +1660,9 @@
"license": "ISC"
},
"node_modules/browserslist": {
- "version": "4.25.1",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
- "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==",
+ "version": "4.25.4",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.4.tgz",
+ "integrity": "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==",
"funding": [
{
"type": "opencollective",
@@ -1676,8 +1679,8 @@
],
"license": "MIT",
"dependencies": {
- "caniuse-lite": "^1.0.30001726",
- "electron-to-chromium": "^1.5.173",
+ "caniuse-lite": "^1.0.30001737",
+ "electron-to-chromium": "^1.5.211",
"node-releases": "^2.0.19",
"update-browserslist-db": "^1.1.3"
},
@@ -1821,21 +1824,21 @@
}
},
"node_modules/cacheable-request": {
- "version": "10.2.14",
- "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz",
- "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==",
+ "version": "12.0.1",
+ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-12.0.1.tgz",
+ "integrity": "sha512-Yo9wGIQUaAfIbk+qY0X4cDQgCosecfBe3V9NSyeY4qPC2SAkbCS4Xj79VP8WOzitpJUZKc/wsRCYF5ariDIwkg==",
"license": "MIT",
"dependencies": {
- "@types/http-cache-semantics": "^4.0.2",
- "get-stream": "^6.0.1",
+ "@types/http-cache-semantics": "^4.0.4",
+ "get-stream": "^9.0.1",
"http-cache-semantics": "^4.1.1",
- "keyv": "^4.5.3",
+ "keyv": "^4.5.4",
"mimic-response": "^4.0.0",
- "normalize-url": "^8.0.0",
+ "normalize-url": "^8.0.1",
"responselike": "^3.0.0"
},
"engines": {
- "node": ">=14.16"
+ "node": ">=18"
}
},
"node_modules/call-bind-apply-helpers": {
@@ -1902,9 +1905,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001727",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
- "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==",
+ "version": "1.0.30001739",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz",
+ "integrity": "sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==",
"funding": [
{
"type": "opencollective",
@@ -2296,6 +2299,12 @@
"typedarray": "^0.0.6"
}
},
+ "node_modules/concat-stream/node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "license": "MIT"
+ },
"node_modules/concat-stream/node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
@@ -2355,9 +2364,9 @@
"license": "MIT"
},
"node_modules/cookie": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
- "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
@@ -2376,15 +2385,6 @@
"node": ">= 0.8.0"
}
},
- "node_modules/cookie-parser/node_modules/cookie": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
- "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@@ -2504,6 +2504,21 @@
"url": "https://github.com/sponsors/fb55"
}
},
+ "node_modules/css-tree": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
+ "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "mdn-data": "2.0.30",
+ "source-map-js": "^1.0.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
"node_modules/css-what": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
@@ -2529,12 +2544,12 @@
}
},
"node_modules/cssnano": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.0.tgz",
- "integrity": "sha512-Pu3rlKkd0ZtlCUzBrKL1Z4YmhKppjC1H9jo7u1o4qaKqyhvixFgu5qLyNIAOjSTg9DjVPtUqdROq2EfpVMEe+w==",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.1.tgz",
+ "integrity": "sha512-fm4D8ti0dQmFPeF8DXSAA//btEmqCOgAc/9Oa3C1LW94h5usNrJEfrON7b4FkPZgnDEn6OUs5NdxiJZmAtGOpQ==",
"license": "MIT",
"dependencies": {
- "cssnano-preset-default": "^7.0.8",
+ "cssnano-preset-default": "^7.0.9",
"lilconfig": "^3.1.3"
},
"engines": {
@@ -2549,9 +2564,9 @@
}
},
"node_modules/cssnano-preset-default": {
- "version": "7.0.8",
- "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.8.tgz",
- "integrity": "sha512-d+3R2qwrUV3g4LEMOjnndognKirBZISylDZAF/TPeCWVjEwlXS2e4eN4ICkoobRe7pD3H6lltinKVyS1AJhdjQ==",
+ "version": "7.0.9",
+ "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.9.tgz",
+ "integrity": "sha512-tCD6AAFgYBOVpMBX41KjbvRh9c2uUjLXRyV7KHSIrwHiq5Z9o0TFfUCoM3TwVrRsRteN3sVXGNvjVNxYzkpTsA==",
"license": "MIT",
"dependencies": {
"browserslist": "^4.25.1",
@@ -2559,7 +2574,7 @@
"cssnano-utils": "^5.0.1",
"postcss-calc": "^10.1.1",
"postcss-colormin": "^7.0.4",
- "postcss-convert-values": "^7.0.6",
+ "postcss-convert-values": "^7.0.7",
"postcss-discard-comments": "^7.0.4",
"postcss-discard-duplicates": "^7.0.2",
"postcss-discard-empty": "^7.0.1",
@@ -3005,9 +3020,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
- "version": "1.5.190",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.190.tgz",
- "integrity": "sha512-k4McmnB2091YIsdCgkS0fMVMPOJgxl93ltFzaryXqwip1AaxeDqKCGLxkXODDA5Ab/D+tV5EL5+aTx76RvLRxw==",
+ "version": "1.5.213",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.213.tgz",
+ "integrity": "sha512-xr9eRzSLNa4neDO0xVFrkXu3vyIzG4Ay08dApecw42Z1NbmCt+keEpXdvlYGVe0wtvY5dhW0Ay0lY0IOfsCg0Q==",
"license": "ISC"
},
"node_modules/emitter-component": {
@@ -3189,20 +3204,20 @@
}
},
"node_modules/eslint": {
- "version": "9.31.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz",
- "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==",
+ "version": "9.34.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.34.0.tgz",
+ "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.21.0",
- "@eslint/config-helpers": "^0.3.0",
- "@eslint/core": "^0.15.0",
+ "@eslint/config-helpers": "^0.3.1",
+ "@eslint/core": "^0.15.2",
"@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.31.0",
- "@eslint/plugin-kit": "^0.3.1",
+ "@eslint/js": "9.34.0",
+ "@eslint/plugin-kit": "^0.3.5",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
@@ -3283,6 +3298,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/eslint-plugin-mocha/node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/eslint-scope": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
@@ -3496,6 +3524,15 @@
"express": ">= 4.11"
}
},
+ "node_modules/express/node_modules/cookie": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
+ "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/express/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -3698,9 +3735,9 @@
"license": "MIT"
},
"node_modules/follow-redirects": {
- "version": "1.15.9",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
- "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
@@ -3750,12 +3787,12 @@
}
},
"node_modules/form-data-encoder": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz",
- "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.1.0.tgz",
+ "integrity": "sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw==",
"license": "MIT",
"engines": {
- "node": ">= 14.17"
+ "node": ">= 18"
}
},
"node_modules/formdata-polyfill": {
@@ -3840,6 +3877,82 @@
"node": ">=16.20.0"
}
},
+ "node_modules/gamedig/node_modules/@sindresorhus/is": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz",
+ "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/is?sponsor=1"
+ }
+ },
+ "node_modules/gamedig/node_modules/cacheable-request": {
+ "version": "10.2.14",
+ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz",
+ "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-cache-semantics": "^4.0.2",
+ "get-stream": "^6.0.1",
+ "http-cache-semantics": "^4.1.1",
+ "keyv": "^4.5.3",
+ "mimic-response": "^4.0.0",
+ "normalize-url": "^8.0.0",
+ "responselike": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14.16"
+ }
+ },
+ "node_modules/gamedig/node_modules/form-data-encoder": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz",
+ "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.17"
+ }
+ },
+ "node_modules/gamedig/node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gamedig/node_modules/got": {
+ "version": "13.0.0",
+ "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz",
+ "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==",
+ "license": "MIT",
+ "dependencies": {
+ "@sindresorhus/is": "^5.2.0",
+ "@szmarczak/http-timer": "^5.0.1",
+ "cacheable-lookup": "^7.0.0",
+ "cacheable-request": "^10.2.8",
+ "decompress-response": "^6.0.0",
+ "form-data-encoder": "^2.1.2",
+ "get-stream": "^6.0.1",
+ "http2-wrapper": "^2.1.10",
+ "lowercase-keys": "^3.0.0",
+ "p-cancelable": "^3.0.0",
+ "responselike": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/got?sponsor=1"
+ }
+ },
"node_modules/gamedig/node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -3852,6 +3965,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/gamedig/node_modules/p-cancelable": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz",
+ "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20"
+ }
+ },
"node_modules/gaxios": {
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz",
@@ -3868,6 +3990,18 @@
"node": ">=14"
}
},
+ "node_modules/gaxios/node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/gbxremote": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/gbxremote/-/gbxremote-0.2.1.tgz",
@@ -3930,12 +4064,16 @@
}
},
"node_modules/get-stream": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
- "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz",
+ "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==",
"license": "MIT",
+ "dependencies": {
+ "@sec-ant/readable-stream": "^0.4.1",
+ "is-stream": "^4.0.1"
+ },
"engines": {
- "node": ">=10"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -4024,25 +4162,25 @@
}
},
"node_modules/got": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz",
- "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==",
+ "version": "14.4.7",
+ "resolved": "https://registry.npmjs.org/got/-/got-14.4.7.tgz",
+ "integrity": "sha512-DI8zV1231tqiGzOiOzQWDhsBmncFW7oQDH6Zgy6pDPrqJuVZMtoSgPLLsBZQj8Jg4JFfwoOsDA8NGtLQLnIx2g==",
"license": "MIT",
"dependencies": {
- "@sindresorhus/is": "^5.2.0",
+ "@sindresorhus/is": "^7.0.1",
"@szmarczak/http-timer": "^5.0.1",
"cacheable-lookup": "^7.0.0",
- "cacheable-request": "^10.2.8",
+ "cacheable-request": "^12.0.1",
"decompress-response": "^6.0.0",
- "form-data-encoder": "^2.1.2",
- "get-stream": "^6.0.1",
- "http2-wrapper": "^2.1.10",
+ "form-data-encoder": "^4.0.2",
+ "http2-wrapper": "^2.2.1",
"lowercase-keys": "^3.0.0",
- "p-cancelable": "^3.0.0",
- "responselike": "^3.0.0"
+ "p-cancelable": "^4.0.1",
+ "responselike": "^3.0.0",
+ "type-fest": "^4.26.1"
},
"engines": {
- "node": ">=16"
+ "node": ">=20"
},
"funding": {
"url": "https://github.com/sindresorhus/got?sponsor=1"
@@ -4157,11 +4295,12 @@
"license": "MIT"
},
"node_modules/htmlnano": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.2.tgz",
- "integrity": "sha512-8Fst+0bhAfU362S6oHVb4wtJj/UYEFr0qiCLAEi8zioqmp1JYBQx5crZAADlFVX0Ly/6s/IQz6G7PL9/hgoJaQ==",
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.3.tgz",
+ "integrity": "sha512-mzTUHhxdfDw80X36rv5qFvjvor7r0uCwXI5mzo8CW61tImbe5Jpvl3JzPAetVh54wUYVuoa8x3qw8LFn8B3gHQ==",
"license": "MIT",
"dependencies": {
+ "@types/relateurl": "^0.2.33",
"cosmiconfig": "^9.0.0",
"posthtml": "^0.16.5"
},
@@ -4373,12 +4512,12 @@
"license": "ISC"
},
"node_modules/ioredis": {
- "version": "5.6.1",
- "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz",
- "integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==",
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.7.0.tgz",
+ "integrity": "sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g==",
"license": "MIT",
"dependencies": {
- "@ioredis/commands": "^1.1.1",
+ "@ioredis/commands": "^1.3.0",
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.4",
"denque": "^2.1.0",
@@ -4494,12 +4633,12 @@
"license": "MIT"
},
"node_modules/is-stream": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
- "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
+ "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==",
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -4519,9 +4658,9 @@
}
},
"node_modules/isarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
"license": "MIT"
},
"node_modules/isexe": {
@@ -4597,9 +4736,9 @@
}
},
"node_modules/istanbul-reports": {
- "version": "3.1.7",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
- "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
+ "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -4946,14 +5085,6 @@
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
"license": "MIT"
},
- "node_modules/lodash.get": {
- "version": "4.4.2",
- "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
- "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
- "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.",
- "dev": true,
- "license": "MIT"
- },
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
@@ -5062,9 +5193,9 @@
"license": "Apache-2.0"
},
"node_modules/loupe": {
- "version": "3.1.4",
- "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz",
- "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==",
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
+ "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
"dev": true,
"license": "MIT"
},
@@ -5131,6 +5262,14 @@
"node": ">= 0.4"
}
},
+ "node_modules/mdn-data": {
+ "version": "2.0.30",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
+ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
+ "license": "CC0-1.0",
+ "optional": true,
+ "peer": true
+ },
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -5799,9 +5938,9 @@
}
},
"node_modules/mongodb": {
- "version": "6.18.0",
- "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.18.0.tgz",
- "integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==",
+ "version": "6.19.0",
+ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.19.0.tgz",
+ "integrity": "sha512-H3GtYujOJdeKIMLKBT9PwlDhGrQfplABNF1G904w6r5ZXKWyv77aB0X9B+rhmaAwjtllHzaEkvi9mkGVZxs2Bw==",
"license": "Apache-2.0",
"dependencies": {
"@mongodb-js/saslprep": "^1.1.9",
@@ -5817,7 +5956,7 @@
"gcp-metadata": "^5.2.0",
"kerberos": "^2.0.1",
"mongodb-client-encryption": ">=6.0.0 <7",
- "snappy": "^7.2.2",
+ "snappy": "^7.3.2",
"socks": "^2.7.1"
},
"peerDependenciesMeta": {
@@ -5855,14 +5994,14 @@
}
},
"node_modules/mongoose": {
- "version": "8.16.4",
- "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.4.tgz",
- "integrity": "sha512-jslgdQ8pY2vcNSKPv3Dbi5ogo/NT8zcvf6kPDyD8Sdsjsa1at3AFAF0F5PT+jySPGSPbvlNaQ49nT9h+Kx2UDA==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.18.0.tgz",
+ "integrity": "sha512-3TixPihQKBdyaYDeJqRjzgb86KbilEH07JmzV8SoSjgoskNTpa6oTBmDxeoF9p8YnWQoz7shnCyPkSV/48y3yw==",
"license": "MIT",
"dependencies": {
"bson": "^6.10.4",
"kareem": "2.6.3",
- "mongodb": "~6.17.0",
+ "mongodb": "~6.18.0",
"mpath": "0.9.0",
"mquery": "5.0.0",
"ms": "2.1.3",
@@ -5877,9 +6016,9 @@
}
},
"node_modules/mongoose/node_modules/mongodb": {
- "version": "6.17.0",
- "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz",
- "integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==",
+ "version": "6.18.0",
+ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.18.0.tgz",
+ "integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==",
"license": "Apache-2.0",
"dependencies": {
"@mongodb-js/saslprep": "^1.1.9",
@@ -6067,13 +6206,14 @@
}
},
"node_modules/nise/node_modules/path-to-regexp": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
- "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
+ "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=16"
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/node-abort-controller": {
@@ -6350,12 +6490,12 @@
}
},
"node_modules/p-cancelable": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz",
- "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-4.0.1.tgz",
+ "integrity": "sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==",
"license": "MIT",
"engines": {
- "node": ">=12.20"
+ "node": ">=14.16"
}
},
"node_modules/p-limit": {
@@ -6631,9 +6771,9 @@
}
},
"node_modules/postcss-convert-values": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.6.tgz",
- "integrity": "sha512-MD/eb39Mr60hvgrqpXsgbiqluawYg/8K4nKsqRsuDX9f+xN1j6awZCUv/5tLH8ak3vYp/EMXwdcnXvfZYiejCQ==",
+ "version": "7.0.7",
+ "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.7.tgz",
+ "integrity": "sha512-HR9DZLN04Xbe6xugRH6lS4ZQH2zm/bFh/ZyRkpedZozhvh+awAfbA0P36InO4fZfDhvYfNJeNvlTf1sjwGbw/A==",
"license": "MIT",
"dependencies": {
"browserslist": "^4.25.1",
@@ -7231,9 +7371,9 @@
"license": "MIT"
},
"node_modules/protobufjs": {
- "version": "7.5.3",
- "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz",
- "integrity": "sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==",
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
+ "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -7875,9 +8015,9 @@
"license": "ISC"
},
"node_modules/ssh2": {
- "version": "1.16.0",
- "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.16.0.tgz",
- "integrity": "sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==",
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz",
+ "integrity": "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==",
"hasInstallScript": true,
"dependencies": {
"asn1": "^0.2.6",
@@ -7888,7 +8028,7 @@
},
"optionalDependencies": {
"cpu-features": "~0.0.10",
- "nan": "^2.20.0"
+ "nan": "^2.23.0"
}
},
"node_modules/ssl-checker": {
@@ -7957,6 +8097,12 @@
"readable-stream": "^2.1.0"
}
},
+ "node_modules/string-to-stream/node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "license": "MIT"
+ },
"node_modules/string-to-stream/node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
@@ -8125,86 +8271,15 @@
}
},
"node_modules/super-simple-scheduler": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.3.0.tgz",
- "integrity": "sha512-iEGZ+a9Xv7pIaA+XhVuHSGaasQg2T6/afTRlQhosQiiU/7ykpZBsiZB/L2Hqfem5YUrbr3Q8tzrjjXCXKMg81A==",
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.4.1.tgz",
+ "integrity": "sha512-jKVwxdRBXOHrUstA2pvO4jBZIA3CUinKiQ6k5C2Wn5tscoV3srppPxW0zG3VNKTCaGxo3yPn6Zaf3IDVBcAwRA==",
"license": "MIT",
"dependencies": {
- "human-interval": "2.0.1",
- "ioredis": "5.6.1",
- "mongoose": "8.16.1",
"uuid": "11.1.0",
"winston": "3.17.0"
}
},
- "node_modules/super-simple-scheduler/node_modules/mongodb": {
- "version": "6.17.0",
- "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz",
- "integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@mongodb-js/saslprep": "^1.1.9",
- "bson": "^6.10.4",
- "mongodb-connection-string-url": "^3.0.0"
- },
- "engines": {
- "node": ">=16.20.1"
- },
- "peerDependencies": {
- "@aws-sdk/credential-providers": "^3.188.0",
- "@mongodb-js/zstd": "^1.1.0 || ^2.0.0",
- "gcp-metadata": "^5.2.0",
- "kerberos": "^2.0.1",
- "mongodb-client-encryption": ">=6.0.0 <7",
- "snappy": "^7.2.2",
- "socks": "^2.7.1"
- },
- "peerDependenciesMeta": {
- "@aws-sdk/credential-providers": {
- "optional": true
- },
- "@mongodb-js/zstd": {
- "optional": true
- },
- "gcp-metadata": {
- "optional": true
- },
- "kerberos": {
- "optional": true
- },
- "mongodb-client-encryption": {
- "optional": true
- },
- "snappy": {
- "optional": true
- },
- "socks": {
- "optional": true
- }
- }
- },
- "node_modules/super-simple-scheduler/node_modules/mongoose": {
- "version": "8.16.1",
- "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.1.tgz",
- "integrity": "sha512-Q+0TC+KLdY4SYE+u9gk9pdW1tWu/pl0jusyEkMGTgBoAbvwQdfy4f9IM8dmvCwb/blSfp7IfLkob7v76x6ZGpQ==",
- "license": "MIT",
- "dependencies": {
- "bson": "^6.10.4",
- "kareem": "2.6.3",
- "mongodb": "~6.17.0",
- "mpath": "0.9.0",
- "mquery": "5.0.0",
- "ms": "2.1.3",
- "sift": "17.1.3"
- },
- "engines": {
- "node": ">=16.20.1"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/mongoose"
- }
- },
"node_modules/super-simple-scheduler/node_modules/uuid": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
@@ -8231,10 +8306,48 @@
"node": ">=8"
}
},
+ "node_modules/svgo": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz",
+ "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "@trysound/sax": "0.2.0",
+ "commander": "^7.2.0",
+ "css-select": "^5.1.0",
+ "css-tree": "^2.3.1",
+ "css-what": "^6.1.0",
+ "csso": "^5.0.5",
+ "picocolors": "^1.0.0"
+ },
+ "bin": {
+ "svgo": "bin/svgo"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/svgo"
+ }
+ },
+ "node_modules/svgo/node_modules/commander": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/swagger-ui-dist": {
- "version": "5.27.0",
- "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.27.0.tgz",
- "integrity": "sha512-tS6LRyBhY6yAqxrfsA9IYpGWPUJOri6sclySa7TdC7XQfGLvTwDY531KLgfQwHEtQsn+sT4JlUspbeQDBVGWig==",
+ "version": "5.28.1",
+ "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.28.1.tgz",
+ "integrity": "sha512-IvPrtNi8MvjiuDgoSmPYgg27Lvu38fnLD1OSd8Y103xXsPAqezVNnNeHnVCZ/d+CMXJblflGaIyHxAYIF3O71w==",
"license": "Apache-2.0",
"dependencies": {
"@scarf/scarf": "=1.4.0"
@@ -8468,13 +8581,12 @@
}
},
"node_modules/type-fest": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
- "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
- "dev": true,
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
+ "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
"license": "(MIT OR CC0-1.0)",
"engines": {
- "node": ">=10"
+ "node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -8520,18 +8632,18 @@
"license": "MIT"
},
"node_modules/undici": {
- "version": "7.12.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-7.12.0.tgz",
- "integrity": "sha512-GrKEsc3ughskmGA9jevVlIOPMiiAHJ4OFUtaAH+NhfTUSiZ1wMPIQqQvAJUrJspFXJt3EBWgpAeoHEDVT1IBug==",
+ "version": "7.15.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.15.0.tgz",
+ "integrity": "sha512-7oZJCPvvMvTd0OlqWsIxTuItTpJBpU1tcbVl24FMn3xt3+VSunwUasmfPJRE57oNO1KsZ4PgA1xTdAX4hq8NyQ==",
"license": "MIT",
"engines": {
"node": ">=20.18.1"
}
},
"node_modules/undici-types": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
- "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
+ "version": "7.10.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
+ "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
"license": "MIT"
},
"node_modules/unfetch": {
@@ -8911,6 +9023,18 @@
"node": ">= 12.0.0"
}
},
+ "node_modules/winston/node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
diff --git a/server/package.json b/server/package.json
index 929fab58a..d879776a8 100755
--- a/server/package.json
+++ b/server/package.json
@@ -28,6 +28,7 @@
"express": "^4.19.2",
"express-rate-limit": "8.0.1",
"gamedig": "^5.3.1",
+ "got": "14.4.7",
"handlebars": "^4.7.8",
"helmet": "^8.0.0",
"ioredis": "^5.4.2",
@@ -45,7 +46,7 @@
"ping": "0.4.4",
"sharp": "0.33.5",
"ssl-checker": "2.0.10",
- "super-simple-scheduler": "1.3.0",
+ "super-simple-scheduler": "1.4.1",
"swagger-ui-express": "5.0.1",
"winston": "^3.13.0"
},
diff --git a/server/src/config/services.js b/server/src/config/services.js
index 73220128f..08c2525f3 100644
--- a/server/src/config/services.js
+++ b/server/src/config/services.js
@@ -19,8 +19,10 @@ import MaintenanceWindowService from "../service/business/maintenanceWindowServi
import MonitorService from "../service/business/monitorService.js";
import papaparse from "papaparse";
import axios from "axios";
+import got from "got";
import ping from "ping";
import http from "http";
+import https from "https";
import Docker from "dockerode";
import net from "net";
import fs from "fs";
@@ -32,6 +34,8 @@ import mjml2html from "mjml";
import jwt from "jsonwebtoken";
import crypto from "crypto";
import { games } from "gamedig";
+import jmespath from "jmespath";
+import { GameDig } from "gamedig";
import { fileURLToPath } from "url";
import { ObjectId } from "mongodb";
@@ -43,8 +47,6 @@ import { ParseBoolean } from "../utils/utils.js";
// Models
import Check from "../db/models/Check.js";
-import HardwareCheck from "../db/models/HardwareCheck.js";
-import PageSpeedCheck from "../db/models/PageSpeedCheck.js";
import Monitor from "../db/models/Monitor.js";
import User from "../db/models/User.js";
import InviteToken from "../db/models/InviteToken.js";
@@ -52,7 +54,6 @@ import StatusPage from "../db/models/StatusPage.js";
import Team from "../db/models/Team.js";
import MaintenanceWindow from "../db/models/MaintenanceWindow.js";
import MonitorStats from "../db/models/MonitorStats.js";
-import NetworkCheck from "../db/models/NetworkCheck.js";
import Notification from "../db/models/Notification.js";
import RecoveryToken from "../db/models/RecoveryToken.js";
import AppSettings from "../db/models/AppSettings.js";
@@ -61,12 +62,9 @@ import InviteModule from "../db/mongo/modules/inviteModule.js";
import CheckModule from "../db/mongo/modules/checkModule.js";
import StatusPageModule from "../db/mongo/modules/statusPageModule.js";
import UserModule from "../db/mongo/modules/userModule.js";
-import HardwareCheckModule from "../db/mongo/modules/hardwareCheckModule.js";
import MaintenanceWindowModule from "../db/mongo/modules/maintenanceWindowModule.js";
import MonitorModule from "../db/mongo/modules/monitorModule.js";
-import NetworkCheckModule from "../db/mongo/modules/networkCheckModule.js";
import NotificationModule from "../db/mongo/modules/notificationModule.js";
-import PageSpeedCheckModule from "../db/mongo/modules/pageSpeedCheckModule.js";
import RecoveryModule from "../db/mongo/modules/recoveryModule.js";
import SettingsModule from "../db/mongo/modules/settingsModule.js";
@@ -80,18 +78,15 @@ export const initializeServices = async ({ logger, envSettings, settingsService
const stringService = new StringService(translationService);
// Create DB
- const checkModule = new CheckModule({ logger, Check, HardwareCheck, PageSpeedCheck, Monitor, User });
+ const checkModule = new CheckModule({ logger, Check, Monitor, User });
const inviteModule = new InviteModule({ InviteToken, crypto, stringService });
const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService });
const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean, stringService });
- const hardwareCheckModule = new HardwareCheckModule({ HardwareCheck, Monitor, logger });
const maintenanceWindowModule = new MaintenanceWindowModule({ MaintenanceWindow });
const monitorModule = new MonitorModule({
Monitor,
MonitorStats,
Check,
- PageSpeedCheck,
- HardwareCheck,
stringService,
fs,
path,
@@ -100,9 +95,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService
NormalizeData,
NormalizeDataUptimeDetails,
});
- const networkCheckModule = new NetworkCheckModule({ NetworkCheck });
const notificationModule = new NotificationModule({ Notification, Monitor });
- const pageSpeedCheckModule = new PageSpeedCheckModule({ PageSpeedCheck });
const recoveryModule = new RecoveryModule({ User, RecoveryToken, crypto, stringService });
const settingsModule = new SettingsModule({ AppSettings });
@@ -113,19 +106,29 @@ export const initializeServices = async ({ logger, envSettings, settingsService
inviteModule,
statusPageModule,
userModule,
- hardwareCheckModule,
maintenanceWindowModule,
monitorModule,
- networkCheckModule,
notificationModule,
- pageSpeedCheckModule,
recoveryModule,
settingsModule,
});
await db.connect();
- const networkService = new NetworkService(axios, ping, logger, http, Docker, net, stringService, settingsService);
+ const networkService = new NetworkService({
+ axios,
+ got,
+ https,
+ jmespath,
+ GameDig,
+ ping,
+ logger,
+ http,
+ Docker,
+ net,
+ stringService,
+ settingsService,
+ });
const emailService = new EmailService(settingsService, fs, path, compile, mjml2html, nodemailer, logger);
const bufferService = new BufferService({ db, logger, envSettings });
const statusService = new StatusService({ db, logger, buffer: bufferService });
diff --git a/server/src/controllers/statusPageController.js b/server/src/controllers/statusPageController.js
index 1cbcb49a2..a46688590 100755
--- a/server/src/controllers/statusPageController.js
+++ b/server/src/controllers/statusPageController.js
@@ -95,7 +95,7 @@ class StatusPageController extends BaseController {
deleteStatusPage = this.asyncHandler(
async (req, res) => {
- await this.db.deleteStatusPage(req.params.url);
+ await this.db.statusPageModule.deleteStatusPage(req.params.url);
return res.success({
msg: this.stringService.statusPageDelete,
});
diff --git a/server/src/db/models/Check.js b/server/src/db/models/Check.js
index 8bb6cd028..789cebaa5 100755
--- a/server/src/db/models/Check.js
+++ b/server/src/db/models/Check.js
@@ -1,99 +1,176 @@
import mongoose from "mongoose";
-const BaseCheckSchema = mongoose.Schema({
- /**
- * Reference to the associated Monitor document.
- *
- * @type {mongoose.Schema.Types.ObjectId}
- */
- monitorId: {
- type: mongoose.Schema.Types.ObjectId,
- ref: "Monitor",
- immutable: true,
- index: true,
- },
-
- teamId: {
- type: mongoose.Schema.Types.ObjectId,
- ref: "Team",
- immutable: true,
- index: true,
- },
- /**
- * Status of the check (true for up, false for down).
- *
- * @type {Boolean}
- */
- status: {
- type: Boolean,
- index: true,
- },
- /**
- * Response time of the check in milliseconds.
- *
- * @type {Number}
- */
- responseTime: {
- type: Number,
- },
- /**
- * HTTP status code received during the check.
- *
- * @type {Number}
- */
- statusCode: {
- type: Number,
- index: true,
- },
- /**
- * Message or description of the check result.
- *
- * @type {String}
- */
- message: {
- type: String,
- },
- /**
- * Expiry date of the check, auto-calculated to expire after 30 days.
- *
- * @type {Date}
- */
-
- expiry: {
- type: Date,
- default: Date.now,
- expires: 60 * 60 * 24 * 30, // 30 days
- },
- /**
- * Acknowledgment of the check.
- *
- * @type {Boolean}
- */
- ack: {
- type: Boolean,
- default: false,
- },
- /**
- * Resolution date of the check (when the check was resolved).
- *
- * @type {Date}
- */
- ackAt: {
- type: Date,
- },
+const cpuSchema = mongoose.Schema({
+ physical_core: { type: Number, default: 0 },
+ logical_core: { type: Number, default: 0 },
+ frequency: { type: Number, default: 0 },
+ temperature: { type: [Number], default: [] },
+ free_percent: { type: Number, default: 0 },
+ usage_percent: { type: Number, default: 0 },
});
-/**
- * Check Schema for MongoDB collection.
- *
- * Represents a check associated with a monitor, storing information
- * about the status and response of a particular check event.
- */
-const CheckSchema = mongoose.Schema({ ...BaseCheckSchema.obj }, { timestamps: true });
+const memorySchema = mongoose.Schema({
+ total_bytes: { type: Number, default: 0 },
+ available_bytes: { type: Number, default: 0 },
+ used_bytes: { type: Number, default: 0 },
+ usage_percent: { type: Number, default: 0 },
+});
+
+const diskSchema = mongoose.Schema({
+ read_speed_bytes: { type: Number, default: 0 },
+ write_speed_bytes: { type: Number, default: 0 },
+ total_bytes: { type: Number, default: 0 },
+ free_bytes: { type: Number, default: 0 },
+ usage_percent: { type: Number, default: 0 },
+});
+
+const hostSchema = mongoose.Schema({
+ os: { type: String, default: "" },
+ platform: { type: String, default: "" },
+ kernel_version: { type: String, default: "" },
+});
+
+const errorSchema = mongoose.Schema({
+ metric: { type: [String], default: [] },
+ err: { type: String, default: "" },
+});
+
+const captureSchema = mongoose.Schema({
+ version: { type: String, default: "" },
+ mode: { type: String, default: "" },
+});
+
+const networkInterfaceSchema = mongoose.Schema({
+ name: { type: String },
+ bytes_sent: { type: Number, default: 0 },
+ bytes_recv: { type: Number, default: 0 },
+ packets_sent: { type: Number, default: 0 },
+ packets_recv: { type: Number, default: 0 },
+ err_in: { type: Number, default: 0 },
+ err_out: { type: Number, default: 0 },
+ drop_in: { type: Number, default: 0 },
+ drop_out: { type: Number, default: 0 },
+ fifo_in: { type: Number, default: 0 },
+ fifo_out: { type: Number, default: 0 },
+});
+
+const CheckSchema = new mongoose.Schema(
+ {
+ // Common fields
+ monitorId: {
+ type: mongoose.Schema.Types.ObjectId,
+ ref: "Monitor",
+ immutable: true,
+ index: true,
+ },
+
+ teamId: {
+ type: mongoose.Schema.Types.ObjectId,
+ ref: "Team",
+ immutable: true,
+ index: true,
+ },
+ type: {
+ type: String,
+ enum: ["http", "ping", "pagespeed", "hardware", "docker", "port", "game"],
+ required: true,
+ index: true,
+ },
+
+ status: {
+ type: Boolean,
+ index: true,
+ },
+
+ responseTime: {
+ type: Number,
+ },
+
+ timings: {
+ type: Object,
+ default: {},
+ },
+
+ statusCode: {
+ type: Number,
+ index: true,
+ },
+
+ message: {
+ type: String,
+ },
+
+ expiry: {
+ type: Date,
+ default: Date.now,
+ expires: 60 * 60 * 24 * 30, // 30 days
+ },
+
+ ack: {
+ type: Boolean,
+ default: false,
+ },
+
+ ackAt: {
+ type: Date,
+ },
+
+ // Hardware fields
+ cpu: {
+ type: cpuSchema,
+ default: () => ({}),
+ },
+ memory: {
+ type: memorySchema,
+ default: () => ({}),
+ },
+ disk: {
+ type: [diskSchema],
+ default: () => [],
+ },
+ host: {
+ type: hostSchema,
+ default: () => ({}),
+ },
+
+ errors: {
+ type: [errorSchema],
+ default: () => [],
+ },
+
+ capture: {
+ type: captureSchema,
+ default: () => ({}),
+ },
+
+ net: {
+ type: [networkInterfaceSchema],
+ default: () => [],
+ },
+
+ // PageSpeed fields
+ accessibility: {
+ type: Number,
+ },
+ bestPractices: {
+ type: Number,
+ },
+ seo: {
+ type: Number,
+ },
+ performance: {
+ type: Number,
+ },
+ audits: {
+ type: Object,
+ },
+ },
+ { timestamps: true }
+);
+
CheckSchema.index({ updatedAt: 1 });
CheckSchema.index({ monitorId: 1, updatedAt: 1 });
CheckSchema.index({ monitorId: 1, updatedAt: -1 });
CheckSchema.index({ teamId: 1, updatedAt: -1 });
export default mongoose.model("Check", CheckSchema);
-export { BaseCheckSchema };
diff --git a/server/src/db/models/HardwareCheck.js b/server/src/db/models/HardwareCheck.js
deleted file mode 100755
index 5b7d56213..000000000
--- a/server/src/db/models/HardwareCheck.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import mongoose from "mongoose";
-import { BaseCheckSchema } from "./Check.js";
-const cpuSchema = mongoose.Schema({
- physical_core: { type: Number, default: 0 },
- logical_core: { type: Number, default: 0 },
- frequency: { type: Number, default: 0 },
- temperature: { type: [Number], default: [] },
- free_percent: { type: Number, default: 0 },
- usage_percent: { type: Number, default: 0 },
-});
-
-const memorySchema = mongoose.Schema({
- total_bytes: { type: Number, default: 0 },
- available_bytes: { type: Number, default: 0 },
- used_bytes: { type: Number, default: 0 },
- usage_percent: { type: Number, default: 0 },
-});
-
-const diskSchema = mongoose.Schema({
- read_speed_bytes: { type: Number, default: 0 },
- write_speed_bytes: { type: Number, default: 0 },
- total_bytes: { type: Number, default: 0 },
- free_bytes: { type: Number, default: 0 },
- usage_percent: { type: Number, default: 0 },
-});
-
-const hostSchema = mongoose.Schema({
- os: { type: String, default: "" },
- platform: { type: String, default: "" },
- kernel_version: { type: String, default: "" },
-});
-
-const errorSchema = mongoose.Schema({
- metric: { type: [String], default: [] },
- err: { type: String, default: "" },
-});
-
-const captureSchema = mongoose.Schema({
- version: { type: String, default: "" },
- mode: { type: String, default: "" },
-});
-
-const networkInterfaceSchema = mongoose.Schema({
- name: { type: String },
- bytes_sent: { type: Number, default: 0 },
- bytes_recv: { type: Number, default: 0 },
- packets_sent: { type: Number, default: 0 },
- packets_recv: { type: Number, default: 0 },
- err_in: { type: Number, default: 0 },
- err_out: { type: Number, default: 0 },
- drop_in: { type: Number, default: 0 },
- drop_out: { type: Number, default: 0 },
- fifo_in: { type: Number, default: 0 },
- fifo_out: { type: Number, default: 0 },
-});
-
-const HardwareCheckSchema = mongoose.Schema(
- {
- ...BaseCheckSchema.obj,
- cpu: {
- type: cpuSchema,
- default: () => ({}),
- },
- memory: {
- type: memorySchema,
- default: () => ({}),
- },
- disk: {
- type: [diskSchema],
- default: () => [],
- },
- host: {
- type: hostSchema,
- default: () => ({}),
- },
-
- errors: {
- type: [errorSchema],
- default: () => [],
- },
-
- capture: {
- type: captureSchema,
- default: () => ({}),
- },
-
- net: {
- type: [networkInterfaceSchema],
- default: () => [],
- required: false,
- },
- },
- { timestamps: true }
-);
-
-HardwareCheckSchema.index({ createdAt: 1 });
-HardwareCheckSchema.index({ monitorId: 1, createdAt: 1 });
-HardwareCheckSchema.index({ monitorId: 1, createdAt: -1 });
-
-export default mongoose.model("HardwareCheck", HardwareCheckSchema);
diff --git a/server/src/db/models/Monitor.js b/server/src/db/models/Monitor.js
index 1ee467999..0e12d3c26 100755
--- a/server/src/db/models/Monitor.js
+++ b/server/src/db/models/Monitor.js
@@ -1,6 +1,4 @@
import mongoose from "mongoose";
-import HardwareCheck from "./HardwareCheck.js";
-import PageSpeedCheck from "./PageSpeedCheck.js";
import Check from "./Check.js";
import MonitorStats from "./MonitorStats.js";
import StatusPage from "./StatusPage.js";
@@ -30,6 +28,18 @@ const MonitorSchema = mongoose.Schema(
type: Boolean,
default: undefined,
},
+ statusWindow: {
+ type: [Boolean],
+ default: [],
+ },
+ statusWindowSize: {
+ type: Number,
+ default: 5,
+ },
+ statusWindowThreshold: {
+ type: Number,
+ default: 60,
+ },
type: {
type: String,
required: true,
@@ -133,13 +143,7 @@ MonitorSchema.pre("findOneAndDelete", async function (next) {
throw new Error("Monitor not found");
}
- if (doc?.type === "pagespeed") {
- await PageSpeedCheck.deleteMany({ monitorId: doc._id });
- } else if (doc?.type === "hardware") {
- await HardwareCheck.deleteMany({ monitorId: doc._id });
- } else {
- await Check.deleteMany({ monitorId: doc._id });
- }
+ await Check.deleteMany({ monitorId: doc._id });
// Deal with status pages
await StatusPage.updateMany({ monitors: doc?._id }, { $pull: { monitors: doc?._id } });
@@ -156,13 +160,7 @@ MonitorSchema.pre("deleteMany", async function (next) {
const monitors = await this.model.find(filter).select(["_id", "type"]).lean();
for (const monitor of monitors) {
- if (monitor.type === "pagespeed") {
- await PageSpeedCheck.deleteMany({ monitorId: monitor._id });
- } else if (monitor.type === "hardware") {
- await HardwareCheck.deleteMany({ monitorId: monitor._id });
- } else {
- await Check.deleteMany({ monitorId: monitor._id });
- }
+ await Check.deleteMany({ monitorId: monitor._id });
await StatusPage.updateMany({ monitors: monitor._id }, { $pull: { monitors: monitor._id } });
await MonitorStats.deleteMany({ monitorId: monitor._id.toString() });
}
diff --git a/server/src/db/models/MonitorStats.js b/server/src/db/models/MonitorStats.js
index c28811b33..c081c8a4f 100755
--- a/server/src/db/models/MonitorStats.js
+++ b/server/src/db/models/MonitorStats.js
@@ -40,10 +40,6 @@ const MonitorStatsSchema = new mongoose.Schema(
type: Number,
default: 0,
},
- uptBurnt: {
- type: mongoose.Schema.Types.Decimal128,
- required: false,
- },
},
{ timestamps: true }
);
diff --git a/server/src/db/models/NetworkCheck.js b/server/src/db/models/NetworkCheck.js
deleted file mode 100644
index eca69eef9..000000000
--- a/server/src/db/models/NetworkCheck.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import mongoose from "mongoose";
-import { BaseCheckSchema } from "./Check.js";
-
-const networkInterfaceSchema = mongoose.Schema({
- name: { type: String, required: true },
- bytes_sent: { type: Number, default: 0 },
- bytes_recv: { type: Number, default: 0 },
- packets_sent: { type: Number, default: 0 },
- packets_recv: { type: Number, default: 0 },
- err_in: { type: Number, default: 0 },
- err_out: { type: Number, default: 0 },
- drop_in: { type: Number, default: 0 },
- drop_out: { type: Number, default: 0 },
- fifo_in: { type: Number, default: 0 },
- fifo_out: { type: Number, default: 0 },
-});
-
-const captureSchema = mongoose.Schema({
- version: { type: String, default: "" },
- mode: { type: String, default: "" },
-});
-
-const NetworkCheckSchema = mongoose.Schema(
- {
- ...BaseCheckSchema.obj,
- data: {
- type: [networkInterfaceSchema],
- default: () => [],
- },
- capture: {
- type: captureSchema,
- default: () => ({}),
- },
- errors: {
- type: mongoose.Schema.Types.Mixed,
- default: null,
- },
- },
- { timestamps: true }
-);
-
-NetworkCheckSchema.index({ createdAt: 1 });
-NetworkCheckSchema.index({ monitorId: 1, createdAt: 1 });
-NetworkCheckSchema.index({ monitorId: 1, createdAt: -1 });
-
-export default mongoose.model("NetworkCheck", NetworkCheckSchema);
diff --git a/server/src/db/models/PageSpeedCheck.js b/server/src/db/models/PageSpeedCheck.js
deleted file mode 100755
index 67c7c375e..000000000
--- a/server/src/db/models/PageSpeedCheck.js
+++ /dev/null
@@ -1,83 +0,0 @@
-import mongoose from "mongoose";
-import { BaseCheckSchema } from "./Check.js";
-const AuditSchema = mongoose.Schema({
- id: { type: String, required: true },
- title: { type: String, required: true },
- description: { type: String, required: true },
- score: { type: Number, required: true },
- scoreDisplayMode: { type: String, required: true },
- displayValue: { type: String, required: true },
- numericValue: { type: Number, required: true },
- numericUnit: { type: String, required: true },
-});
-
-const AuditsSchema = mongoose.Schema({
- cls: {
- type: AuditSchema,
- required: true,
- },
- si: {
- type: AuditSchema,
- required: true,
- },
- fcp: {
- type: AuditSchema,
- required: true,
- },
- lcp: {
- type: AuditSchema,
- required: true,
- },
- tbt: {
- type: AuditSchema,
- required: true,
- },
-});
-
-/**
- * Mongoose schema for storing metrics from Google Lighthouse.
- * @typedef {Object} PageSpeedCheck
- * @property {mongoose.Schema.Types.ObjectId} monitorId - Reference to the Monitor model.
- * @property {number} accessibility - Accessibility score.
- * @property {number} bestPractices - Best practices score.
- * @property {number} seo - SEO score.
- * @property {number} performance - Performance score.
- */
-
-const PageSpeedCheck = mongoose.Schema(
- {
- ...BaseCheckSchema.obj,
- accessibility: {
- type: Number,
- required: true,
- },
- bestPractices: {
- type: Number,
- required: true,
- },
- seo: {
- type: Number,
- required: true,
- },
- performance: {
- type: Number,
- required: true,
- },
- audits: {
- type: AuditsSchema,
- required: true,
- },
- },
- { timestamps: true }
-);
-
-/**
- * Mongoose model for storing metrics from Google Lighthouse.
- * @typedef {mongoose.Model} LighthouseMetricsModel
- */
-
-PageSpeedCheck.index({ createdAt: 1 });
-PageSpeedCheck.index({ monitorId: 1, createdAt: 1 });
-PageSpeedCheck.index({ monitorId: 1, createdAt: -1 });
-
-export default mongoose.model("PageSpeedCheck", PageSpeedCheck);
diff --git a/server/src/db/mongo/MongoDB.js b/server/src/db/mongo/MongoDB.js
index 0c6274320..74fcbd89f 100755
--- a/server/src/db/mongo/MongoDB.js
+++ b/server/src/db/mongo/MongoDB.js
@@ -1,6 +1,6 @@
import mongoose from "mongoose";
import AppSettings from "../models/AppSettings.js";
-
+import { runMigrations } from "./migration/index.js";
class MongoDB {
static SERVICE_NAME = "MongoDB";
@@ -65,6 +65,8 @@ class MongoDB {
service: this.SERVICE_NAME,
method: "connect",
});
+
+ await runMigrations();
} catch (error) {
this.logger.error({
message: error.message,
diff --git a/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js b/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js
new file mode 100644
index 000000000..8f6988de4
--- /dev/null
+++ b/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js
@@ -0,0 +1,18 @@
+import Monitor from "../../models/Monitor.js";
+async function migrateStatusWindowThreshold() {
+ try {
+ const monitors = await Monitor.find({ statusWindowThreshold: { $lt: 1 } });
+ for (const monitor of monitors) {
+ monitor.statusWindowThreshold = monitor.statusWindowThreshold * 100;
+ await monitor.save();
+ console.log(`Migrated monitor ${monitor._id}: statusWindowThreshold set to ${monitor.statusWindowThreshold}`);
+ }
+ console.log("StatusWindowThreshold migration complete.");
+ return true;
+ } catch (err) {
+ console.error("Migration error:", err);
+ return false;
+ }
+}
+
+export { migrateStatusWindowThreshold };
diff --git a/server/src/db/mongo/migration/index.js b/server/src/db/mongo/migration/index.js
new file mode 100644
index 000000000..4bf56895a
--- /dev/null
+++ b/server/src/db/mongo/migration/index.js
@@ -0,0 +1,7 @@
+import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshold.js";
+
+const runMigrations = async () => {
+ await migrateStatusWindowThreshold();
+};
+
+export { runMigrations };
diff --git a/server/src/db/mongo/modules/checkModule.js b/server/src/db/mongo/modules/checkModule.js
index a606a593b..dfca02bed 100755
--- a/server/src/db/mongo/modules/checkModule.js
+++ b/server/src/db/mongo/modules/checkModule.js
@@ -31,7 +31,7 @@ class CheckModule {
}
};
- getChecksByMonitor = async ({ monitorId, type, sortOrder, dateRange, filter, ack, page, rowsPerPage, status }) => {
+ getChecksByMonitor = async ({ monitorId, sortOrder, dateRange, filter, ack, page, rowsPerPage, status }) => {
try {
status = status === "true" ? true : status === "false" ? false : undefined;
page = parseInt(page);
@@ -79,19 +79,7 @@ class CheckModule {
skip = page * rowsPerPage;
}
- const checkModels = {
- http: this.Check,
- ping: this.Check,
- docker: this.Check,
- port: this.Check,
- pagespeed: this.PageSpeedCheck,
- hardware: this.HardwareCheck,
- game: this.Check,
- };
-
- const Model = checkModels[type];
-
- const checks = await Model.aggregate([
+ const checks = await this.Check.aggregate([
{ $match: matchStage },
{ $sort: { createdAt: sortOrder } },
{
@@ -166,18 +154,6 @@ class CheckModule {
const aggregatePipeline = [
{ $match: matchStage },
- {
- $unionWith: {
- coll: "hardwarechecks",
- pipeline: [{ $match: matchStage }],
- },
- },
- {
- $unionWith: {
- coll: "pagespeedchecks",
- pipeline: [{ $match: matchStage }],
- },
- },
{ $sort: { createdAt: sortOrder } },
{
diff --git a/server/src/db/mongo/modules/hardwareCheckModule.js b/server/src/db/mongo/modules/hardwareCheckModule.js
deleted file mode 100755
index 448e72fed..000000000
--- a/server/src/db/mongo/modules/hardwareCheckModule.js
+++ /dev/null
@@ -1,22 +0,0 @@
-const SERVICE_NAME = "hardwareCheckModule";
-
-class HardwareCheckModule {
- constructor({ HardwareCheck, Monitor, logger }) {
- this.HardwareCheck = HardwareCheck;
- this.Monitor = Monitor;
- this.logger = logger;
- }
-
- createHardwareChecks = async (hardwareChecks) => {
- try {
- await this.HardwareCheck.insertMany(hardwareChecks, { ordered: false });
- return true;
- } catch (error) {
- error.service = SERVICE_NAME;
- error.method = "createHardwareChecks";
- throw error;
- }
- };
-}
-
-export default HardwareCheckModule;
diff --git a/server/src/db/mongo/modules/monitorModule.js b/server/src/db/mongo/modules/monitorModule.js
index c4a896030..8beb9316c 100755
--- a/server/src/db/mongo/modules/monitorModule.js
+++ b/server/src/db/mongo/modules/monitorModule.js
@@ -11,25 +11,10 @@ import {
const SERVICE_NAME = "monitorModule";
class MonitorModule {
- constructor({
- Monitor,
- MonitorStats,
- Check,
- PageSpeedCheck,
- HardwareCheck,
- stringService,
- fs,
- path,
- fileURLToPath,
- ObjectId,
- NormalizeData,
- NormalizeDataUptimeDetails,
- }) {
+ constructor({ Monitor, MonitorStats, Check, stringService, fs, path, fileURLToPath, ObjectId, NormalizeData, NormalizeDataUptimeDetails }) {
this.Monitor = Monitor;
this.MonitorStats = MonitorStats;
this.Check = Check;
- this.PageSpeedCheck = PageSpeedCheck;
- this.HardwareCheck = HardwareCheck;
this.stringService = stringService;
this.fs = fs;
this.path = path;
@@ -38,15 +23,6 @@ class MonitorModule {
this.NormalizeData = NormalizeData;
this.NormalizeDataUptimeDetails = NormalizeDataUptimeDetails;
- this.CHECK_MODEL_LOOKUP = {
- http: Check,
- ping: Check,
- docker: Check,
- port: Check,
- pagespeed: PageSpeedCheck,
- hardware: HardwareCheck,
- };
-
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -146,19 +122,18 @@ class MonitorModule {
};
//Helper
- getMonitorChecks = async (monitorId, model, dateRange, sortOrder) => {
+ getMonitorChecks = async (monitorId, dateRange, sortOrder) => {
const indexSpec = {
monitorId: 1,
- createdAt: sortOrder, // This will be 1 or -1
+ updatedAt: sortOrder, // This will be 1 or -1
};
const [checksAll, checksForDateRange] = await Promise.all([
- model.find({ monitorId }).sort({ createdAt: sortOrder }).hint(indexSpec).lean(),
- model
- .find({
- monitorId,
- createdAt: { $gte: dateRange.start, $lte: dateRange.end },
- })
+ this.Check.find({ monitorId }).sort({ createdAt: sortOrder }).hint(indexSpec).lean(),
+ this.Check.find({
+ monitorId,
+ createdAt: { $gte: dateRange.start, $lte: dateRange.end },
+ })
.hint(indexSpec)
.lean(),
]);
@@ -295,9 +270,8 @@ class MonitorModule {
const sort = sortOrder === "asc" ? 1 : -1;
// Get Checks for monitor in date range requested
- const model = this.CHECK_MODEL_LOOKUP[monitor.type];
const dates = this.getDateRange(dateRange);
- const { checksAll, checksForDateRange } = await this.getMonitorChecks(monitorId, model, dates, sort);
+ const { checksAll, checksForDateRange } = await this.getMonitorChecks(monitorId, dates, sort);
// Build monitor stats
const monitorStats = {
@@ -330,6 +304,7 @@ class MonitorModule {
try {
const monitor = await this.Monitor.findById(monitorId);
const dates = this.getDateRange(dateRange);
+
const formatLookup = {
recent: "%Y-%m-%dT%H:%M:00Z",
day: "%Y-%m-%dT%H:00:00Z",
@@ -337,13 +312,15 @@ class MonitorModule {
month: "%Y-%m-%dT00:00:00Z",
};
const dateString = formatLookup[dateRange];
- const hardwareStats = await this.HardwareCheck.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString));
- const monitorStats = {
+ const hardwareStats = await this.Check.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString));
+
+ const stats = hardwareStats[0];
+
+ return {
...monitor.toObject(),
- stats: hardwareStats[0],
+ stats,
};
- return monitorStats;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getHardwareDetailsById";
diff --git a/server/src/db/mongo/modules/monitorModuleQueries.js b/server/src/db/mongo/modules/monitorModuleQueries.js
index a5a56ff9b..32aed1529 100755
--- a/server/src/db/mongo/modules/monitorModuleQueries.js
+++ b/server/src/db/mongo/modules/monitorModuleQueries.js
@@ -5,12 +5,12 @@ const buildUptimeDetailsPipeline = (monitorId, dates, dateString) => {
{
$match: {
monitorId: new ObjectId(monitorId),
- createdAt: { $gte: dates.start, $lte: dates.end },
+ updatedAt: { $gte: dates.start, $lte: dates.end },
},
},
{
$sort: {
- createdAt: 1,
+ updatedAt: 1,
},
},
{
@@ -173,6 +173,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
{
$match: {
monitorId: monitor._id,
+ type: "hardware",
createdAt: { $gte: dates.start, $lte: dates.end },
},
},
@@ -218,14 +219,14 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
{
$project: {
diskCount: {
- $size: "$disk",
+ $size: { $ifNull: ["$disk", []] },
},
- netCount: { $size: "$net" },
+ netCount: { $size: { $ifNull: ["$net", []] } },
},
},
{
$lookup: {
- from: "hardwarechecks",
+ from: "checks",
let: {
diskCount: "$diskCount",
netCount: "$netCount",
@@ -263,6 +264,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
net: {
$push: "$net",
},
+ updatedAts: {
+ $push: "$updatedAt",
+ },
},
},
{
@@ -378,90 +382,399 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
},
net: {
$map: {
- input: { $range: [0, "$$netCount"] },
+ input: { $range: [0, { $size: { $ifNull: [{ $arrayElemAt: ["$net", 0] }, []] } }] },
as: "netIndex",
in: {
name: {
$arrayElemAt: [
{
$map: {
- input: "$net",
- as: "netArray",
- in: { $arrayElemAt: ["$$netArray.name", "$$netIndex"] },
+ input: { $arrayElemAt: ["$net", 0] },
+ as: "iface",
+ in: "$$iface.name",
},
},
- 0,
+ "$$netIndex",
],
},
- avgBytesSent: {
- $avg: {
- $map: {
- input: "$net",
- as: "netArray",
- in: {
- $arrayElemAt: ["$$netArray.bytes_sent", "$$netIndex"],
+ bytesSentPerSecond: {
+ $let: {
+ vars: {
+ first: {
+ $arrayElemAt: [
+ { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.bytes_sent" } },
+ "$$netIndex",
+ ],
},
+ last: {
+ $arrayElemAt: [
+ {
+ $map: {
+ input: {
+ $arrayElemAt: [{ $ifNull: ["$net", []] }, { $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1] }],
+ },
+ as: "iface",
+ in: "$$iface.bytes_sent",
+ },
+ },
+ "$$netIndex",
+ ],
+ },
+ tFirst: { $arrayElemAt: ["$updatedAts", 0] },
+ tLast: {
+ $arrayElemAt: [{ $ifNull: ["$updatedAts", []] }, { $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1] }],
+ },
+ },
+ in: {
+ $cond: [
+ { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] },
+ {
+ $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }],
+ },
+ 0,
+ ],
},
},
},
- avgBytesRecv: {
- $avg: {
- $map: {
- input: "$net",
- as: "netArray",
- in: {
- $arrayElemAt: ["$$netArray.bytes_recv", "$$netIndex"],
+ deltaBytesRecv: {
+ $let: {
+ vars: {
+ first: {
+ $arrayElemAt: [
+ { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.bytes_recv" } },
+ "$$netIndex",
+ ],
},
+ last: {
+ $arrayElemAt: [
+ {
+ $map: {
+ input: {
+ $arrayElemAt: [{ $ifNull: ["$net", []] }, { $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1] }],
+ },
+ as: "iface",
+ in: "$$iface.bytes_recv",
+ },
+ },
+ "$$netIndex",
+ ],
+ },
+ tFirst: { $arrayElemAt: ["$updatedAts", 0] },
+ tLast: {
+ $arrayElemAt: [{ $ifNull: ["$updatedAts", []] }, { $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1] }],
+ },
+ },
+ in: {
+ $cond: [
+ { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] },
+ {
+ $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }],
+ },
+ 0,
+ ],
},
},
},
- avgPacketsSent: {
- $avg: {
- $map: {
- input: "$net",
- as: "netArray",
- in: {
- $arrayElemAt: ["$$netArray.packets_sent", "$$netIndex"],
+ deltaPacketsSent: {
+ $let: {
+ vars: {
+ first: {
+ $arrayElemAt: [
+ { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.packets_sent" } },
+ "$$netIndex",
+ ],
},
+ last: {
+ $arrayElemAt: [
+ {
+ $map: {
+ input: {
+ $arrayElemAt: [{ $ifNull: ["$net", []] }, { $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1] }],
+ },
+ as: "iface",
+ in: "$$iface.packets_sent",
+ },
+ },
+ "$$netIndex",
+ ],
+ },
+ tFirst: { $arrayElemAt: ["$updatedAts", 0] },
+ tLast: {
+ $arrayElemAt: [{ $ifNull: ["$updatedAts", []] }, { $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1] }],
+ },
+ },
+ in: {
+ $cond: [
+ { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] },
+ {
+ $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }],
+ },
+ 0,
+ ],
},
},
},
- avgPacketsRecv: {
- $avg: {
- $map: {
- input: "$net",
- as: "netArray",
- in: {
- $arrayElemAt: ["$$netArray.packets_recv", "$$netIndex"],
+ deltaPacketsRecv: {
+ $let: {
+ vars: {
+ first: {
+ $arrayElemAt: [
+ {
+ $map: {
+ input: { $arrayElemAt: ["$net", 0] },
+ as: "iface",
+ in: "$$iface.packets_recv",
+ },
+ },
+ "$$netIndex",
+ ],
},
+ last: {
+ $arrayElemAt: [
+ {
+ $map: {
+ input: {
+ $arrayElemAt: [
+ { $ifNull: ["$net", []] },
+ {
+ $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1],
+ },
+ ],
+ },
+ as: "iface",
+ in: "$$iface.packets_recv",
+ },
+ },
+ "$$netIndex",
+ ],
+ },
+ tFirst: { $arrayElemAt: ["$updatedAts", 0] },
+ tLast: {
+ $arrayElemAt: [
+ { $ifNull: ["$updatedAts", []] },
+ {
+ $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1],
+ },
+ ],
+ },
+ },
+ in: {
+ $cond: [
+ { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] },
+ {
+ $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }],
+ },
+ 0,
+ ],
},
},
},
- avgErrIn: {
- $avg: {
- $map: {
- input: "$net",
- as: "netArray",
- in: {
- $arrayElemAt: ["$$netArray.err_in", "$$netIndex"],
+ deltaErrIn: {
+ $let: {
+ vars: {
+ first: {
+ $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.err_in" } }, "$$netIndex"],
},
+ last: {
+ $arrayElemAt: [
+ {
+ $map: {
+ input: {
+ $arrayElemAt: [{ $ifNull: ["$net", []] }, { $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1] }],
+ },
+ as: "iface",
+ in: "$$iface.err_in",
+ },
+ },
+ "$$netIndex",
+ ],
+ },
+ tFirst: { $arrayElemAt: ["$updatedAts", 0] },
+ tLast: {
+ $arrayElemAt: [{ $ifNull: ["$updatedAts", []] }, { $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1] }],
+ },
+ },
+ in: {
+ $cond: [
+ { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] },
+ {
+ $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }],
+ },
+ 0,
+ ],
},
},
},
- avgErrOut: {
- $avg: {
- $map: {
- input: "$net",
- as: "netArray",
- in: {
- $arrayElemAt: ["$$netArray.err_out", "$$netIndex"],
+ deltaErrOut: {
+ $let: {
+ vars: {
+ first: {
+ $arrayElemAt: [
+ {
+ $map: {
+ input: { $arrayElemAt: ["$net", 0] },
+ as: "iface",
+ in: "$$iface.err_out",
+ },
+ },
+ "$$netIndex",
+ ],
},
+ last: {
+ $arrayElemAt: [
+ {
+ $map: {
+ input: {
+ $arrayElemAt: [
+ { $ifNull: ["$net", []] },
+ {
+ $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1],
+ },
+ ],
+ },
+ as: "iface",
+ in: "$$iface.err_out",
+ },
+ },
+ "$$netIndex",
+ ],
+ },
+ tFirst: { $arrayElemAt: ["$updatedAts", 0] },
+ tLast: {
+ $arrayElemAt: [
+ { $ifNull: ["$updatedAts", []] },
+ {
+ $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1],
+ },
+ ],
+ },
+ },
+ in: {
+ $cond: [
+ { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] },
+ {
+ $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }],
+ },
+ 0,
+ ],
+ },
+ },
+ },
+ deltaDropIn: {
+ $let: {
+ vars: {
+ first: {
+ $arrayElemAt: [
+ {
+ $map: {
+ input: { $arrayElemAt: ["$net", 0] },
+ as: "iface",
+ in: "$$iface.drop_in",
+ },
+ },
+ "$$netIndex",
+ ],
+ },
+ last: {
+ $arrayElemAt: [
+ {
+ $map: {
+ input: {
+ $arrayElemAt: [
+ { $ifNull: ["$net", []] },
+ {
+ $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1],
+ },
+ ],
+ },
+ as: "iface",
+ in: "$$iface.drop_in",
+ },
+ },
+ "$$netIndex",
+ ],
+ },
+ tFirst: { $arrayElemAt: ["$updatedAts", 0] },
+ tLast: {
+ $arrayElemAt: [
+ { $ifNull: ["$updatedAts", []] },
+ {
+ $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1],
+ },
+ ],
+ },
+ },
+ in: {
+ $cond: [
+ { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] },
+ {
+ $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }],
+ },
+ 0,
+ ],
+ },
+ },
+ },
+ deltaDropOut: {
+ $let: {
+ vars: {
+ first: {
+ $arrayElemAt: [
+ {
+ $map: {
+ input: { $arrayElemAt: ["$net", 0] },
+ as: "iface",
+ in: "$$iface.drop_out",
+ },
+ },
+ "$$netIndex",
+ ],
+ },
+ last: {
+ $arrayElemAt: [
+ {
+ $map: {
+ input: {
+ $arrayElemAt: [
+ { $ifNull: ["$net", []] },
+ {
+ $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1],
+ },
+ ],
+ },
+ as: "iface",
+ in: "$$iface.drop_out",
+ },
+ },
+ "$$netIndex",
+ ],
+ },
+ tFirst: { $arrayElemAt: ["$updatedAts", 0] },
+ tLast: {
+ $arrayElemAt: [
+ { $ifNull: ["$updatedAts", []] },
+ {
+ $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1],
+ },
+ ],
+ },
+ },
+ in: {
+ $cond: [
+ { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] },
+ {
+ $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }],
+ },
+ 0,
+ ],
},
},
},
},
},
},
+ firstUpdatedAt: 1,
+ lastUpdatedAt: 1,
},
},
],
@@ -674,12 +987,7 @@ const buildMonitorsWithChecksByTeamIdPipeline = ({ matchStage, filter, page, row
// Add checks
if (limit) {
- let checksCollection = "checks";
- if (type === "pagespeed") {
- checksCollection = "pagespeedchecks";
- } else if (type === "hardware") {
- checksCollection = "hardwarechecks";
- }
+ const checksCollection = "checks";
monitorsPipeline.push({
$lookup: {
from: checksCollection,
@@ -744,12 +1052,8 @@ const buildFilteredMonitorsByTeamIdPipeline = ({ matchStage, filter, page, rowsP
// Add checks
if (limit) {
- let checksCollection = "checks";
- if (type === "pagespeed") {
- checksCollection = "pagespeedchecks";
- } else if (type === "hardware") {
- checksCollection = "hardwarechecks";
- }
+ const checksCollection = "checks";
+
pipeline.push({
$lookup: {
from: checksCollection,
@@ -863,46 +1167,6 @@ const buildGetMonitorsByTeamIdPipeline = (req) => {
},
]
: []),
- ...(limit
- ? [
- {
- $lookup: {
- from: "pagespeedchecks",
- let: { monitorId: "$_id" },
- pipeline: [
- {
- $match: {
- $expr: { $eq: ["$monitorId", "$$monitorId"] },
- },
- },
- { $sort: { createdAt: -1 } },
- ...(limit ? [{ $limit: limit }] : []),
- ],
- as: "pagespeedchecks",
- },
- },
- ]
- : []),
- ...(limit
- ? [
- {
- $lookup: {
- from: "hardwarechecks",
- let: { monitorId: "$_id" },
- pipeline: [
- {
- $match: {
- $expr: { $eq: ["$monitorId", "$$monitorId"] },
- },
- },
- { $sort: { createdAt: -1 } },
- ...(limit ? [{ $limit: limit }] : []),
- ],
- as: "hardwarechecks",
- },
- },
- ]
- : []),
{
$addFields: {
@@ -913,14 +1177,6 @@ const buildGetMonitorsByTeamIdPipeline = (req) => {
case: { $in: ["$type", ["http", "ping", "docker", "port", "game"]] },
then: "$standardchecks",
},
- {
- case: { $eq: ["$type", "pagespeed"] },
- then: "$pagespeedchecks",
- },
- {
- case: { $eq: ["$type", "hardware"] },
- then: "$hardwarechecks",
- },
],
default: [],
},
@@ -930,8 +1186,6 @@ const buildGetMonitorsByTeamIdPipeline = (req) => {
{
$project: {
standardchecks: 0,
- pagespeedchecks: 0,
- hardwarechecks: 0,
},
},
],
diff --git a/server/src/db/mongo/modules/networkCheckModule.js b/server/src/db/mongo/modules/networkCheckModule.js
deleted file mode 100644
index 917e68440..000000000
--- a/server/src/db/mongo/modules/networkCheckModule.js
+++ /dev/null
@@ -1,30 +0,0 @@
-const SERVICE_NAME = "networkCheckModule";
-
-class NetworkCheckModule {
- constructor({ NetworkCheck }) {
- this.NetworkCheck = NetworkCheck;
- }
- createNetworkCheck = async (networkCheckData) => {
- try {
- const networkCheck = await new this.NetworkCheck(networkCheckData);
- await networkCheck.save();
- return networkCheck;
- } catch (error) {
- error.service = SERVICE_NAME;
- error.method = "createNetworkCheck";
- throw error;
- }
- };
- getNetworkChecksByMonitorId = async (monitorId, limit = 100) => {
- try {
- const networkChecks = await this.NetworkCheck.find({ monitorId }).sort({ createdAt: -1 }).limit(limit);
- return networkChecks;
- } catch (error) {
- error.service = SERVICE_NAME;
- error.method = "getNetworkChecksByMonitorId";
- throw error;
- }
- };
-}
-
-export default NetworkCheckModule;
diff --git a/server/src/db/mongo/modules/pageSpeedCheckModule.js b/server/src/db/mongo/modules/pageSpeedCheckModule.js
deleted file mode 100755
index b2a3eb71d..000000000
--- a/server/src/db/mongo/modules/pageSpeedCheckModule.js
+++ /dev/null
@@ -1,31 +0,0 @@
-// import PageSpeedCheck from "../../models/PageSpeedCheck.js";
-const SERVICE_NAME = "pageSpeedCheckModule";
-
-class PageSpeedCheckModule {
- constructor({ PageSpeedCheck }) {
- this.PageSpeedCheck = PageSpeedCheck;
- }
-
- createPageSpeedChecks = async (pageSpeedChecks) => {
- try {
- await this.PageSpeedCheck.insertMany(pageSpeedChecks, { ordered: false });
- return true;
- } catch (error) {
- error.service = SERVICE_NAME;
- error.method = "createPageSpeedCheck";
- throw error;
- }
- };
-
- deletePageSpeedChecksByMonitorId = async (monitorId) => {
- try {
- const result = await this.PageSpeedCheck.deleteMany({ monitorId });
- return result.deletedCount;
- } catch (error) {
- error.service = SERVICE_NAME;
- error.method = "deletePageSpeedChecksByMonitorId";
- throw error;
- }
- };
-}
-export default PageSpeedCheckModule;
diff --git a/server/src/db/mongo/modules/statusPageModule.js b/server/src/db/mongo/modules/statusPageModule.js
index 10710d0f7..aadde0c81 100755
--- a/server/src/db/mongo/modules/statusPageModule.js
+++ b/server/src/db/mongo/modules/statusPageModule.js
@@ -162,6 +162,14 @@ class StatusPageModule {
as: "monitors.checks",
},
},
+ {
+ $lookup: {
+ from: "monitorstats",
+ localField: "monitors._id",
+ foreignField: "monitorId",
+ as: "monitors.stats",
+ },
+ },
{
$addFields: {
"monitors.orderIndex": {
@@ -181,6 +189,9 @@ class StatusPageModule {
},
},
},
+ "monitors.uptimePercentage": {
+ $arrayElemAt: ["$monitors.stats.uptimePercentage", 0],
+ },
},
},
{ $match: { "monitors.orderIndex": { $ne: -1 } } },
diff --git a/server/src/index.js b/server/src/index.js
index 6b12eb25d..d1ff649eb 100755
--- a/server/src/index.js
+++ b/server/src/index.js
@@ -18,7 +18,7 @@ const startApp = async () => {
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const openApiSpec = JSON.parse(fs.readFileSync(path.join(__dirname, "../openapi.json"), "utf8"));
- const frontendPath = path.join(__dirname, "public");
+ const frontendPath = path.join(__dirname, "..", "public");
// Create services
const settingsService = new SettingsService(AppSettings);
diff --git a/server/src/service/business/checkService.js b/server/src/service/business/checkService.js
index 7966fdbb4..9beb43326 100644
--- a/server/src/service/business/checkService.js
+++ b/server/src/service/business/checkService.js
@@ -33,10 +33,9 @@ class CheckService {
throw this.errorService.createAuthorizationError();
}
- let { type, sortOrder, dateRange, filter, ack, page, rowsPerPage, status } = query;
+ let { sortOrder, dateRange, filter, ack, page, rowsPerPage, status } = query;
const result = await this.db.checkModule.getChecksByMonitor({
monitorId,
- type,
sortOrder,
dateRange,
filter,
diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js
index 60bcb0754..c1e82af65 100644
--- a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js
+++ b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js
@@ -54,6 +54,7 @@ class SuperSimpleQueue {
id: monitorId.toString(),
template: "monitor-job",
repeat: monitor.interval,
+ active: monitor.isActive,
data: monitor.toObject(),
});
};
@@ -87,7 +88,7 @@ class SuperSimpleQueue {
};
updateJob = async (monitor) => {
- this.scheduler.updateJob(monitor._id.toString(), monitor.interval);
+ this.scheduler.updateJob(monitor._id.toString(), { repeat: monitor.interval, data: monitor.toObject() });
};
shutdown = async () => {
@@ -140,6 +141,7 @@ class SuperSimpleQueue {
monitorId: job.id,
monitorUrl: job?.data?.url || null,
monitorType: job?.data?.type || null,
+ monitorInterval: job?.data?.interval || null,
active: job.active,
lockedAt: job.lockedAt,
runCount: job.runCount || 0,
diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js
index ca2e5f642..ac8da9169 100644
--- a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js
+++ b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js
@@ -2,6 +2,16 @@ const SERVICE_NAME = "JobQueueHelper";
class SuperSimpleQueueHelper {
static SERVICE_NAME = SERVICE_NAME;
+
+ /**
+ * @param {{
+ * db: import("../database").Database,
+ * logger: import("../logger").Logger,
+ * networkService: import("../networkService").NetworkService,
+ * statusService: import("../statusService").StatusService,
+ * notificationService: import("../notificationService").NotificationService
+ * }}
+ */
constructor({ db, logger, networkService, statusService, notificationService }) {
this.db = db;
this.logger = logger;
@@ -31,7 +41,8 @@ class SuperSimpleQueueHelper {
});
return;
}
- const networkResponse = await this.networkService.getStatus(monitor);
+ const networkResponse = await this.networkService.requestStatus(monitor);
+
if (!networkResponse) {
throw new Error("No network response");
}
diff --git a/server/src/service/infrastructure/bufferService.js b/server/src/service/infrastructure/bufferService.js
index dc86597b2..ed48cc654 100755
--- a/server/src/service/infrastructure/bufferService.js
+++ b/server/src/service/infrastructure/bufferService.js
@@ -1,32 +1,13 @@
const SERVICE_NAME = "BufferService";
-const TYPE_MAP = {
- http: "checks",
- ping: "checks",
- port: "checks",
- docker: "checks",
- pagespeed: "pagespeedChecks",
- hardware: "hardwareChecks",
- game: "checks",
-};
class BufferService {
static SERVICE_NAME = SERVICE_NAME;
constructor({ db, logger, envSettings }) {
- this.BUFFER_TIMEOUT = envSettings.nodeEnv === "development" ? 5000 : 1000 * 60 * 1; // 1 minute
+ this.BUFFER_TIMEOUT = envSettings.nodeEnv === "development" ? 1000 : 1000 * 60 * 1; // 1 minute
this.db = db;
this.logger = logger;
this.SERVICE_NAME = SERVICE_NAME;
- this.buffers = {
- checks: [],
- pagespeedChecks: [],
- hardwareChecks: [],
- };
- this.OPERATION_MAP = {
- checks: this.db.checkModule.createChecks,
- pagespeedChecks: this.db.pageSpeedCheckModule.createPageSpeedChecks,
- hardwareChecks: this.db.hardwareCheckModule.createHardwareChecks,
- };
-
+ this.buffer = [];
this.scheduleNextFlush();
this.logger.info({
message: `Buffer service initialized, flushing every ${this.BUFFER_TIMEOUT / 1000}s`,
@@ -39,9 +20,9 @@ class BufferService {
return BufferService.SERVICE_NAME;
}
- addToBuffer({ check, type }) {
+ addToBuffer({ check }) {
try {
- this.buffers[TYPE_MAP[type]].push(check);
+ this.buffer.push(check);
} catch (error) {
this.logger.error({
message: error.message,
@@ -55,7 +36,7 @@ class BufferService {
scheduleNextFlush() {
this.bufferTimer = setTimeout(async () => {
try {
- await this.flushBuffers();
+ await this.flushBuffer();
} catch (error) {
this.logger.error({
message: `Error in flush cycle: ${error.message}`,
@@ -69,35 +50,24 @@ class BufferService {
}
}, this.BUFFER_TIMEOUT);
}
- async flushBuffers() {
- let items = 0;
- for (const [bufferName, buffer] of Object.entries(this.buffers)) {
- items += buffer.length;
- const operation = this.OPERATION_MAP[bufferName];
- if (!operation) {
- this.logger.error({
- message: `No operation found for ${bufferName}`,
- service: this.SERVICE_NAME,
- method: "flushBuffers",
- });
- continue;
- }
- try {
- await operation(buffer);
- } catch (error) {
- this.logger.error({
- message: error.message,
- service: this.SERVICE_NAME,
- method: "flushBuffers",
- stack: error.stack,
- });
- }
- this.buffers[bufferName] = [];
+ async flushBuffer() {
+ let items = this.buffer.length;
+
+ try {
+ await this.db.checkModule.createChecks(this.buffer);
+ } catch (error) {
+ this.logger.error({
+ message: error.message,
+ service: this.SERVICE_NAME,
+ method: "flushBuffer",
+ stack: error.stack,
+ });
}
+ this.buffer = [];
this.logger.debug({
message: `Flushed ${items} items`,
service: this.SERVICE_NAME,
- method: "flushBuffers",
+ method: "flushBuffer",
});
items = 0;
}
diff --git a/server/src/service/infrastructure/networkService.js b/server/src/service/infrastructure/networkService.js
old mode 100755
new mode 100644
index 8f904bf56..50cf16b85
--- a/server/src/service/infrastructure/networkService.js
+++ b/server/src/service/infrastructure/networkService.js
@@ -1,23 +1,11 @@
-import jmespath from "jmespath";
-import https from "https";
-import { GameDig } from "gamedig";
+import { match } from "assert";
const SERVICE_NAME = "NetworkService";
-const UPROCK_ENDPOINT = "https://api.uprock.com/checkmate/push";
-/**
- * Constructs a new NetworkService instance.
- *
- * @param {Object} axios - The axios instance for HTTP requests.
- * @param {Object} ping - The ping utility for network checks.
- * @param {Object} logger - The logger instance for logging.
- * @param {Object} http - The HTTP utility for network operations.
- * @param {Object} net - The net utility for network operations.
- */
class NetworkService {
static SERVICE_NAME = SERVICE_NAME;
- constructor(axios, ping, logger, http, Docker, net, stringService, settingsService) {
+ constructor({ axios, got, https, jmespath, GameDig, ping, logger, http, Docker, net, stringService, settingsService }) {
this.TYPE_PING = "ping";
this.TYPE_HTTP = "http";
this.TYPE_PAGESPEED = "pagespeed";
@@ -29,6 +17,10 @@ class NetworkService {
this.NETWORK_ERROR = 5000;
this.PING_ERROR = 5001;
this.axios = axios;
+ this.got = got;
+ this.https = https;
+ this.jmespath = jmespath;
+ this.GameDig = GameDig;
this.ping = ping;
this.logger = logger;
this.http = http;
@@ -38,153 +30,147 @@ class NetworkService {
this.settingsService = settingsService;
}
- get serviceName() {
- return NetworkService.SERVICE_NAME;
- }
-
- /**
- * Times the execution of an asynchronous operation.
- *
- * @param {Function} operation - The asynchronous operation to be timed.
- * @returns {Promise