resolve: merge conflicts with develop branch
- Resolved conflict in translationService.js by using develop branch path format - Updated locales directory path to use 'src/locales' format - Maintained existing login error handling improvements
This commit is contained in:
commit
25d538504c
154 files changed
+7691
-4966
No files matched your search
@@ -0,0 +1 @@
|
||||
release_notes: false
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
.vscode
|
||||
@@ -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.
|
||||
|
||||
|
||||
Generated
+12
-633
@@ -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"
|
||||
|
||||
+1
-4
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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 (
|
||||
<Box
|
||||
position="relative"
|
||||
width={width}
|
||||
height={height}
|
||||
backgroundColor={backgroundColor}
|
||||
sx={{
|
||||
borderRadius: borderRadius || theme.spacing(1.5),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<Box
|
||||
ref={ref}
|
||||
position="relative"
|
||||
width={width}
|
||||
height={height}
|
||||
backgroundColor={backgroundColor}
|
||||
sx={{
|
||||
borderRadius: borderRadius || theme.spacing(1.5),
|
||||
}}
|
||||
{...otherProps}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 (
|
||||
<Text
|
||||
x={x - 20}
|
||||
y={y}
|
||||
textAnchor="middle"
|
||||
fill={theme.palette.primary.contrastTextTertiary}
|
||||
fontSize={11}
|
||||
fontWeight={400}
|
||||
>
|
||||
{formatter(payload?.value, true)}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
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])}`}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }) =
|
||||
>
|
||||
<Stack
|
||||
gap={theme.spacing(2)}
|
||||
width={width}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
@@ -39,6 +40,7 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) =
|
||||
fontSize={16}
|
||||
color={theme.palette.primary.contrastText}
|
||||
fontWeight={600}
|
||||
marginBottom={theme.spacing(4)}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
@@ -46,6 +48,7 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) =
|
||||
<Typography
|
||||
id={descriptionId}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
marginBottom={theme.spacing(4)}
|
||||
>
|
||||
{description}
|
||||
</Typography>
|
||||
@@ -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 };
|
||||
@@ -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 = () => {
|
||||
<Box
|
||||
component="img"
|
||||
src={mode === "light" ? OutputAnimation : DarkmodeOutput}
|
||||
Background="transparent"
|
||||
background="transparent"
|
||||
alt="Loading animation"
|
||||
sx={{
|
||||
zIndex: 1,
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import GenericFallback from "../GenericFallback";
|
||||
import Fallback from "../Fallback";
|
||||
import { Typography } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useIsAdmin } from "../../Hooks/useIsAdmin";
|
||||
import PropTypes from "prop-types";
|
||||
const PageStateWrapper = ({
|
||||
networkError,
|
||||
isLoading,
|
||||
items,
|
||||
type,
|
||||
fallbackLink,
|
||||
fallbackChildren,
|
||||
children,
|
||||
}) => {
|
||||
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 (
|
||||
<GenericFallback>
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{t("common.toasts.networkError")}
|
||||
</Typography>
|
||||
<Typography>{t("common.toasts.checkConnection")}</Typography>
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
if (!hasInitialized) {
|
||||
return null;
|
||||
}
|
||||
if (showEmpty) {
|
||||
return (
|
||||
<Fallback
|
||||
type={type}
|
||||
title={t(`${type}.fallback.title`)}
|
||||
checks={t(`${type}.fallback.checks`, { returnObjects: true })}
|
||||
link={fallbackLink}
|
||||
isAdmin={isAdmin}
|
||||
>
|
||||
{fallbackChildren}
|
||||
</Fallback>
|
||||
);
|
||||
}
|
||||
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;
|
||||
@@ -74,7 +74,7 @@ const StarPrompt = ({ repoUrl = "https://github.com/bluewave-labs/checkmate" })
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
color: theme.palette.text.secondary,
|
||||
color: theme.palette.primary.contrastTextTertiary,
|
||||
fontSize: "0.938rem",
|
||||
lineHeight: 1.5,
|
||||
mb: 1,
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
TableHead,
|
||||
TableRow,
|
||||
} from "@mui/material";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import SkeletonLayout from "./skeleton";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@emotion/react";
|
||||
@@ -41,6 +42,7 @@ const DataTable = ({
|
||||
data = [],
|
||||
config = {
|
||||
emptyView: "No data",
|
||||
tooltipContent: null,
|
||||
onRowClick: () => {},
|
||||
},
|
||||
}) => {
|
||||
@@ -101,24 +103,54 @@ const DataTable = ({
|
||||
data.map((row) => {
|
||||
const key = row.id || row._id || Math.random();
|
||||
return (
|
||||
<TableRow
|
||||
<Tooltip
|
||||
key={key}
|
||||
sx={config?.rowSX ?? {}}
|
||||
onClick={config?.onRowClick ? () => 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 (
|
||||
<TableCell
|
||||
align={index === 0 ? "left" : "center"}
|
||||
key={header.id}
|
||||
onClick={header.onClick ? (e) => header.onClick(e, row) : null}
|
||||
sx={header.getCellSx ? header.getCellSx(row) : {}}
|
||||
>
|
||||
{header.render(row)}
|
||||
</TableCell>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
<TableRow
|
||||
sx={config?.rowSX ?? {}}
|
||||
onClick={config?.onRowClick ? () => config.onRowClick(row) : null}
|
||||
>
|
||||
{headers.map((header, index) => {
|
||||
return (
|
||||
<TableCell
|
||||
align={index === 0 ? "left" : "center"}
|
||||
key={header.id}
|
||||
onClick={header.onClick ? (e) => header.onClick(e, row) : null}
|
||||
sx={header.getCellSx ? header.getCellSx(row) : {}}
|
||||
>
|
||||
{header.render(row)}
|
||||
</TableCell>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
</Tooltip>
|
||||
);
|
||||
})
|
||||
)}
|
||||
|
||||
@@ -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 };
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
endIcon={<ArrowDropDownIcon sx={{ color: theme.palette.secondary.light }} />}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{t("teamPanel.addTeamMember.addMemberMenu")}
|
||||
</Button>
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
sx={{
|
||||
"& .MuiPaper-root": {
|
||||
minWidth: anchorEl?.offsetWidth || "auto",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
handleClose();
|
||||
handleInviteOpen();
|
||||
}}
|
||||
>
|
||||
{t("teamPanel.inviteTeamMember")}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
handleClose();
|
||||
handleIsRegisterOpen(true);
|
||||
}}
|
||||
>
|
||||
{t("teamPanel.register")}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
AddMemberMenu.propTypes = {
|
||||
handleInviteOpen: Proptypes.func.isRequired,
|
||||
handleIsRegisterOpen: Proptypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default AddMemberMenu;
|
||||
@@ -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;
|
||||
@@ -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 (
|
||||
<>
|
||||
<GenericDialog
|
||||
title={t("teamPanel.addTeamMember.title")}
|
||||
description={t("teamPanel.addTeamMember.description")}
|
||||
open={isRegisterOpen}
|
||||
onClose={closeAddMemberModal}
|
||||
theme={theme}
|
||||
width={{ sm: "55%", md: "50%", lg: "40%", xl: "30%" }}
|
||||
>
|
||||
<TextInput
|
||||
name="firstName"
|
||||
label={t("auth.common.inputs.firstName.label")}
|
||||
isRequired={true}
|
||||
gap={theme.spacing(4)}
|
||||
placeholder={t("auth.common.inputs.firstName.placeholder")}
|
||||
value={form.firstName}
|
||||
onChange={onChange}
|
||||
error={errors.firstName ? true : false}
|
||||
helperText={errors.firstName ? tErr(errors.firstName) : null}
|
||||
sx={{ mb: errors.firstName ? theme.spacing(15) : theme.spacing(5) }}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
name="lastName"
|
||||
label={t("auth.common.inputs.lastName.label")}
|
||||
isRequired={true}
|
||||
gap={theme.spacing(4)}
|
||||
placeholder={t("auth.common.inputs.lastName.placeholder")}
|
||||
value={form.lastName}
|
||||
onChange={onChange}
|
||||
error={errors.lastName ? true : false}
|
||||
helperText={errors.lastName ? tErr(errors.lastName) : null}
|
||||
sx={{ mb: errors.lastName ? theme.spacing(15) : theme.spacing(5) }}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
type="email"
|
||||
label={t("auth.common.inputs.email.label")}
|
||||
name="email"
|
||||
gap={theme.spacing(4)}
|
||||
isRequired={true}
|
||||
id="input-team-member"
|
||||
placeholder={t("teamPanel.email")}
|
||||
value={form.email}
|
||||
onChange={onChange}
|
||||
error={errors.email ? true : false}
|
||||
helperText={errors.email ? tErr(errors.email) : null}
|
||||
sx={{ mb: errors.email ? theme.spacing(15) : theme.spacing(5) }}
|
||||
/>
|
||||
|
||||
<Select
|
||||
label={t("teamPanel.role")}
|
||||
id="team-member-role"
|
||||
name="role"
|
||||
required={true}
|
||||
placeholder={t("teamPanel.selectRole")}
|
||||
isHidden={true}
|
||||
value={role[0] || ""}
|
||||
sx={{ mb: theme.spacing(5) }}
|
||||
onChange={(event) => setRole([event.target.value])}
|
||||
items={[
|
||||
{ _id: "admin", name: t("roles.admin") },
|
||||
{ _id: "user", name: t("roles.teamMember") },
|
||||
]}
|
||||
/>
|
||||
|
||||
<PasswordTooltip
|
||||
feedback={feedback}
|
||||
form={form}
|
||||
>
|
||||
<TextInput
|
||||
type="password"
|
||||
id="register-password-input"
|
||||
name="password"
|
||||
label={t("auth.common.inputs.password.label")}
|
||||
isRequired={true}
|
||||
placeholder="••••••••••"
|
||||
value={form.password}
|
||||
onChange={onChange}
|
||||
error={errors.password && errors.password[0] ? true : false}
|
||||
endAdornment={<PasswordEndAdornment />}
|
||||
sx={{ mb: theme.spacing(5) }}
|
||||
/>
|
||||
</PasswordTooltip>
|
||||
<TextInput
|
||||
type="password"
|
||||
id="register-confirm-input"
|
||||
name="confirm"
|
||||
label={t("auth.common.inputs.passwordConfirm.label")}
|
||||
gap={theme.spacing(4)}
|
||||
isRequired={true}
|
||||
placeholder={t("auth.common.inputs.passwordConfirm.placeholder")}
|
||||
autoComplete="current-password"
|
||||
value={form.confirm}
|
||||
onChange={onChange}
|
||||
error={errors.confirm && errors.confirm[0] ? true : false}
|
||||
endAdornment={<PasswordEndAdornment />}
|
||||
sx={{ mb: theme.spacing(5) }}
|
||||
/>
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={theme.spacing(10)}
|
||||
mt={theme.spacing(8)}
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={closeAddMemberModal}
|
||||
disabled={isLoadingSubmit}
|
||||
>
|
||||
{t("teamPanel.cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={onsubmitAddMember}
|
||||
disabled={isLoadingSubmit}
|
||||
>
|
||||
{t("teamPanel.addTeamMember.addButton")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</GenericDialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
AddTeamMember.propTypes = {
|
||||
handleIsRegisterOpen: PropTypes.func.isRequired,
|
||||
isRegisterOpen: PropTypes.bool.isRequired,
|
||||
onMemberAdded: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default AddTeamMember;
|
||||
@@ -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 = () => {
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={() => setIsOpen(true)}
|
||||
>
|
||||
{t("teamPanel.inviteTeamMember")}
|
||||
</Button>
|
||||
|
||||
<AddTeamMember
|
||||
handleIsRegisterOpen={handleIsRegisterOpen}
|
||||
isRegisterOpen={isRegisterOpen}
|
||||
onMemberAdded={refreshTeamList}
|
||||
/>
|
||||
<AddMemberMenu
|
||||
handleInviteOpen={() => setIsOpen(true)}
|
||||
handleIsRegisterOpen={handleIsRegisterOpen}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<DataTable
|
||||
@@ -264,7 +275,6 @@ const TeamPanel = () => {
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<GenericDialog
|
||||
title={t("teamPanel.inviteNewTeamMember")}
|
||||
description={t("teamPanel.inviteDescription")}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// Hook to avoid double submits and manage loading state
|
||||
import { useState, useCallback } from "react";
|
||||
|
||||
const useLoadingSubmit = () => {
|
||||
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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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 (
|
||||
<AuthPageWrapper
|
||||
@@ -109,7 +60,7 @@ const Login = () => {
|
||||
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={<PasswordEndAdornment />}
|
||||
@@ -130,6 +81,7 @@ const Login = () => {
|
||||
color="accent"
|
||||
type="submit"
|
||||
sx={{ width: "100%", alignSelf: "center", fontWeight: 700 }}
|
||||
loading={submitting}
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
@@ -147,5 +99,4 @@ const Login = () => {
|
||||
</AuthPageWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
@@ -93,14 +93,14 @@ const PasswordTooltip = ({ feedback, form, children }) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<span style={{ display: "inline-block", width: "100%" }}>{children}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
PasswordTooltip.propTypes = {
|
||||
feedback: PropTypes.shape({
|
||||
length: PropTypes.string.isRequired,
|
||||
length: PropTypes.string,
|
||||
special: PropTypes.string,
|
||||
number: PropTypes.string,
|
||||
uppercase: PropTypes.string,
|
||||
|
||||
@@ -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;
|
||||
@@ -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 (
|
||||
<Stack
|
||||
backgroundColor={lighten(theme.palette.primary.main, 0.1)}
|
||||
border={`1px solid ${theme.palette.primary.lowContrast}`}
|
||||
borderRadius={theme.shape.borderRadius}
|
||||
py={theme.spacing(2)}
|
||||
px={theme.spacing(4)}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color={theme.palette.primary.contrastText}
|
||||
>{`Status code: ${row?.statusCode}`}</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color={theme.palette.primary.contrastText}
|
||||
>{`Response time: ${row?.responseTime} ms`}</Typography>
|
||||
{phases && (
|
||||
<>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color={theme.palette.primary.contrastText}
|
||||
>{`Request timing: `}</Typography>
|
||||
<Table
|
||||
size="small"
|
||||
sx={{ ml: theme.spacing(2), mt: theme.spacing(2) }}
|
||||
>
|
||||
<TableBody>
|
||||
{Object.keys(phases)?.map((phaseKey) => (
|
||||
<TableRow key={phaseKey}>
|
||||
<TableCell sx={{ border: "none", p: 0 }}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="success"
|
||||
>
|
||||
{`${phaseKeyFormattingMap[phaseKey] || phaseKey}:`}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell sx={{ border: "none", p: 0 }}>
|
||||
<Typography
|
||||
color={theme.palette.primary.contrastText}
|
||||
variant="body2"
|
||||
>{`${phases[phaseKey]} ms`}</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const IncidentTable = ({
|
||||
isLoading,
|
||||
@@ -167,9 +233,10 @@ const IncidentTable = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table
|
||||
<DataTable
|
||||
headers={headers}
|
||||
data={checks}
|
||||
config={{ tooltipContent: GetTooltip }}
|
||||
/>
|
||||
<Pagination
|
||||
paginationLabel={t("incidentsTablePaginationLabel")}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import ConfigBox from "../../../../Components/ConfigBox";
|
||||
import { Box, Stack, Typography } from "@mui/material";
|
||||
import { CustomThreshold } from "../Components/CustomThreshold";
|
||||
import { capitalizeFirstLetter } from "../../../../Utils/stringUtils";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import PropTypes from "prop-types";
|
||||
const CustomAlertsSection = ({
|
||||
errors,
|
||||
onChange,
|
||||
infrastructureMonitor,
|
||||
handleCheckboxChange,
|
||||
}) => {
|
||||
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 (
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="h2"
|
||||
>
|
||||
{t("infrastructureCustomizeAlerts")}
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
{t("infrastructureAlertNotificationDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
{METRICS.map((metric) => {
|
||||
return (
|
||||
<CustomThreshold
|
||||
key={metric}
|
||||
infrastructureMonitor={infrastructureMonitor}
|
||||
errors={errors}
|
||||
checkboxId={metric}
|
||||
checkboxName={metric}
|
||||
checkboxLabel={
|
||||
metric !== "cpu" ? capitalizeFirstLetter(metric) : metric.toUpperCase()
|
||||
}
|
||||
onCheckboxChange={handleCheckboxChange}
|
||||
isChecked={infrastructureMonitor[metric]}
|
||||
fieldId={METRIC_PREFIX + metric}
|
||||
fieldName={METRIC_PREFIX + metric}
|
||||
fieldValue={String(infrastructureMonitor[METRIC_PREFIX + metric])}
|
||||
onFieldChange={onChange}
|
||||
alertUnit={metric == "temperature" ? "°C" : "%"}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{hasAlertError(errors) && (
|
||||
<Typography
|
||||
component="span"
|
||||
className="input-error"
|
||||
color={theme.palette.error.main}
|
||||
mt={theme.spacing(2)}
|
||||
sx={{
|
||||
opacity: 0.8,
|
||||
}}
|
||||
>
|
||||
{getAlertError(errors)}
|
||||
</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
);
|
||||
};
|
||||
|
||||
CustomAlertsSection.propTypes = {
|
||||
errors: PropTypes.object.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
infrastructureMonitor: PropTypes.object.isRequired,
|
||||
handleCheckboxChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default CustomAlertsSection;
|
||||
@@ -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 (
|
||||
<Box
|
||||
alignSelf="flex-end"
|
||||
ml="auto"
|
||||
>
|
||||
<Button
|
||||
onClick={handlePause}
|
||||
loading={isBusy}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
sx={{
|
||||
pl: theme.spacing(4),
|
||||
pr: theme.spacing(6),
|
||||
"& svg": {
|
||||
mr: theme.spacing(2),
|
||||
"& path": {
|
||||
stroke: theme.palette.primary.contrastTextTertiary,
|
||||
strokeWidth: 0.1,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{monitor?.isActive ? (
|
||||
<>
|
||||
<PauseCircleOutlineIcon />
|
||||
{t("pause")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<PlayCircleOutlineRoundedIcon />
|
||||
{t("resume")}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
loading={isBusy}
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={() => setIsOpen(true)}
|
||||
sx={{ ml: theme.spacing(6) }}
|
||||
>
|
||||
{t("remove")}
|
||||
</Button>
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
theme={theme}
|
||||
title={t("deleteDialogTitle")}
|
||||
description={t("deleteDialogDescription")}
|
||||
onCancel={() => setIsOpen(false)}
|
||||
confirmationButtonLabel={t("delete")}
|
||||
onConfirm={handleRemove}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
MonitorActionButtons.propTypes = {
|
||||
monitor: PropTypes.object.isRequired,
|
||||
isBusy: PropTypes.bool.isRequired,
|
||||
handlePause: PropTypes.func.isRequired,
|
||||
handleRemove: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default MonitorActionButtons;
|
||||
@@ -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 (
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
height="fit-content"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Tooltip
|
||||
title={pagespeedStatusMsg[determineState(monitor)]}
|
||||
disableInteractive
|
||||
slotProps={{
|
||||
popper: {
|
||||
modifiers: [
|
||||
{
|
||||
name: "offset",
|
||||
options: { offset: [0, -8] },
|
||||
},
|
||||
],
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<PulseDot color={statusColor[determineState(monitor)]} />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="monitorUrl"
|
||||
>
|
||||
{infrastructureMonitor.url?.replace(/^https?:\/\//, "") || "..."}
|
||||
</Typography>
|
||||
<Typography
|
||||
position="relative"
|
||||
variant="body2"
|
||||
ml={theme.spacing(6)}
|
||||
mt={theme.spacing(1)}
|
||||
sx={{
|
||||
"&:before": {
|
||||
position: "absolute",
|
||||
content: `""`,
|
||||
width: theme.spacing(2),
|
||||
height: theme.spacing(2),
|
||||
borderRadius: "50%",
|
||||
backgroundColor: theme.palette.primary.contrastTextTertiary,
|
||||
opacity: 0.8,
|
||||
left: theme.spacing(-5),
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{t("editing")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
MonitorStatusHeader.propTypes = {
|
||||
monitor: PropTypes.object.isRequired,
|
||||
infrastructureMonitor: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default MonitorStatusHeader;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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 = () => {
|
||||
)}
|
||||
</Typography>
|
||||
{!isCreate && (
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
height="fit-content"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Tooltip
|
||||
title={pagespeedStatusMsg[determineState(monitor)]}
|
||||
disableInteractive
|
||||
slotProps={{
|
||||
popper: {
|
||||
modifiers: [
|
||||
{
|
||||
name: "offset",
|
||||
options: { offset: [0, -8] },
|
||||
},
|
||||
],
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<PulseDot color={statusColor[determineState(monitor)]} />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="monitorUrl"
|
||||
>
|
||||
{infrastructureMonitor.url?.replace(/^https?:\/\//, "") || "..."}
|
||||
</Typography>
|
||||
<Typography
|
||||
position="relative"
|
||||
variant="body2"
|
||||
ml={theme.spacing(6)}
|
||||
mt={theme.spacing(1)}
|
||||
sx={{
|
||||
"&:before": {
|
||||
position: "absolute",
|
||||
content: `""`,
|
||||
width: theme.spacing(2),
|
||||
height: theme.spacing(2),
|
||||
borderRadius: "50%",
|
||||
backgroundColor: theme.palette.primary.contrastTextTertiary,
|
||||
opacity: 0.8,
|
||||
left: theme.spacing(-5),
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{t("editing")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<MonitorStatusHeader
|
||||
monitor={monitor}
|
||||
infrastructureMonitor={infrastructureMonitor}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
{!isCreate && (
|
||||
<Box
|
||||
alignSelf="flex-end"
|
||||
ml="auto"
|
||||
>
|
||||
<Button
|
||||
onClick={handlePause}
|
||||
loading={isBusy}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
sx={{
|
||||
pl: theme.spacing(4),
|
||||
pr: theme.spacing(6),
|
||||
"& svg": {
|
||||
mr: theme.spacing(2),
|
||||
"& path": {
|
||||
stroke: theme.palette.primary.contrastTextTertiary,
|
||||
strokeWidth: 0.1,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{monitor?.isActive ? (
|
||||
<>
|
||||
<PauseCircleOutlineIcon />
|
||||
{t("pause")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<PlayCircleOutlineRoundedIcon />
|
||||
{t("resume")}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
loading={isBusy}
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={() => setIsOpen(true)}
|
||||
sx={{ ml: theme.spacing(6) }}
|
||||
>
|
||||
{t("remove")}
|
||||
</Button>
|
||||
</Box>
|
||||
<MonitorActionButtons
|
||||
monitor={monitor}
|
||||
isBusy={isBusy}
|
||||
handlePause={handlePause}
|
||||
handleRemove={handleRemove}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
<ConfigBox>
|
||||
@@ -534,58 +281,12 @@ const CreateInfrastructureMonitor = () => {
|
||||
setNotifications={infrastructureMonitor.notifications}
|
||||
/>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="h2"
|
||||
>
|
||||
{t("infrastructureCustomizeAlerts")}
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
{t("infrastructureAlertNotificationDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
{METRICS.map((metric) => {
|
||||
return (
|
||||
<CustomThreshold
|
||||
key={metric}
|
||||
infrastructureMonitor={infrastructureMonitor}
|
||||
errors={errors}
|
||||
checkboxId={metric}
|
||||
checkboxName={metric}
|
||||
checkboxLabel={
|
||||
metric !== "cpu"
|
||||
? capitalizeFirstLetter(metric)
|
||||
: metric.toUpperCase()
|
||||
}
|
||||
onCheckboxChange={handleCheckboxChange}
|
||||
isChecked={infrastructureMonitor[metric]}
|
||||
fieldId={METRIC_PREFIX + metric}
|
||||
fieldName={METRIC_PREFIX + metric}
|
||||
fieldValue={String(infrastructureMonitor[METRIC_PREFIX + metric])}
|
||||
onFieldChange={onChange}
|
||||
alertUnit={metric == "temperature" ? "°C" : "%"}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{/* Error text */}
|
||||
{hasAlertError(errors) && (
|
||||
<Typography
|
||||
component="span"
|
||||
className="input-error"
|
||||
color={theme.palette.error.main}
|
||||
mt={theme.spacing(2)}
|
||||
sx={{
|
||||
opacity: 0.8,
|
||||
}}
|
||||
>
|
||||
{getAlertError(errors)}
|
||||
</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
<CustomAlertsSection
|
||||
errors={errors}
|
||||
onChange={onChange}
|
||||
infrastructureMonitor={infrastructureMonitor}
|
||||
handleCheckboxChange={handleCheckboxChange}
|
||||
/>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography
|
||||
@@ -620,17 +321,6 @@ const CreateInfrastructureMonitor = () => {
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
{!isCreate && (
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
theme={theme}
|
||||
title={t("deleteDialogTitle")}
|
||||
description={t("deleteDialogDescription")}
|
||||
onCancel={() => setIsOpen(false)}
|
||||
confirmationButtonLabel={t("delete")}
|
||||
onConfirm={handleRemove}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 <Typography>{t("noNetworkStatsAvailable")}</Typography>;
|
||||
}
|
||||
|
||||
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: <TzTick dateRange={dateRange} />,
|
||||
yTick: <NetworkTick formatter={formatBytesPerSecondString} />,
|
||||
toolTip: (
|
||||
<InfrastructureTooltip
|
||||
dotColor={theme.palette.info.main}
|
||||
yKey={"bytesPerSec"}
|
||||
yLabel={t("dataRate") + ": "}
|
||||
dateRange={dateRange}
|
||||
formatter={formatBytesPerSecondString}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
type: "network-packets",
|
||||
data: ethernetData,
|
||||
dataKeys: ["packetsPerSec"],
|
||||
heading: t("packetsReceivedRate"),
|
||||
strokeColor: theme.palette.success.main,
|
||||
gradientStartColor: theme.palette.success.main,
|
||||
yLabel: t("rate"),
|
||||
xTick: <TzTick dateRange={dateRange} />,
|
||||
yTick: <NetworkTick formatter={formatPacketsPerSecondString} />,
|
||||
toolTip: (
|
||||
<InfrastructureTooltip
|
||||
dotColor={theme.palette.success.main}
|
||||
yKey={"packetsPerSec"}
|
||||
yLabel={t("packetsPerSecond") + ": "}
|
||||
dateRange={dateRange}
|
||||
formatter={formatPacketsPerSecondString}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
type: "network-errors",
|
||||
data: ethernetData,
|
||||
dataKeys: ["errors"],
|
||||
heading: t("networkErrors"),
|
||||
strokeColor: theme.palette.error.main,
|
||||
gradientStartColor: theme.palette.error.main,
|
||||
yLabel: t("errors"),
|
||||
xTick: <TzTick dateRange={dateRange} />,
|
||||
toolTip: (
|
||||
<InfrastructureTooltip
|
||||
dotColor={theme.palette.error.main}
|
||||
yKey={"errors"}
|
||||
yLabel={t("errors") + ": "}
|
||||
dateRange={dateRange}
|
||||
formatter={(value) => 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: <TzTick dateRange={dateRange} />,
|
||||
toolTip: (
|
||||
<InfrastructureTooltip
|
||||
dotColor={theme.palette.warning.main}
|
||||
yKey={"drops"}
|
||||
yLabel={t("drops") + ": "}
|
||||
dateRange={dateRange}
|
||||
formatter={(value) => Math.round(value).toLocaleString()}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction={"row"}
|
||||
gap={theme.spacing(8)}
|
||||
flexWrap="wrap"
|
||||
sx={{
|
||||
"& > *": {
|
||||
flexBasis: `calc(50% - ${theme.spacing(8)})`,
|
||||
maxWidth: `calc(50% - ${theme.spacing(8)})`,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{configs.map((config) => (
|
||||
<InfraAreaChart
|
||||
key={config.type}
|
||||
config={config}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
NetworkCharts.propTypes = {
|
||||
ethernetData: PropTypes.array.isRequired,
|
||||
dateRange: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default NetworkCharts;
|
||||
@@ -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 <Typography>{t("noNetworkStatsAvailable")}</Typography>;
|
||||
}
|
||||
|
||||
return (
|
||||
<StatusBoxes
|
||||
shouldRender={shouldRender}
|
||||
flexWrap="wrap"
|
||||
>
|
||||
{filtered
|
||||
.map((iface) => [
|
||||
<StatBox
|
||||
key={`${iface.name}-bytes-sent`}
|
||||
heading={t("bytesSent")}
|
||||
subHeading={formatBytes(iface.bytes_sent)}
|
||||
/>,
|
||||
<StatBox
|
||||
key={`${iface.name}-bytes-recv`}
|
||||
heading={t("bytesReceived")}
|
||||
subHeading={formatBytes(iface.bytes_recv)}
|
||||
/>,
|
||||
<StatBox
|
||||
key={`${iface.name}-packets-sent`}
|
||||
heading={t("packetsSent")}
|
||||
subHeading={formatNumber(iface.packets_sent)}
|
||||
/>,
|
||||
<StatBox
|
||||
key={`${iface.name}-packets-recv`}
|
||||
heading={t("packetsReceived")}
|
||||
subHeading={formatNumber(iface.packets_recv)}
|
||||
/>,
|
||||
<StatBox
|
||||
key={`${iface.name}-err-in`}
|
||||
heading={t("errorsIn")}
|
||||
subHeading={formatNumber(iface.err_in)}
|
||||
/>,
|
||||
<StatBox
|
||||
key={`${iface.name}-err-out`}
|
||||
heading={t("errorsOut")}
|
||||
subHeading={formatNumber(iface.err_out)}
|
||||
/>,
|
||||
])
|
||||
.flat()}
|
||||
</StatusBoxes>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
@@ -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 (
|
||||
<>
|
||||
<NetworkStatBoxes
|
||||
shouldRender={!isLoading}
|
||||
net={net}
|
||||
ifaceName={selectedInterface}
|
||||
/>
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
alignItems="flex-end"
|
||||
gap={theme.spacing(4)}
|
||||
>
|
||||
{availableInterfaces.length > 0 && (
|
||||
<Select
|
||||
name="networkInterface"
|
||||
label={t("networkInterface")}
|
||||
value={selectedInterface}
|
||||
onChange={(e) => setSelectedInterface(e.target.value)}
|
||||
items={availableInterfaces.map((interfaceName) => ({
|
||||
_id: interfaceName,
|
||||
name: interfaceName,
|
||||
}))}
|
||||
sx={{ minWidth: 200 }}
|
||||
/>
|
||||
)}
|
||||
<MonitorTimeFrameHeader
|
||||
isLoading={isLoading}
|
||||
dateRange={dateRange}
|
||||
setDateRange={setDateRange}
|
||||
/>
|
||||
</Box>
|
||||
<NetworkCharts
|
||||
ethernetData={ethernetData}
|
||||
dateRange={dateRange}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Network.propTypes = {
|
||||
net: PropTypes.array,
|
||||
checks: PropTypes.array,
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
dateRange: PropTypes.string.isRequired,
|
||||
setDateRange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Network;
|
||||
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
Skeleton,
|
||||
Table,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableBody,
|
||||
} from "@mui/material";
|
||||
|
||||
const SkeletonLayout = () => {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Skeleton
|
||||
variant="text"
|
||||
width={180}
|
||||
height={32}
|
||||
/>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Bytes Sent</TableCell>
|
||||
<TableCell>Bytes Received</TableCell>
|
||||
<TableCell>Packets Sent</TableCell>
|
||||
<TableCell>Packets Received</TableCell>
|
||||
<TableCell>Errors In</TableCell>
|
||||
<TableCell>Errors Out</TableCell>
|
||||
<TableCell>Drops In</TableCell>
|
||||
<TableCell>Drops Out</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{Array.from({ length: 5 }).map((_, idx) => (
|
||||
<TableRow key={idx}>
|
||||
{Array.from({ length: 9 }).map((__, colIdx) => (
|
||||
<TableCell key={colIdx}>
|
||||
<Skeleton
|
||||
variant="text"
|
||||
width={80}
|
||||
height={24}
|
||||
/>
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default SkeletonLayout;
|
||||
@@ -57,7 +57,7 @@ const useHardwareUtils = () => {
|
||||
if (GB >= 1) {
|
||||
return (
|
||||
<>
|
||||
{Number(GB.toFixed(0))}
|
||||
{Number(GB.toFixed(2))}
|
||||
{space ? " " : ""}
|
||||
<Typography component="span">{t("gb")}</Typography>
|
||||
</>
|
||||
@@ -65,7 +65,7 @@ const useHardwareUtils = () => {
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
{Number(MB.toFixed(0))}
|
||||
{Number(MB.toFixed(2))}
|
||||
{space ? " " : ""}
|
||||
<Typography component="span">{t("mb")}</Typography>
|
||||
</>
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
<StatusBoxes
|
||||
shouldRender={!isLoading}
|
||||
monitor={monitor}
|
||||
/>
|
||||
<GaugeBoxes
|
||||
isLoading={isLoading}
|
||||
monitor={monitor}
|
||||
/>
|
||||
<MonitorTimeFrameHeader
|
||||
isLoading={isLoading}
|
||||
dateRange={dateRange}
|
||||
setDateRange={setDateRange}
|
||||
/>
|
||||
<AreaChartBoxes
|
||||
shouldRender={!isLoading}
|
||||
monitor={monitor}
|
||||
/>
|
||||
<TabContext value={tab}>
|
||||
<CustomTabList
|
||||
value={tab}
|
||||
onChange={(e, v) => setTab(v)}
|
||||
>
|
||||
<Tab
|
||||
label={t("details")}
|
||||
value="details"
|
||||
/>
|
||||
<Tab
|
||||
label={t("network")}
|
||||
value="network"
|
||||
/>
|
||||
</CustomTabList>
|
||||
{tab === "details" && (
|
||||
<>
|
||||
<StatusBoxes
|
||||
shouldRender={!isLoading}
|
||||
monitor={monitor}
|
||||
/>
|
||||
<GaugeBoxes
|
||||
isLoading={isLoading}
|
||||
monitor={monitor}
|
||||
/>
|
||||
<MonitorTimeFrameHeader
|
||||
isLoading={isLoading}
|
||||
dateRange={dateRange}
|
||||
setDateRange={setDateRange}
|
||||
/>
|
||||
<AreaChartBoxes
|
||||
shouldRender={!isLoading}
|
||||
monitor={monitor}
|
||||
dateRange={dateRange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{tab === "network" && (
|
||||
<NetworkStats
|
||||
net={monitor?.stats?.aggregateData?.latestCheck?.net || []}
|
||||
isLoading={isLoading}
|
||||
checks={monitor?.stats?.checks}
|
||||
dateRange={dateRange}
|
||||
setDateRange={setDateRange}
|
||||
/>
|
||||
)}
|
||||
</TabContext>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 (
|
||||
<GenericFallback>
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{t("common.toasts.networkError")}
|
||||
</Typography>
|
||||
<Typography>{t("common.toasts.checkConnection")}</Typography>
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isLoading && typeof summary?.totalMonitors === "undefined") {
|
||||
return (
|
||||
<Fallback
|
||||
type="infrastructureMonitor"
|
||||
title={t("infrastructureMonitor.fallback.title")}
|
||||
checks={t("infrastructureMonitor.fallback.checks", { returnObjects: true })}
|
||||
link="/infrastructure/create"
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<MonitorCreateHeader
|
||||
isAdmin={isAdmin}
|
||||
<>
|
||||
<PageStateWrapper
|
||||
networkError={networkError}
|
||||
isLoading={isLoading}
|
||||
path="/infrastructure/create"
|
||||
/>
|
||||
<Stack direction={"row"}>
|
||||
<MonitorCountHeader
|
||||
isLoading={isLoading}
|
||||
monitorCount={summary?.totalMonitors ?? 0}
|
||||
/>
|
||||
<Filter
|
||||
selectedStatus={selectedStatus}
|
||||
setSelectedStatus={setSelectedStatus}
|
||||
setToFilterStatus={setToFilterStatus}
|
||||
handleReset={handleReset}
|
||||
/>
|
||||
<SearchComponent
|
||||
monitors={monitors}
|
||||
onSearchChange={setSearch}
|
||||
setIsSearching={setIsSearching}
|
||||
/>
|
||||
</Stack>
|
||||
items={monitors}
|
||||
type="infrastructureMonitor"
|
||||
fallbackLink="/infrastructure/create"
|
||||
>
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<MonitorCreateHeader
|
||||
isAdmin={isAdmin}
|
||||
isLoading={isLoading}
|
||||
path="/infrastructure/create"
|
||||
/>
|
||||
<Stack direction={"row"}>
|
||||
<MonitorCountHeader
|
||||
isLoading={isLoading}
|
||||
monitorCount={summary?.totalMonitors ?? 0}
|
||||
/>
|
||||
<Filter
|
||||
selectedStatus={selectedStatus}
|
||||
setSelectedStatus={setSelectedStatus}
|
||||
setToFilterStatus={setToFilterStatus}
|
||||
handleReset={handleReset}
|
||||
/>
|
||||
<SearchComponent
|
||||
monitors={monitors}
|
||||
onSearchChange={setSearch}
|
||||
setIsSearching={setIsSearching}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<MonitorsTable
|
||||
isLoading={isLoading}
|
||||
monitors={monitors}
|
||||
isAdmin={isAdmin}
|
||||
handleActionMenuDelete={handleActionMenuDelete}
|
||||
isSearching={isSearching}
|
||||
/>
|
||||
<Pagination
|
||||
itemCount={summary?.totalMonitors}
|
||||
paginationLabel={t("monitors")}
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
handleChangePage={handleChangePage}
|
||||
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
</Stack>
|
||||
<MonitorsTable
|
||||
isLoading={isLoading}
|
||||
monitors={monitors}
|
||||
isAdmin={isAdmin}
|
||||
handleActionMenuDelete={handleActionMenuDelete}
|
||||
isSearching={isSearching}
|
||||
/>
|
||||
<Pagination
|
||||
itemCount={summary?.totalMonitors}
|
||||
paginationLabel={t("monitors")}
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
handleChangePage={handleChangePage}
|
||||
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
</Stack>
|
||||
</PageStateWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -51,16 +51,18 @@ const Logs = () => {
|
||||
];
|
||||
return (
|
||||
<Stack gap={theme.spacing(4)}>
|
||||
<Box>
|
||||
<Typography variant="h2">{t("logsPage.description")}</Typography>
|
||||
</Box>
|
||||
<Divider color={theme.palette.accent.main} />
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(4)}
|
||||
mt={theme.spacing(10)}
|
||||
sx={{
|
||||
position: "sticky",
|
||||
top: theme.spacing(17),
|
||||
backdropFilter: "blur(10px)",
|
||||
paddingTop: theme.spacing(4),
|
||||
paddingLeft: theme.spacing(6),
|
||||
}}
|
||||
>
|
||||
<Typography>{t("logsPage.logLevelSelect.title")}</Typography>
|
||||
<Select
|
||||
|
||||
@@ -46,6 +46,11 @@ const JobTable = ({ jobs = [] }) => {
|
||||
content: t("queuePage.jobTable.urlHeader"),
|
||||
render: (row) => row.monitorUrl,
|
||||
},
|
||||
{
|
||||
id: "interval",
|
||||
content: t("queuePage.jobTable.intervalHeader"),
|
||||
render: (row) => `${row.monitorInterval} ms`,
|
||||
},
|
||||
{
|
||||
id: "type",
|
||||
content: t("queuePage.jobTable.typeHeader"),
|
||||
|
||||
@@ -29,8 +29,6 @@ const QueueDetails = () => {
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(4)}>
|
||||
<Typography variant="h2">{t("queuePage.metricsTable.title")}</Typography>
|
||||
<Divider color={theme.palette.accent.main} />
|
||||
<Stack
|
||||
gap={theme.spacing(20)}
|
||||
mt={theme.spacing(10)}
|
||||
|
||||
@@ -15,7 +15,7 @@ const Logs = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
// Local state
|
||||
const [value, setValue] = useState(2);
|
||||
const [value, setValue] = useState(0);
|
||||
|
||||
// Handlers
|
||||
const handleChange = (event, newValue) => {
|
||||
@@ -29,6 +29,12 @@ const Logs = () => {
|
||||
<Tabs
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
sx={{
|
||||
position: "sticky",
|
||||
top: theme.spacing(0),
|
||||
backdropFilter: "blur(10px)",
|
||||
zIndex: theme.zIndex.appBar,
|
||||
}}
|
||||
>
|
||||
<Tab label={t("logsPage.tabs.logs")} />
|
||||
<Tab label={t("logsPage.tabs.queue")} />
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import Select from "../../../../../Components/Inputs/Select";
|
||||
import PropTypes from "prop-types";
|
||||
const ConfigSelect = ({ configSelection, valueSelect, onChange, ...props }) => {
|
||||
const getValueById = (config, id) => {
|
||||
const item = config.find((config) => config._id === id);
|
||||
return item ? (item.value ? item.value : item.name) : null;
|
||||
};
|
||||
|
||||
const getIdByValue = (config, name) => {
|
||||
const item = config.find((config) => {
|
||||
if (config.value) {
|
||||
return config.value === name;
|
||||
} else {
|
||||
return config.name === name;
|
||||
}
|
||||
});
|
||||
return item ? item._id : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
value={getIdByValue(configSelection, valueSelect)}
|
||||
onChange={(event) => {
|
||||
const newValue = getValueById(configSelection, event.target.value);
|
||||
onChange(newValue);
|
||||
}}
|
||||
items={configSelection}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
ConfigSelect.propTypes = {
|
||||
configSelection: PropTypes.array.isRequired,
|
||||
valueSelect: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
};
|
||||
export default ConfigSelect;
|
||||
@@ -0,0 +1,64 @@
|
||||
import { networkService } from "../../../../main";
|
||||
import dayjs from "dayjs";
|
||||
import {
|
||||
MS_PER_SECOND,
|
||||
MS_PER_MINUTE,
|
||||
MS_PER_HOUR,
|
||||
MS_PER_DAY,
|
||||
MS_PER_WEEK,
|
||||
} from "../../../../Utils/timeUtils";
|
||||
|
||||
const useMaintenanceActions = () => {
|
||||
const MS_LOOKUP = {
|
||||
seconds: MS_PER_SECOND,
|
||||
minutes: MS_PER_MINUTE,
|
||||
hours: MS_PER_HOUR,
|
||||
days: MS_PER_DAY,
|
||||
weeks: MS_PER_WEEK,
|
||||
};
|
||||
const REPEAT_LOOKUP = {
|
||||
none: 0,
|
||||
daily: MS_PER_DAY,
|
||||
weekly: MS_PER_DAY * 7,
|
||||
};
|
||||
const handleSubmitForm = async (maintenanceWindowId, form) => {
|
||||
const start = dayjs(form.startDate)
|
||||
.set("hour", form.startTime.hour())
|
||||
.set("minute", form.startTime.minute());
|
||||
|
||||
const MS_MULTIPLIER = MS_LOOKUP[form.durationUnit];
|
||||
const durationInMs = form.duration * MS_MULTIPLIER;
|
||||
const end = start.add(durationInMs);
|
||||
|
||||
const repeat = REPEAT_LOOKUP[form.repeat];
|
||||
|
||||
const submit = {
|
||||
monitors: form.monitors.map((monitor) => monitor._id),
|
||||
name: form.name,
|
||||
start: start.toISOString(),
|
||||
end: end.toISOString(),
|
||||
repeat,
|
||||
};
|
||||
|
||||
if (repeat === 0) {
|
||||
submit.expiry = end;
|
||||
}
|
||||
|
||||
const requestConfig = { maintenanceWindow: submit };
|
||||
|
||||
if (maintenanceWindowId !== undefined) {
|
||||
requestConfig.maintenanceWindowId = maintenanceWindowId;
|
||||
}
|
||||
const request =
|
||||
maintenanceWindowId === undefined
|
||||
? networkService.createMaintenanceWindow(requestConfig)
|
||||
: networkService.editMaintenanceWindow(requestConfig);
|
||||
return request;
|
||||
};
|
||||
|
||||
return {
|
||||
handleSubmitForm,
|
||||
};
|
||||
};
|
||||
|
||||
export default useMaintenanceActions;
|
||||
@@ -0,0 +1,74 @@
|
||||
import { networkService } from "../../../../main";
|
||||
import dayjs from "dayjs";
|
||||
import {
|
||||
MS_PER_SECOND,
|
||||
MS_PER_MINUTE,
|
||||
MS_PER_HOUR,
|
||||
MS_PER_DAY,
|
||||
MS_PER_WEEK,
|
||||
} from "../../../../Utils/timeUtils";
|
||||
const useMaintenanceData = () => {
|
||||
const REVERSE_REPEAT_LOOKUP = {
|
||||
0: "none",
|
||||
[MS_PER_DAY]: "daily",
|
||||
[MS_PER_WEEK]: "weekly",
|
||||
};
|
||||
const getDurationAndUnit = (durationInMs) => {
|
||||
if (durationInMs % MS_PER_DAY === 0) {
|
||||
return {
|
||||
duration: (durationInMs / MS_PER_DAY).toString(),
|
||||
durationUnit: "days",
|
||||
};
|
||||
} else if (durationInMs % MS_PER_HOUR === 0) {
|
||||
return {
|
||||
duration: (durationInMs / MS_PER_HOUR).toString(),
|
||||
durationUnit: "hours",
|
||||
};
|
||||
} else if (durationInMs % MS_PER_MINUTE === 0) {
|
||||
return {
|
||||
duration: (durationInMs / MS_PER_MINUTE).toString(),
|
||||
durationUnit: "minutes",
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
duration: (durationInMs / MS_PER_SECOND).toString(),
|
||||
durationUnit: "seconds",
|
||||
};
|
||||
}
|
||||
};
|
||||
const fetchMonitorsMaintenance = async () => {
|
||||
const response = await networkService.getMonitorsByTeamId({
|
||||
limit: null,
|
||||
types: ["http", "ping", "pagespeed", "port"],
|
||||
});
|
||||
const fetchedMonitors = response.data.data.monitors;
|
||||
return fetchedMonitors;
|
||||
};
|
||||
|
||||
const initializeMaintenanceForEdit = async (maintenanceWindowId, monitorList) => {
|
||||
const res = await networkService.getMaintenanceWindowById({
|
||||
maintenanceWindowId: maintenanceWindowId,
|
||||
});
|
||||
const maintenanceWindow = res.data.data;
|
||||
const { name, start, end, repeat, monitorId } = maintenanceWindow;
|
||||
const startTime = dayjs(start);
|
||||
const endTime = dayjs(end);
|
||||
const durationInMs = endTime.diff(startTime, "milliseconds").toString();
|
||||
const { duration, durationUnit } = getDurationAndUnit(durationInMs);
|
||||
const monitor = monitorList.find((monitor) => monitor._id === monitorId);
|
||||
const maintenanceWindowInformation = {
|
||||
name,
|
||||
repeat: REVERSE_REPEAT_LOOKUP[repeat],
|
||||
startDate: startTime,
|
||||
startTime,
|
||||
duration,
|
||||
durationUnit,
|
||||
monitors: monitor ? [monitor] : [],
|
||||
};
|
||||
return maintenanceWindowInformation;
|
||||
};
|
||||
|
||||
return { fetchMonitorsMaintenance, initializeMaintenanceForEdit };
|
||||
};
|
||||
|
||||
export default useMaintenanceData;
|
||||
@@ -10,71 +10,21 @@ import { MobileTimePicker } from "@mui/x-date-pickers/MobileTimePicker";
|
||||
import { maintenanceWindowValidation } from "../../../Validation/validation";
|
||||
import { createToast } from "../../../Utils/toastUtils";
|
||||
import MonitorList from "./Components/MonitorList";
|
||||
import Checkbox from "../../../Components/Inputs/Checkbox";
|
||||
import ConfigSelect from "./Components/ConfigSelect";
|
||||
import useMaintenanceData from "./hooks/useMaintenanceData";
|
||||
import useMaintenanceActions from "./hooks/useMaintenanceActions";
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import Select from "../../../Components/Inputs/Select";
|
||||
import TextInput from "../../../Components/Inputs/TextInput";
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
import CalendarIcon from "../../../assets/icons/calendar.svg?react";
|
||||
import "./index.css";
|
||||
import Search from "../../../Components/Inputs/Search";
|
||||
import { networkService } from "../../../main";
|
||||
import { logger } from "../../../Utils/Logger";
|
||||
import {
|
||||
MS_PER_SECOND,
|
||||
MS_PER_MINUTE,
|
||||
MS_PER_HOUR,
|
||||
MS_PER_DAY,
|
||||
MS_PER_WEEK,
|
||||
} from "../../../Utils/timeUtils";
|
||||
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { buildErrors, hasValidationErrors } from "../../../Validation/error";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const getDurationAndUnit = (durationInMs) => {
|
||||
if (durationInMs % MS_PER_DAY === 0) {
|
||||
return {
|
||||
duration: (durationInMs / MS_PER_DAY).toString(),
|
||||
durationUnit: "days",
|
||||
};
|
||||
} else if (durationInMs % MS_PER_HOUR === 0) {
|
||||
return {
|
||||
duration: (durationInMs / MS_PER_HOUR).toString(),
|
||||
durationUnit: "hours",
|
||||
};
|
||||
} else if (durationInMs % MS_PER_MINUTE === 0) {
|
||||
return {
|
||||
duration: (durationInMs / MS_PER_MINUTE).toString(),
|
||||
durationUnit: "minutes",
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
duration: (durationInMs / MS_PER_SECOND).toString(),
|
||||
durationUnit: "seconds",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const MS_LOOKUP = {
|
||||
seconds: MS_PER_SECOND,
|
||||
minutes: MS_PER_MINUTE,
|
||||
hours: MS_PER_HOUR,
|
||||
days: MS_PER_DAY,
|
||||
weeks: MS_PER_WEEK,
|
||||
};
|
||||
|
||||
const REPEAT_LOOKUP = {
|
||||
none: 0,
|
||||
daily: MS_PER_DAY,
|
||||
weekly: MS_PER_DAY * 7,
|
||||
};
|
||||
|
||||
const REVERSE_REPEAT_LOOKUP = {
|
||||
0: "none",
|
||||
[MS_PER_DAY]: "daily",
|
||||
[MS_PER_WEEK]: "weekly",
|
||||
};
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const repeatConfig = [
|
||||
{ _id: 0, name: "Don't repeat", value: "none" },
|
||||
@@ -96,24 +46,10 @@ const durationConfig = [
|
||||
},
|
||||
];
|
||||
|
||||
const getValueById = (config, id) => {
|
||||
const item = config.find((config) => config._id === id);
|
||||
return item ? (item.value ? item.value : item.name) : null;
|
||||
};
|
||||
|
||||
const getIdByValue = (config, name) => {
|
||||
const item = config.find((config) => {
|
||||
if (config.value) {
|
||||
return config.value === name;
|
||||
} else {
|
||||
return config.name === name;
|
||||
}
|
||||
});
|
||||
return item ? item._id : null;
|
||||
};
|
||||
|
||||
const CreateMaintenance = () => {
|
||||
const { maintenanceWindowId } = useParams();
|
||||
const { handleSubmitForm } = useMaintenanceActions();
|
||||
const { fetchMonitorsMaintenance, initializeMaintenanceForEdit } = useMaintenanceData();
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
@@ -121,6 +57,7 @@ const CreateMaintenance = () => {
|
||||
const [monitors, setMonitors] = useState([]);
|
||||
const [search, setSearch] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [errors, setErrors] = useState({});
|
||||
const [form, setForm] = useState({
|
||||
repeat: "none",
|
||||
startDate: dayjs(),
|
||||
@@ -130,43 +67,35 @@ const CreateMaintenance = () => {
|
||||
name: "",
|
||||
monitors: [],
|
||||
});
|
||||
const [errors, setErrors] = useState({});
|
||||
const handleFormChange = (key, value) => {
|
||||
setForm((prev) => ({ ...prev, [key]: value }));
|
||||
const { error } = maintenanceWindowValidation.validate(
|
||||
{ [key]: value },
|
||||
{ abortEarly: false }
|
||||
);
|
||||
setErrors((prev) => {
|
||||
return buildErrors(prev, key, error);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMonitors = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await networkService.getMonitorsByTeamId({
|
||||
limit: null,
|
||||
types: ["http", "ping", "pagespeed", "port"],
|
||||
});
|
||||
const monitors = response.data.data.monitors;
|
||||
setMonitors(monitors);
|
||||
const fetchedMonitors = await fetchMonitorsMaintenance();
|
||||
setMonitors(fetchedMonitors);
|
||||
|
||||
if (maintenanceWindowId === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await networkService.getMaintenanceWindowById({
|
||||
maintenanceWindowId: maintenanceWindowId,
|
||||
});
|
||||
const maintenanceWindow = res.data.data;
|
||||
const { name, start, end, repeat, monitorId } = maintenanceWindow;
|
||||
const startTime = dayjs(start);
|
||||
const endTime = dayjs(end);
|
||||
const durationInMs = endTime.diff(startTime, "milliseconds").toString();
|
||||
const { duration, durationUnit } = getDurationAndUnit(durationInMs);
|
||||
const monitor = monitors.find((monitor) => monitor._id === monitorId);
|
||||
setForm({
|
||||
...form,
|
||||
name,
|
||||
repeat: REVERSE_REPEAT_LOOKUP[repeat],
|
||||
startDate: startTime,
|
||||
startTime,
|
||||
duration,
|
||||
durationUnit,
|
||||
monitors: monitor ? [monitor] : [],
|
||||
});
|
||||
const maintenanceWindowInformation = await initializeMaintenanceForEdit(
|
||||
maintenanceWindowId,
|
||||
fetchedMonitors
|
||||
);
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
...maintenanceWindowInformation,
|
||||
}));
|
||||
} catch (error) {
|
||||
createToast({ body: "Failed to fetch data" });
|
||||
logger.error("Failed to fetch monitors", error);
|
||||
@@ -177,90 +106,9 @@ const CreateMaintenance = () => {
|
||||
fetchMonitors();
|
||||
}, [user]);
|
||||
|
||||
const handleSearch = (value) => {
|
||||
setSearch(value);
|
||||
};
|
||||
|
||||
const handleSelectMonitors = (monitors) => {
|
||||
setForm({ ...form, monitors });
|
||||
const { error } = maintenanceWindowValidation.validate(
|
||||
{ monitors },
|
||||
{ abortEarly: false }
|
||||
);
|
||||
setErrors((prev) => {
|
||||
return buildErrors(prev, "monitors", error);
|
||||
});
|
||||
};
|
||||
|
||||
const handleFormChange = (key, value) => {
|
||||
setForm({ ...form, [key]: value });
|
||||
const { error } = maintenanceWindowValidation.validate(
|
||||
{ [key]: value },
|
||||
{ abortEarly: false }
|
||||
);
|
||||
setErrors((prev) => {
|
||||
return buildErrors(prev, key, error);
|
||||
});
|
||||
};
|
||||
|
||||
const handleTimeChange = (key, newTime) => {
|
||||
setForm({ ...form, [key]: newTime });
|
||||
const { error } = maintenanceWindowValidation.validate(
|
||||
{ [key]: newTime },
|
||||
{ abortEarly: false }
|
||||
);
|
||||
setErrors((prev) => {
|
||||
return buildErrors(prev, key, error);
|
||||
});
|
||||
};
|
||||
|
||||
const handleMonitorsChange = (selected) => {
|
||||
setForm((prev) => ({ ...prev, monitors: selected }));
|
||||
const { error } = maintenanceWindowValidation.validate(
|
||||
{ monitors: selected },
|
||||
{ abortEarly: false }
|
||||
);
|
||||
setErrors((prev) => {
|
||||
return buildErrors(prev, "monitors", error);
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (hasValidationErrors(form, maintenanceWindowValidation, setErrors)) return;
|
||||
// Build timestamp for maintenance window from startDate and startTime
|
||||
const start = dayjs(form.startDate)
|
||||
.set("hour", form.startTime.hour())
|
||||
.set("minute", form.startTime.minute());
|
||||
// Build end timestamp for maintenance window
|
||||
const MS_MULTIPLIER = MS_LOOKUP[form.durationUnit];
|
||||
const durationInMs = form.duration * MS_MULTIPLIER;
|
||||
const end = start.add(durationInMs);
|
||||
|
||||
// Get repeat value in milliseconds
|
||||
const repeat = REPEAT_LOOKUP[form.repeat];
|
||||
|
||||
const submit = {
|
||||
monitors: form.monitors.map((monitor) => monitor._id),
|
||||
name: form.name,
|
||||
start: start.toISOString(),
|
||||
end: end.toISOString(),
|
||||
repeat,
|
||||
};
|
||||
|
||||
if (repeat === 0) {
|
||||
submit.expiry = end;
|
||||
}
|
||||
|
||||
const requestConfig = { maintenanceWindow: submit };
|
||||
|
||||
if (maintenanceWindowId !== undefined) {
|
||||
requestConfig.maintenanceWindowId = maintenanceWindowId;
|
||||
}
|
||||
const request =
|
||||
maintenanceWindowId === undefined
|
||||
? networkService.createMaintenanceWindow(requestConfig)
|
||||
: networkService.editMaintenanceWindow(requestConfig);
|
||||
|
||||
const request = handleSubmitForm(maintenanceWindowId, form);
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await request;
|
||||
@@ -353,18 +201,13 @@ const CreateMaintenance = () => {
|
||||
error={errors["name"] ? true : false}
|
||||
helperText={errors["name"]}
|
||||
/>
|
||||
<Select
|
||||
<ConfigSelect
|
||||
id="repeat"
|
||||
name="maintenance-repeat"
|
||||
label={t("maintenanceRepeat")}
|
||||
value={getIdByValue(repeatConfig, form.repeat)}
|
||||
onChange={(event) => {
|
||||
handleFormChange(
|
||||
"repeat",
|
||||
getValueById(repeatConfig, event.target.value)
|
||||
);
|
||||
}}
|
||||
items={repeatConfig}
|
||||
valueSelect={form.repeat}
|
||||
configSelection={repeatConfig}
|
||||
onChange={(value) => handleFormChange("repeat", value)}
|
||||
/>
|
||||
<Stack>
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||
@@ -437,7 +280,7 @@ const CreateMaintenance = () => {
|
||||
}}
|
||||
sx={{}}
|
||||
onChange={(newDate) => {
|
||||
handleTimeChange("startDate", newDate);
|
||||
handleFormChange("startDate", newDate);
|
||||
}}
|
||||
error={errors["startDate"]}
|
||||
/>
|
||||
@@ -453,7 +296,7 @@ const CreateMaintenance = () => {
|
||||
>
|
||||
{t("startTime")}
|
||||
</Typography>
|
||||
<Typography>{t("timeZoneInfo")}</Typography>
|
||||
<Typography> {t("timeZoneInfo")} </Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(15)}>
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||
@@ -462,7 +305,7 @@ const CreateMaintenance = () => {
|
||||
label={t("startTime")}
|
||||
value={form.startTime}
|
||||
onChange={(newTime) => {
|
||||
handleTimeChange("startTime", newTime);
|
||||
handleFormChange("startTime", newTime);
|
||||
}}
|
||||
slotProps={{
|
||||
nextIconButton: { sx: { ml: theme.spacing(2) } },
|
||||
@@ -508,16 +351,11 @@ const CreateMaintenance = () => {
|
||||
error={errors["duration"] ? true : false}
|
||||
helperText={errors["duration"]}
|
||||
/>
|
||||
<Select
|
||||
<ConfigSelect
|
||||
id="durationUnit"
|
||||
value={getIdByValue(durationConfig, form.durationUnit)}
|
||||
items={durationConfig}
|
||||
onChange={(event) => {
|
||||
handleFormChange(
|
||||
"durationUnit",
|
||||
getValueById(durationConfig, event.target.value)
|
||||
);
|
||||
}}
|
||||
valueSelect={form.durationUnit}
|
||||
configSelection={durationConfig}
|
||||
onChange={(value) => handleFormChange("durationUnit", value)}
|
||||
error={errors["durationUnit"]}
|
||||
/>
|
||||
</Stack>
|
||||
@@ -545,7 +383,9 @@ const CreateMaintenance = () => {
|
||||
inputValue={search}
|
||||
value={form.monitors}
|
||||
handleInputChange={setSearch}
|
||||
handleChange={handleMonitorsChange}
|
||||
handleChange={(selected) => {
|
||||
handleFormChange("monitors", selected);
|
||||
}}
|
||||
error={errors["monitors"]}
|
||||
/>
|
||||
<MonitorList
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Stack, Button } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import Fallback from "../../Components/Fallback";
|
||||
import { useState, useEffect } from "react";
|
||||
import "./index.css";
|
||||
import MaintenanceTable from "./MaintenanceTable";
|
||||
@@ -8,24 +7,21 @@ import { useSelector } from "react-redux";
|
||||
import { networkService } from "../../main";
|
||||
import Breadcrumbs from "../../Components/Breadcrumbs";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useIsAdmin } from "../../Hooks/useIsAdmin";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Typography } from "@mui/material";
|
||||
import GenericFallback from "../../Components/GenericFallback";
|
||||
import PageStateWrapper from "../../Components/PageStateWrapper";
|
||||
|
||||
const Maintenance = () => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const rowsPerPage = useSelector((state) => state?.ui?.maintenance?.rowsPerPage ?? 5);
|
||||
const isAdmin = useIsAdmin();
|
||||
const [maintenanceWindows, setMaintenanceWindows] = useState([]);
|
||||
const [maintenanceWindows, setMaintenanceWindows] = useState(undefined);
|
||||
const [maintenanceWindowCount, setMaintenanceWindowCount] = useState(0);
|
||||
const [page, setPage] = useState(0);
|
||||
const [sort, setSort] = useState({});
|
||||
const [updateTrigger, setUpdateTrigger] = useState(false);
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
const [isDataFetched, setIsDataFetched] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const handleActionMenuDelete = () => {
|
||||
setUpdateTrigger((prev) => !prev);
|
||||
@@ -34,80 +30,65 @@ const Maintenance = () => {
|
||||
useEffect(() => {
|
||||
const fetchMaintenanceWindows = async () => {
|
||||
try {
|
||||
setNetworkError(false);
|
||||
setIsLoading(true);
|
||||
const response = await networkService.getMaintenanceWindowsByTeamId({
|
||||
page: page,
|
||||
rowsPerPage: rowsPerPage,
|
||||
});
|
||||
const { maintenanceWindows, maintenanceWindowCount } = response.data.data;
|
||||
setMaintenanceWindows(maintenanceWindows);
|
||||
setMaintenanceWindowCount(maintenanceWindowCount);
|
||||
setMaintenanceWindows(maintenanceWindows ?? []);
|
||||
setMaintenanceWindowCount(maintenanceWindowCount ?? 0);
|
||||
} catch (error) {
|
||||
setNetworkError(true);
|
||||
} finally {
|
||||
setIsDataFetched(true);
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
fetchMaintenanceWindows();
|
||||
}, [page, rowsPerPage, updateTrigger]);
|
||||
|
||||
if (networkError) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{t("common.toasts.networkError")}
|
||||
</Typography>
|
||||
<Typography>{t("common.toasts.checkConnection")}</Typography>
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
// Only show the fallback if we've fetched data and there are no maintenance windows
|
||||
if (isDataFetched && maintenanceWindows.length === 0) {
|
||||
return (
|
||||
<Fallback
|
||||
type="maintenanceWindow"
|
||||
title={t("maintenanceWindow.fallback.title")}
|
||||
checks={t("maintenanceWindow.fallback.checks", { returnObjects: true })}
|
||||
link="/maintenance/create"
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
mt={theme.spacing(5)}
|
||||
<>
|
||||
<PageStateWrapper
|
||||
networkError={networkError}
|
||||
isLoading={isLoading}
|
||||
items={maintenanceWindows}
|
||||
type="maintenanceWindow"
|
||||
fallbackLink="/maintenance/create"
|
||||
>
|
||||
<Breadcrumbs list={[{ name: "maintenance", path: "/maintenance" }]} />
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={() => {
|
||||
navigate("/maintenance/create");
|
||||
}}
|
||||
sx={{ fontWeight: 500 }}
|
||||
>
|
||||
{t("createMaintenanceWindow")}
|
||||
</Button>
|
||||
</Stack>
|
||||
<MaintenanceTable
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
rowsPerPage={rowsPerPage}
|
||||
sort={sort}
|
||||
setSort={setSort}
|
||||
maintenanceWindows={maintenanceWindows}
|
||||
maintenanceWindowCount={maintenanceWindowCount}
|
||||
updateCallback={handleActionMenuDelete}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
mt={theme.spacing(5)}
|
||||
>
|
||||
<Breadcrumbs list={[{ name: "maintenance", path: "/maintenance" }]} />
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={() => {
|
||||
navigate("/maintenance/create");
|
||||
}}
|
||||
sx={{ fontWeight: 500 }}
|
||||
>
|
||||
{t("createMaintenanceWindow")}
|
||||
</Button>
|
||||
</Stack>
|
||||
<MaintenanceTable
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
rowsPerPage={rowsPerPage}
|
||||
sort={sort}
|
||||
setSort={setSort}
|
||||
maintenanceWindows={maintenanceWindows}
|
||||
maintenanceWindowCount={maintenanceWindowCount}
|
||||
updateCallback={handleActionMenuDelete}
|
||||
/>
|
||||
</Stack>
|
||||
</PageStateWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -89,8 +89,6 @@ const CreateNotifications = () => {
|
||||
error.details.forEach((err) => {
|
||||
newErrors[err.path[0]] = err.message;
|
||||
});
|
||||
console.log(JSON.stringify(newErrors));
|
||||
console.log(JSON.stringify(form, null, 2));
|
||||
createToast({ body: Object.values(newErrors)[0] });
|
||||
setErrors(newErrors);
|
||||
return;
|
||||
|
||||
@@ -4,11 +4,10 @@ import Typography from "@mui/material/Typography";
|
||||
import Breadcrumbs from "../../Components/Breadcrumbs";
|
||||
import Button from "@mui/material/Button";
|
||||
import DataTable from "../../Components/Table";
|
||||
import Fallback from "../../Components/Fallback";
|
||||
import ActionMenu from "./components/ActionMenu";
|
||||
import PageStateWrapper from "../../Components/PageStateWrapper";
|
||||
|
||||
// Utils
|
||||
import { useIsAdmin } from "../../Hooks/useIsAdmin";
|
||||
import { useState } from "react";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -23,7 +22,6 @@ const Notifications = () => {
|
||||
const theme = useTheme();
|
||||
const BREADCRUMBS = [{ name: "notifications", path: "/notifications" }];
|
||||
const [updateTrigger, setUpdateTrigger] = useState(false);
|
||||
const isAdmin = useIsAdmin();
|
||||
const [notifications, isLoading, error] = useGetNotificationsByTeamId(updateTrigger);
|
||||
const [deleteNotification, isDeleting, deleteError] = useDeleteNotification();
|
||||
const { t } = useTranslation();
|
||||
@@ -76,49 +74,47 @@ const Notifications = () => {
|
||||
},
|
||||
];
|
||||
|
||||
if (notifications?.length === 0) {
|
||||
return (
|
||||
<Fallback
|
||||
type="notifications"
|
||||
title={t("notifications.fallback.title")}
|
||||
checks={t("notifications.fallback.checks", { returnObjects: true })}
|
||||
link="/notifications/create"
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="flex-end"
|
||||
<>
|
||||
<PageStateWrapper
|
||||
networkError={error}
|
||||
isLoading={isLoading}
|
||||
items={notifications}
|
||||
type="notifications"
|
||||
fallbackLink="/notifications/create"
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={() => navigate("/notifications/create")}
|
||||
>
|
||||
{t("notifications.createButton")}
|
||||
</Button>
|
||||
</Stack>
|
||||
<Typography variant="h1">{t("notifications.createTitle")}</Typography>
|
||||
<DataTable
|
||||
config={{
|
||||
onRowClick: (row) => navigate(`/notifications/${row._id}`),
|
||||
rowSX: {
|
||||
cursor: "pointer",
|
||||
"&:hover td": {
|
||||
backgroundColor: theme.palette.tertiary.main,
|
||||
transition: "background-color .3s ease",
|
||||
},
|
||||
},
|
||||
}}
|
||||
headers={headers}
|
||||
data={notifications}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={() => navigate("/notifications/create")}
|
||||
>
|
||||
{t("notifications.createButton")}
|
||||
</Button>
|
||||
</Stack>
|
||||
<Typography variant="h1">{t("notifications.createTitle")}</Typography>
|
||||
<DataTable
|
||||
config={{
|
||||
onRowClick: (row) => navigate(`/notifications/${row._id}`),
|
||||
rowSX: {
|
||||
cursor: "pointer",
|
||||
"&:hover td": {
|
||||
backgroundColor: theme.palette.tertiary.main,
|
||||
transition: "background-color .3s ease",
|
||||
},
|
||||
},
|
||||
}}
|
||||
headers={headers}
|
||||
data={notifications}
|
||||
/>
|
||||
</Stack>
|
||||
</PageStateWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ export const NOTIFICATION_TYPES = [
|
||||
export const TITLE_MAP = {
|
||||
email: "createNotifications.emailSettings.title",
|
||||
slack: "createNotifications.slackSettings.title",
|
||||
pager_duty: "createNotifications.pagerDutySettings.title",
|
||||
pager_duty: "createNotifications.pagerdutySettings.title",
|
||||
webhook: "createNotifications.webhookSettings.title",
|
||||
discord: "createNotifications.discordSettings.title",
|
||||
};
|
||||
@@ -17,7 +17,7 @@ export const TITLE_MAP = {
|
||||
export const DESCRIPTION_MAP = {
|
||||
email: "createNotifications.emailSettings.description",
|
||||
slack: "createNotifications.slackSettings.description",
|
||||
pager_duty: "createNotifications.pagerDutySettings.description",
|
||||
pager_duty: "createNotifications.pagerdutySettings.description",
|
||||
webhook: "createNotifications.webhookSettings.description",
|
||||
discord: "createNotifications.discordSettings.description",
|
||||
};
|
||||
@@ -25,7 +25,7 @@ export const DESCRIPTION_MAP = {
|
||||
export const LABEL_MAP = {
|
||||
email: "createNotifications.emailSettings.emailLabel",
|
||||
slack: "createNotifications.slackSettings.webhookLabel",
|
||||
pager_duty: "createNotifications.pagerDutySettings.integrationKeyLabel",
|
||||
pager_duty: "createNotifications.pagerdutySettings.integrationKeyLabel",
|
||||
webhook: "createNotifications.webhookSettings.webhookLabel",
|
||||
discord: "createNotifications.discordSettings.webhookLabel",
|
||||
};
|
||||
@@ -33,7 +33,7 @@ export const LABEL_MAP = {
|
||||
export const PLACEHOLDER_MAP = {
|
||||
email: "createNotifications.emailSettings.emailPlaceholder",
|
||||
slack: "createNotifications.slackSettings.webhookPlaceholder",
|
||||
pager_duty: "createNotifications.pagerDutySettings.integrationKeyPlaceholder",
|
||||
pager_duty: "createNotifications.pagerdutySettings.integrationKeyPlaceholder",
|
||||
webhook: "createNotifications.webhookSettings.webhookPlaceholder",
|
||||
discord: "createNotifications.discordSettings.webhookPlaceholder",
|
||||
};
|
||||
@@ -152,7 +152,7 @@ const PageSpeedSetup = () => {
|
||||
});
|
||||
|
||||
const { error } = monitorValidation.validate(
|
||||
{ [name]: value },
|
||||
{ [name]: value, type: monitor.type },
|
||||
{ abortEarly: false }
|
||||
);
|
||||
setErrors((prev) => ({
|
||||
|
||||
@@ -29,7 +29,7 @@ const PieChartLegend = ({ audits }) => {
|
||||
: theme.palette.tertiary.main;
|
||||
|
||||
// Find the position where the number ends and the unit begins
|
||||
const match = audit.displayValue.match(/(\d+\.?\d*)\s*([a-zA-Z]+)/);
|
||||
const match = audit?.displayValue?.match(/(\d+\.?\d*)\s*([a-zA-Z]+)/);
|
||||
let value;
|
||||
let unit;
|
||||
if (match) {
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
// Components
|
||||
import { useState } from "react";
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import { Stack } from "@mui/material";
|
||||
import CreateMonitorHeader from "../../../Components/MonitorCreateHeader";
|
||||
import MonitorCountHeader from "../../../Components/MonitorCountHeader";
|
||||
import MonitorGrid from "./Components/MonitorGrid";
|
||||
import Fallback from "../../../Components/Fallback";
|
||||
import GenericFallback from "../../../Components/GenericFallback";
|
||||
import PageStateWrapper from "../../../Components/PageStateWrapper";
|
||||
import FallbackPageSpeedWarning from "../../../Components/Fallback/FallbackPageSpeedWarning";
|
||||
|
||||
// Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFetchMonitorsByTeamId } from "../../../Hooks/monitorHooks";
|
||||
import { useFetchSettings } from "../../../Hooks/settingsHooks";
|
||||
// Constants
|
||||
@@ -20,7 +18,6 @@ const BREADCRUMBS = [{ name: `pagespeed`, path: "/pagespeed" }];
|
||||
const TYPES = ["pagespeed"];
|
||||
const PageSpeed = () => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const isAdmin = useIsAdmin();
|
||||
|
||||
const [monitors, monitorsSummary, isLoading, networkError] = useFetchMonitorsByTeamId({
|
||||
@@ -40,56 +37,42 @@ const PageSpeed = () => {
|
||||
setIsEmailPasswordSet: () => {},
|
||||
});
|
||||
|
||||
if (networkError === true) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{t("common.toasts.networkError")}
|
||||
</Typography>
|
||||
<Typography>{t("common.toasts.checkConnection")}</Typography>
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isLoading && monitors?.length === 0) {
|
||||
return (
|
||||
<Fallback
|
||||
type="pageSpeed"
|
||||
title={t("pageSpeed.fallback.title")}
|
||||
checks={t("pageSpeed.fallback.checks", { returnObjects: true })}
|
||||
link="/pagespeed/create"
|
||||
isAdmin={isAdmin}
|
||||
>
|
||||
{isAdmin && settingsData && !settingsData.pagespeedApiKey && (
|
||||
<FallbackPageSpeedWarning settingsData={settingsData} />
|
||||
)}
|
||||
</Fallback>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<CreateMonitorHeader
|
||||
isAdmin={isAdmin}
|
||||
<>
|
||||
<PageStateWrapper
|
||||
networkError={networkError}
|
||||
isLoading={isLoading}
|
||||
path="/pagespeed/create"
|
||||
/>
|
||||
<MonitorCountHeader
|
||||
isLoading={isLoading}
|
||||
monitorCount={monitorsSummary?.totalMonitors}
|
||||
sx={{ mb: theme.spacing(8) }}
|
||||
/>
|
||||
<MonitorGrid
|
||||
size={6}
|
||||
shouldRender={!isLoading}
|
||||
monitors={monitors}
|
||||
/>
|
||||
</Stack>
|
||||
items={monitors}
|
||||
type="pageSpeed"
|
||||
fallbackLink="/pagespeed/create"
|
||||
fallbackChildren={
|
||||
isAdmin &&
|
||||
settingsData &&
|
||||
!settingsData.pagespeedApiKey && (
|
||||
<FallbackPageSpeedWarning settingsData={settingsData} />
|
||||
)
|
||||
}
|
||||
>
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<CreateMonitorHeader
|
||||
isAdmin={isAdmin}
|
||||
isLoading={isLoading}
|
||||
path="/pagespeed/create"
|
||||
/>
|
||||
<MonitorCountHeader
|
||||
isLoading={isLoading}
|
||||
monitorCount={monitorsSummary?.totalMonitors}
|
||||
sx={{ mb: theme.spacing(8) }}
|
||||
/>
|
||||
<MonitorGrid
|
||||
size={6}
|
||||
shouldRender={!isLoading}
|
||||
monitors={monitors}
|
||||
/>
|
||||
</Stack>
|
||||
</PageStateWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ const SettingsURL = ({ HEADING_SX, handleChange, showURL = false }) => {
|
||||
<Select
|
||||
name="showURL"
|
||||
label={t("settingsPage.urlSettings.label")}
|
||||
value={showURL || ""}
|
||||
value={showURL === true}
|
||||
onChange={handleChange}
|
||||
items={[
|
||||
{ _id: true, name: t("settingsPage.urlSettings.selectEnabled") },
|
||||
|
||||
@@ -11,7 +11,12 @@ import PropTypes from "prop-types";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const MonitorsList = ({ isLoading = false, shouldRender = true, monitors = [] }) => {
|
||||
const MonitorsList = ({
|
||||
isLoading = false,
|
||||
shouldRender = true,
|
||||
monitors = [],
|
||||
statusPage = {},
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const { determineState } = useMonitorUtils();
|
||||
|
||||
@@ -40,10 +45,12 @@ const MonitorsList = ({ isLoading = false, shouldRender = true, monitors = [] })
|
||||
alignItems="center"
|
||||
gap={theme.spacing(20)}
|
||||
>
|
||||
<Box flex={9}>
|
||||
<StatusPageBarChart checks={monitor?.checks?.slice().reverse()} />
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
{statusPage.showCharts !== false && (
|
||||
<Box flex={9}>
|
||||
<StatusPageBarChart checks={monitor?.checks?.slice().reverse()} />
|
||||
</Box>
|
||||
)}
|
||||
<Box flex={statusPage.showCharts !== false ? 1 : 10}>
|
||||
<StatusLabel
|
||||
status={status}
|
||||
text={status}
|
||||
@@ -60,6 +67,7 @@ const MonitorsList = ({ isLoading = false, shouldRender = true, monitors = [] })
|
||||
|
||||
MonitorsList.propTypes = {
|
||||
monitors: PropTypes.array.isRequired,
|
||||
statusPage: PropTypes.object,
|
||||
};
|
||||
|
||||
export default MonitorsList;
|
||||
@@ -154,7 +154,10 @@ const PublicStatus = () => {
|
||||
/>
|
||||
<Typography variant="h2">{t("statusPageStatusServiceStatus")}</Typography>
|
||||
<StatusBar monitors={monitors} />
|
||||
<MonitorsList monitors={monitors} />
|
||||
<MonitorsList
|
||||
monitors={monitors}
|
||||
statusPage={statusPage}
|
||||
/>
|
||||
{link}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
// Components
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import { Stack } from "@mui/material";
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
import Fallback from "../../../Components/Fallback";
|
||||
import MonitorCreateHeader from "../../../Components/MonitorCreateHeader";
|
||||
import GenericFallback from "../../../Components/GenericFallback";
|
||||
import StatusPagesTable from "./Components/StatusPagesTable";
|
||||
import SkeletonLayout from "../../../Components/Skeletons/FullPage";
|
||||
import PageStateWrapper from "../../../Components/PageStateWrapper";
|
||||
// Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useStatusPagesFetch } from "./Hooks/useStatusPagesFetch";
|
||||
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
|
||||
const BREADCRUMBS = [{ name: `Status Pages`, path: "" }];
|
||||
@@ -16,52 +13,30 @@ const BREADCRUMBS = [{ name: `Status Pages`, path: "" }];
|
||||
const StatusPages = () => {
|
||||
// Utils
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const isAdmin = useIsAdmin();
|
||||
const [isLoading, networkError, statusPages] = useStatusPagesFetch();
|
||||
|
||||
if (isLoading) {
|
||||
return <SkeletonLayout />;
|
||||
}
|
||||
|
||||
if (networkError === true) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{t("common.toasts.networkError")}
|
||||
</Typography>
|
||||
<Typography>{t("common.toasts.checkConnection")}</Typography>
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isLoading && typeof statusPages !== "undefined" && statusPages.length === 0) {
|
||||
return (
|
||||
<Fallback
|
||||
title={t("statusPage.fallback.title")}
|
||||
checks={t("statusPage.fallback.checks", { returnObjects: true })}
|
||||
type="statusPage"
|
||||
link="/status/uptime/create"
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<MonitorCreateHeader
|
||||
label="Create status page"
|
||||
isAdmin={isAdmin}
|
||||
path="/status/uptime/create"
|
||||
<>
|
||||
<PageStateWrapper
|
||||
networkError={networkError}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<StatusPagesTable data={statusPages} />
|
||||
</Stack>
|
||||
items={statusPages}
|
||||
type="statusPage"
|
||||
fallbackLink="/status/uptime/create"
|
||||
>
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<MonitorCreateHeader
|
||||
label="Create status page"
|
||||
isAdmin={isAdmin}
|
||||
path="/status/uptime/create"
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<StatusPagesTable data={statusPages} />
|
||||
</Stack>
|
||||
</PageStateWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -44,20 +44,6 @@ import {
|
||||
useFetchMonitorGames,
|
||||
} from "../../../Hooks/monitorHooks";
|
||||
|
||||
/**
|
||||
* Parses a URL string and returns a URL object.
|
||||
*
|
||||
* @param {string} url - The URL string to parse.
|
||||
* @returns {URL} - The parsed URL object if valid, otherwise an empty string.
|
||||
*/
|
||||
const parseUrl = (url) => {
|
||||
try {
|
||||
return new URL(url);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create page renders monitor creation or configuration views.
|
||||
* @component
|
||||
@@ -69,6 +55,8 @@ const UptimeCreate = ({ isClone = false }) => {
|
||||
// States
|
||||
const [monitor, setMonitor] = useState({
|
||||
type: "http",
|
||||
statusWindowSize: 5,
|
||||
statusWindowThreshold: 60,
|
||||
matchMethod: "equal",
|
||||
expectedValue: "",
|
||||
jsonPath: "",
|
||||
@@ -191,7 +179,10 @@ const UptimeCreate = ({ isClone = false }) => {
|
||||
? `http${https ? "s" : ""}://` + monitor.url
|
||||
: monitor.url,
|
||||
name: monitor.name || monitor.url.substring(0, 50),
|
||||
statusWindowSize: monitor.statusWindowSize,
|
||||
statusWindowThreshold: monitor.statusWindowThreshold,
|
||||
type: monitor.type,
|
||||
|
||||
port:
|
||||
monitor.type === "port" || monitor.type === "game" ? monitor.port : undefined,
|
||||
interval: monitor.interval,
|
||||
@@ -206,6 +197,8 @@ const UptimeCreate = ({ isClone = false }) => {
|
||||
_id: monitor._id,
|
||||
url: monitor.url,
|
||||
name: monitor.name || monitor.url.substring(0, 50),
|
||||
statusWindowSize: monitor.statusWindowSize,
|
||||
statusWindowThreshold: monitor.statusWindowThreshold,
|
||||
type: monitor.type,
|
||||
matchMethod: monitor.matchMethod,
|
||||
expectedValue: monitor.expectedValue,
|
||||
@@ -299,7 +292,7 @@ const UptimeCreate = ({ isClone = false }) => {
|
||||
|
||||
const isBusy = isLoading || isCreating || isDeleting || isUpdating || isPausing;
|
||||
const displayInterval = monitor?.interval / MS_PER_MINUTE || 1;
|
||||
const parsedUrl = parseUrl(monitor?.url);
|
||||
const parsedUrl = monitor?.url;
|
||||
const protocol = parsedUrl?.protocol?.replace(":", "") || "";
|
||||
|
||||
useEffect(() => {
|
||||
@@ -616,6 +609,39 @@ const UptimeCreate = ({ isClone = false }) => {
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="h2"
|
||||
>
|
||||
{t("createMonitorPage.incidentConfigTitle")}
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
{t("createMonitorPage.incidentConfigDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<TextInput
|
||||
name="statusWindowSize"
|
||||
label={t("createMonitorPage.incidentConfigStatusWindowLabel")}
|
||||
type="number"
|
||||
value={monitor.statusWindowSize}
|
||||
onChange={onChange}
|
||||
error={errors["statusWindowSize"] ? true : false}
|
||||
helperText={errors["statusWindowSize"]}
|
||||
/>
|
||||
<TextInput
|
||||
name="statusWindowThreshold"
|
||||
label={t("createMonitorPage.incidentConfigStatusWindowThresholdLabel")}
|
||||
type="number"
|
||||
value={monitor.statusWindowThreshold}
|
||||
onChange={onChange}
|
||||
error={errors["statusWindowThreshold"] ? true : false}
|
||||
helperText={errors["statusWindowThreshold"]}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography
|
||||
|
||||
@@ -10,16 +10,14 @@ import StatusBoxes from "./Components/StatusBoxes";
|
||||
import UptimeDataTable from "./Components/UptimeDataTable";
|
||||
import Pagination from "../../../Components/Table/TablePagination";
|
||||
import CreateMonitorHeader from "../../../Components/MonitorCreateHeader";
|
||||
import Fallback from "../../../Components/Fallback";
|
||||
import GenericFallback from "../../../Components/GenericFallback";
|
||||
import SearchComponent from "./Components/SearchComponent";
|
||||
import Filter from "./Components/Filter";
|
||||
import PageStateWrapper from "../../../Components/PageStateWrapper";
|
||||
|
||||
import MonitorCountHeader from "../../../Components/MonitorCountHeader";
|
||||
|
||||
// MUI Components
|
||||
import { Stack, Box, Button, Typography } from "@mui/material";
|
||||
|
||||
import { Stack, Box, Button } from "@mui/material";
|
||||
// Utils
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
|
||||
@@ -153,93 +151,75 @@ const UptimeMonitors = () => {
|
||||
}, [isSearching]);
|
||||
|
||||
const isLoading = monitorsWithSummaryIsLoading || monitorsWithChecksIsLoading;
|
||||
if (networkError) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{t("common.toasts.networkError")}
|
||||
</Typography>
|
||||
<Typography>{t("common.toasts.checkConnection")}</Typography>
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
if (
|
||||
!isLoading &&
|
||||
(monitorsSummary?.totalMonitors === 0 ||
|
||||
typeof monitorsSummary?.totalMonitors === "undefined")
|
||||
) {
|
||||
return (
|
||||
<Fallback
|
||||
type="uptimeMonitor"
|
||||
title={t("uptimeMonitor.fallback.title")}
|
||||
checks={t("uptimeMonitor.fallback.checks", { returnObjects: true })}
|
||||
link="/uptime/create"
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Stack
|
||||
className="monitors"
|
||||
gap={theme.spacing(10)}
|
||||
>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<CreateMonitorHeader
|
||||
isAdmin={isAdmin}
|
||||
isLoading={isLoading}
|
||||
path="/uptime/create"
|
||||
bulkPath="/uptime/bulk-import"
|
||||
/>
|
||||
<Greeting type="uptime" />
|
||||
<StatusBoxes
|
||||
monitorsSummary={monitorsSummary}
|
||||
shouldRender={!monitorsWithSummaryIsLoading}
|
||||
/>
|
||||
|
||||
<Stack direction={"row"}>
|
||||
<MonitorCountHeader
|
||||
isLoading={monitorsWithSummaryIsLoading}
|
||||
monitorCount={monitorsSummary?.totalMonitors}
|
||||
/>
|
||||
<Filter
|
||||
selectedTypes={selectedTypes}
|
||||
setSelectedTypes={setSelectedTypes}
|
||||
selectedStatus={selectedStatus}
|
||||
setSelectedStatus={setSelectedStatus}
|
||||
selectedState={selectedState}
|
||||
setSelectedState={setSelectedState}
|
||||
setToFilterStatus={setToFilterStatus}
|
||||
setToFilterActive={setToFilterActive}
|
||||
handleReset={handleReset}
|
||||
/>
|
||||
<SearchComponent
|
||||
monitors={monitors}
|
||||
onSearchChange={setSearch}
|
||||
setIsSearching={setIsSearching}
|
||||
/>
|
||||
</Stack>
|
||||
<UptimeDataTable
|
||||
isAdmin={isAdmin}
|
||||
isSearching={isSearching}
|
||||
filteredMonitors={monitorsWithChecks}
|
||||
sort={sort}
|
||||
setSort={setSort}
|
||||
monitorsAreLoading={monitorsWithChecksIsLoading}
|
||||
triggerUpdate={triggerUpdate}
|
||||
/>
|
||||
<Pagination
|
||||
itemCount={monitorsWithChecksCount}
|
||||
paginationLabel="monitors"
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
handleChangePage={handleChangePage}
|
||||
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
</Stack>
|
||||
return (
|
||||
<>
|
||||
<PageStateWrapper
|
||||
networkError={networkError}
|
||||
isLoading={isLoading}
|
||||
items={monitors}
|
||||
type="uptimeMonitor"
|
||||
fallbackLink="/uptime/create"
|
||||
>
|
||||
<Stack
|
||||
className="monitors"
|
||||
gap={theme.spacing(10)}
|
||||
>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<CreateMonitorHeader
|
||||
isAdmin={isAdmin}
|
||||
isLoading={isLoading}
|
||||
path="/uptime/create"
|
||||
bulkPath="/uptime/bulk-import"
|
||||
/>
|
||||
<Greeting type="uptime" />
|
||||
<StatusBoxes
|
||||
monitorsSummary={monitorsSummary}
|
||||
shouldRender={!monitorsWithSummaryIsLoading}
|
||||
/>
|
||||
|
||||
<Stack direction={"row"}>
|
||||
<MonitorCountHeader
|
||||
isLoading={monitorsWithSummaryIsLoading}
|
||||
monitorCount={monitorsSummary?.totalMonitors}
|
||||
/>
|
||||
<Filter
|
||||
selectedTypes={selectedTypes}
|
||||
setSelectedTypes={setSelectedTypes}
|
||||
selectedStatus={selectedStatus}
|
||||
setSelectedStatus={setSelectedStatus}
|
||||
selectedState={selectedState}
|
||||
setSelectedState={setSelectedState}
|
||||
setToFilterStatus={setToFilterStatus}
|
||||
setToFilterActive={setToFilterActive}
|
||||
handleReset={handleReset}
|
||||
/>
|
||||
<SearchComponent
|
||||
monitors={monitors}
|
||||
onSearchChange={setSearch}
|
||||
setIsSearching={setIsSearching}
|
||||
/>
|
||||
</Stack>
|
||||
<UptimeDataTable
|
||||
isAdmin={isAdmin}
|
||||
isSearching={isSearching}
|
||||
filteredMonitors={monitorsWithChecks}
|
||||
sort={sort}
|
||||
setSort={setSort}
|
||||
monitorsAreLoading={monitorsWithChecksIsLoading}
|
||||
triggerUpdate={triggerUpdate}
|
||||
/>
|
||||
<Pagination
|
||||
itemCount={monitorsWithChecksCount}
|
||||
paginationLabel="monitors"
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
handleChangePage={handleChangePage}
|
||||
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
</Stack>
|
||||
</PageStateWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -223,7 +223,7 @@ const Routes = () => {
|
||||
<Route
|
||||
exact
|
||||
path="/register/:token"
|
||||
element={<AuthRegister />}
|
||||
element={<AuthRegister superAdminExists={true} />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
|
||||
@@ -564,6 +564,10 @@ const baseTheme = (palette) => ({
|
||||
"& .MuiSelect-icon": {
|
||||
color: theme.palette.primary.contrastTextSecondary, // Dropdown + color
|
||||
},
|
||||
"& .MuiSelect-select": {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.primary.main, // Background on hover
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
}),
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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": ""
|
||||
}
|
||||
+325
-220
@@ -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 <template>Vorlage</template> oder <sample>Beispiel</sample> 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 <short_id> 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 <a1>Servicebedingungen</a1> und <a2>Datenschutzrichtlinien</a2> zu.",
|
||||
"links": {
|
||||
"login": "Du hast bereits einen Benutzer? <a>Anmelden</a>"
|
||||
},
|
||||
"toasts": {
|
||||
"success": "Willkommen! Benutzer erfolgreich angelegt"
|
||||
}
|
||||
},
|
||||
"welcome": ""
|
||||
},
|
||||
"forgotPassword": {
|
||||
"heading": "Passwort vergessen?",
|
||||
@@ -624,7 +629,7 @@
|
||||
"emailNotFound": "Email nicht gefunden.",
|
||||
"redirect": "Umleitung in <seconds/>...",
|
||||
"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": ""
|
||||
}
|
||||
+930
-856
File diff suppressed because it is too large.
Load diff
@@ -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": ""
|
||||
}
|
||||
@@ -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": ""
|
||||
}
|
||||
+130
-29
@@ -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": ""
|
||||
}
|
||||
@@ -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": ""
|
||||
}
|
||||
+400
-295
@@ -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 <a1>Termos de Serviço</a1> e <a2>Política de Privacidade</a2>.",
|
||||
"links": {
|
||||
"login": ""
|
||||
"login": "Já tem uma conta? <a>Entrar</a>"
|
||||
},
|
||||
"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 <email/>",
|
||||
"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? <a>Clique para reenviar</a>"
|
||||
},
|
||||
"toasts": {
|
||||
"sent": "",
|
||||
"emailNotFound": "",
|
||||
"redirect": "",
|
||||
"success": "",
|
||||
"error": ""
|
||||
"sent": "Instruções enviadas para <email/>.",
|
||||
"emailNotFound": "E-mail não encontrado.",
|
||||
"redirect": "Redirecionando em <segundos/>...",
|
||||
"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": ""
|
||||
}
|
||||
+100
-20
@@ -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": ""
|
||||
}
|
||||
@@ -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": ""
|
||||
}
|
||||
+284
-200
@@ -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": ""
|
||||
}
|
||||
@@ -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: "/",
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Whitespace-only changes.
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
retries: 30
|
||||
@@ -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
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
FROM redis
|
||||
EXPOSE 6379
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
Loaded 100 of 154 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user