diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index d3d4c0d6b..76cb300f1 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -59,4 +59,4 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh pr close "$PR_NUMBER" --delete-branch --comment "❌ Formatting check failed — PR auto-closed. - Please run \`npm run format\` and push again." + Please run \`npm run format\` in both `client` and `server` directories and push again." diff --git a/.github/workflows/deploy-images-on-release.yml b/.github/workflows/deploy-images-on-release.yml index f1d0dd7ce..cf06f7840 100644 --- a/.github/workflows/deploy-images-on-release.yml +++ b/.github/workflows/deploy-images-on-release.yml @@ -11,6 +11,9 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Extract version from tag id: extract_tag run: echo "version=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" @@ -93,6 +96,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -119,12 +124,16 @@ jobs: platforms: linux/amd64,linux/arm64 labels: | org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate + build-args: | + VITE_APP_VERSION=${{ steps.extract_tag.outputs.version }} docker-build-and-push-server-mono: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Extract version id: extract_tag @@ -143,6 +152,7 @@ jobs: -t ghcr.io/bluewave-labs/checkmate-backend-mono:${{ steps.extract_tag.outputs.version }} \ -f ./docker/dist-mono/server.Dockerfile \ --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \ + --build-arg VITE_APP_VERSION=${{ steps.extract_tag.outputs.version }} \ . - name: Push Server Docker image diff --git a/.github/workflows/deploy-images.yml b/.github/workflows/deploy-images.yml index d3d70b90b..fa39fed85 100644 --- a/.github/workflows/deploy-images.yml +++ b/.github/workflows/deploy-images.yml @@ -10,6 +10,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Log in to GitHub Container Registry uses: docker/login-action@v3 @@ -18,12 +20,16 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get version + id: vars + run: echo "VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV - name: Build Client Docker image run: | docker build \ -t ghcr.io/bluewave-labs/checkmate-client:latest \ -f ./docker/dist/client.Dockerfile \ --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \ + --build-arg VITE_APP_VERSION=${{ env.VERSION }} \ . - name: Push Client Docker image @@ -85,6 +91,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -96,6 +104,10 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get version + id: vars + run: echo "VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV + - name: Build and push multi-arch Docker image uses: docker/build-push-action@v5 with: @@ -107,12 +119,16 @@ jobs: platforms: linux/amd64,linux/arm64 labels: | org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate + build-args: | + VITE_APP_VERSION=${{ env.VERSION }} docker-build-and-push-server-mono: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Log in to GitHub Container Registry uses: docker/login-action@v3 @@ -121,12 +137,17 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get version + id: vars + run: echo "VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV + - name: Build Server Docker image run: | docker build \ -t ghcr.io/bluewave-labs/checkmate-backend-mono:latest \ -f ./docker/dist-mono/server.Dockerfile \ --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \ + --build-arg VITE_APP_VERSION=${{ env.VERSION }} \ . - name: Push Server Docker image diff --git a/.github/workflows/production-deploy.yml b/.github/workflows/production-deploy.yml index 6956f5caa..7764c488b 100644 --- a/.github/workflows/production-deploy.yml +++ b/.github/workflows/production-deploy.yml @@ -10,6 +10,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Log in to GitHub Container Registry uses: docker/login-action@v3 @@ -18,12 +20,16 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get version + id: vars + run: echo "VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV - name: Build Client Docker image run: | docker build \ -t ghcr.io/bluewave-labs/checkmate:frontend-demo \ -f ./docker/prod/client.Dockerfile \ --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \ + --build-arg VITE_APP_VERSION=${{ env.VERSION }} \ . - name: Push Client Docker image diff --git a/.github/workflows/staging-deploy.yml b/.github/workflows/staging-deploy.yml index 34fa243c6..ea5477815 100644 --- a/.github/workflows/staging-deploy.yml +++ b/.github/workflows/staging-deploy.yml @@ -10,6 +10,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Log in to GitHub Container Registry uses: docker/login-action@v3 @@ -18,12 +20,16 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get version + id: vars + run: echo "VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV - name: Build Client Docker image run: | docker build \ -t ghcr.io/bluewave-labs/checkmate:frontend-staging \ -f ./docker/staging/client.Dockerfile \ --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \ + --build-arg VITE_APP_VERSION=${{ env.VERSION }} \ . - name: Push Client Docker image diff --git a/client/.eslintrc.cjs b/client/.eslintrc.cjs index 7d6029d4b..86d523a6f 100644 --- a/client/.eslintrc.cjs +++ b/client/.eslintrc.cjs @@ -16,4 +16,8 @@ module.exports = { "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], "react/no-unescaped-entities": "off", }, + globals: { + __APP_VERSION__: "readonly", + process: "readonly", + }, }; diff --git a/client/package-lock.json b/client/package-lock.json index 4050adfbb..f58f56f44 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -45,7 +45,7 @@ "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.6", "prettier": "^3.3.3", - "vite": "^5.4.19" + "vite": "6.3.5" }, "optionalDependencies": { "@rollup/rollup-linux-arm64-musl": "4.41.0" @@ -492,9 +492,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "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" ], @@ -504,13 +504,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", "cpu": [ "arm" ], @@ -520,13 +520,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "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" ], @@ -536,13 +536,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "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" ], @@ -552,13 +552,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "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" ], @@ -568,13 +568,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "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" ], @@ -584,13 +584,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "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" ], @@ -600,13 +600,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "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" ], @@ -616,13 +616,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", "cpu": [ "arm" ], @@ -632,13 +632,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "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" ], @@ -648,13 +648,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "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" ], @@ -664,13 +664,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", "cpu": [ "loong64" ], @@ -680,13 +680,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "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" ], @@ -696,13 +696,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "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" ], @@ -712,13 +712,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "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" ], @@ -728,13 +728,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", "cpu": [ "s390x" ], @@ -744,13 +744,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", "cpu": [ "x64" ], @@ -760,13 +760,29 @@ "linux" ], "engines": { - "node": ">=12" + "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.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "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" ], @@ -776,13 +792,29 @@ "netbsd" ], "engines": { - "node": ">=12" + "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.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "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" ], @@ -792,13 +824,13 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "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" ], @@ -808,13 +840,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "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" ], @@ -824,13 +856,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", "cpu": [ "ia32" ], @@ -840,13 +872,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "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" ], @@ -856,7 +888,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -3040,9 +3072,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -3172,23 +3204,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -3259,6 +3274,15 @@ "node": ">=10" } }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3820,41 +3844,43 @@ } }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", "hasInstallScript": true, "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" } }, "node_modules/escalade": { @@ -4052,6 +4078,23 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/eslint/node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -4184,6 +4227,20 @@ "reusify": "^1.0.4" } }, + "node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -6713,6 +6770,22 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -6923,20 +6996,23 @@ } }, "node_modules/vite": { - "version": "5.4.19", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", - "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -6945,19 +7021,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", - "terser": "^5.4.0" + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -6978,6 +7060,12 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, @@ -7133,12 +7221,17 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, "engines": { - "node": ">= 6" + "node": ">= 14.6" } }, "node_modules/yocto-queue": { diff --git a/client/package.json b/client/package.json index 83dfe8407..611ba5093 100644 --- a/client/package.json +++ b/client/package.json @@ -58,7 +58,7 @@ "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.6", "prettier": "^3.3.3", - "vite": "^5.4.19" + "vite": "6.3.5" }, "optionalDependencies": { "@rollup/rollup-linux-arm64-musl": "4.41.0" diff --git a/client/src/Components/Layouts/AppLayout/index.jsx b/client/src/Components/Layouts/AppLayout/index.jsx index 564012d72..d13f6d879 100644 --- a/client/src/Components/Layouts/AppLayout/index.jsx +++ b/client/src/Components/Layouts/AppLayout/index.jsx @@ -2,16 +2,17 @@ import Box from "@mui/material/Box"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import BackgroundSVG from "../../../assets/Images/background.svg"; +import { useSelector } from "react-redux"; const AppLayout = ({ children }) => { const theme = useTheme(); - + const ui = useSelector((state) => state.ui); return ( { +const Link = ({ level, label, url, external = true }) => { const theme = useTheme(); const levelConfig = { @@ -49,11 +51,12 @@ const Link = ({ level, label, url }) => { const { sx, color } = levelConfig[level]; return ( {label} @@ -64,6 +67,7 @@ Link.propTypes = { url: PropTypes.string.isRequired, level: PropTypes.oneOf(["primary", "secondary", "tertiary", "error"]), label: PropTypes.string.isRequired, + external: PropTypes.bool, }; export default Link; diff --git a/client/src/Components/MonitorActions/index.jsx b/client/src/Components/MonitorActions/index.jsx new file mode 100644 index 000000000..ac5d89a47 --- /dev/null +++ b/client/src/Components/MonitorActions/index.jsx @@ -0,0 +1,116 @@ +import * as React from "react"; +import Button from "@mui/material/Button"; +import ButtonGroup from "@mui/material/ButtonGroup"; +import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown"; +import ClickAwayListener from "@mui/material/ClickAwayListener"; +import Grow from "@mui/material/Grow"; +import Paper from "@mui/material/Paper"; +import Popper from "@mui/material/Popper"; +import MenuItem from "@mui/material/MenuItem"; +import MenuList from "@mui/material/MenuList"; +import { useNavigate } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { createToast } from "../../Utils/toastUtils"; +import { useExportMonitors } from "../../Hooks/monitorHooks"; + +const MonitorActions = ({ isLoading }) => { + const [open, setOpen] = React.useState(false); + const anchorRef = React.useRef(null); + const [selectedIndex, setSelectedIndex] = React.useState(0); + const navigate = useNavigate(); + const { t } = useTranslation(); + const [exportMonitors, isExporting] = useExportMonitors(); + + const options = [t("monitorActions.import"), t("monitorActions.export")]; + + const handleClick = async () => { + if (selectedIndex === 0) { + // Import + navigate("/uptime/bulk-import"); + } else { + // Export + const [success, error] = await exportMonitors(); + if (!success) { + createToast({ body: error || t("export.failed") }); + } + } + }; + + const handleMenuItemClick = (event, index) => { + setSelectedIndex(index); + setOpen(false); + }; + + const handleToggle = () => { + setOpen((prevOpen) => !prevOpen); + }; + + const handleClose = (event) => { + if (anchorRef.current && anchorRef.current.contains(event.target)) { + return; + } + setOpen(false); + }; + + return ( + + + + + + + {({ TransitionProps, placement }) => ( + + + + + {options.map((option, index) => ( + handleMenuItemClick(event, index)} + > + {option} + + ))} + + + + + )} + + + ); +}; + +export default MonitorActions; diff --git a/client/src/Components/MonitorCreateHeader/index.jsx b/client/src/Components/MonitorCreateHeader/index.jsx index b630cb259..b3b50681c 100644 --- a/client/src/Components/MonitorCreateHeader/index.jsx +++ b/client/src/Components/MonitorCreateHeader/index.jsx @@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; +import MonitorActions from "../MonitorActions"; const CreateMonitorHeader = ({ isAdmin, label, isLoading = true, path, bulkPath }) => { const navigate = useNavigate(); @@ -28,18 +29,7 @@ const CreateMonitorHeader = ({ isAdmin, label, isLoading = true, path, bulkPath > {label || t("createNew")} - {bulkPath && ( - - )} + {/* {bulkPath && } */} ); }; diff --git a/client/src/Components/MonitorDetailsControlHeader/index.jsx b/client/src/Components/MonitorDetailsControlHeader/index.jsx index 5ad665069..1d253e3d5 100644 --- a/client/src/Components/MonitorDetailsControlHeader/index.jsx +++ b/client/src/Components/MonitorDetailsControlHeader/index.jsx @@ -83,23 +83,24 @@ const MonitorDetailsControlHeader = ({ > {t("menu.incidents")} - - + {isAdmin && ( + + )} {isAdmin && ( + + + + ); +}; + +export default QueueDetails; diff --git a/client/src/Pages/Logs/index.jsx b/client/src/Pages/Logs/index.jsx new file mode 100644 index 000000000..482491ad0 --- /dev/null +++ b/client/src/Pages/Logs/index.jsx @@ -0,0 +1,41 @@ +import Stack from "@mui/material/Stack"; +import Breadcrumbs from "../../Components/Breadcrumbs"; +import Tabs from "@mui/material/Tabs"; +import Tab from "@mui/material/Tab"; +import Queue from "./Queue"; +import LogsComponent from "./Logs"; + +import { useTheme } from "@emotion/react"; +import { useTranslation } from "react-i18next"; +import { useState } from "react"; + +const Logs = () => { + const { t } = useTranslation(); + const theme = useTheme(); + + // Local state + const [value, setValue] = useState(0); + + // Handlers + const handleChange = (event, newValue) => { + setValue(newValue); + }; + + const BREADCRUMBS = [{ name: t("logsPage.title"), path: "/logs" }]; + return ( + + + + + + + {value === 0 && } + {value === 1 && } + + ); +}; + +export default Logs; diff --git a/client/src/Pages/Maintenance/MaintenanceTable/index.jsx b/client/src/Pages/Maintenance/MaintenanceTable/index.jsx index a798838cb..bb5842e9f 100644 --- a/client/src/Pages/Maintenance/MaintenanceTable/index.jsx +++ b/client/src/Pages/Maintenance/MaintenanceTable/index.jsx @@ -11,6 +11,8 @@ import { formatDurationRounded } from "../../../Utils/timeUtils"; import { StatusLabel } from "../../../Components/Label"; import { setRowsPerPage } from "../../../Features/UI/uiSlice"; import { useTranslation } from "react-i18next"; +import { useTheme } from "@emotion/react"; +import { useNavigate } from "react-router-dom"; import dayjs from "dayjs"; /** * Component for pagination actions (first, previous, next, last). @@ -36,7 +38,8 @@ const MaintenanceTable = ({ }) => { const rowsPerPage = useSelector((state) => state?.ui?.maintenance?.rowsPerPage ?? 5); const dispatch = useDispatch(); - + const theme = useTheme(); + const navigate = useNavigate(); const handleChangePage = (event, newPage) => { setPage(newPage); }; @@ -180,6 +183,18 @@ const MaintenanceTable = ({ return ( <> { + navigate(`/maintenance/create/${row._id}`); + }, + }} headers={headers} data={maintenanceWindows} /> diff --git a/client/src/Pages/Notifications/components/ActionMenu.jsx b/client/src/Pages/Notifications/components/ActionMenu.jsx index bd719bfee..e19ccecae 100644 --- a/client/src/Pages/Notifications/components/ActionMenu.jsx +++ b/client/src/Pages/Notifications/components/ActionMenu.jsx @@ -18,6 +18,7 @@ const ActionMenu = ({ notification, onDelete }) => { // Handlers const handleClick = (event) => { + event.stopPropagation(); setAnchorEl(event.currentTarget); }; @@ -25,7 +26,8 @@ const ActionMenu = ({ notification, onDelete }) => { setAnchorEl(null); }; - const handleRemove = () => { + const handleRemove = (e) => { + e.stopPropagation(); onDelete(notification._id); handleClose(); }; diff --git a/client/src/Pages/Notifications/create/index.jsx b/client/src/Pages/Notifications/create/index.jsx index fba30185f..0fb6cce08 100644 --- a/client/src/Pages/Notifications/create/index.jsx +++ b/client/src/Pages/Notifications/create/index.jsx @@ -204,7 +204,7 @@ const CreateNotifications = () => { spacing={theme.spacing(2)} > @@ -57,14 +58,13 @@ const SettingsDemoMonitors = ({ isAdmin, HEADER_SX, handleChange, isLoading }) = component="h1" variant="h2" > - {t("settingsSystemReset")} + {t("settingsPage.systemResetSettings.title")} - {t("settingsSystemResetDescription")} + {t("settingsPage.systemResetSettings.description")} - {t("settingsRemoveAllMonitors")} setIsOpen(false)} - confirmationButtonLabel={t("settingsRemoveAllMonitorsDialogConfirm")} + confirmationButtonLabel={t("settingsPage.systemResetSettings.dialogConfirm")} onConfirm={() => { const syntheticEvent = { target: { diff --git a/client/src/Pages/Settings/SettingsEmail.jsx b/client/src/Pages/Settings/SettingsEmail.jsx index 4bd6ff3ad..6c527eaec 100644 --- a/client/src/Pages/Settings/SettingsEmail.jsx +++ b/client/src/Pages/Settings/SettingsEmail.jsx @@ -4,15 +4,16 @@ import ConfigBox from "../../Components/ConfigBox"; import TextInput from "../../Components/Inputs/TextInput"; import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; +import { Switch } from "@mui/material"; +import TextLink from "../../Components/TextLink"; // Utils import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; -import { useState, useEffect } from "react"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments"; import { useSendTestEmail } from "../../Hooks/useSendTestEmail"; import { createToast } from "../../Utils/toastUtils"; -import { Switch } from "@mui/material"; const SettingsEmail = ({ isAdmin, @@ -110,14 +111,16 @@ const SettingsEmail = ({ component="h1" variant="h2" > - {t("settingsEmail")} + {t("settingsPage.emailSettings.title")} + + + {t("settingsPage.emailSettings.description")} - {t("settingsEmailDescription")} - {t("settingsEmailHost")} + {t("settingsPage.emailSettings.labelHost")} - {t("settingsEmailPort")} + {t("settingsPage.emailSettings.labelPort")} - {t("settingsEmailUser")} + {t("settingsPage.emailSettings.labelUser")} - {t("settingsEmailAddress")} + {t("settingsPage.emailSettings.labelAddress")} {(isEmailPasswordSet === false || emailPasswordHasBeenReset === true) && ( - {t("settingsEmailPassword")} + {t("settingsPage.emailSettings.labelPassword")} - {t("settingsEmailFieldResetLabel")} + {t("settingsPage.emailSettings.labelPasswordSet")} )} diff --git a/client/src/Pages/Settings/SettingsPagespeed.jsx b/client/src/Pages/Settings/SettingsPagespeed.jsx index 7d7cf0216..157c75dac 100644 --- a/client/src/Pages/Settings/SettingsPagespeed.jsx +++ b/client/src/Pages/Settings/SettingsPagespeed.jsx @@ -8,7 +8,7 @@ import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornmen // Utils import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; -import { useState, useEffect } from "react"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; const SettingsPagespeed = ({ @@ -46,15 +46,17 @@ const SettingsPagespeed = ({ component="h1" variant="h2" > - {t("pageSpeedApiKeyFieldTitle")} + {t("settingsPage.pageSpeedSettings.title")} + + + {t("settingsPage.pageSpeedSettings.description")} - {t("pageSpeedApiKeyFieldDescription")} {(isApiKeySet === false || apiKeyHasBeenReset === true) && ( - {t("pageSpeedApiKeyFieldResetLabel")} + {t("settingsPage.pageSpeedSettings.labelApiKeySet")} setIsOpen(false)} - confirmationButtonLabel={t("settingsClearAllStatsDialogConfirm")} + confirmationButtonLabel={t( + "settingsPage.statsSettings.clearAllStatsDialogConfirm" + )} onConfirm={() => { const syntheticEvent = { target: { diff --git a/client/src/Pages/Settings/SettingsTimeZone.jsx b/client/src/Pages/Settings/SettingsTimeZone.jsx index 86fc72992..346e4e546 100644 --- a/client/src/Pages/Settings/SettingsTimeZone.jsx +++ b/client/src/Pages/Settings/SettingsTimeZone.jsx @@ -19,16 +19,17 @@ const SettingsTimeZone = ({ HEADING_SX, handleChange, timezone }) => { component="h1" variant="h2" > - {t("settingsGeneralSettings")} + {t("settingsPage.timezoneSettings.title")} - {t("settingsDisplayTimezone")}-{" "} - {t("settingsDisplayTimezoneDescription")} + + {t("settingsPage.timezoneSettings.description")} + { > diff --git a/client/src/Pages/Settings/index.jsx b/client/src/Pages/Settings/index.jsx index 76eaccd6c..dae6bd680 100644 --- a/client/src/Pages/Settings/index.jsx +++ b/client/src/Pages/Settings/index.jsx @@ -157,7 +157,7 @@ const Settings = () => { return ( - Settings + {t("settingsPage.title")} { emailPasswordHasBeenReset={emailPasswordHasBeenReset} setEmailPasswordHasBeenReset={setEmailPasswordHasBeenReset} /> + { sx={{ px: theme.spacing(12), py: theme.spacing(8) }} onClick={handleSave} > - {t("settingsSave")} + {t("settingsPage.saveButtonLabel")} diff --git a/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx index 7423b05bd..327a2fb0a 100644 --- a/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx +++ b/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx @@ -90,6 +90,13 @@ const Content = ({ isChecked={form.showUptimePercentage} onChange={handleFormChange} /> + diff --git a/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx index 4242f931c..a1ba117b0 100644 --- a/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx +++ b/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx @@ -117,7 +117,7 @@ const TabSettings = ({ component="h2" variant="h2" > - {t("settingsAppearance")} + {t("statusPageCreateAppearanceTitle")} {t("statusPageCreateAppearanceDescription")} diff --git a/client/src/Pages/StatusPage/Create/index.jsx b/client/src/Pages/StatusPage/Create/index.jsx index 50535cd90..3a10ab2cd 100644 --- a/client/src/Pages/StatusPage/Create/index.jsx +++ b/client/src/Pages/StatusPage/Create/index.jsx @@ -20,7 +20,7 @@ const TAB_LIST = ["General settings", "Contents"]; const ERROR_TAB_MAPPING = [ ["companyName", "url", "timezone", "color", "isPublished", "logo"], - ["monitors", "showUptimePercentage", "showCharts"], + ["monitors", "showUptimePercentage", "showCharts", "showAdminLoginLink"], ]; const CreateStatusPage = () => { @@ -39,6 +39,7 @@ const CreateStatusPage = () => { monitors: [], showCharts: true, showUptimePercentage: true, + showAdminLoginLink: false, }); const [errors, setErrors] = useState({}); const [selectedMonitors, setSelectedMonitors] = useState([]); @@ -59,6 +60,7 @@ const CreateStatusPage = () => { const [statusPage, statusPageMonitors, statusPageIsLoading, statusPageNetworkError] = useStatusPageFetch(isCreate, url); + console.log(JSON.stringify(form, null, 2)); // Handlers const handleFormChange = (e) => { let { type, name, value, checked } = e.target; @@ -176,7 +178,7 @@ const CreateStatusPage = () => { } let newLogo = undefined; - if (statusPage.logo) { + if (statusPage.logo && Object.keys(statusPage.logo).length > 0) { newLogo = { src: `data:${statusPage.logo.contentType};base64,${statusPage.logo.data}`, name: "logo", @@ -195,6 +197,7 @@ const CreateStatusPage = () => { logo: newLogo, showCharts: statusPage?.showCharts ?? true, showUptimePercentage: statusPage?.showUptimePercentage ?? true, + showAdminLoginLink: statusPage?.showAdminLoginLink ?? false, }; }); setSelectedMonitors(statusPageMonitors); @@ -244,7 +247,7 @@ const CreateStatusPage = () => { color="accent" onClick={handleSubmit} > - {t("settingsSave")} + {t("statusPageCreate.buttonSave")} diff --git a/client/src/Pages/StatusPage/Status/index.jsx b/client/src/Pages/StatusPage/Status/index.jsx index 727daeeb1..0d4ed61f2 100644 --- a/client/src/Pages/StatusPage/Status/index.jsx +++ b/client/src/Pages/StatusPage/Status/index.jsx @@ -48,7 +48,7 @@ const PublicStatus = () => { let link = undefined; const isPublic = location.pathname.startsWith("/status/uptime/public"); // Public status page - if (isPublic) { + if (isPublic && statusPage && statusPage.showAdminLoginLink === true) { sx = { paddingTop: theme.spacing(20), paddingLeft: "20vw", @@ -62,7 +62,7 @@ const PublicStatus = () => { return ; } - if (monitors.length === 0) { + if (monitors?.length === 0) { return ( { return ( + { monitor={monitor} triggerUpdate={triggerUpdate} /> + + + {t("distributedUptimeDetailsNoMonitorHistory")} diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index 29daa2942..965ed2676 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -48,9 +48,11 @@ import Settings from "../Pages/Settings"; import Maintenance from "../Pages/Maintenance"; import ProtectedRoute from "../Components/ProtectedRoute"; +import RoleProtectedRoute from "../Components/RoleProtectedRoute"; import CreateNewMaintenanceWindow from "../Pages/Maintenance/CreateMaintenance"; import withAdminCheck from "../Components/HOC/withAdminCheck"; import BulkImport from "../Pages/Uptime/BulkImport"; +import Logs from "../Pages/Logs"; const Routes = () => { const AdminCheckedRegister = withAdminCheck(AuthRegister); @@ -186,6 +188,15 @@ const Routes = () => { path="account/team" element={} /> + + + + + } + /> } The response from the axios GET request. @@ -562,6 +563,7 @@ class NetworkService { if (config.limit) params.append("limit", config.limit); if (config.dateRange) params.append("dateRange", config.dateRange); if (config.filter) params.append("filter", config.filter); + if (config.ack !== undefined) params.append("ack", config.ack); if (config.page) params.append("page", config.page); if (config.rowsPerPage) params.append("rowsPerPage", config.rowsPerPage); if (config.status !== undefined) params.append("status", config.status); @@ -581,6 +583,7 @@ class NetworkService { * @param {number} config.limit - The maximum number of checks to retrieve. * @param {string} config.dateRange - The range of dates for which to retrieve checks. * @param {string} config.filter - The filter to apply to the checks. + * @param {boolean} config.ack - The acknowledgment status to apply to the checks. * @param {number} config.page - The page number to retrieve in a paginated list. * @param {number} config.rowsPerPage - The number of rows per page in a paginated list. * @returns {Promise} The response from the axios GET request. @@ -592,12 +595,30 @@ class NetworkService { if (config.limit) params.append("limit", config.limit); if (config.dateRange) params.append("dateRange", config.dateRange); if (config.filter) params.append("filter", config.filter); + if (config.ack !== undefined) params.append("ack", config.ack); if (config.page) params.append("page", config.page); if (config.rowsPerPage) params.append("rowsPerPage", config.rowsPerPage); - if (config.status !== undefined) params.append("status", config.status); return this.axiosInstance.get(`/checks/team?${params.toString()}`); }; + /** + * ************************************ + * Update the status of a check + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.checkId - The ID of the check to update. + * @param {boolean} config.ack - The acknowledgment to update the check to. + * @returns {Promise} The response from the axios PUT request. + * + */ + async updateCheckStatus(config) { + return this.axiosInstance.put(`/checks/check/${config.checkId}`, { + ack: config.ack, + }); + } + /** * ************************************ * Get all checks for a given user @@ -871,6 +892,9 @@ class NetworkService { if (form.showUptimePercentage !== undefined) { fd.append("showUptimePercentage", String(form.showUptimePercentage)); } + if (form.showAdminLoginLink !== undefined) { + fd.append("showAdminLoginLink", String(form.showAdminLoginLink)); + } form.monitors && form.monitors.forEach((monitorId) => { fd.append("monitors[]", monitorId); @@ -1022,6 +1046,33 @@ class NetworkService { const { id, notification } = config; return this.axiosInstance.put(`/notifications/${id}`, notification); } + + async getQueueData() { + return this.axiosInstance.get(`/queue/all-metrics`, { + headers: { + "Content-Type": "application/json", + }, + }); + } + + async flushQueue() { + return this.axiosInstance.post(`/queue/flush`); + } + + async exportMonitors() { + const response = await this.axiosInstance.get("/monitors/export", { + responseType: "blob", + }); + return response; + } + + async getLogs() { + return this.axiosInstance.get(`/logs`, { + headers: { + "Content-Type": "application/json", + }, + }); + } } export default NetworkService; diff --git a/client/src/Utils/Theme/constants.js b/client/src/Utils/Theme/constants.js index 48dcdfd85..1b905bb79 100644 --- a/client/src/Utils/Theme/constants.js +++ b/client/src/Utils/Theme/constants.js @@ -85,6 +85,7 @@ const semanticColors = { const newColors = { offWhite: "#FEFEFE", offBlack: "#131315", + gray0: "#FDFDFD", gray10: "#F4F4FF", gray100: "#F3F3F3", gray200: "#EFEFEF", @@ -178,7 +179,7 @@ const newSemanticColors = { }, primaryBackground: { main: { - light: newColors.gray10, + light: newColors.gray0, dark: "#000000", }, }, diff --git a/client/src/Utils/Theme/globalTheme.js b/client/src/Utils/Theme/globalTheme.js index 58d9943be..eb827a907 100644 --- a/client/src/Utils/Theme/globalTheme.js +++ b/client/src/Utils/Theme/globalTheme.js @@ -36,6 +36,11 @@ const baseTheme = (palette) => ({ color: palette.primary.contrastTextTertiary, fontWeight: 400, }, + label: { + fontSize: "var(--env-var-font-size-medium)", + color: palette.primary.contrastTextSecondary, + fontWeight: 500, + }, }, /* TODO change to 4 */ spacing: 2, @@ -407,6 +412,7 @@ const baseTheme = (palette) => ({ }), }, }, + MuiTab: { styleOverrides: { root: ({ theme }) => ({ @@ -475,6 +481,8 @@ const baseTheme = (palette) => ({ MuiTabs: { styleOverrides: { root: ({ theme }) => ({ + height: "34px", + minHeight: "34px", display: "inline-flex", borderRadius: 0, "& .MuiTabs-indicator": { diff --git a/client/src/Utils/i18n.js b/client/src/Utils/i18n.js index 4468917a2..030091d48 100644 --- a/client/src/Utils/i18n.js +++ b/client/src/Utils/i18n.js @@ -24,6 +24,7 @@ i18n.use(initReactI18next).init({ interpolation: { escapeValue: false, }, + returnEmptyString: false, }); export default i18n; diff --git a/client/src/Utils/monitorUtils.js b/client/src/Utils/monitorUtils.js index cc92a86ae..e3645b0d0 100644 --- a/client/src/Utils/monitorUtils.js +++ b/client/src/Utils/monitorUtils.js @@ -36,3 +36,12 @@ export const parseDomainName = (url) => { return url; }; + +export const TypeToPathMap = { + http: "uptime", + port: "uptime", + docker: "uptime", + ping: "uptime", + hardware: "infrastructure", + pagespeed: "pagespeed", +}; diff --git a/client/src/Validation/validation.js b/client/src/Validation/validation.js index 4e6a806c8..d86bbd556 100644 --- a/client/src/Validation/validation.js +++ b/client/src/Validation/validation.js @@ -277,6 +277,7 @@ const statusPageValidation = joi.object({ logo: logoImageValidation, showUptimePercentage: joi.boolean(), showCharts: joi.boolean(), + showAdminLoginLink: joi.boolean(), }); const settingsValidation = joi.object({ diff --git a/client/src/assets/icons/logs.svg b/client/src/assets/icons/logs.svg new file mode 100644 index 000000000..556c3ba33 --- /dev/null +++ b/client/src/assets/icons/logs.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/client/src/locales/ar.json b/client/src/locales/ar.json index fd4d925a9..a7b64dd80 100644 --- a/client/src/locales/ar.json +++ b/client/src/locales/ar.json @@ -3,40 +3,11 @@ "title": "", "distributedStatusHeaderText": "", "distributedStatusSubHeaderText": "", - "settingsGeneralSettings": "", - "settingsDisplayTimezone": "", - "settingsDisplayTimezoneDescription": "", - "settingsAppearance": "", - "settingsAppearanceDescription": "", - "settingsThemeMode": "", - "settingsLanguage": "", - "settingsEnabled": "", "settingsDisabled": "", - "settingsHistoryAndMonitoring": "", - "settingsHistoryAndMonitoringDescription": "", - "settingsTTLLabel": "", - "settingsTTLOptionalLabel": "", - "settingsClearAllStats": "", - "settingsClearAllStatsButton": "", - "settingsClearAllStatsDialogTitle": "", - "settingsClearAllStatsDialogDescription": "", - "settingsClearAllStatsDialogConfirm": "", - "settingsDemoMonitors": "", - "settingsDemoMonitorsDescription": "", - "settingsAddDemoMonitors": "", - "settingsAddDemoMonitorsButton": "", - "settingsRemoveAllMonitors": "", - "settingsRemoveAllMonitorsButton": "", - "settingsRemoveAllMonitorsDialogTitle": "", - "settingsRemoveAllMonitorsDialogConfirm": "", - "settingsAbout": "", - "settingsDevelopedBy": "", - "settingsSave": "", "settingsSuccessSaved": "", "settingsFailedToSave": "", "settingsStatsCleared": "", "settingsFailedToClearStats": "", - "settingsDemoMonitorsAdded": "", "settingsFailedToAddDemoMonitors": "", "settingsMonitorsDeleted": "", "settingsFailedToDeleteMonitors": "", @@ -362,16 +333,10 @@ "noFileSelected": "", "fallbackPage": "" }, - "settingsSystemReset": "", - "settingsSystemResetDescription": "", "DeleteAccountTitle": "", "DeleteAccountButton": "", "publicLink": "", "maskedPageSpeedKeyPlaceholder": "", - "pageSpeedApiKeyFieldTitle": "", - "pageSpeedApiKeyFieldLabel": "", - "pageSpeedApiKeyFieldDescription": "", - "pageSpeedApiKeyFieldResetLabel": "", "reset": "", "ignoreTLSError": "", "tlsErrorIgnored": "", @@ -433,22 +398,13 @@ "password": "", "team": "", "logOut": "", - "notifications": "" + "notifications": "", + "logs": "" }, - "settingsEmail": "", - "settingsEmailDescription": "", - "settingsEmailHost": "", - "settingsEmailPort": "", - "settingsEmailAddress": "", - "settingsEmailPassword": "", "settingsEmailUser": "", - "settingsEmailFieldResetLabel": "", "state": "", "statusBreadCrumbsStatusPages": "", "statusBreadCrumbsDetails": "", - "settingsThemeModeLight": "", - "settingsThemeModeDark": "", - "settingsClearAllStatsDialogCancel": "", "commonSaving": "", "navControls": "", "incidentsPageTitle": "", @@ -463,26 +419,18 @@ "passwordRequirements": "", "saving": "" }, - "settingsEmailConnectionHost": "", "emailSent": "", "failedToSendEmail": "", - "settingsTestEmail": "", "settingsTestEmailSuccess": "", "settingsTestEmailFailed": "", "settingsTestEmailFailedWithReason": "", "settingsTestEmailUnknownError": "", - "settingsEmailRequiredFields": "", "statusMsg": { "paused": "", "up": "", "down": "", "pending": "" }, - "settingsURLTitle": "", - "settingsURLDescription": "", - "settingsURLSelectTitle": "", - "settingsURLEnabled": "", - "settingURLDisabled": "", "uptimeGeneralInstructions": { "http": "", "ping": "", @@ -583,6 +531,13 @@ }, "errors": { "validation": "" + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + } } }, "login": { @@ -709,13 +664,13 @@ "integrationKeyLabel": "", "integrationKeyPlaceholder": "" }, - "genericWebhookSettings": { + "discordSettings": { "title": "", "description": "", "webhookLabel": "", "webhookPlaceholder": "" }, - "discordSettings": { + "webhookSettings": { "title": "", "description": "", "webhookLabel": "", @@ -734,11 +689,147 @@ "paused": "" }, "advancedMatching": "", - "settingsEmailTLSServername": "", - "settingsEmailIgnoreTLS": "", - "settingsEmailRequireTLS": "", - "settingsEmailRejectUnauthorized": "", - "settingsEmailSecure": "", - "settingsEmailPool": "", - "sendTestNotifications": "" + "sendTestNotifications": "", + "selectAll": "", + "showAdminLoginLink": "", + "logsPage": { + "title": "", + "description": "", + "tabs": { + "queue": "", + "logs": "" + }, + "toast": { + "fetchLogsSuccess": "" + }, + "logLevelSelect": { + "title": "", + "values": { + "all": "", + "info": "", + "warn": "", + "error": "", + "debug": "" + } + } + }, + "queuePage": { + "title": "", + "refreshButton": "", + "flushButton": "", + "jobTable": { + "title": "", + "idHeader": "", + "urlHeader": "", + "typeHeader": "", + "activeHeader": "", + "lockedAtHeader": "", + "runCountHeader": "", + "failCountHeader": "", + "lastRunHeader": "", + "lastFinishedAtHeader": "", + "lastRunTookHeader": "" + }, + "metricsTable": { + "title": "", + "metricHeader": "", + "valueHeader": "" + }, + "failedJobTable": { + "title": "", + "monitorIdHeader": "", + "monitorUrlHeader": "", + "failCountHeader": "", + "failedAtHeader": "", + "failReasonHeader": "" + } + }, + "export": { + "title": "", + "success": "", + "failed": "" + }, + "monitorActions": { + "title": "", + "import": "", + "export": "" + }, + "settingsPage": { + "aboutSettings": { + "labelDevelopedBy": "", + "labelVersion": "", + "title": "" + }, + "demoMonitorsSettings": { + "buttonAddMonitors": "", + "description": "", + "title": "" + }, + "emailSettings": { + "buttonSendTestEmail": "", + "description": "", + "descriptionTransport": "", + "labelAddress": "", + "labelConnectionHost": "", + "labelHost": "", + "labelIgnoreTLS": "", + "labelPassword": "", + "labelPasswordSet": "", + "labelPool": "", + "labelPort": "", + "labelRejectUnauthorized": "", + "labelRequireTLS": "", + "labelSecure": "", + "labelTLSServername": "", + "labelUser": "", + "linkTransport": "", + "placeholderUser": "", + "title": "" + }, + "pageSpeedSettings": { + "description": "", + "labelApiKeySet": "", + "labelApiKey": "", + "title": "" + }, + "saveButtonLabel": "", + "statsSettings": { + "clearAllStatsButton": "", + "clearAllStatsDescription": "", + "clearAllStatsDialogConfirm": "", + "clearAllStatsDialogDescription": "", + "clearAllStatsDialogTitle": "", + "description": "", + "labelTTL": "", + "labelTTLOptional": "", + "title": "" + }, + "systemResetSettings": { + "buttonRemoveAllMonitors": "", + "description": "", + "dialogConfirm": "", + "dialogDescription": "", + "dialogTitle": "", + "title": "" + }, + "timezoneSettings": { + "description": "", + "label": "", + "title": "" + }, + "title": "", + "uiSettings": { + "description": "", + "labelLanguage": "", + "labelTheme": "", + "title": "" + }, + "urlSettings": { + "description": "", + "label": "", + "selectDisabled": "", + "selectEnabled": "", + "title": "" + } + } } diff --git a/client/src/locales/cs.json b/client/src/locales/cs.json index 66b2993d2..a5249ebbd 100644 --- a/client/src/locales/cs.json +++ b/client/src/locales/cs.json @@ -3,40 +3,11 @@ "title": "", "distributedStatusHeaderText": "", "distributedStatusSubHeaderText": "", - "settingsGeneralSettings": "Obecná nastavení", - "settingsDisplayTimezone": "", - "settingsDisplayTimezoneDescription": "", - "settingsAppearance": "Vzhled", - "settingsAppearanceDescription": "", - "settingsThemeMode": "", - "settingsLanguage": "Jazyk", - "settingsEnabled": "Zapnuto", "settingsDisabled": "Vypnuto", - "settingsHistoryAndMonitoring": "Historie a monitorování", - "settingsHistoryAndMonitoringDescription": "", - "settingsTTLLabel": "", - "settingsTTLOptionalLabel": "", - "settingsClearAllStats": "", - "settingsClearAllStatsButton": "", - "settingsClearAllStatsDialogTitle": "", - "settingsClearAllStatsDialogDescription": "", - "settingsClearAllStatsDialogConfirm": "", - "settingsDemoMonitors": "", - "settingsDemoMonitorsDescription": "", - "settingsAddDemoMonitors": "", - "settingsAddDemoMonitorsButton": "", - "settingsRemoveAllMonitors": "", - "settingsRemoveAllMonitorsButton": "", - "settingsRemoveAllMonitorsDialogTitle": "", - "settingsRemoveAllMonitorsDialogConfirm": "", - "settingsAbout": "", - "settingsDevelopedBy": "Vyvinuto společností Bluewave Labs.", - "settingsSave": "Uložit", "settingsSuccessSaved": "Nastavení bylo úspěšně uloženo", "settingsFailedToSave": "Nepodařilo se uložit nastavení", "settingsStatsCleared": "", "settingsFailedToClearStats": "", - "settingsDemoMonitorsAdded": "", "settingsFailedToAddDemoMonitors": "", "settingsMonitorsDeleted": "", "settingsFailedToDeleteMonitors": "", @@ -362,16 +333,10 @@ "noFileSelected": "", "fallbackPage": "" }, - "settingsSystemReset": "", - "settingsSystemResetDescription": "", "DeleteAccountTitle": "", "DeleteAccountButton": "", "publicLink": "", "maskedPageSpeedKeyPlaceholder": "", - "pageSpeedApiKeyFieldTitle": "", - "pageSpeedApiKeyFieldLabel": "", - "pageSpeedApiKeyFieldDescription": "", - "pageSpeedApiKeyFieldResetLabel": "", "reset": "", "ignoreTLSError": "", "tlsErrorIgnored": "", @@ -433,22 +398,13 @@ "password": "", "team": "", "logOut": "", - "notifications": "" + "notifications": "", + "logs": "" }, - "settingsEmail": "", - "settingsEmailDescription": "", - "settingsEmailHost": "", - "settingsEmailPort": "", - "settingsEmailAddress": "", - "settingsEmailPassword": "", "settingsEmailUser": "", - "settingsEmailFieldResetLabel": "", "state": "", "statusBreadCrumbsStatusPages": "", "statusBreadCrumbsDetails": "", - "settingsThemeModeLight": "", - "settingsThemeModeDark": "", - "settingsClearAllStatsDialogCancel": "", "commonSaving": "", "navControls": "", "incidentsPageTitle": "", @@ -463,26 +419,18 @@ "passwordRequirements": "", "saving": "" }, - "settingsEmailConnectionHost": "", "emailSent": "", "failedToSendEmail": "", - "settingsTestEmail": "", "settingsTestEmailSuccess": "", "settingsTestEmailFailed": "", "settingsTestEmailFailedWithReason": "", "settingsTestEmailUnknownError": "", - "settingsEmailRequiredFields": "", "statusMsg": { "paused": "", "up": "", "down": "", "pending": "" }, - "settingsURLTitle": "", - "settingsURLDescription": "", - "settingsURLSelectTitle": "", - "settingsURLEnabled": "", - "settingURLDisabled": "", "uptimeGeneralInstructions": { "http": "", "ping": "", @@ -583,6 +531,13 @@ }, "errors": { "validation": "Nastala chyba při ověřování dat." + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + } } }, "login": { @@ -709,13 +664,13 @@ "integrationKeyLabel": "", "integrationKeyPlaceholder": "" }, - "genericWebhookSettings": { + "discordSettings": { "title": "", "description": "", "webhookLabel": "", "webhookPlaceholder": "" }, - "discordSettings": { + "webhookSettings": { "title": "", "description": "", "webhookLabel": "", @@ -734,11 +689,147 @@ "paused": "" }, "advancedMatching": "", - "settingsEmailTLSServername": "", - "settingsEmailIgnoreTLS": "", - "settingsEmailRequireTLS": "", - "settingsEmailRejectUnauthorized": "", - "settingsEmailSecure": "", - "settingsEmailPool": "", - "sendTestNotifications": "" + "sendTestNotifications": "", + "selectAll": "", + "showAdminLoginLink": "", + "logsPage": { + "title": "", + "description": "", + "tabs": { + "queue": "", + "logs": "" + }, + "toast": { + "fetchLogsSuccess": "" + }, + "logLevelSelect": { + "title": "", + "values": { + "all": "", + "info": "", + "warn": "", + "error": "", + "debug": "" + } + } + }, + "queuePage": { + "title": "", + "refreshButton": "", + "flushButton": "", + "jobTable": { + "title": "", + "idHeader": "", + "urlHeader": "", + "typeHeader": "", + "activeHeader": "", + "lockedAtHeader": "", + "runCountHeader": "", + "failCountHeader": "", + "lastRunHeader": "", + "lastFinishedAtHeader": "", + "lastRunTookHeader": "" + }, + "metricsTable": { + "title": "", + "metricHeader": "", + "valueHeader": "" + }, + "failedJobTable": { + "title": "", + "monitorIdHeader": "", + "monitorUrlHeader": "", + "failCountHeader": "", + "failedAtHeader": "", + "failReasonHeader": "" + } + }, + "export": { + "title": "", + "success": "", + "failed": "" + }, + "monitorActions": { + "title": "", + "import": "", + "export": "" + }, + "settingsPage": { + "aboutSettings": { + "labelDevelopedBy": "", + "labelVersion": "", + "title": "" + }, + "demoMonitorsSettings": { + "buttonAddMonitors": "", + "description": "", + "title": "" + }, + "emailSettings": { + "buttonSendTestEmail": "", + "description": "", + "descriptionTransport": "", + "labelAddress": "", + "labelConnectionHost": "", + "labelHost": "", + "labelIgnoreTLS": "", + "labelPassword": "", + "labelPasswordSet": "", + "labelPool": "", + "labelPort": "", + "labelRejectUnauthorized": "", + "labelRequireTLS": "", + "labelSecure": "", + "labelTLSServername": "", + "labelUser": "", + "linkTransport": "", + "placeholderUser": "", + "title": "" + }, + "pageSpeedSettings": { + "description": "", + "labelApiKeySet": "", + "labelApiKey": "", + "title": "" + }, + "saveButtonLabel": "", + "statsSettings": { + "clearAllStatsButton": "", + "clearAllStatsDescription": "", + "clearAllStatsDialogConfirm": "", + "clearAllStatsDialogDescription": "", + "clearAllStatsDialogTitle": "", + "description": "", + "labelTTL": "", + "labelTTLOptional": "", + "title": "" + }, + "systemResetSettings": { + "buttonRemoveAllMonitors": "", + "description": "", + "dialogConfirm": "", + "dialogDescription": "", + "dialogTitle": "", + "title": "" + }, + "timezoneSettings": { + "description": "", + "label": "", + "title": "" + }, + "title": "", + "uiSettings": { + "description": "", + "labelLanguage": "", + "labelTheme": "", + "title": "" + }, + "urlSettings": { + "description": "", + "label": "", + "selectDisabled": "", + "selectEnabled": "", + "title": "" + } + } } diff --git a/client/src/locales/de.json b/client/src/locales/de.json index 6d5c5a4d8..948c384d8 100644 --- a/client/src/locales/de.json +++ b/client/src/locales/de.json @@ -3,59 +3,30 @@ "title": "Titel", "distributedStatusHeaderText": "", "distributedStatusSubHeaderText": "", - "settingsGeneralSettings": "Einstellungen", - "settingsDisplayTimezone": "Zeitzone anzeigen", - "settingsDisplayTimezoneDescription": "", - "settingsAppearance": "", - "settingsAppearanceDescription": "", - "settingsThemeMode": "Theme modus", - "settingsLanguage": "Sprache", - "settingsEnabled": "Aktiv", - "settingsDisabled": "", - "settingsHistoryAndMonitoring": "", - "settingsHistoryAndMonitoringDescription": "", - "settingsTTLLabel": "", - "settingsTTLOptionalLabel": "", - "settingsClearAllStats": "", - "settingsClearAllStatsButton": "", - "settingsClearAllStatsDialogTitle": "", - "settingsClearAllStatsDialogDescription": "", - "settingsClearAllStatsDialogConfirm": "", - "settingsDemoMonitors": "", - "settingsDemoMonitorsDescription": "", - "settingsAddDemoMonitors": "", - "settingsAddDemoMonitorsButton": "", - "settingsRemoveAllMonitors": "", - "settingsRemoveAllMonitorsButton": "", - "settingsRemoveAllMonitorsDialogTitle": "", - "settingsRemoveAllMonitorsDialogConfirm": "", - "settingsAbout": "", - "settingsDevelopedBy": "", - "settingsSave": "", - "settingsSuccessSaved": "", - "settingsFailedToSave": "", - "settingsStatsCleared": "", - "settingsFailedToClearStats": "", - "settingsDemoMonitorsAdded": "", + "settingsDisabled": "Inaktiv", + "settingsSuccessSaved": "Einstellungen erfolgreich gespeichert", + "settingsFailedToSave": "Fehler beim Speichern der Einstellungen", + "settingsStatsCleared": "Statistiken gelöscht", + "settingsFailedToClearStats": "Fehler beim löschen der Statistiken", "settingsFailedToAddDemoMonitors": "", "settingsMonitorsDeleted": "", "settingsFailedToDeleteMonitors": "", "starPromptTitle": "", "starPromptDescription": "", - "https": "", - "http": "", + "https": "HTTPS", + "http": "HTTP", "monitor": "", - "aboutus": "", - "now": "", - "delete": "", - "configure": "", - "responseTime": "", - "ms": "", + "aboutus": "Über uns", + "now": "Jetzt", + "delete": "Löschen", + "configure": "Einstellungen", + "responseTime": "Antwortzeit", + "ms": "ms", "bar": "", "area": "", - "country": "", - "city": "", - "response": "", + "country": "Land", + "city": "Stadt", + "response": "Antwort", "monitorStatusUp": "", "monitorStatusDown": "", "webhookSendSuccess": "", @@ -69,7 +40,7 @@ "distributedUptimeCreateChecksDescription": "", "distributedUptimeCreateIncidentNotification": "", "distributedUptimeCreateIncidentDescription": "", - "distributedUptimeCreateAdvancedSettings": "", + "distributedUptimeCreateAdvancedSettings": "Erweiterte Einstellungen", "distributedUptimeDetailsNoMonitorHistory": "", "distributedUptimeDetailsStatusHeaderUptime": "", "distributedUptimeDetailsStatusHeaderLastUpdate": "", @@ -78,33 +49,33 @@ "testNotification": "", "addOrEditNotifications": "", "slack": { - "label": "", + "label": "Slack", "description": "", - "webhookLabel": "", + "webhookLabel": "Webhook URL", "webhookPlaceholder": "", "webhookRequired": "" }, "discord": { - "label": "", + "label": "Discord", "description": "", - "webhookLabel": "", + "webhookLabel": "Discord Webhook URL", "webhookPlaceholder": "", "webhookRequired": "" }, "telegram": { - "label": "", + "label": "Telegram", "description": "", "tokenLabel": "", - "tokenPlaceholder": "", + "tokenPlaceholder": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", "chatIdLabel": "", - "chatIdPlaceholder": "", + "chatIdPlaceholder": "-1001234567890", "fieldsRequired": "" }, "webhook": { - "label": "", + "label": "Webhooks", "description": "", - "urlLabel": "", - "urlPlaceholder": "", + "urlLabel": "Webhook URL", + "urlPlaceholder": "https://your-server.com/webhook", "urlRequired": "" }, "testNotificationDevelop": "", @@ -153,14 +124,14 @@ "distributedUptimeStatusLogoUploadButton": "", "distributedUptimeStatusStandardMonitorsHeader": "", "distributedUptimeStatusStandardMonitorsDescription": "", - "distributedUptimeStatusCreateYour": "", - "distributedUptimeStatusEditYour": "", + "distributedUptimeStatusCreateYour": "Erstelle Dein", + "distributedUptimeStatusEditYour": "Ändere Dein", "distributedUptimeStatusPublishedLabel": "", - "distributedUptimeStatusCompanyNameLabel": "", + "distributedUptimeStatusCompanyNameLabel": "Firmenname", "distributedUptimeStatusPageAddressLabel": "", - "distributedUptimeStatus30Days": "", - "distributedUptimeStatus60Days": "", - "distributedUptimeStatus90Days": "", + "distributedUptimeStatus30Days": "30 Tage", + "distributedUptimeStatus60Days": "60 Tage", + "distributedUptimeStatus90Days": "90 Tage", "distributedUptimeStatusPageNotSetUp": "", "distributedUptimeStatusContactAdmin": "", "distributedUptimeStatusPageNotPublic": "", @@ -362,16 +333,10 @@ "noFileSelected": "", "fallbackPage": "" }, - "settingsSystemReset": "", - "settingsSystemResetDescription": "", "DeleteAccountTitle": "", "DeleteAccountButton": "", "publicLink": "", "maskedPageSpeedKeyPlaceholder": "", - "pageSpeedApiKeyFieldTitle": "", - "pageSpeedApiKeyFieldLabel": "", - "pageSpeedApiKeyFieldDescription": "", - "pageSpeedApiKeyFieldResetLabel": "", "reset": "", "ignoreTLSError": "", "tlsErrorIgnored": "", @@ -433,22 +398,13 @@ "password": "", "team": "", "logOut": "", - "notifications": "" + "notifications": "", + "logs": "" }, - "settingsEmail": "", - "settingsEmailDescription": "", - "settingsEmailHost": "", - "settingsEmailPort": "", - "settingsEmailAddress": "", - "settingsEmailPassword": "", "settingsEmailUser": "", - "settingsEmailFieldResetLabel": "", "state": "", "statusBreadCrumbsStatusPages": "", "statusBreadCrumbsDetails": "", - "settingsThemeModeLight": "", - "settingsThemeModeDark": "", - "settingsClearAllStatsDialogCancel": "", "commonSaving": "", "navControls": "", "incidentsPageTitle": "", @@ -463,26 +419,18 @@ "passwordRequirements": "", "saving": "" }, - "settingsEmailConnectionHost": "", "emailSent": "", "failedToSendEmail": "", - "settingsTestEmail": "", "settingsTestEmailSuccess": "", "settingsTestEmailFailed": "", "settingsTestEmailFailedWithReason": "", "settingsTestEmailUnknownError": "", - "settingsEmailRequiredFields": "", "statusMsg": { "paused": "", "up": "", "down": "", "pending": "" }, - "settingsURLTitle": "", - "settingsURLDescription": "", - "settingsURLSelectTitle": "", - "settingsURLEnabled": "", - "settingURLDisabled": "", "uptimeGeneralInstructions": { "http": "", "ping": "", @@ -583,6 +531,13 @@ }, "errors": { "validation": "" + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + } } }, "login": { @@ -709,13 +664,13 @@ "integrationKeyLabel": "", "integrationKeyPlaceholder": "" }, - "genericWebhookSettings": { + "discordSettings": { "title": "", "description": "", "webhookLabel": "", "webhookPlaceholder": "" }, - "discordSettings": { + "webhookSettings": { "title": "", "description": "", "webhookLabel": "", @@ -734,11 +689,147 @@ "paused": "" }, "advancedMatching": "", - "settingsEmailTLSServername": "", - "settingsEmailIgnoreTLS": "", - "settingsEmailRequireTLS": "", - "settingsEmailRejectUnauthorized": "", - "settingsEmailSecure": "", - "settingsEmailPool": "", - "sendTestNotifications": "" + "sendTestNotifications": "", + "selectAll": "", + "showAdminLoginLink": "", + "logsPage": { + "title": "", + "description": "", + "tabs": { + "queue": "", + "logs": "" + }, + "toast": { + "fetchLogsSuccess": "" + }, + "logLevelSelect": { + "title": "", + "values": { + "all": "", + "info": "", + "warn": "", + "error": "", + "debug": "" + } + } + }, + "queuePage": { + "title": "", + "refreshButton": "", + "flushButton": "", + "jobTable": { + "title": "", + "idHeader": "", + "urlHeader": "", + "typeHeader": "", + "activeHeader": "", + "lockedAtHeader": "", + "runCountHeader": "", + "failCountHeader": "", + "lastRunHeader": "", + "lastFinishedAtHeader": "", + "lastRunTookHeader": "" + }, + "metricsTable": { + "title": "", + "metricHeader": "", + "valueHeader": "" + }, + "failedJobTable": { + "title": "", + "monitorIdHeader": "", + "monitorUrlHeader": "", + "failCountHeader": "", + "failedAtHeader": "", + "failReasonHeader": "" + } + }, + "export": { + "title": "", + "success": "", + "failed": "" + }, + "monitorActions": { + "title": "", + "import": "", + "export": "" + }, + "settingsPage": { + "aboutSettings": { + "labelDevelopedBy": "", + "labelVersion": "", + "title": "" + }, + "demoMonitorsSettings": { + "buttonAddMonitors": "", + "description": "", + "title": "" + }, + "emailSettings": { + "buttonSendTestEmail": "", + "description": "", + "descriptionTransport": "", + "labelAddress": "", + "labelConnectionHost": "", + "labelHost": "", + "labelIgnoreTLS": "", + "labelPassword": "", + "labelPasswordSet": "", + "labelPool": "", + "labelPort": "", + "labelRejectUnauthorized": "", + "labelRequireTLS": "", + "labelSecure": "", + "labelTLSServername": "", + "labelUser": "", + "linkTransport": "", + "placeholderUser": "", + "title": "" + }, + "pageSpeedSettings": { + "description": "", + "labelApiKeySet": "", + "labelApiKey": "", + "title": "" + }, + "saveButtonLabel": "", + "statsSettings": { + "clearAllStatsButton": "", + "clearAllStatsDescription": "", + "clearAllStatsDialogConfirm": "", + "clearAllStatsDialogDescription": "", + "clearAllStatsDialogTitle": "", + "description": "", + "labelTTL": "", + "labelTTLOptional": "", + "title": "" + }, + "systemResetSettings": { + "buttonRemoveAllMonitors": "", + "description": "", + "dialogConfirm": "", + "dialogDescription": "", + "dialogTitle": "", + "title": "" + }, + "timezoneSettings": { + "description": "", + "label": "", + "title": "" + }, + "title": "", + "uiSettings": { + "description": "", + "labelLanguage": "", + "labelTheme": "", + "title": "" + }, + "urlSettings": { + "description": "", + "label": "", + "selectDisabled": "", + "selectEnabled": "", + "title": "" + } + } } diff --git a/client/src/locales/en.json b/client/src/locales/en.json index f53da56b9..8d26f75fd 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -1,553 +1,82 @@ { - "submit": "Submit", - "title": "Title", - "distributedStatusHeaderText": "Real-time, real-device coverage", - "distributedStatusSubHeaderText": "Powered by millions devices worldwide, view a system performance by global region, country or city", - "settingsGeneralSettings": "General settings", - "settingsDisplayTimezone": "Display timezone", - "settingsDisplayTimezoneDescription": "Select the timezone used to display dates and times throughout the application.", - "settingsAppearance": "Appearance", - "settingsAppearanceDescription": "Switch between light and dark mode, or change user interface language", - "settingsThemeMode": "Theme Mode", - "settingsLanguage": "Language", - "settingsEnabled": "Enabled", - "settingsDisabled": "Disabled", - "settingsHistoryAndMonitoring": "History of monitoring", - "settingsHistoryAndMonitoringDescription": "Define how long you want to retain historical data. You can also clear all existing data.", - "settingsTTLLabel": "The days you want to keep monitoring history.", - "settingsTTLOptionalLabel": "0 for infinite", - "settingsClearAllStats": "Clear all stats. This is irreversible.", - "settingsClearAllStatsButton": "Clear all stats", - "settingsClearAllStatsDialogTitle": "Do you want to clear all stats?", - "settingsClearAllStatsDialogDescription": "Once removed, the monitoring history and stats cannot be retrieved.", - "settingsClearAllStatsDialogConfirm": "Yes, clear all stats", - "settingsDemoMonitors": "Demo monitors", - "settingsDemoMonitorsDescription": "Add sample monitors for demonstration purposes.", - "settingsAddDemoMonitors": "Adding demo monitors", - "settingsAddDemoMonitorsButton": "Add demo monitors", - "settingsRemoveAllMonitors": "Removing all monitors", - "settingsRemoveAllMonitorsButton": "Remove all monitors", - "settingsRemoveAllMonitorsDialogTitle": "Do you want to remove all monitors?", - "settingsRemoveAllMonitorsDialogConfirm": "Yes, remove all monitors", - "settingsAbout": "About", - "settingsDevelopedBy": "Developed by Bluewave Labs.", - "settingsSave": "Save", - "settingsSuccessSaved": "Settings saved successfully", - "settingsFailedToSave": "Failed to save settings", - "settingsStatsCleared": "Stats cleared successfully", - "settingsFailedToClearStats": "Failed to clear stats", - "settingsDemoMonitorsAdded": "Successfully added demo monitors", - "settingsFailedToAddDemoMonitors": "Failed to add demo monitors", - "settingsMonitorsDeleted": "Successfully deleted all monitors", - "settingsFailedToDeleteMonitors": "Failed to delete all monitors", - "starPromptTitle": "Star Checkmate", - "starPromptDescription": "See the latest releases and help grow the community on GitHub", - "https": "HTTPS", - "http": "HTTP", - "monitor": "monitor", - "aboutus": "About Us", - "now": "Now", - "delete": "Delete", - "configure": "Configure", - "responseTime": "Response time", - "ms": "ms", - "bar": "Bar", - "area": "Area", - "country": "COUNTRY", - "city": "CITY", - "response": "RESPONSE", - "monitorStatusUp": "Monitor {name} ({url}) is now UP and responding", - "monitorStatusDown": "Monitor {name} ({url}) is DOWN and not responding", - "webhookSendSuccess": "Webhook notification sent successfully", - "webhookSendError": "Error sending webhook notification to {platform}", - "webhookUnsupportedPlatform": "Unsupported platform: {platform}", - "distributedRightCategoryTitle": "Monitor", - "distributedStatusServerMonitors": "Server Monitors", - "distributedStatusServerMonitorsDescription": "Monitor status of related servers", - "distributedUptimeCreateSelectURL": "Here you can select the URL of the host, together with the type of monitor.", - "distributedUptimeCreateChecks": "Checks to perform", - "distributedUptimeCreateChecksDescription": "You can always add or remove checks after adding your site.", - "distributedUptimeCreateIncidentNotification": "Incident notifications", - "distributedUptimeCreateIncidentDescription": "When there is an incident, notify users.", - "distributedUptimeCreateAdvancedSettings": "Advanced settings", - "distributedUptimeDetailsNoMonitorHistory": "There is no check history for this monitor yet.", - "distributedUptimeDetailsStatusHeaderUptime": "Uptime:", - "distributedUptimeDetailsStatusHeaderLastUpdate": "Last updated", - "notifications": { - "enableNotifications": "Enable {{platform}} notifications", - "testNotification": "Test notification", - "addOrEditNotifications": "Add or edit notifications", - "slack": { - "label": "Slack", - "description": "To enable Slack notifications, create a Slack app and enable incoming webhooks. After that, simply provide the webhook URL here.", - "webhookLabel": "Webhook URL", - "webhookPlaceholder": "https://hooks.slack.com/services/...", - "webhookRequired": "Slack webhook URL is required" - }, - "discord": { - "label": "Discord", - "description": "To send data to a Discord channel from Checkmate via Discord notifications using webhooks, you can use Discord's incoming Webhooks feature.", - "webhookLabel": "Discord Webhook URL", - "webhookPlaceholder": "https://discord.com/api/webhooks/...", - "webhookRequired": "Discord webhook URL is required" - }, - "telegram": { - "label": "Telegram", - "description": "To enable Telegram notifications, create a Telegram bot using BotFather, an official bot for creating and managing Telegram bots. Then, get the API token and chat ID and write them down here.", - "tokenLabel": "Your bot token", - "tokenPlaceholder": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", - "chatIdLabel": "Your Chat ID", - "chatIdPlaceholder": "-1001234567890", - "fieldsRequired": "Telegram token and chat ID are required" - }, - "webhook": { - "label": "Webhooks", - "description": "You can set up a custom webhook to receive notifications when incidents occur.", - "urlLabel": "Webhook URL", - "urlPlaceholder": "https://your-server.com/webhook", - "urlRequired": "Webhook URL is required" - }, - "testNotificationDevelop": "Test notification 2", - "integrationButton": "Notification Integration", - "testSuccess": "Test notification sent successfully!", - "testFailed": "Failed to send test notification", - "unsupportedType": "Unsupported notification type", - "networkError": "Network error occurred", - "fallback": { - "title": "notification channel", - "checks": [ - "Alert teams about downtime or performance issues", - "Let engineers know when incidents happen", - "Keep administrators informed of system changes" - ] - }, - "createButton": "Create notification channel", - "createTitle": "Notification channel", - "create": { - "success": "Notification created successfully", - "failed": "Failed to create notification" - }, - "fetch": { - "success": "Notifications fetched successfully", - "failed": "Failed to fetch notifications" - }, - "delete": { - "success": "Notification deleted successfully", - "failed": "Failed to delete notification" - }, - "edit": { - "success": "Notification updated successfully", - "failed": "Failed to update notification" - }, - "test": { - "success": "Test notification sent successfully", - "failed": "Failed to send test notification" - } - }, - "testLocale": "testLocale", - "add": "Add", - "monitors": "monitors", - "distributedUptimeStatusCreateStatusPage": "status page", - "distributedUptimeStatusCreateStatusPageAccess": "Access", - "distributedUptimeStatusCreateStatusPageReady": "If your status page is ready, you can mark it as published.", - "distributedUptimeStatusBasicInfoHeader": "Basic Information", - "distributedUptimeStatusBasicInfoDescription": "Define company name and the subdomain that your status page points to.", - "distributedUptimeStatusLogoHeader": "Logo", - "distributedUptimeStatusLogoDescription": "Upload a logo for your status page", - "distributedUptimeStatusLogoUploadButton": "Upload logo", - "distributedUptimeStatusStandardMonitorsHeader": "Standard Monitors", - "distributedUptimeStatusStandardMonitorsDescription": "Attach standard monitors to your status page.", - "distributedUptimeStatusCreateYour": "Create your", - "distributedUptimeStatusEditYour": "Edit your", - "distributedUptimeStatusPublishedLabel": "Published and visible to the public", - "distributedUptimeStatusCompanyNameLabel": "Company name", - "distributedUptimeStatusPageAddressLabel": "Your status page address", - "distributedUptimeStatus30Days": "30 days", - "distributedUptimeStatus60Days": "60 days", - "distributedUptimeStatus90Days": "90 days", - "distributedUptimeStatusPageNotSetUp": "A status page is not set up.", - "distributedUptimeStatusContactAdmin": "Please contact your administrator", - "distributedUptimeStatusPageNotPublic": "This status page is not public.", - "distributedUptimeStatusPageDeleteDialog": "Do you want to delete this status page?", - "distributedUptimeStatusPageDeleteConfirm": "Yes, delete status page", - "distributedUptimeStatusPageDeleteDescription": "Once deleted, your status page cannot be retrieved.", - "distributedUptimeStatusDevices": "Devices", - "distributedUptimeStatusUpt": "UPT", - "distributedUptimeStatusUptBurned": "UPT Burned", - "distributedUptimeStatusUptLogo": "Upt Logo", - "incidentsTableNoIncidents": "No incidents recorded", - "incidentsTablePaginationLabel": "incidents", - "incidentsTableMonitorName": "Monitor Name", - "incidentsTableStatus": "Status", - "incidentsTableDateTime": "Date & Time", - "incidentsTableStatusCode": "Status Code", - "incidentsTableMessage": "Message", - "incidentsOptionsHeader": "Incidents for:", - "incidentsOptionsHeaderFilterBy": "Filter by:", - "incidentsOptionsHeaderFilterAll": "All", - "incidentsOptionsHeaderFilterDown": "Down", - "incidentsOptionsHeaderFilterCannotResolve": "Cannot resolve", - "incidentsOptionsHeaderShow": "Show:", - "incidentsOptionsHeaderLastHour": "Last hour", - "incidentsOptionsHeaderLastDay": "Last day", - "incidentsOptionsHeaderLastWeek": "Last week", - "incidentsOptionsPlaceholderAllServers": "All servers", - "infrastructureCreateYour": "Create your", - "infrastructureCreateGeneralSettingsDescription": "Here you can select the URL of the host, together with the friendly name and authorization secret to connect to the server agent.", - "infrastructureServerRequirement": "The server you are monitoring must be running the", - "infrastructureCustomizeAlerts": "Customize alerts", - "infrastructureAlertNotificationDescription": "Send a notification to user(s) when thresholds exceed a specified percentage.", - "infrastructureCreateMonitor": "Create Infrastructure Monitor", - "infrastructureProtocol": "Protocol", - "infrastructureServerUrlLabel": "Server URL", - "infrastructureDisplayNameLabel": "Display name", - "infrastructureAuthorizationSecretLabel": "Authorization secret", - "gb": "GB", - "mb": "MB", - "mem": "Mem", - "memoryUsage": "Memory usage", - "cpu": "CPU", - "cpuUsage": "CPU usage", - "cpuTemperature": "CPU Temperature", - "diskUsage": "Disk Usage", - "used": "Used", - "total": "Total", - "cores": "Cores", - "frequency": "Frequency", - "status": "Status", - "cpuPhysical": "CPU (Physical)", - "cpuLogical": "CPU (Logical)", - "cpuFrequency": "CPU Frequency", - "avgCpuTemperature": "Average CPU Temperature", - "memory": "Memory", - "disk": "Disk", - "uptime": "Uptime", - "os": "OS", - "host": "Host", - "actions": "Actions", - "integrations": "Integrations", - "integrationsPrism": "Connect Prism to your favorite service.", - "integrationsSlack": "Slack", - "integrationsSlackInfo": "Connect with Slack and see incidents in a channel", - "integrationsDiscord": "Discord", - "integrationsDiscordInfo": "Connect with Discord and view incidents directly in a channel", - "integrationsZapier": "Zapier", - "integrationsZapierInfo": "Send all incidents to Zapier, and then see them everywhere", - "commonSave": "Save", - "createYour": "Create your", - "createMonitor": "Create monitor", - "pause": "Pause", - "resume": "Resume", - "editing": "Editing...", - "url": "URL", - "access": "Access", - "timezone": "Timezone", - "features": "Features", - "administrator": "Administrator?", - "loginHere": "Login here", - "displayName": "Display name", - "urlMonitor": "URL to monitor", - "portToMonitor": "Port to monitor", - "websiteMonitoring": "Website monitoring", - "websiteMonitoringDescription": "Use HTTP(s) to monitor your website or API endpoint.", - "pingMonitoring": "Ping monitoring", - "pingMonitoringDescription": "Check whether your server is available or not.", - "dockerContainerMonitoring": "Docker container monitoring", - "dockerContainerMonitoringDescription": "Check whether your Docker container is running or not.", - "portMonitoring": "Port monitoring", - "portMonitoringDescription": "Check whether your port is open or not.", - "createMaintenanceWindow": "Create maintenance window", - "createMaintenance": "Create maintenance", - "editMaintenance": "Edit maintenance", - "maintenanceWindowName": "Maintenance Window Name", - "friendlyNameInput": "Friendly name", - "friendlyNamePlaceholder": "Maintenance at __ : __ for ___ minutes", - "maintenanceRepeat": "Maintenance Repeat", - "maintenance": "maintenance", - "duration": "Duration", - "addMonitors": "Add monitors", - "window": "window", - "cancel": "Cancel", - "message": "Message", - "low": "low", - "high": "high", - "statusCode": "Status code", - "date&Time": "Date & Time", - "type": "Type", - "statusPageName": "Status page name", - "publicURL": "Public URL", - "repeat": "Repeat", - "edit": "Edit", - "createA": "Create a", - "remove": "Remove", - "monitorActions": { - "details": "Details", - "paused": "paused", - "resumed": "resumed", - "pauseResumeSuccess": "Monitor {{action}} successfully", - "pauseResumeFailed": "Failed to {{action}} monitor", - "deleteSuccess": "Monitor deleted successfully", - "deleteFailed": "Failed to delete monitor" - }, - "maintenanceWindowDescription": "Your pings won't be sent during this time frame", - "startTime": "Start time", - "timeZoneInfo": "All dates and times are in GMT+0 time zone.", - "monitorsToApply": "Monitors to apply maintenance window to", - "nextWindow": "Next window", - "notFoundButton": "Go to the main dashboard", - "pageSpeedConfigureSettingsDescription": "Here you can select the URL of the host, together with the type of monitor.", - "monitorDisplayName": "Monitor display name", - "whenNewIncident": "When there is a new incident,", - "notifySMS": "Notify via SMS (coming soon)", - "notifyEmails": "Also notify via email to multiple addresses (coming soon)", - "seperateEmails": "You can separate multiple emails with a comma", - "checkFrequency": "Check frequency", - "matchMethod": "Match Method", - "expectedValue": "Expected value", - "deleteDialogTitle": "Do you really want to delete this monitor?", - "deleteDialogDescription": "Once deleted, this monitor cannot be retrieved.", - "pageSpeedMonitor": "PageSpeed monitor", - "shown": "Shown", - "ago": "ago", - "companyName": "Company name", - "pageSpeedDetailsPerformanceReport": "Values are estimated and may vary.", - "pageSpeedDetailsPerformanceReportCalculator": "See calculator", - "checkingEvery": "Checking every", - "statusPageCreateSettings": "If your status page is ready, you can mark it as published.", - "basicInformation": "Basic Information", - "statusPageCreateBasicInfoDescription": "Define company name and the subdomain that your status page points to.", - "statusPageCreateSelectTimeZoneDescription": "Select the timezone that your status page will be displayed in.", - "statusPageCreateAppearanceDescription": "Define the default look and feel of your public status page.", - "statusPageCreateSettingsCheckboxLabel": "Published and visible to the public", - "statusPageCreateBasicInfoStatusPageAddress": "Your status page address", - "statusPageCreateTabsContent": "Status page servers", - "statusPageCreateTabsContentDescription": "You can add any number of servers that you monitor to your status page. You can also reorder them for the best viewing experience.", - "statusPageCreateTabsContentFeaturesDescription": "Show more details on the status page", - "showCharts": "Show charts", - "showUptimePercentage": "Show uptime percentage", - "removeLogo": "Remove Logo", - "statusPageStatus": "A public status page is not set up.", - "statusPageStatusContactAdmin": "Please contact to your administrator", - "statusPageStatusNotPublic": "This status page is not public.", - "statusPageStatusNoPage": "There's no status page here.", - "statusPageStatusServiceStatus": "Service status", - "deleteStatusPage": "Do you want to delete this status page?", - "deleteStatusPageConfirm": "Yes, delete status page", - "deleteStatusPageDescription": "Once deleted, your status page cannot be retrieved.", - "uptimeCreate": "The expected value is used to match against response result, and the match determines the status.", - "uptimeCreateJsonPath": "This expression will be evaluated against the reponse JSON data and the result will be used to match against the expected value. See", - "uptimeCreateJsonPathQuery": "for query language documentation.", - "maintenanceTableActionMenuDialogTitle": "Do you really want to remove this maintenance window?", - "infrastructureEditYour": "Edit your", - "infrastructureEditMonitor": "Save Infrastructure Monitor", - "infrastructureMonitorCreated": "Infrastructure monitor created successfully!", - "infrastructureMonitorUpdated": "Infrastructure monitor updated successfully!", - "errorInvalidTypeId": "Invalid notification type provided", - "errorInvalidFieldId": "Invalid field ID provided", - "inviteNoTokenFound": "No invite token found", - "pageSpeedWarning": "Warning: You haven't added a Google PageSpeed API key yet. Without it, the PageSpeed monitor won't function.", - "pageSpeedLearnMoreLink": "Click here", - "pageSpeedAddApiKey": "to add your API key.", - "update": "Update", - "invalidFileFormat": "Unsupported file format!", - "invalidFileSize": "File size is too large!", "ClickUpload": "Click to upload", + "DeleteAccountButton": "Remove account", + "DeleteAccountTitle": "Remove account", + "DeleteAccountWarning": "Removing your account means you won't be able to sign in again and all your data will be removed. This isn't reversible.", + "DeleteDescriptionText": "This will remove the account and all associated data from the server. This isn't reversible.", + "DeleteWarningTitle": "Really remove this account?", "DragandDrop": "drag and drop", - "MaxSize": "Maximum Size", - "SupportedFormats": "Supported formats", + "EmailDescriptionText": "This is your current email address — it cannot be changed.", "FirstName": "First name", "LastName": "Last name", - "EmailDescriptionText": "This is your current email address — it cannot be changed.", - "YourPhoto": "Profile photo", + "MaxSize": "Maximum Size", "PhotoDescriptionText": "This photo will be displayed in your profile page.", - "save": "Save", - "DeleteDescriptionText": "This will remove the account and all associated data from the server. This isn't reversible.", - "DeleteAccountWarning": "Removing your account means you won't be able to sign in again and all your data will be removed. This isn't reversible.", - "DeleteWarningTitle": "Really remove this account?", - "bulkImport": { - "title": "Bulk Import", - "selectFileTips": "Select CSV file to upload", - "selectFileDescription": "You can download our or sample", - "selectFile": "Select File", - "parsingFailed": "Parsing failed", - "uploadSuccess": "Monitors created successfully!", - "validationFailed": "Validation failed", - "noFileSelected": "No file selected", - "fallbackPage": "Import a file to upload a list of servers in bulk" - }, - "settingsSystemReset": "System reset", - "settingsSystemResetDescription": "Remove all monitors from your system.", - "DeleteAccountTitle": "Remove account", - "DeleteAccountButton": "Remove account", - "publicLink": "Public link", - "maskedPageSpeedKeyPlaceholder": "*************************************", - "pageSpeedApiKeyFieldTitle": "Google PageSpeed API key", - "pageSpeedApiKeyFieldLabel": "PageSpeed API key", - "pageSpeedApiKeyFieldDescription": "Enter your Google PageSpeed API key to enable pagespeed monitoring. Click Reset to update the key.", - "pageSpeedApiKeyFieldResetLabel": "API key is set. Click Reset to change it.", - "reset": "Reset", - "ignoreTLSError": "Ignore TLS/SSL error", - "tlsErrorIgnored": "TLS/SSL errors ignored", - "ignoreTLSErrorDescription": "Ignore TLS/SSL errors and continue checking the website's availability", - "createNew": "Create new", - "greeting": { - "prepend": "Hey there", - "append": "The afternoon is your playground—let's make it epic!", - "overview": "Here's an overview of your {{type}} monitors." - }, - "roles": { - "superAdmin": "Super admin", - "admin": "Admin", - "teamMember": "Team member", - "demoUser": "Demo user" - }, - "teamPanel": { - "teamMembers": "Team members", - "filter": { - "all": "All", - "member": "Member" - }, - "inviteTeamMember": "Invite a team member", - "inviteNewTeamMember": "Invite new team member", - "inviteDescription": "When you add a new team member, they will get access to all monitors.", - "email": "Email", - "selectRole": "Select role", - "inviteLink": "Invite link", - "cancel": "Cancel", - "noMembers": "There are no team members with this role", - "getToken": "Get token", - "emailToken": "E-mail token", - "table": { - "name": "Name", - "email": "Email", - "role": "Role", - "created": "Created" - } - }, - "monitorState": { - "paused": "Paused", - "resumed": "Resumed", - "active": "Active" - }, - "menu": { - "uptime": "Uptime", - "pagespeed": "Pagespeed", - "infrastructure": "Infrastructure", - "incidents": "Incidents", - "statusPages": "Status pages", - "maintenance": "Maintenance", - "integrations": "Integrations", - "settings": "Settings", - "support": "Support", - "discussions": "Discussions", - "docs": "Docs", - "changelog": "Changelog", - "profile": "Profile", - "password": "Password", - "team": "Team", - "logOut": "Log out", - "notifications": "Notifications" - }, - "settingsEmail": "Email", - "settingsEmailDescription": "Configure the email settings for your system. This is used to send notifications and alerts.", - "settingsEmailHost": "Email host - Hostname or IP address to connect to", - "settingsEmailPort": "Email port - Port to connect to", - "settingsEmailAddress": "Email address - Used for authentication", - "settingsEmailPassword": "Email password - Password for authentication", - "settingsEmailUser": "Email user - Username for authentication, overrides email address if specified", - "settingsEmailFieldResetLabel": "Password is set. Click Reset to change it.", - "state": "State", - "statusBreadCrumbsStatusPages": "Status Pages", - "statusBreadCrumbsDetails": "Details", - "settingsThemeModeLight": "Light", - "settingsThemeModeDark": "Dark", - "settingsClearAllStatsDialogCancel": "Cancel", - "commonSaving": "Saving...", - "navControls": "Controls", - "incidentsPageTitle": "Incidents", - "passwordPanel": { - "passwordChangedSuccess": "Your password was changed successfully.", - "passwordInputIncorrect": "Your password input was incorrect.", - "currentPassword": "Current password", - "enterCurrentPassword": "Enter your current password", - "newPassword": "New password", - "enterNewPassword": "Enter your new password", - "confirmNewPassword": "Confirm new password", - "passwordRequirements": "New password must contain at least 8 characters and must have at least one uppercase letter, one lowercase letter, one number and one special character.", - "saving": "Saving..." - }, - "settingsEmailConnectionHost": "Email connection host - Hostname to use in the HELO/EHLO greeting", - "emailSent": "Email sent successfully", - "failedToSendEmail": "Failed to send email", - "settingsTestEmail": "Send test e-mail", - "settingsTestEmailSuccess": "Test email sent successfully", - "settingsTestEmailFailed": "Failed to send test email", - "settingsTestEmailFailedWithReason": "Failed to send test email: {{reason}}", - "settingsTestEmailUnknownError": "Unknown error", - "settingsEmailRequiredFields": "Email address, host, port and password are required", - "statusMsg": { - "paused": "Monitoring is paused.", - "up": "Your site is up.", - "down": "Your site is down.", - "pending": "Pending..." - }, - "settingsURLTitle": "Monitor IP/URL on Status Page", - "settingsURLDescription": "Display the IP address or URL of monitor on the public Status page. If it's disabled, only the monitor name will be shown to protect sensitive information.", - "settingsURLSelectTitle": "Display IP/URL on status page", - "settingsURLEnabled": "Enabled", - "settingURLDisabled": "Disabled", - "uptimeGeneralInstructions": { - "http": "Enter the URL or IP to monitor (e.g., https://example.com/ or 192.168.1.100) and add a clear display name that appears on the dashboard.", - "ping": "Enter the IP address or hostname to ping (e.g., 192.168.1.100 or example.com) and add a clear display name that appears on the dashboard.", - "docker": "Enter the Docker ID of your container. Docker IDs must be the full 64 char Docker ID. You can run docker inspect to get the full container ID.", - "port": "Enter the URL or IP of the server, the port number and a clear display name that appears on the dashboard." - }, - "common": { - "appName": "Checkmate", - "monitoringAgentName": "Capture", - "buttons": { - "toggleTheme": "Toggles light & dark" - }, - "toasts": { - "networkError": "Network error", - "checkConnection": "Please check your connection", - "unknownError": "Unknown error" - } - }, + "SupportedFormats": "Supported formats", + "YourPhoto": "Profile photo", + "aboutus": "About Us", + "access": "Access", + "actions": "Actions", + "add": "Add", + "addMonitors": "Add monitors", + "administrator": "Administrator?", + "advancedMatching": "Advanced matching", + "ago": "ago", + "area": "Area", "auth": { "common": { - "navigation": { - "continue": "Continue", - "back": "Back" + "errors": { + "validation": "Error validating data." + }, + "fields": { + "password": { + "errors": { + "incorrect": "The password you provided does not match our records" + } + } }, "inputs": { "email": { - "label": "Email", - "placeholder": "jordan.ellis@domain.com", "errors": { "empty": "To continue, please enter your email address", "invalid": "Please recheck validity of entered email address" - } + }, + "label": "Email", + "placeholder": "jordan.ellis@domain.com" + }, + "firstName": { + "errors": { + "empty": "Please enter your name", + "length": "Name must be less than 50 characters", + "pattern": "Name must contain only letters, spaces, apostrophes, or hyphens" + }, + "label": "Name", + "placeholder": "Jordan" + }, + "lastName": { + "errors": { + "empty": "Please enter your surname", + "length": "Surname must be less than 50 characters", + "pattern": "Surname must contain only letters, spaces, apostrophes, or hyphens" + }, + "label": "Surname", + "placeholder": "Ellis" }, "password": { + "errors": { + "empty": "Please enter your password", + "incorrect": "The password you provided does not match our records", + "length": "Password must be at least 8 characters long", + "lowercase": "Password must contain at least 1 lowercase letter", + "number": "Password must contain at least 1 number", + "special": "Password must contain at least 1 special character", + "uppercase": "Password must contain at least 1 uppercase letter" + }, "label": "Password", "rules": { "length": { "beginning": "Must be at least", "highlighted": "8 characters long" }, - "special": { - "beginning": "Must contain at least", - "highlighted": "one special character" - }, - "number": { - "beginning": "Must contain at least", - "highlighted": "one number" - }, - "uppercase": { - "beginning": "Must contain at least", - "highlighted": "one upper character" - }, "lowercase": { "beginning": "Must contain at least", "highlighted": "one lower character" @@ -555,81 +84,88 @@ "match": { "beginning": "Confirm password and password", "highlighted": "must match" + }, + "number": { + "beginning": "Must contain at least", + "highlighted": "one number" + }, + "special": { + "beginning": "Must contain at least", + "highlighted": "one special character" + }, + "uppercase": { + "beginning": "Must contain at least", + "highlighted": "one upper character" } - }, - "errors": { - "empty": "Please enter your password", - "length": "Password must be at least 8 characters long", - "uppercase": "Password must contain at least 1 uppercase letter", - "lowercase": "Password must contain at least 1 lowercase letter", - "number": "Password must contain at least 1 number", - "special": "Password must contain at least 1 special character", - "incorrect": "The password you provided does not match our records" } }, "passwordConfirm": { + "errors": { + "different": "Entered passwords don't match, so one of them is probably mistyped", + "empty": "Please enter your password again for confirmation (helps with typos)" + }, "label": "Confirm password", - "placeholder": "Re-enter password to confirm", - "errors": { - "empty": "Please enter your password again for confirmation (helps with typos)", - "different": "Entered passwords don't match, so one of them is probably mistyped" - } - }, - "firstName": { - "label": "Name", - "placeholder": "Jordan", - "errors": { - "empty": "Please enter your name", - "length": "Name must be less than 50 characters", - "pattern": "Name must contain only letters, spaces, apostrophes, or hyphens" - } - }, - "lastName": { - "label": "Surname", - "placeholder": "Ellis", - "errors": { - "empty": "Please enter your surname", - "length": "Surname must be less than 50 characters", - "pattern": "Surname must contain only letters, spaces, apostrophes, or hyphens" - } + "placeholder": "Re-enter password to confirm" } }, - "errors": { - "validation": "Error validating data." + "navigation": { + "back": "Back", + "continue": "Continue" + } + }, + "forgotPassword": { + "buttons": { + "openEmail": "Open email app", + "resetPassword": "Reset password" + }, + "heading": "Forgot password?", + "imageAlts": { + "email": "Email icon", + "lock": "Lock icon", + "passwordConfirm": "Password confirm icon", + "passwordKey": "Password key icon" + }, + "links": { + "login": "Go back to Log In", + "resend": "Didn't receive the email? Click to resend" + }, + "subheadings": { + "stepFour": "Your password has been successfully reset. Click below to log in magically.", + "stepOne": "No worries, we'll send you reset instructions.", + "stepThree": "Your new password must be different from previously used passwords.", + "stepTwo": "We sent a password reset link to " + }, + "toasts": { + "emailNotFound": "Email not found.", + "error": "Unable to reset password. Please try again later or contact support.", + "redirect": "Redirecting in ...", + "sent": "Instructions sent to .", + "success": "Your password was reset successfully." } }, "login": { - "heading": "Log In", - "subheadings": { - "stepOne": "Enter your email", - "stepTwo": "Enter your password" - }, - "links": { - "forgotPassword": "Forgot password?", - "register": "Do not have an account?", - "forgotPasswordLink": "Reset password", - "registerLink": "Register here" - }, - "toasts": { - "success": "Welcome back! You're successfully logged in.", - "incorrectPassword": "Incorrect password" - }, "errors": { "password": { "incorrect": "The password you provided does not match our records" } + }, + "heading": "Log In", + "links": { + "forgotPassword": "Forgot password?", + "forgotPasswordLink": "Reset password", + "register": "Do not have an account?", + "registerLink": "Register here" + }, + "subheadings": { + "stepOne": "Enter your email", + "stepTwo": "Enter your password" + }, + "toasts": { + "incorrectPassword": "Incorrect password", + "success": "Welcome back! You're successfully logged in." } }, "registration": { - "heading": { - "superAdmin": "Create super admin", - "user": "Sign Up" - }, - "subheadings": { - "stepOne": "Enter your personal details", - "stepTwo": "Enter your email", - "stepThree": "Create your password" - }, "description": { "superAdmin": "Create your super admin account to get started", "user": "Sign up as a user and ask super admin for access to your monitors" @@ -638,121 +174,672 @@ "superAdmin": "Create super admin account", "user": "Sign up regular user" }, - "termsAndPolicies": "By creating an account, you agree to our Terms of Service and Privacy Policy.", + "heading": { + "superAdmin": "Create super admin", + "user": "Sign Up" + }, "links": { "login": "Already have an account? Log In" }, + "subheadings": { + "stepOne": "Enter your personal details", + "stepThree": "Create your password", + "stepTwo": "Enter your email" + }, + "termsAndPolicies": "By creating an account, you agree to our Terms of Service and Privacy Policy.", "toasts": { "success": "Welcome! Your account was created successfully." } - }, - "forgotPassword": { - "heading": "Forgot password?", - "subheadings": { - "stepOne": "No worries, we'll send you reset instructions.", - "stepTwo": "We sent a password reset link to ", - "stepThree": "Your new password must be different from previously used passwords.", - "stepFour": "Your password has been successfully reset. Click below to log in magically." - }, - "buttons": { - "openEmail": "Open email app", - "resetPassword": "Reset password" - }, - "imageAlts": { - "passwordKey": "Password key icon", - "email": "Email icon", - "lock": "Lock icon", - "passwordConfirm": "Password confirm icon" - }, - "links": { - "login": "Go back to Log In", - "resend": "Didn't receive the email? Click to resend" - }, - "toasts": { - "sent": "Instructions sent to .", - "emailNotFound": "Email not found.", - "redirect": "Redirecting in ...", - "success": "Your password was reset successfully.", - "error": "Unable to reset password. Please try again later or contact support." - } } }, + "avgCpuTemperature": "Average CPU Temperature", + "bar": "Bar", + "basicInformation": "Basic Information", + "bulkImport": { + "fallbackPage": "Import a file to upload a list of servers in bulk", + "noFileSelected": "No file selected", + "parsingFailed": "Parsing failed", + "selectFile": "Select File", + "selectFileDescription": "You can download our or sample", + "selectFileTips": "Select CSV file to upload", + "title": "Bulk Import", + "uploadSuccess": "Monitors created successfully!", + "validationFailed": "Validation failed" + }, + "cancel": "Cancel", + "checkFrequency": "Check frequency", + "checkingEvery": "Checking every", + "city": "CITY", + "common": { + "appName": "Checkmate", + "buttons": { + "toggleTheme": "Toggles light & dark" + }, + "monitoringAgentName": "Capture", + "toasts": { + "checkConnection": "Please check your connection", + "networkError": "Network error", + "unknownError": "Unknown error" + } + }, + "commonSave": "Save", + "commonSaving": "Saving...", + "companyName": "Company name", + "configure": "Configure", + "cores": "Cores", + "country": "COUNTRY", + "cpu": "CPU", + "cpuFrequency": "CPU Frequency", + "cpuLogical": "CPU (Logical)", + "cpuPhysical": "CPU (Physical)", + "cpuTemperature": "CPU Temperature", + "cpuUsage": "CPU usage", + "createA": "Create a", + "createMaintenance": "Create maintenance", + "createMaintenanceWindow": "Create maintenance window", + "createMonitor": "Create monitor", + "createNew": "Create new", + "createNotifications": { + "discordSettings": { + "description": "Configure your Discord webhook here", + "title": "Discord", + "webhookLabel": "Discord Webhook URL", + "webhookPlaceholder": "https://your-server.com/webhook" + }, + "emailSettings": { + "description": "Destination email addresses.", + "emailLabel": "Email address", + "emailPlaceholder": "e.g. john@example.com", + "title": "Email" + }, + "nameSettings": { + "description": "A descriptive name for your integration.", + "nameLabel": "Name", + "namePlaceholder": "e.g. Slack notifications", + "title": "Name" + }, + "pagerdutySettings": { + "description": "Configure your PagerDuty integration here", + "integrationKeyLabel": "Integration key", + "integrationKeyPlaceholder": "1234567890", + "title": "PagerDuty" + }, + "slackSettings": { + "description": "Configure your Slack webhook here", + "title": "Slack", + "webhookLabel": "Slack webhook URL", + "webhookPlaceholder": "https://hooks.slack.com/services/..." + }, + "title": "Create notification channel", + "typeSettings": { + "description": "Select the type of notification channel you want to create.", + "title": "Type", + "typeLabel": "Type" + }, + "webhookSettings": { + "description": "Configure your webhook here", + "title": "Webhook", + "webhookLabel": "Webhook URL", + "webhookPlaceholder": "https://your-server.com/webhook" + } + }, + "createYour": "Create your", + "date&Time": "Date & Time", + "delete": "Delete", + "deleteDialogDescription": "Once deleted, this monitor cannot be retrieved.", + "deleteDialogTitle": "Do you really want to delete this monitor?", + "deleteStatusPage": "Do you want to delete this status page?", + "deleteStatusPageConfirm": "Yes, delete status page", + "deleteStatusPageDescription": "Once deleted, your status page cannot be retrieved.", + "disk": "Disk", + "diskUsage": "Disk Usage", + "displayName": "Display name", + "distributedRightCategoryTitle": "Monitor", + "distributedStatusHeaderText": "Real-time, real-device coverage", + "distributedStatusServerMonitors": "Server Monitors", + "distributedStatusServerMonitorsDescription": "Monitor status of related servers", + "distributedStatusSubHeaderText": "Powered by millions devices worldwide, view a system performance by global region, country or city", + "distributedUptimeCreateAdvancedSettings": "Advanced settings", + "distributedUptimeCreateChecks": "Checks to perform", + "distributedUptimeCreateChecksDescription": "You can always add or remove checks after adding your site.", + "distributedUptimeCreateIncidentDescription": "When there is an incident, notify users.", + "distributedUptimeCreateIncidentNotification": "Incident notifications", + "distributedUptimeCreateSelectURL": "Here you can select the URL of the host, together with the type of monitor.", + "distributedUptimeDetailsNoMonitorHistory": "There is no check history for this monitor yet.", + "distributedUptimeDetailsStatusHeaderLastUpdate": "Last updated", + "distributedUptimeDetailsStatusHeaderUptime": "Uptime:", + "distributedUptimeStatus30Days": "30 days", + "distributedUptimeStatus60Days": "60 days", + "distributedUptimeStatus90Days": "90 days", + "distributedUptimeStatusBasicInfoDescription": "Define company name and the subdomain that your status page points to.", + "distributedUptimeStatusBasicInfoHeader": "Basic Information", + "distributedUptimeStatusCompanyNameLabel": "Company name", + "distributedUptimeStatusContactAdmin": "Please contact your administrator", + "distributedUptimeStatusCreateStatusPage": "status page", + "distributedUptimeStatusCreateStatusPageAccess": "Access", + "distributedUptimeStatusCreateStatusPageReady": "If your status page is ready, you can mark it as published.", + "distributedUptimeStatusCreateYour": "Create your", + "distributedUptimeStatusDevices": "Devices", + "distributedUptimeStatusEditYour": "Edit your", + "distributedUptimeStatusLogoDescription": "Upload a logo for your status page", + "distributedUptimeStatusLogoHeader": "Logo", + "distributedUptimeStatusLogoUploadButton": "Upload logo", + "distributedUptimeStatusPageAddressLabel": "Your status page address", + "distributedUptimeStatusPageDeleteConfirm": "Yes, delete status page", + "distributedUptimeStatusPageDeleteDescription": "Once deleted, your status page cannot be retrieved.", + "distributedUptimeStatusPageDeleteDialog": "Do you want to delete this status page?", + "distributedUptimeStatusPageNotPublic": "This status page is not public.", + "distributedUptimeStatusPageNotSetUp": "A status page is not set up.", + "distributedUptimeStatusPublishedLabel": "Published and visible to the public", + "distributedUptimeStatusStandardMonitorsDescription": "Attach standard monitors to your status page.", + "distributedUptimeStatusStandardMonitorsHeader": "Standard Monitors", + "distributedUptimeStatusUpt": "UPT", + "distributedUptimeStatusUptBurned": "UPT Burned", + "distributedUptimeStatusUptLogo": "Upt Logo", + "dockerContainerMonitoring": "Docker container monitoring", + "dockerContainerMonitoringDescription": "Check whether your Docker container is running or not.", + "duration": "Duration", + "edit": "Edit", + "editMaintenance": "Edit maintenance", + "editing": "Editing...", + "emailSent": "Email sent successfully", + "errorInvalidFieldId": "Invalid field ID provided", + "errorInvalidTypeId": "Invalid notification type provided", "errorPages": { "serverUnreachable": { - "toasts": { - "reconnected": "Successfully reconnected to the server.", - "stillUnreachable": "Server is still unreachable. Please try again later." - }, "alertBox": "Server Connection Error", "description": "We're unable to connect to the server. Please check your internet connection or verify your deployment configuration if the problem persists.", "retryButton": { "default": "Retry connection", "processing": "Connecting..." + }, + "toasts": { + "reconnected": "Successfully reconnected to the server.", + "stillUnreachable": "Server is still unreachable. Please try again later." } } }, - "createNotifications": { - "title": "Create notification channel", - "nameSettings": { - "title": "Name", - "description": "A descriptive name for your integration.", - "nameLabel": "Name", - "namePlaceholder": "e.g. Slack notifications" + "expectedValue": "Expected value", + "export": { + "failed": "Failed to export monitors", + "success": "Monitors exported successfully!", + "title": "Export Monitors" + }, + "failedToSendEmail": "Failed to send email", + "features": "Features", + "frequency": "Frequency", + "friendlyNameInput": "Friendly name", + "friendlyNamePlaceholder": "Maintenance at __ : __ for ___ minutes", + "gb": "GB", + "greeting": { + "append": "The afternoon is your playground—let's make it epic!", + "overview": "Here's an overview of your {{type}} monitors.", + "prepend": "Hey there" + }, + "high": "high", + "host": "Host", + "http": "HTTP", + "https": "HTTPS", + "ignoreTLSError": "Ignore TLS/SSL error", + "ignoreTLSErrorDescription": "Ignore TLS/SSL errors and continue checking the website's availability", + "incidentsOptionsHeader": "Incidents for:", + "incidentsOptionsHeaderFilterAll": "All", + "incidentsOptionsHeaderFilterBy": "Filter by:", + "incidentsOptionsHeaderFilterCannotResolve": "Cannot resolve", + "incidentsOptionsHeaderFilterDown": "Down", + "incidentsOptionsHeaderFilterResolved": "Resolved", + "incidentsOptionsHeaderLastDay": "Last day", + "incidentsOptionsHeaderLastHour": "Last hour", + "incidentsOptionsHeaderLastWeek": "Last week", + "incidentsOptionsHeaderShow": "Show:", + "incidentsOptionsPlaceholderAllServers": "All servers", + "incidentsPageTitle": "Incidents", + "incidentsTableDateTime": "Date & Time", + "incidentsTableMessage": "Message", + "incidentsTableMonitorName": "Monitor Name", + "incidentsTableNoIncidents": "No incidents recorded", + "incidentsTablePaginationLabel": "incidents", + "incidentsTableStatus": "Status", + "incidentsTableStatusCode": "Status Code", + "infrastructureAlertNotificationDescription": "Send a notification to user(s) when thresholds exceed a specified percentage.", + "infrastructureAuthorizationSecretLabel": "Authorization secret", + "infrastructureCreateGeneralSettingsDescription": "Here you can select the URL of the host, together with the friendly name and authorization secret to connect to the server agent.", + "infrastructureCreateMonitor": "Create Infrastructure Monitor", + "infrastructureCreateYour": "Create your", + "infrastructureCustomizeAlerts": "Customize alerts", + "infrastructureDisplayNameLabel": "Display name", + "infrastructureEditMonitor": "Save Infrastructure Monitor", + "infrastructureEditYour": "Edit your", + "infrastructureMonitorCreated": "Infrastructure monitor created successfully!", + "infrastructureMonitorUpdated": "Infrastructure monitor updated successfully!", + "infrastructureProtocol": "Protocol", + "infrastructureServerRequirement": "The server you are monitoring must be running the", + "infrastructureServerUrlLabel": "Server URL", + "integrations": "Integrations", + "integrationsDiscord": "Discord", + "integrationsDiscordInfo": "Connect with Discord and view incidents directly in a channel", + "integrationsPrism": "Connect Prism to your favorite service.", + "integrationsSlack": "Slack", + "integrationsSlackInfo": "Connect with Slack and see incidents in a channel", + "integrationsZapier": "Zapier", + "integrationsZapierInfo": "Send all incidents to Zapier, and then see them everywhere", + "invalidFileFormat": "Unsupported file format!", + "invalidFileSize": "File size is too large!", + "inviteNoTokenFound": "No invite token found", + "loginHere": "Login here", + "logsPage": { + "description": "This page shows the latest 1000 lines of logs from the Checkmate server", + "logLevelSelect": { + "title": "Log level", + "values": { + "all": "All", + "debug": "Debug", + "error": "Error", + "info": "Info", + "warn": "Warn" + } }, - "typeSettings": { - "title": "Type", - "description": "Select the type of notification channel you want to create.", - "typeLabel": "Type" + "tabs": { + "logs": "Server logs", + "queue": "Job queue" }, - "emailSettings": { - "title": "Email", - "description": "Destination email addresses.", - "emailLabel": "Email address", - "emailPlaceholder": "e.g. john@example.com" - }, - "slackSettings": { - "title": "Slack", - "description": "Configure your Slack webhook here", - "webhookLabel": "Slack webhook URL", - "webhookPlaceholder": "https://hooks.slack.com/services/..." - }, - "pagerDutySettings": { - "title": "PagerDuty", - "description": "Configure your PagerDuty integration here", - "integrationKeyLabel": "Integration key", - "integrationKeyPlaceholder": "1234567890" - }, - "webhookSettings": { - "title": "Webhook", - "description": "Configure your webhook here", - "webhookLabel": "Webhook URL", - "webhookPlaceholder": "https://your-server.com/webhook" - }, - "discordSettings": { - "title": "Discord", - "description": "Configure your Discord webhook here", - "webhookLabel": "Discord Webhook URL", - "webhookPlaceholder": "https://your-server.com/webhook" + "title": "Logs", + "toast": { + "fetchLogsSuccess": "Logs fetched successfully" } }, - "notificationConfig": { - "title": "Notifications", - "description": "Select the notifications channels you want to use" + "low": "low", + "maintenance": "maintenance", + "maintenanceRepeat": "Maintenance Repeat", + "maintenanceTableActionMenuDialogTitle": "Do you really want to remove this maintenance window?", + "maintenanceWindowDescription": "Your pings won't be sent during this time frame", + "maintenanceWindowName": "Maintenance Window Name", + "maskedPageSpeedKeyPlaceholder": "*************************************", + "matchMethod": "Match Method", + "mb": "MB", + "mem": "Mem", + "memory": "Memory", + "memoryUsage": "Memory usage", + "menu": { + "changelog": "Changelog", + "discussions": "Discussions", + "docs": "Docs", + "incidents": "Incidents", + "infrastructure": "Infrastructure", + "integrations": "Integrations", + "logOut": "Log out", + "logs": "Logs", + "maintenance": "Maintenance", + "notifications": "Notifications", + "pagespeed": "Pagespeed", + "password": "Password", + "profile": "Profile", + "settings": "Settings", + "statusPages": "Status pages", + "support": "Support", + "team": "Team", + "uptime": "Uptime" + }, + "message": "Message", + "monitor": "monitor", + "monitorActions": { + "export": "Export Monitors", + "import": "Import Monitors", + "title": "Export/Import" + }, + "monitorDisplayName": "Monitor display name", + "monitorState": { + "active": "Active", + "paused": "Paused", + "resumed": "Resumed" }, "monitorStatus": { "checkingEvery": "Checking every {{interval}}", - "withCaptureAgent": "with Capture agent {{version}}", - "up": "up", "down": "down", - "paused": "paused" + "paused": "paused", + "up": "up", + "withCaptureAgent": "with Capture agent {{version}}" }, - "advancedMatching": "Advanced matching", - "settingsEmailTLSServername": "TLS Servername - Optional Hostname for TLS Validation when host is an IP", - "settingsEmailIgnoreTLS": "Ignore TLS - Disable STARTTLS", - "settingsEmailRequireTLS": "Require TLS - Force STARTTLS", - "settingsEmailRejectUnauthorized": "Reject Unauthorized", - "settingsEmailSecure": "Secure - Use SSL", - "settingsEmailPool": "Pool - Enable connection pooling", + "monitorStatusDown": "Monitor {name} ({url}) is DOWN and not responding", + "monitorStatusUp": "Monitor {name} ({url}) is now UP and responding", + "monitors": "monitors", + "monitorsToApply": "Monitors to apply maintenance window to", + "ms": "ms", + "navControls": "Controls", + "nextWindow": "Next window", + "notFoundButton": "Go to the main dashboard", + "notificationConfig": { + "description": "Select the notifications channels you want to use", + "title": "Notifications" + }, + "notifications": { + "addOrEditNotifications": "Add or edit notifications", + "create": { + "failed": "Failed to create notification", + "success": "Notification created successfully" + }, + "createButton": "Create notification channel", + "createTitle": "Notification channel", + "delete": { + "failed": "Failed to delete notification", + "success": "Notification deleted successfully" + }, + "discord": { + "description": "To send data to a Discord channel from Checkmate via Discord notifications using webhooks, you can use Discord's incoming Webhooks feature.", + "label": "Discord", + "webhookLabel": "Discord Webhook URL", + "webhookPlaceholder": "https://discord.com/api/webhooks/...", + "webhookRequired": "Discord webhook URL is required" + }, + "edit": { + "failed": "Failed to update notification", + "success": "Notification updated successfully" + }, + "enableNotifications": "Enable {{platform}} notifications", + "fallback": { + "checks": [ + "Alert teams about downtime or performance issues", + "Let engineers know when incidents happen", + "Keep administrators informed of system changes" + ], + "title": "notification channel" + }, + "fetch": { + "failed": "Failed to fetch notifications", + "success": "Notifications fetched successfully" + }, + "integrationButton": "Notification Integration", + "networkError": "Network error occurred", + "slack": { + "description": "To enable Slack notifications, create a Slack app and enable incoming webhooks. After that, simply provide the webhook URL here.", + "label": "Slack", + "webhookLabel": "Webhook URL", + "webhookPlaceholder": "https://hooks.slack.com/services/...", + "webhookRequired": "Slack webhook URL is required" + }, + "telegram": { + "chatIdLabel": "Your Chat ID", + "chatIdPlaceholder": "-1001234567890", + "description": "To enable Telegram notifications, create a Telegram bot using BotFather, an official bot for creating and managing Telegram bots. Then, get the API token and chat ID and write them down here.", + "fieldsRequired": "Telegram token and chat ID are required", + "label": "Telegram", + "tokenLabel": "Your bot token", + "tokenPlaceholder": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" + }, + "test": { + "failed": "Failed to send test notification", + "success": "Test notification sent successfully" + }, + "testFailed": "Failed to send test notification", + "testNotification": "Test notification", + "testNotificationDevelop": "Test notification 2", + "testSuccess": "Test notification sent successfully!", + "unsupportedType": "Unsupported notification type", + "webhook": { + "description": "You can set up a custom webhook to receive notifications when incidents occur.", + "label": "Webhooks", + "urlLabel": "Webhook URL", + "urlPlaceholder": "https://your-server.com/webhook", + "urlRequired": "Webhook URL is required" + } + }, + "notifyEmails": "Also notify via email to multiple addresses (coming soon)", + "notifySMS": "Notify via SMS (coming soon)", + "now": "Now", + "os": "OS", + "pageSpeedAddApiKey": "to add your API key.", + "pageSpeedConfigureSettingsDescription": "Here you can select the URL of the host, together with the type of monitor.", + "pageSpeedDetailsPerformanceReport": "Values are estimated and may vary.", + "pageSpeedDetailsPerformanceReportCalculator": "See calculator", + "pageSpeedLearnMoreLink": "Click here", + "pageSpeedMonitor": "PageSpeed monitor", + "pageSpeedWarning": "Warning: You haven't added a Google PageSpeed API key yet. Without it, the PageSpeed monitor won't function.", + "passwordPanel": { + "confirmNewPassword": "Confirm new password", + "currentPassword": "Current password", + "enterCurrentPassword": "Enter your current password", + "enterNewPassword": "Enter your new password", + "newPassword": "New password", + "passwordChangedSuccess": "Your password was changed successfully.", + "passwordInputIncorrect": "Your password input was incorrect.", + "passwordRequirements": "New password must contain at least 8 characters and must have at least one uppercase letter, one lowercase letter, one number and one special character.", + "saving": "Saving..." + }, + "pause": "Pause", + "pingMonitoring": "Ping monitoring", + "pingMonitoringDescription": "Check whether your server is available or not.", + "portMonitoring": "Port monitoring", + "portMonitoringDescription": "Check whether your port is open or not.", + "portToMonitor": "Port to monitor", + "publicLink": "Public link", + "publicURL": "Public URL", + "queuePage": { + "failedJobTable": { + "failCountHeader": "Fail count", + "failReasonHeader": "Fail reason", + "failedAtHeader": "Last failed at", + "monitorIdHeader": "Monitor ID", + "monitorUrlHeader": "Monitor URL", + "title": "Failed jobs" + }, + "flushButton": "Flush queue", + "jobTable": { + "activeHeader": "Active", + "failCountHeader": "Fail count", + "idHeader": "Monitor ID", + "lastFinishedAtHeader": "Last finished at", + "lastRunHeader": "Last run at", + "lastRunTookHeader": "Last run took", + "lockedAtHeader": "Locked at", + "runCountHeader": "Run count", + "title": "Jobs currently in queue", + "typeHeader": "Type", + "urlHeader": "URL" + }, + "metricsTable": { + "metricHeader": "Metric", + "title": "Queue metrics", + "valueHeader": "Value" + }, + "refreshButton": "Refresh", + "title": "Queue" + }, + "remove": "Remove", + "removeLogo": "Remove Logo", + "repeat": "Repeat", + "reset": "Reset", + "response": "RESPONSE", + "responseTime": "Response time", + "resume": "Resume", + "roles": { + "admin": "Admin", + "demoUser": "Demo user", + "superAdmin": "Super admin", + "teamMember": "Team member" + }, + "save": "Save", + "selectAll": "Select all", "sendTestNotifications": "Send test notifications", - "selectAll": "Select all" + "seperateEmails": "You can separate multiple emails with a comma", + "settingsDisabled": "Disabled", + "settingsEmailUser": "Email user - Username for authentication, overrides email address if specified", + "settingsFailedToAddDemoMonitors": "Failed to add demo monitors", + "settingsFailedToClearStats": "Failed to clear stats", + "settingsFailedToDeleteMonitors": "Failed to delete all monitors", + "settingsFailedToSave": "Failed to save settings", + "settingsMonitorsDeleted": "Successfully deleted all monitors", + "settingsPage": { + "aboutSettings": { + "labelDevelopedBy": "Developed by Bluewave Labs", + "labelVersion": "Version", + "title": "About" + }, + "demoMonitorsSettings": { + "buttonAddMonitors": "Add demo monitors", + "description": "Add sample monitors for demonstration purposes.", + "title": "Demo monitors" + }, + "emailSettings": { + "buttonSendTestEmail": "Send test e-mail", + "description": "Configure the email settings for your system. This is used to send notifications and alerts.", + "descriptionTransport": "This builds an SMTP transport for NodeMailer", + "labelAddress": "Email address - Used for authentication", + "labelConnectionHost": "Email connection host - Hostname to use in the HELO/EHLO greeting", + "labelHost": "Email host - Hostname or IP address to connect to", + "labelIgnoreTLS": "Disable STARTTLS: Don't use TLS even if the server supports it", + "labelPassword": "Email password - Password for authentication", + "labelPasswordSet": "Password is set. Click Reset to change it.", + "labelPool": "Enable connection pooling: Reuse existing connections to improve performance", + "labelPort": "Email port - Port to connect to", + "labelRejectUnauthorized": "Reject invalid certificates: Reject connections with self-signed or untrusted certificates", + "labelRequireTLS": "Force STARTTLS: Require TLS upgrade, fail if not supported", + "labelSecure": "Use SSL (recommended): Encrypt the connection using SSL/TLS", + "labelTLSServername": "TLS Servername - Optional Hostname for TLS Validation when host is an IP", + "labelUser": "Email user - Username for authentication, overrides email address if specified", + "linkTransport": "See specifications here", + "placeholderUser": "Leave empty if not required", + "title": "Email" + }, + "pageSpeedSettings": { + "description": "Enter your Google PageSpeed API key to enable Google PageSpeed monitoring. Click Reset to update the key.", + "labelApiKey": "PageSpeed API key", + "labelApiKeySet": "API key is set. Click Reset to change it.", + "title": "Google PageSpeed API key" + }, + "saveButtonLabel": "Save", + "statsSettings": { + "clearAllStatsButton": "Clear all stats", + "clearAllStatsDescription": "Clear all stats. This is irreversible.", + "clearAllStatsDialogConfirm": "Yes, clear all stats", + "clearAllStatsDialogDescription": "Once removed, the monitoring history and stats cannot be retrieved.", + "clearAllStatsDialogTitle": "Do you want to clear all stats?", + "description": "Define how long you want to retain historical data. You can also clear all existing data.", + "labelTTL": "The days you want to keep monitoring history.", + "labelTTLOptional": "0 for infinite", + "title": "Monitor history" + }, + "systemResetSettings": { + "buttonRemoveAllMonitors": "Remove all monitors", + "description": "Remove all monitors from your system.", + "dialogConfirm": "Yes, remove all monitors", + "dialogDescription": "Once removed, the monitors cannot be retrieved.", + "dialogTitle": "Do you want to remove all monitors?", + "title": "System reset" + }, + "timezoneSettings": { + "description": "Select the timezone used to display dates and times throughout the application.", + "label": "Display timezone", + "title": "Display timezone" + }, + "title": "Settings", + "uiSettings": { + "description": "Switch between light and dark mode, or change user interface language.", + "labelLanguage": "Language", + "labelTheme": "Theme mode", + "title": "Appearance" + }, + "urlSettings": { + "description": "Display the IP address or URL of monitor on the public Status page. If it's disabled, only the monitor name will be shown to protect sensitive information.", + "label": "Display IP/URL on status page", + "selectDisabled": "Disabled", + "selectEnabled": "Enabled", + "title": "Monitor IP/URL on Status Page" + } + }, + "settingsSave": "Save", + "settingsStatsCleared": "Stats cleared successfully", + "settingsSuccessSaved": "Settings saved successfully", + "settingsTestEmailFailed": "Failed to send test email", + "settingsTestEmailFailedWithReason": "Failed to send test email: {{reason}}", + "settingsTestEmailSuccess": "Test email sent successfully", + "settingsTestEmailUnknownError": "Unknown error", + "showAdminLoginLink": "Show \"Administrator? Login Here\" link on the status page", + "showCharts": "Show charts", + "showUptimePercentage": "Show uptime percentage", + "shown": "Shown", + "starPromptDescription": "See the latest releases and help grow the community on GitHub", + "starPromptTitle": "Star Checkmate", + "startTime": "Start time", + "state": "State", + "status": "Status", + "statusBreadCrumbsDetails": "Details", + "statusBreadCrumbsStatusPages": "Status Pages", + "statusCode": "Status code", + "statusMsg": { + "down": "Your site is down.", + "paused": "Monitoring is paused.", + "pending": "Pending...", + "up": "Your site is up." + }, + "statusPageCreate": { + "buttonSave": "Save" + }, + "statusPageCreateAppearanceDescription": "Define the default look and feel of your public status page.", + "statusPageCreateAppearanceTitle": "Appearance", + "statusPageCreateBasicInfoDescription": "Define company name and the subdomain that your status page points to.", + "statusPageCreateBasicInfoStatusPageAddress": "Your status page address", + "statusPageCreateSelectTimeZoneDescription": "Select the timezone that your status page will be displayed in.", + "statusPageCreateSettings": "If your status page is ready, you can mark it as published.", + "statusPageCreateSettingsCheckboxLabel": "Published and visible to the public", + "statusPageCreateTabsContent": "Status page servers", + "statusPageCreateTabsContentDescription": "You can add any number of servers that you monitor to your status page. You can also reorder them for the best viewing experience.", + "statusPageCreateTabsContentFeaturesDescription": "Show more details on the status page", + "statusPageName": "Status page name", + "statusPageStatus": "A public status page is not set up.", + "statusPageStatusContactAdmin": "Please contact to your administrator", + "statusPageStatusNoPage": "There's no status page here.", + "statusPageStatusNotPublic": "This status page is not public.", + "statusPageStatusServiceStatus": "Service status", + "submit": "Submit", + "teamPanel": { + "cancel": "Cancel", + "email": "Email", + "emailToken": "E-mail token", + "filter": { + "all": "All", + "member": "Member" + }, + "getToken": "Get token", + "inviteDescription": "When you add a new team member, they will get access to all monitors.", + "inviteLink": "Invite link", + "inviteNewTeamMember": "Invite new team member", + "inviteTeamMember": "Invite a team member", + "noMembers": "There are no team members with this role", + "selectRole": "Select role", + "table": { + "created": "Created", + "email": "Email", + "name": "Name", + "role": "Role" + }, + "teamMembers": "Team members" + }, + "testLocale": "testLocale", + "timeZoneInfo": "All dates and times are in GMT+0 time zone.", + "timezone": "Timezone", + "title": "Title", + "tlsErrorIgnored": "TLS/SSL errors ignored", + "total": "Total", + "type": "Type", + "update": "Update", + "uptime": "Uptime", + "uptimeCreate": "The expected value is used to match against response result, and the match determines the status.", + "uptimeCreateJsonPath": "This expression will be evaluated against the reponse JSON data and the result will be used to match against the expected value. See", + "uptimeCreateJsonPathQuery": "for query language documentation.", + "uptimeGeneralInstructions": { + "docker": "Enter the Docker ID of your container. Docker IDs must be the full 64 char Docker ID. You can run docker inspect to get the full container ID.", + "http": "Enter the URL or IP to monitor (e.g., https://example.com/ or 192.168.1.100) and add a clear display name that appears on the dashboard.", + "ping": "Enter the IP address or hostname to ping (e.g., 192.168.1.100 or example.com) and add a clear display name that appears on the dashboard.", + "port": "Enter the URL or IP of the server, the port number and a clear display name that appears on the dashboard." + }, + "url": "URL", + "urlMonitor": "URL to monitor", + "used": "Used", + "webhookSendError": "Error sending webhook notification to {platform}", + "webhookSendSuccess": "Webhook notification sent successfully", + "webhookUnsupportedPlatform": "Unsupported platform: {platform}", + "websiteMonitoring": "Website monitoring", + "websiteMonitoringDescription": "Use HTTP(s) to monitor your website or API endpoint.", + "whenNewIncident": "When there is a new incident,", + "window": "window" } diff --git a/client/src/locales/es.json b/client/src/locales/es.json index fb8207e48..4cbd91336 100644 --- a/client/src/locales/es.json +++ b/client/src/locales/es.json @@ -3,40 +3,11 @@ "title": "Titulo", "distributedStatusHeaderText": "", "distributedStatusSubHeaderText": "", - "settingsGeneralSettings": "", - "settingsDisplayTimezone": "", - "settingsDisplayTimezoneDescription": "", - "settingsAppearance": "", - "settingsAppearanceDescription": "", - "settingsThemeMode": "", - "settingsLanguage": "", - "settingsEnabled": "", "settingsDisabled": "", - "settingsHistoryAndMonitoring": "", - "settingsHistoryAndMonitoringDescription": "", - "settingsTTLLabel": "", - "settingsTTLOptionalLabel": "", - "settingsClearAllStats": "", - "settingsClearAllStatsButton": "", - "settingsClearAllStatsDialogTitle": "", - "settingsClearAllStatsDialogDescription": "", - "settingsClearAllStatsDialogConfirm": "", - "settingsDemoMonitors": "", - "settingsDemoMonitorsDescription": "", - "settingsAddDemoMonitors": "", - "settingsAddDemoMonitorsButton": "", - "settingsRemoveAllMonitors": "", - "settingsRemoveAllMonitorsButton": "", - "settingsRemoveAllMonitorsDialogTitle": "", - "settingsRemoveAllMonitorsDialogConfirm": "", - "settingsAbout": "", - "settingsDevelopedBy": "", - "settingsSave": "", "settingsSuccessSaved": "", "settingsFailedToSave": "", "settingsStatsCleared": "", "settingsFailedToClearStats": "", - "settingsDemoMonitorsAdded": "", "settingsFailedToAddDemoMonitors": "", "settingsMonitorsDeleted": "", "settingsFailedToDeleteMonitors": "", @@ -362,16 +333,10 @@ "noFileSelected": "", "fallbackPage": "" }, - "settingsSystemReset": "", - "settingsSystemResetDescription": "", "DeleteAccountTitle": "", "DeleteAccountButton": "", "publicLink": "", "maskedPageSpeedKeyPlaceholder": "", - "pageSpeedApiKeyFieldTitle": "", - "pageSpeedApiKeyFieldLabel": "", - "pageSpeedApiKeyFieldDescription": "", - "pageSpeedApiKeyFieldResetLabel": "", "reset": "", "ignoreTLSError": "", "tlsErrorIgnored": "", @@ -433,22 +398,13 @@ "password": "", "team": "", "logOut": "", - "notifications": "" + "notifications": "", + "logs": "" }, - "settingsEmail": "", - "settingsEmailDescription": "", - "settingsEmailHost": "", - "settingsEmailPort": "", - "settingsEmailAddress": "", - "settingsEmailPassword": "", "settingsEmailUser": "", - "settingsEmailFieldResetLabel": "", "state": "", "statusBreadCrumbsStatusPages": "", "statusBreadCrumbsDetails": "", - "settingsThemeModeLight": "", - "settingsThemeModeDark": "", - "settingsClearAllStatsDialogCancel": "", "commonSaving": "", "navControls": "", "incidentsPageTitle": "", @@ -463,26 +419,18 @@ "passwordRequirements": "", "saving": "" }, - "settingsEmailConnectionHost": "", "emailSent": "", "failedToSendEmail": "", - "settingsTestEmail": "", "settingsTestEmailSuccess": "", "settingsTestEmailFailed": "", "settingsTestEmailFailedWithReason": "", "settingsTestEmailUnknownError": "", - "settingsEmailRequiredFields": "", "statusMsg": { "paused": "", "up": "", "down": "", "pending": "" }, - "settingsURLTitle": "", - "settingsURLDescription": "", - "settingsURLSelectTitle": "", - "settingsURLEnabled": "", - "settingURLDisabled": "", "uptimeGeneralInstructions": { "http": "", "ping": "", @@ -583,6 +531,13 @@ }, "errors": { "validation": "" + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + } } }, "login": { @@ -709,13 +664,13 @@ "integrationKeyLabel": "", "integrationKeyPlaceholder": "" }, - "genericWebhookSettings": { + "discordSettings": { "title": "", "description": "", "webhookLabel": "", "webhookPlaceholder": "" }, - "discordSettings": { + "webhookSettings": { "title": "", "description": "", "webhookLabel": "", @@ -734,11 +689,147 @@ "paused": "" }, "advancedMatching": "", - "settingsEmailTLSServername": "", - "settingsEmailIgnoreTLS": "", - "settingsEmailRequireTLS": "", - "settingsEmailRejectUnauthorized": "", - "settingsEmailSecure": "", - "settingsEmailPool": "", - "sendTestNotifications": "" + "sendTestNotifications": "", + "selectAll": "", + "showAdminLoginLink": "", + "logsPage": { + "title": "", + "description": "", + "tabs": { + "queue": "", + "logs": "" + }, + "toast": { + "fetchLogsSuccess": "" + }, + "logLevelSelect": { + "title": "", + "values": { + "all": "", + "info": "", + "warn": "", + "error": "", + "debug": "" + } + } + }, + "queuePage": { + "title": "", + "refreshButton": "", + "flushButton": "", + "jobTable": { + "title": "", + "idHeader": "", + "urlHeader": "", + "typeHeader": "", + "activeHeader": "", + "lockedAtHeader": "", + "runCountHeader": "", + "failCountHeader": "", + "lastRunHeader": "", + "lastFinishedAtHeader": "", + "lastRunTookHeader": "" + }, + "metricsTable": { + "title": "", + "metricHeader": "", + "valueHeader": "" + }, + "failedJobTable": { + "title": "", + "monitorIdHeader": "", + "monitorUrlHeader": "", + "failCountHeader": "", + "failedAtHeader": "", + "failReasonHeader": "" + } + }, + "export": { + "title": "", + "success": "", + "failed": "" + }, + "monitorActions": { + "title": "", + "import": "", + "export": "" + }, + "settingsPage": { + "aboutSettings": { + "labelDevelopedBy": "", + "labelVersion": "", + "title": "" + }, + "demoMonitorsSettings": { + "buttonAddMonitors": "", + "description": "", + "title": "" + }, + "emailSettings": { + "buttonSendTestEmail": "", + "description": "", + "descriptionTransport": "", + "labelAddress": "", + "labelConnectionHost": "", + "labelHost": "", + "labelIgnoreTLS": "", + "labelPassword": "", + "labelPasswordSet": "", + "labelPool": "", + "labelPort": "", + "labelRejectUnauthorized": "", + "labelRequireTLS": "", + "labelSecure": "", + "labelTLSServername": "", + "labelUser": "", + "linkTransport": "", + "placeholderUser": "", + "title": "" + }, + "pageSpeedSettings": { + "description": "", + "labelApiKeySet": "", + "labelApiKey": "", + "title": "" + }, + "saveButtonLabel": "", + "statsSettings": { + "clearAllStatsButton": "", + "clearAllStatsDescription": "", + "clearAllStatsDialogConfirm": "", + "clearAllStatsDialogDescription": "", + "clearAllStatsDialogTitle": "", + "description": "", + "labelTTL": "", + "labelTTLOptional": "", + "title": "" + }, + "systemResetSettings": { + "buttonRemoveAllMonitors": "", + "description": "", + "dialogConfirm": "", + "dialogDescription": "", + "dialogTitle": "", + "title": "" + }, + "timezoneSettings": { + "description": "", + "label": "", + "title": "" + }, + "title": "", + "uiSettings": { + "description": "", + "labelLanguage": "", + "labelTheme": "", + "title": "" + }, + "urlSettings": { + "description": "", + "label": "", + "selectDisabled": "", + "selectEnabled": "", + "title": "" + } + } } diff --git a/client/src/locales/fi.json b/client/src/locales/fi.json index f085b5857..1783f8a07 100644 --- a/client/src/locales/fi.json +++ b/client/src/locales/fi.json @@ -3,40 +3,11 @@ "title": "Otsikko", "distributedStatusHeaderText": "Reaaliaikainen kattavuus oikeilla laitteilla", "distributedStatusSubHeaderText": "Miljoonien laitteiden voimin ympäri maailman näet järjestelmän suorituskyvyn maailman alueittain, maittain tai kaupungeittain", - "settingsGeneralSettings": "Yleiset asetukset", - "settingsDisplayTimezone": "Näytä aikavyöhyke", - "settingsDisplayTimezoneDescription": "Valitse sovelluksessa käytettävä aikavyöhyke päivämäärien ja aikojen näyttämiseen.", - "settingsAppearance": "Ulkonäkö", - "settingsAppearanceDescription": "Vaihda teema vaaleaksi tai tummaksi tai vaihda käyttöliittymän kieli", - "settingsThemeMode": "Teematila", - "settingsLanguage": "Kieli", - "settingsEnabled": "Käytössä", "settingsDisabled": "Poistettu käytöstä", - "settingsHistoryAndMonitoring": "Valvonnan historia", - "settingsHistoryAndMonitoringDescription": "Määritä, kuinka pitkään haluat säilyttää historiallisia tietoja. Voit myös tyhjentää kaikki olemassa olevat tiedot.", - "settingsTTLLabel": "Kuinka monta päivää haluat säilyttää valvontahistorian.", - "settingsTTLOptionalLabel": "0 on ääretön", - "settingsClearAllStats": "Tyhjennä kaikki tilastot. Tämä on peruuttamaton.", - "settingsClearAllStatsButton": "Tyhjennä kaikki tilastot", - "settingsClearAllStatsDialogTitle": "Haluatko tyhjentää kaikki tilastot?", - "settingsClearAllStatsDialogDescription": "Kun valvontahistoria ja tilastot on poistettu, niitä ei voi palauttaa.", - "settingsClearAllStatsDialogConfirm": "", - "settingsDemoMonitors": "", - "settingsDemoMonitorsDescription": "", - "settingsAddDemoMonitors": "", - "settingsAddDemoMonitorsButton": "", - "settingsRemoveAllMonitors": "", - "settingsRemoveAllMonitorsButton": "", - "settingsRemoveAllMonitorsDialogTitle": "", - "settingsRemoveAllMonitorsDialogConfirm": "", - "settingsAbout": "Tietoja", - "settingsDevelopedBy": "Kehittänyt Bluewave Labs.", - "settingsSave": "Tallenna", "settingsSuccessSaved": "", "settingsFailedToSave": "", "settingsStatsCleared": "", "settingsFailedToClearStats": "", - "settingsDemoMonitorsAdded": "", "settingsFailedToAddDemoMonitors": "", "settingsMonitorsDeleted": "", "settingsFailedToDeleteMonitors": "", @@ -362,16 +333,10 @@ "noFileSelected": "Ei valittua tiedostoa", "fallbackPage": "" }, - "settingsSystemReset": "Järjestelmän palautus", - "settingsSystemResetDescription": "", "DeleteAccountTitle": "Poista tili", "DeleteAccountButton": "Poista tili", "publicLink": "Julkinen linkki", "maskedPageSpeedKeyPlaceholder": "*************************************", - "pageSpeedApiKeyFieldTitle": "Google PageSpeed API-avain", - "pageSpeedApiKeyFieldLabel": "PageSpeed API-avain", - "pageSpeedApiKeyFieldDescription": "", - "pageSpeedApiKeyFieldResetLabel": "", "reset": "Palauta", "ignoreTLSError": "", "tlsErrorIgnored": "", @@ -433,22 +398,13 @@ "password": "Salasana", "team": "Tiimi", "logOut": "Kirjaudu ulos", - "notifications": "" + "notifications": "", + "logs": "" }, - "settingsEmail": "Sähköpostiasetukset", - "settingsEmailDescription": "Määritä sähköpostiasetukset", - "settingsEmailHost": "", - "settingsEmailPort": "Sähköpostin portti - Portti johon yhdistetään", - "settingsEmailAddress": "Sähköpostiosoite - Käytetään tunnistautumiseen", - "settingsEmailPassword": "Sähköpostin salasana - Salasana tunnistautumiseen", "settingsEmailUser": "", - "settingsEmailFieldResetLabel": "Salasana on asetettu. Paina Palauta vaihtaaksesi sen.", "state": "Tila", "statusBreadCrumbsStatusPages": "Tilasivut", "statusBreadCrumbsDetails": "Tiedot", - "settingsThemeModeLight": "Vaalea", - "settingsThemeModeDark": "Tumma", - "settingsClearAllStatsDialogCancel": "Peruuta", "commonSaving": "Tallennetaan...", "navControls": "Ohjaimet", "incidentsPageTitle": "Häiriöt", @@ -463,26 +419,18 @@ "passwordRequirements": "Uuden salasanan on oltava vähintään 8 merkkiä pitkä ja sen on sisältävä vähintään yksi iso kirjain, yksi pieni kirjain, yksi numero sekä yksi erikoismerkki.", "saving": "Tallennetaan..." }, - "settingsEmailConnectionHost": "", "emailSent": "Sähköpostin lähetys onnistui", "failedToSendEmail": "Sähköpostin lähetys epäonnistui", - "settingsTestEmail": "Lähetä testi sähköposti", "settingsTestEmailSuccess": "Testisähköpostin lähetys onnistui", "settingsTestEmailFailed": "Testisähköpostin lähetys epäonnistui", "settingsTestEmailFailedWithReason": "Testisähköpostin lähetys epäonnistui: {{reason}}", "settingsTestEmailUnknownError": "Tuntematon virhe", - "settingsEmailRequiredFields": "", "statusMsg": { "paused": "Valvonta on keskeytetty.", "up": "Sivusi on ylhäällä.", "down": "Sivusi on alhaalla.", "pending": "Odottaa..." }, - "settingsURLTitle": "", - "settingsURLDescription": "", - "settingsURLSelectTitle": "", - "settingsURLEnabled": "", - "settingURLDisabled": "", "uptimeGeneralInstructions": { "http": "", "ping": "", @@ -583,6 +531,13 @@ }, "errors": { "validation": "" + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + } } }, "login": { @@ -709,13 +664,13 @@ "integrationKeyLabel": "", "integrationKeyPlaceholder": "" }, - "genericWebhookSettings": { + "discordSettings": { "title": "", "description": "", "webhookLabel": "", "webhookPlaceholder": "" }, - "discordSettings": { + "webhookSettings": { "title": "", "description": "", "webhookLabel": "", @@ -734,11 +689,147 @@ "paused": "" }, "advancedMatching": "", - "settingsEmailTLSServername": "", - "settingsEmailIgnoreTLS": "", - "settingsEmailRequireTLS": "", - "settingsEmailRejectUnauthorized": "", - "settingsEmailSecure": "", - "settingsEmailPool": "", - "sendTestNotifications": "" + "sendTestNotifications": "", + "selectAll": "", + "showAdminLoginLink": "", + "logsPage": { + "title": "", + "description": "", + "tabs": { + "queue": "", + "logs": "" + }, + "toast": { + "fetchLogsSuccess": "" + }, + "logLevelSelect": { + "title": "", + "values": { + "all": "", + "info": "", + "warn": "", + "error": "", + "debug": "" + } + } + }, + "queuePage": { + "title": "", + "refreshButton": "", + "flushButton": "", + "jobTable": { + "title": "", + "idHeader": "", + "urlHeader": "", + "typeHeader": "", + "activeHeader": "", + "lockedAtHeader": "", + "runCountHeader": "", + "failCountHeader": "", + "lastRunHeader": "", + "lastFinishedAtHeader": "", + "lastRunTookHeader": "" + }, + "metricsTable": { + "title": "", + "metricHeader": "", + "valueHeader": "" + }, + "failedJobTable": { + "title": "", + "monitorIdHeader": "", + "monitorUrlHeader": "", + "failCountHeader": "", + "failedAtHeader": "", + "failReasonHeader": "" + } + }, + "export": { + "title": "", + "success": "", + "failed": "" + }, + "monitorActions": { + "title": "", + "import": "", + "export": "" + }, + "settingsPage": { + "aboutSettings": { + "labelDevelopedBy": "", + "labelVersion": "", + "title": "" + }, + "demoMonitorsSettings": { + "buttonAddMonitors": "", + "description": "", + "title": "" + }, + "emailSettings": { + "buttonSendTestEmail": "", + "description": "", + "descriptionTransport": "", + "labelAddress": "", + "labelConnectionHost": "", + "labelHost": "", + "labelIgnoreTLS": "", + "labelPassword": "", + "labelPasswordSet": "", + "labelPool": "", + "labelPort": "", + "labelRejectUnauthorized": "", + "labelRequireTLS": "", + "labelSecure": "", + "labelTLSServername": "", + "labelUser": "", + "linkTransport": "", + "placeholderUser": "", + "title": "" + }, + "pageSpeedSettings": { + "description": "", + "labelApiKeySet": "", + "labelApiKey": "", + "title": "" + }, + "saveButtonLabel": "", + "statsSettings": { + "clearAllStatsButton": "", + "clearAllStatsDescription": "", + "clearAllStatsDialogConfirm": "", + "clearAllStatsDialogDescription": "", + "clearAllStatsDialogTitle": "", + "description": "", + "labelTTL": "", + "labelTTLOptional": "", + "title": "" + }, + "systemResetSettings": { + "buttonRemoveAllMonitors": "", + "description": "", + "dialogConfirm": "", + "dialogDescription": "", + "dialogTitle": "", + "title": "" + }, + "timezoneSettings": { + "description": "", + "label": "", + "title": "" + }, + "title": "", + "uiSettings": { + "description": "", + "labelLanguage": "", + "labelTheme": "", + "title": "" + }, + "urlSettings": { + "description": "", + "label": "", + "selectDisabled": "", + "selectEnabled": "", + "title": "" + } + } } diff --git a/client/src/locales/fr.json b/client/src/locales/fr.json index 48834602e..5aabeb6e3 100644 --- a/client/src/locales/fr.json +++ b/client/src/locales/fr.json @@ -3,40 +3,11 @@ "title": "Titre", "distributedStatusHeaderText": "", "distributedStatusSubHeaderText": "", - "settingsGeneralSettings": "Paramètres généraux", - "settingsDisplayTimezone": "Fuseau horaire d'affichage", - "settingsDisplayTimezoneDescription": "Le fuseau horaire du tableau de bord que vous affichez publiquement.", - "settingsAppearance": "Apparence", - "settingsAppearanceDescription": "", - "settingsThemeMode": "", - "settingsLanguage": "Langue", - "settingsEnabled": "Activé", "settingsDisabled": "Désactivé", - "settingsHistoryAndMonitoring": "", - "settingsHistoryAndMonitoringDescription": "", - "settingsTTLLabel": "", - "settingsTTLOptionalLabel": "0 pour infini", - "settingsClearAllStats": "Effacer les stats. C'est irréversible.", - "settingsClearAllStatsButton": "Effacer les stats", - "settingsClearAllStatsDialogTitle": "Voulez-vous effacer toutes les stats ?", - "settingsClearAllStatsDialogDescription": "", - "settingsClearAllStatsDialogConfirm": "Oui, effacer les stats", - "settingsDemoMonitors": "", - "settingsDemoMonitorsDescription": "", - "settingsAddDemoMonitors": "", - "settingsAddDemoMonitorsButton": "", - "settingsRemoveAllMonitors": "", - "settingsRemoveAllMonitorsButton": "", - "settingsRemoveAllMonitorsDialogTitle": "", - "settingsRemoveAllMonitorsDialogConfirm": "", - "settingsAbout": "", - "settingsDevelopedBy": "", - "settingsSave": "", "settingsSuccessSaved": "", "settingsFailedToSave": "", "settingsStatsCleared": "", "settingsFailedToClearStats": "", - "settingsDemoMonitorsAdded": "", "settingsFailedToAddDemoMonitors": "", "settingsMonitorsDeleted": "", "settingsFailedToDeleteMonitors": "", @@ -362,16 +333,10 @@ "noFileSelected": "", "fallbackPage": "" }, - "settingsSystemReset": "", - "settingsSystemResetDescription": "", "DeleteAccountTitle": "Supprimer le compte", "DeleteAccountButton": "Supprimer le compte", "publicLink": "", "maskedPageSpeedKeyPlaceholder": "", - "pageSpeedApiKeyFieldTitle": "", - "pageSpeedApiKeyFieldLabel": "", - "pageSpeedApiKeyFieldDescription": "", - "pageSpeedApiKeyFieldResetLabel": "", "reset": "", "ignoreTLSError": "", "tlsErrorIgnored": "", @@ -433,22 +398,13 @@ "password": "", "team": "", "logOut": "Déconnexion", - "notifications": "" + "notifications": "", + "logs": "" }, - "settingsEmail": "Email", - "settingsEmailDescription": "", - "settingsEmailHost": "", - "settingsEmailPort": "", - "settingsEmailAddress": "", - "settingsEmailPassword": "", "settingsEmailUser": "", - "settingsEmailFieldResetLabel": "", "state": "", "statusBreadCrumbsStatusPages": "", "statusBreadCrumbsDetails": "", - "settingsThemeModeLight": "Clair", - "settingsThemeModeDark": "Sombre", - "settingsClearAllStatsDialogCancel": "Annuler", "commonSaving": "Sauvegarde...", "navControls": "", "incidentsPageTitle": "Incidents", @@ -463,26 +419,18 @@ "passwordRequirements": "Le nouveau mot de passe doit contenir au moins 8 caractères et au moins une lettre majuscule, une lettre minuscule, un chiffre et un caractère spécial.", "saving": "Sauvegarde..." }, - "settingsEmailConnectionHost": "", "emailSent": "", "failedToSendEmail": "", - "settingsTestEmail": "", "settingsTestEmailSuccess": "", "settingsTestEmailFailed": "", "settingsTestEmailFailedWithReason": "", "settingsTestEmailUnknownError": "Erreur inconnue", - "settingsEmailRequiredFields": "", "statusMsg": { "paused": "", "up": "", "down": "", "pending": "" }, - "settingsURLTitle": "", - "settingsURLDescription": "", - "settingsURLSelectTitle": "", - "settingsURLEnabled": "", - "settingURLDisabled": "", "uptimeGeneralInstructions": { "http": "", "ping": "", @@ -583,6 +531,13 @@ }, "errors": { "validation": "" + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + } } }, "login": { @@ -709,13 +664,13 @@ "integrationKeyLabel": "", "integrationKeyPlaceholder": "" }, - "genericWebhookSettings": { + "discordSettings": { "title": "", "description": "", "webhookLabel": "", "webhookPlaceholder": "" }, - "discordSettings": { + "webhookSettings": { "title": "", "description": "", "webhookLabel": "", @@ -734,11 +689,147 @@ "paused": "" }, "advancedMatching": "", - "settingsEmailTLSServername": "", - "settingsEmailIgnoreTLS": "", - "settingsEmailRequireTLS": "", - "settingsEmailRejectUnauthorized": "", - "settingsEmailSecure": "", - "settingsEmailPool": "", - "sendTestNotifications": "" + "sendTestNotifications": "", + "selectAll": "", + "showAdminLoginLink": "", + "logsPage": { + "title": "", + "description": "", + "tabs": { + "queue": "", + "logs": "" + }, + "toast": { + "fetchLogsSuccess": "" + }, + "logLevelSelect": { + "title": "", + "values": { + "all": "", + "info": "", + "warn": "", + "error": "", + "debug": "" + } + } + }, + "queuePage": { + "title": "", + "refreshButton": "", + "flushButton": "", + "jobTable": { + "title": "", + "idHeader": "", + "urlHeader": "", + "typeHeader": "", + "activeHeader": "", + "lockedAtHeader": "", + "runCountHeader": "", + "failCountHeader": "", + "lastRunHeader": "", + "lastFinishedAtHeader": "", + "lastRunTookHeader": "" + }, + "metricsTable": { + "title": "", + "metricHeader": "", + "valueHeader": "" + }, + "failedJobTable": { + "title": "", + "monitorIdHeader": "", + "monitorUrlHeader": "", + "failCountHeader": "", + "failedAtHeader": "", + "failReasonHeader": "" + } + }, + "export": { + "title": "", + "success": "", + "failed": "" + }, + "monitorActions": { + "title": "", + "import": "", + "export": "" + }, + "settingsPage": { + "aboutSettings": { + "labelDevelopedBy": "", + "labelVersion": "", + "title": "" + }, + "demoMonitorsSettings": { + "buttonAddMonitors": "", + "description": "", + "title": "" + }, + "emailSettings": { + "buttonSendTestEmail": "", + "description": "", + "descriptionTransport": "", + "labelAddress": "", + "labelConnectionHost": "", + "labelHost": "", + "labelIgnoreTLS": "", + "labelPassword": "", + "labelPasswordSet": "", + "labelPool": "", + "labelPort": "", + "labelRejectUnauthorized": "", + "labelRequireTLS": "", + "labelSecure": "", + "labelTLSServername": "", + "labelUser": "", + "linkTransport": "", + "placeholderUser": "", + "title": "" + }, + "pageSpeedSettings": { + "description": "", + "labelApiKeySet": "", + "labelApiKey": "", + "title": "" + }, + "saveButtonLabel": "", + "statsSettings": { + "clearAllStatsButton": "", + "clearAllStatsDescription": "", + "clearAllStatsDialogConfirm": "", + "clearAllStatsDialogDescription": "", + "clearAllStatsDialogTitle": "", + "description": "", + "labelTTL": "", + "labelTTLOptional": "", + "title": "" + }, + "systemResetSettings": { + "buttonRemoveAllMonitors": "", + "description": "", + "dialogConfirm": "", + "dialogDescription": "", + "dialogTitle": "", + "title": "" + }, + "timezoneSettings": { + "description": "", + "label": "", + "title": "" + }, + "title": "", + "uiSettings": { + "description": "", + "labelLanguage": "", + "labelTheme": "", + "title": "" + }, + "urlSettings": { + "description": "", + "label": "", + "selectDisabled": "", + "selectEnabled": "", + "title": "" + } + } } diff --git a/client/src/locales/pt-BR.json b/client/src/locales/pt-BR.json index a783a820c..37604b53b 100644 --- a/client/src/locales/pt-BR.json +++ b/client/src/locales/pt-BR.json @@ -3,40 +3,11 @@ "title": "Título", "distributedStatusHeaderText": "Cobertura em tempo real e em dispositivos reais", "distributedStatusSubHeaderText": "Impulsionado por milhões de dispositivos em todo o mundo, visualize o desempenho do sistema por região global, país ou cidade", - "settingsGeneralSettings": "Configurações gerais", - "settingsDisplayTimezone": "Fuso horário", - "settingsDisplayTimezoneDescription": "Selecione o fuso horário usado para exibir datas e horas em todo o aplicativo.", - "settingsAppearance": "Aparência", - "settingsAppearanceDescription": "Alterne entre o modo claro e escuro ou altere o idioma da interface do usuário", - "settingsThemeMode": "Tema", - "settingsLanguage": "Linguagem", - "settingsEnabled": "Habilitado", "settingsDisabled": "Desabilitado", - "settingsHistoryAndMonitoring": "Histórico de monitoramento", - "settingsHistoryAndMonitoringDescription": "Defina por quanto tempo você deseja manter os dados de histórico. Você também pode limpar todos os dados existentes.", - "settingsTTLLabel": "Os dias em que você deseja manter os dados de histórico.", - "settingsTTLOptionalLabel": "0 para infinito", - "settingsClearAllStats": "Limpe todas as estatísticas. Isso é irreversível.", - "settingsClearAllStatsButton": "Limpar todas as estatísticas", - "settingsClearAllStatsDialogTitle": "Você quer limpar todas as estatísticas?", - "settingsClearAllStatsDialogDescription": "Uma vez limpo, o histórico de monitoramento e as estatísticas não podem ser recuperados.", - "settingsClearAllStatsDialogConfirm": "Sim, limpar todas as estatísticas", - "settingsDemoMonitors": "Monitores de demonstração", - "settingsDemoMonitorsDescription": "Adicione monitores de genéricos para fins de demonstração.", - "settingsAddDemoMonitors": "Adicionando monitores de demonstração", - "settingsAddDemoMonitorsButton": "Adicionar monitores de demonstração", - "settingsRemoveAllMonitors": "Removendo todos os monitores", - "settingsRemoveAllMonitorsButton": "Remover todos os monitores", - "settingsRemoveAllMonitorsDialogTitle": "Você deseja remover todos os monitores?", - "settingsRemoveAllMonitorsDialogConfirm": "Sim, remova todos os monitores", - "settingsAbout": "Sobre", - "settingsDevelopedBy": "Desenvolvido pela Bluewave Labs.", - "settingsSave": "Salvar", "settingsSuccessSaved": "Configurações salvas com sucesso", "settingsFailedToSave": "Falha ao salvar as configurações", "settingsStatsCleared": "Estatísticas limpas com sucesso", "settingsFailedToClearStats": "Falha ao limpar estatísticas", - "settingsDemoMonitorsAdded": "Monitores de demonstração adicionados com sucesso", "settingsFailedToAddDemoMonitors": "Falha ao adicionar monitores de demonstração", "settingsMonitorsDeleted": "Todos os monitores foram excluídos com sucesso", "settingsFailedToDeleteMonitors": "Falha ao excluir todos os monitores", @@ -362,16 +333,10 @@ "noFileSelected": "Nenhum arquivo selecionado", "fallbackPage": "Importe um arquivo para enviar uma lista de servidores em massa" }, - "settingsSystemReset": "Resetar sistema", - "settingsSystemResetDescription": "Remova todos os monitores do seu sistema.", "DeleteAccountTitle": "Remover conta", "DeleteAccountButton": "Remover conta", "publicLink": "Link publico", "maskedPageSpeedKeyPlaceholder": "*************************************", - "pageSpeedApiKeyFieldTitle": "Google PageSpeed API key", - "pageSpeedApiKeyFieldLabel": "PageSpeed API key", - "pageSpeedApiKeyFieldDescription": "Insira sua chave da API do Google PageSpeed ​​para ativar o monitoramento de velocidade de página. Clique em Resetar para atualizar a chave.", - "pageSpeedApiKeyFieldResetLabel": "A chave de API está definida. Clique em Resetar para alterá-la.", "reset": "Resetar", "ignoreTLSError": "Ignorar erro TLS/SSL", "tlsErrorIgnored": "Erros TLS/SSL ignorados", @@ -433,22 +398,13 @@ "password": "Senha", "team": "Equipe", "logOut": "Sair", - "notifications": "" + "notifications": "", + "logs": "" }, - "settingsEmail": "E-mail", - "settingsEmailDescription": "Configurações de e-mail do seu sistema. Elas são usadas para enviar notificações e alertas.", - "settingsEmailHost": "Host de e-mail - Nome do host ou endereço IP do servidor SMTP", - "settingsEmailPort": "Porta de e-mail - Porta para conectar", - "settingsEmailAddress": "Endereço de e-mail - Usado para autenticação", - "settingsEmailPassword": "Senha de e-mail - Senha para autenticação", "settingsEmailUser": "Usuário de e-mail - Nome de usuário para autenticação, substitui o endereço de e-mail, se especificado", - "settingsEmailFieldResetLabel": "A senha foi definida. Clique em Redefinir para alterá-la.", "state": "Estado", "statusBreadCrumbsStatusPages": "Pagina de status", "statusBreadCrumbsDetails": "Detalhes", - "settingsThemeModeLight": "Claro", - "settingsThemeModeDark": "Escuro", - "settingsClearAllStatsDialogCancel": "Cancelar", "commonSaving": "Salvando...", "navControls": "Controles", "incidentsPageTitle": "Incidentes", @@ -463,26 +419,18 @@ "passwordRequirements": "A nova senha deve conter pelo menos 8 caracteres e deve ter pelo menos uma letra maiúscula, uma letra minúscula, um número e um caractere especial.", "saving": "Salvando..." }, - "settingsEmailConnectionHost": "Host de conexão de e-mail - Nome do host a ser usado na saudação HELO/EHLO", "emailSent": "E-mail enviado com sucesso", "failedToSendEmail": "Falha ao enviar e-mail", - "settingsTestEmail": "Enviar e-mail de teste", "settingsTestEmailSuccess": "E-mail de teste enviado com sucesso", "settingsTestEmailFailed": "Falha ao enviar e-mail de teste", "settingsTestEmailFailedWithReason": "Falha ao enviar e-mail de teste: {{reason}}", "settingsTestEmailUnknownError": "Erro desconhecido", - "settingsEmailRequiredFields": "O host e a porta do e-mail são obrigatórios", "statusMsg": { "paused": "O monitoramento está pausado.", "up": "Seu site está no ar.", "down": "Seu site está fora do ar.", "pending": "Pendente..." }, - "settingsURLTitle": "", - "settingsURLDescription": "", - "settingsURLSelectTitle": "", - "settingsURLEnabled": "", - "settingURLDisabled": "", "uptimeGeneralInstructions": { "http": "", "ping": "", @@ -583,6 +531,13 @@ }, "errors": { "validation": "" + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + } } }, "login": { @@ -709,13 +664,13 @@ "integrationKeyLabel": "", "integrationKeyPlaceholder": "" }, - "genericWebhookSettings": { + "discordSettings": { "title": "", "description": "", "webhookLabel": "", "webhookPlaceholder": "" }, - "discordSettings": { + "webhookSettings": { "title": "", "description": "", "webhookLabel": "", @@ -734,11 +689,147 @@ "paused": "" }, "advancedMatching": "", - "settingsEmailTLSServername": "", - "settingsEmailIgnoreTLS": "", - "settingsEmailRequireTLS": "", - "settingsEmailRejectUnauthorized": "", - "settingsEmailSecure": "", - "settingsEmailPool": "", - "sendTestNotifications": "" + "sendTestNotifications": "", + "selectAll": "", + "showAdminLoginLink": "", + "logsPage": { + "title": "", + "description": "", + "tabs": { + "queue": "", + "logs": "" + }, + "toast": { + "fetchLogsSuccess": "" + }, + "logLevelSelect": { + "title": "", + "values": { + "all": "", + "info": "", + "warn": "", + "error": "", + "debug": "" + } + } + }, + "queuePage": { + "title": "", + "refreshButton": "", + "flushButton": "", + "jobTable": { + "title": "", + "idHeader": "", + "urlHeader": "", + "typeHeader": "", + "activeHeader": "", + "lockedAtHeader": "", + "runCountHeader": "", + "failCountHeader": "", + "lastRunHeader": "", + "lastFinishedAtHeader": "", + "lastRunTookHeader": "" + }, + "metricsTable": { + "title": "", + "metricHeader": "", + "valueHeader": "" + }, + "failedJobTable": { + "title": "", + "monitorIdHeader": "", + "monitorUrlHeader": "", + "failCountHeader": "", + "failedAtHeader": "", + "failReasonHeader": "" + } + }, + "export": { + "title": "", + "success": "", + "failed": "" + }, + "monitorActions": { + "title": "", + "import": "", + "export": "" + }, + "settingsPage": { + "aboutSettings": { + "labelDevelopedBy": "", + "labelVersion": "", + "title": "" + }, + "demoMonitorsSettings": { + "buttonAddMonitors": "", + "description": "", + "title": "" + }, + "emailSettings": { + "buttonSendTestEmail": "", + "description": "", + "descriptionTransport": "", + "labelAddress": "", + "labelConnectionHost": "", + "labelHost": "", + "labelIgnoreTLS": "", + "labelPassword": "", + "labelPasswordSet": "", + "labelPool": "", + "labelPort": "", + "labelRejectUnauthorized": "", + "labelRequireTLS": "", + "labelSecure": "", + "labelTLSServername": "", + "labelUser": "", + "linkTransport": "", + "placeholderUser": "", + "title": "" + }, + "pageSpeedSettings": { + "description": "", + "labelApiKeySet": "", + "labelApiKey": "", + "title": "" + }, + "saveButtonLabel": "", + "statsSettings": { + "clearAllStatsButton": "", + "clearAllStatsDescription": "", + "clearAllStatsDialogConfirm": "", + "clearAllStatsDialogDescription": "", + "clearAllStatsDialogTitle": "", + "description": "", + "labelTTL": "", + "labelTTLOptional": "", + "title": "" + }, + "systemResetSettings": { + "buttonRemoveAllMonitors": "", + "description": "", + "dialogConfirm": "", + "dialogDescription": "", + "dialogTitle": "", + "title": "" + }, + "timezoneSettings": { + "description": "", + "label": "", + "title": "" + }, + "title": "", + "uiSettings": { + "description": "", + "labelLanguage": "", + "labelTheme": "", + "title": "" + }, + "urlSettings": { + "description": "", + "label": "", + "selectDisabled": "", + "selectEnabled": "", + "title": "" + } + } } diff --git a/client/src/locales/ru.json b/client/src/locales/ru.json index 539bb2d51..a4ec3290f 100644 --- a/client/src/locales/ru.json +++ b/client/src/locales/ru.json @@ -3,40 +3,11 @@ "title": "Название", "distributedStatusHeaderText": "Охват реального времени и реального устройства", "distributedStatusSubHeaderText": "Работает на миллионах устройств по всему миру, просматривайте производительность системы по глобальному региону, стране или городу", - "settingsGeneralSettings": "Общие настройки", - "settingsDisplayTimezone": "Отображать часовой пояс", - "settingsDisplayTimezoneDescription": "Часовой пояс панели мониторинга, которую вы публично отображаете.", - "settingsAppearance": "Внешний вид", - "settingsAppearanceDescription": "Переключение между светлым и темным режимом или изменение языка пользовательского интерфейса", - "settingsThemeMode": "Тема", - "settingsLanguage": "Язык", - "settingsEnabled": "Включено", "settingsDisabled": "Выключено", - "settingsHistoryAndMonitoring": "История и мониторинг", - "settingsHistoryAndMonitoringDescription": "Определите здесь, как долго вы хотите хранить данные. Вы также можете удалить все прошлые данные.", - "settingsTTLLabel": "Дни, за которыми вы хотите следить.", - "settingsTTLOptionalLabel": "0 для бесконечности", - "settingsClearAllStats": "Очистить всю статистику. Это необратимо.", - "settingsClearAllStatsButton": "Очистить всю статистику", - "settingsClearAllStatsDialogTitle": "Хотите очистить всю статистику?", - "settingsClearAllStatsDialogDescription": "После удаления ваши мониторы не могут быть восстановлены.", - "settingsClearAllStatsDialogConfirm": "Да, очистить всю статистику", - "settingsDemoMonitors": "Демо мониторы", - "settingsDemoMonitorsDescription": "Здесь вы можете добавлять и удалять демонстрационные мониторы.", - "settingsAddDemoMonitors": "Добавьте демонстрационные мониторы", - "settingsAddDemoMonitorsButton": "Добавьте демонстрационные мониторы", - "settingsRemoveAllMonitors": "Удалить все демонстрационные мониторы", - "settingsRemoveAllMonitorsButton": "Удалить все демонстрационные мониторы", - "settingsRemoveAllMonitorsDialogTitle": "Хотите удалить все мониторы?", - "settingsRemoveAllMonitorsDialogConfirm": "Да, очистить все мониторы", - "settingsAbout": "О", - "settingsDevelopedBy": "Developed by Bluewave Labs.", - "settingsSave": "Сохранить", "settingsSuccessSaved": "Настройки успешно сохранены", "settingsFailedToSave": "Не удалось сохранить настройки", "settingsStatsCleared": "Статистика успешно очищена", "settingsFailedToClearStats": "Не удалось очистить статистику", - "settingsDemoMonitorsAdded": "Успешно добавлены демонстрационные мониторы", "settingsFailedToAddDemoMonitors": "Не удалось добавить демонстрационные мониторы", "settingsMonitorsDeleted": "Успешно удалены все мониторы", "settingsFailedToDeleteMonitors": "Не удалось удалить все мониторы", @@ -362,16 +333,10 @@ "noFileSelected": "Файл не выбран", "fallbackPage": "Импортируйте файл для массовой загрузки списка серверов" }, - "settingsSystemReset": "Сброс системы", - "settingsSystemResetDescription": "Удалите все мониторы из вашей системы.", "DeleteAccountTitle": "Удалить аккаунт", "DeleteAccountButton": "Удалить аккаунт", "publicLink": "Публичная ссылка", "maskedPageSpeedKeyPlaceholder": "*************************************", - "pageSpeedApiKeyFieldTitle": "Ключ API Google PageSpeed", - "pageSpeedApiKeyFieldLabel": "Ключ API PageSpeed", - "pageSpeedApiKeyFieldDescription": "Введите свой API-ключ Google PageSpeed, чтобы включить мониторинг скорости страницы. Нажмите \"Сброс\", чтобы обновить ключ.", - "pageSpeedApiKeyFieldResetLabel": "Установлен ключ API. Нажмите \"Сброс\", чтобы изменить его.", "reset": "Сброс", "ignoreTLSError": "Игнорировать ошибку TLS/SSL", "tlsErrorIgnored": "Ошибки TLS/SSL игнорируются", @@ -433,22 +398,13 @@ "password": "Пароль", "team": "Команда", "logOut": "Выйти", - "notifications": "" + "notifications": "", + "logs": "" }, - "settingsEmail": "Настройки Email", - "settingsEmailDescription": "Настройка параметров email", - "settingsEmailHost": "Email host", - "settingsEmailPort": "Email port", - "settingsEmailAddress": "Email address", - "settingsEmailPassword": "Email пароль", "settingsEmailUser": "Email пользователь", - "settingsEmailFieldResetLabel": "Пароль установлен. Нажмите \"Сброс\", чтобы изменить его.", "state": "Состояние", "statusBreadCrumbsStatusPages": "Страницы статуса", "statusBreadCrumbsDetails": "Подробности", - "settingsThemeModeLight": "Светлая", - "settingsThemeModeDark": "Тёмная", - "settingsClearAllStatsDialogCancel": "Отменить", "commonSaving": "Сохранение...", "navControls": "Управления", "incidentsPageTitle": "Инциденты", @@ -463,26 +419,18 @@ "passwordRequirements": "Новый пароль должен содержать не менее 8 символов и содержать как минимум одну заглавную букву, одну строчную букву, одну цифру и один специальный символ.", "saving": "Сохранение..." }, - "settingsEmailConnectionHost": "", "emailSent": "", "failedToSendEmail": "", - "settingsTestEmail": "", "settingsTestEmailSuccess": "", "settingsTestEmailFailed": "", "settingsTestEmailFailedWithReason": "", "settingsTestEmailUnknownError": "", - "settingsEmailRequiredFields": "", "statusMsg": { "paused": "", "up": "", "down": "", "pending": "" }, - "settingsURLTitle": "", - "settingsURLDescription": "", - "settingsURLSelectTitle": "", - "settingsURLEnabled": "", - "settingURLDisabled": "", "uptimeGeneralInstructions": { "http": "", "ping": "", @@ -583,6 +531,13 @@ }, "errors": { "validation": "" + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + } } }, "login": { @@ -709,13 +664,13 @@ "integrationKeyLabel": "", "integrationKeyPlaceholder": "" }, - "genericWebhookSettings": { + "discordSettings": { "title": "", "description": "", "webhookLabel": "", "webhookPlaceholder": "" }, - "discordSettings": { + "webhookSettings": { "title": "", "description": "", "webhookLabel": "", @@ -734,11 +689,147 @@ "paused": "" }, "advancedMatching": "", - "settingsEmailTLSServername": "", - "settingsEmailIgnoreTLS": "", - "settingsEmailRequireTLS": "", - "settingsEmailRejectUnauthorized": "", - "settingsEmailSecure": "", - "settingsEmailPool": "", - "sendTestNotifications": "" + "sendTestNotifications": "", + "selectAll": "", + "showAdminLoginLink": "", + "logsPage": { + "title": "", + "description": "", + "tabs": { + "queue": "", + "logs": "" + }, + "toast": { + "fetchLogsSuccess": "" + }, + "logLevelSelect": { + "title": "", + "values": { + "all": "", + "info": "", + "warn": "", + "error": "", + "debug": "" + } + } + }, + "queuePage": { + "title": "", + "refreshButton": "", + "flushButton": "", + "jobTable": { + "title": "", + "idHeader": "", + "urlHeader": "", + "typeHeader": "", + "activeHeader": "", + "lockedAtHeader": "", + "runCountHeader": "", + "failCountHeader": "", + "lastRunHeader": "", + "lastFinishedAtHeader": "", + "lastRunTookHeader": "" + }, + "metricsTable": { + "title": "", + "metricHeader": "", + "valueHeader": "" + }, + "failedJobTable": { + "title": "", + "monitorIdHeader": "", + "monitorUrlHeader": "", + "failCountHeader": "", + "failedAtHeader": "", + "failReasonHeader": "" + } + }, + "export": { + "title": "", + "success": "", + "failed": "" + }, + "monitorActions": { + "title": "", + "import": "", + "export": "" + }, + "settingsPage": { + "aboutSettings": { + "labelDevelopedBy": "", + "labelVersion": "", + "title": "" + }, + "demoMonitorsSettings": { + "buttonAddMonitors": "", + "description": "", + "title": "" + }, + "emailSettings": { + "buttonSendTestEmail": "", + "description": "", + "descriptionTransport": "", + "labelAddress": "", + "labelConnectionHost": "", + "labelHost": "", + "labelIgnoreTLS": "", + "labelPassword": "", + "labelPasswordSet": "", + "labelPool": "", + "labelPort": "", + "labelRejectUnauthorized": "", + "labelRequireTLS": "", + "labelSecure": "", + "labelTLSServername": "", + "labelUser": "", + "linkTransport": "", + "placeholderUser": "", + "title": "" + }, + "pageSpeedSettings": { + "description": "", + "labelApiKeySet": "", + "labelApiKey": "", + "title": "" + }, + "saveButtonLabel": "", + "statsSettings": { + "clearAllStatsButton": "", + "clearAllStatsDescription": "", + "clearAllStatsDialogConfirm": "", + "clearAllStatsDialogDescription": "", + "clearAllStatsDialogTitle": "", + "description": "", + "labelTTL": "", + "labelTTLOptional": "", + "title": "" + }, + "systemResetSettings": { + "buttonRemoveAllMonitors": "", + "description": "", + "dialogConfirm": "", + "dialogDescription": "", + "dialogTitle": "", + "title": "" + }, + "timezoneSettings": { + "description": "", + "label": "", + "title": "" + }, + "title": "", + "uiSettings": { + "description": "", + "labelLanguage": "", + "labelTheme": "", + "title": "" + }, + "urlSettings": { + "description": "", + "label": "", + "selectDisabled": "", + "selectEnabled": "", + "title": "" + } + } } diff --git a/client/src/locales/tr.json b/client/src/locales/tr.json index 1d86de06c..e9b8f0850 100644 --- a/client/src/locales/tr.json +++ b/client/src/locales/tr.json @@ -3,40 +3,11 @@ "title": "Başlık", "distributedStatusHeaderText": "Gerçek zamanlı, Gerçek cihazlar kapsamı", "distributedStatusSubHeaderText": "Dünya çapında milyonlarca cihaz tarafından desteklenen sistem performansını küresel bölgeye, ülkeye veya şehre göre görüntüleyin", - "settingsGeneralSettings": "Genel ayarlar", - "settingsDisplayTimezone": "Görüntüleme saat dilimi", - "settingsDisplayTimezoneDescription": "Herkese açık olarak görüntülediğiniz kontrol panelinin saat dilimi.", - "settingsAppearance": "Görünüm", - "settingsAppearanceDescription": "Açık ve koyu mod arasında geçiş yapın veya kullanıcı arayüzü dilini değiştirin", - "settingsThemeMode": "Tema", - "settingsLanguage": "Dil", - "settingsEnabled": "Etkin", "settingsDisabled": "Devre dışı", - "settingsHistoryAndMonitoring": "Geçmiş ve izleme", - "settingsHistoryAndMonitoringDescription": "Verileri ne kadar süreyle saklamak istediğinizi burada tanımlayın. Ayrıca tüm geçmiş verileri kaldırabilirsiniz.", - "settingsTTLLabel": "İzleme geçmişini saklamak istediğiniz gün sayısı.", - "settingsTTLOptionalLabel": "Sınırsız için 0", - "settingsClearAllStats": "Tüm istatistikleri temizle. Bu geri alınamaz.", - "settingsClearAllStatsButton": "Tüm istatistikleri temizle", - "settingsClearAllStatsDialogTitle": "Tüm istatistikleri temizlemek istiyor musunuz?", - "settingsClearAllStatsDialogDescription": "Silindikten sonra, monitörleriniz geri alınamaz.", - "settingsClearAllStatsDialogConfirm": "Evet, tüm istatistikleri temizle", - "settingsDemoMonitors": "Demo monitörler", - "settingsDemoMonitorsDescription": "Burada demo monitörler ekleyebilir ve kaldırabilirsiniz.", - "settingsAddDemoMonitors": "Demo monitörler ekle", - "settingsAddDemoMonitorsButton": "Demo monitörler ekle", - "settingsRemoveAllMonitors": "Tüm monitörleri kaldır", - "settingsRemoveAllMonitorsButton": "Tüm monitörleri kaldır", - "settingsRemoveAllMonitorsDialogTitle": "Tüm monitörleri kaldırmak istiyor musunuz?", - "settingsRemoveAllMonitorsDialogConfirm": "Evet, tüm monitörleri temizle", - "settingsAbout": "Hakkında", - "settingsDevelopedBy": "Bluewave Labs tarafından geliştirilmiştir.", - "settingsSave": "Kaydet", "settingsSuccessSaved": "Ayarlar başarıyla kaydedildi", "settingsFailedToSave": "Ayarlar kaydedilemedi", "settingsStatsCleared": "İstatistikler başarıyla temizlendi", "settingsFailedToClearStats": "İstatistikler temizlenemedi", - "settingsDemoMonitorsAdded": "Demo monitörler başarıyla eklendi", "settingsFailedToAddDemoMonitors": "Demo monitörler eklenemedi", "settingsMonitorsDeleted": "Tüm monitörler başarıyla silindi", "settingsFailedToDeleteMonitors": "Monitörler silinemedi", @@ -117,11 +88,11 @@ "title": "", "checks": [""] }, - "createButton": "", - "createTitle": "", + "createButton": "Bildirim kanalı oluştur", + "createTitle": "Bildirim kanalı", "create": { "success": "", - "failed": "" + "failed": "Bildirim kanalı oluşturulamadı" }, "fetch": { "success": "", @@ -136,7 +107,7 @@ "failed": "" }, "test": { - "success": "", + "success": "Test bildirimi başarıyla gönderildi", "failed": "" } }, @@ -362,44 +333,38 @@ "noFileSelected": "Hiçbir dosya seçilmedi", "fallbackPage": "Toplu olarak sunucu listesini yüklemek için bir dosyayı içe aktarın" }, - "settingsSystemReset": "Sistem sıfırla", - "settingsSystemResetDescription": "Sistemdeki tüm monitörleri silin", "DeleteAccountTitle": "Hesabı sil", "DeleteAccountButton": "Hesabı sil", "publicLink": "Herkese açık bağlantı", "maskedPageSpeedKeyPlaceholder": "*************************************", - "pageSpeedApiKeyFieldTitle": "Google PageSpeed API anahtarı", - "pageSpeedApiKeyFieldLabel": "PageSpeed API anahtarı", - "pageSpeedApiKeyFieldDescription": "Sayfa hızı izlemeyi etkinleştirmek için Google PageSpeed API anahtarınızı girin. Anahtarı güncellemek için Sıfırla'ya tıklayın.", - "pageSpeedApiKeyFieldResetLabel": "API anahtarı ayarlandı. Değiştirmek için Sıfırla'ya tıklayın.", "reset": "Sıfırla", - "ignoreTLSError": "", - "tlsErrorIgnored": "", + "ignoreTLSError": "TLS/SSL hatalarını gözardı et", + "tlsErrorIgnored": "TLS/SSL hataları gözardı ediliyor", "ignoreTLSErrorDescription": "", - "createNew": "", + "createNew": "Yeni oluştur", "greeting": { - "prepend": "", + "prepend": "Merhaba", "append": "", "overview": "" }, "roles": { - "superAdmin": "", - "admin": "", - "teamMember": "", - "demoUser": "" + "superAdmin": "Süper yönetici", + "admin": "Yönetici", + "teamMember": "Ekip elemanı", + "demoUser": "Demo kullanıcısı" }, "teamPanel": { - "teamMembers": "", + "teamMembers": "Ekip elemanları", "filter": { - "all": "", - "member": "" + "all": "Tümü", + "member": "Eleman" }, - "inviteTeamMember": "", - "inviteNewTeamMember": "", + "inviteTeamMember": "Ekip elemanı davet et", + "inviteNewTeamMember": "Yeni ekip elemanı davet et", "inviteDescription": "", - "email": "", - "selectRole": "", - "inviteLink": "", + "email": "Eposta", + "selectRole": "Rolü seçin", + "inviteLink": "Davet linki", "cancel": "", "noMembers": "", "getToken": "", @@ -422,33 +387,24 @@ "infrastructure": "", "incidents": "", "statusPages": "", - "maintenance": "", - "integrations": "", - "settings": "", - "support": "", - "discussions": "", - "docs": "", - "changelog": "", - "profile": "", - "password": "", - "team": "", + "maintenance": "Bakım", + "integrations": "Entegrasyonlar", + "settings": "Ayarlar", + "support": "Destek", + "discussions": "Soru-cevap", + "docs": "Dokümanlar", + "changelog": "Değişiklik günlüğü", + "profile": "Profil", + "password": "Parola", + "team": "Ekip", "logOut": "Çıkış yap", - "notifications": "" + "notifications": "Bildirimler", + "logs": "" }, - "settingsEmail": "", - "settingsEmailDescription": "", - "settingsEmailHost": "", - "settingsEmailPort": "", - "settingsEmailAddress": "", - "settingsEmailPassword": "E-posta şifresi - Kimlik doğrulama şifresi.", "settingsEmailUser": "E-posta kullanıcısı – Kimlik doğrulama için kullanıcı adı; belirtilirse e-posta adresinin yerine geçer.", - "settingsEmailFieldResetLabel": "Şifre ayarlandı. Değiştirmek için \"Sıfırla\"ya tıklayın.", "state": "Durum", "statusBreadCrumbsStatusPages": "Durum sayfası", "statusBreadCrumbsDetails": "Detaylar", - "settingsThemeModeLight": "Aydınlat", - "settingsThemeModeDark": "Karart", - "settingsClearAllStatsDialogCancel": "İptal", "commonSaving": "Kaydediliyor.", "navControls": "Kontroller", "incidentsPageTitle": "Olaylar", @@ -463,26 +419,18 @@ "passwordRequirements": "Yeni şifreniz en az 8 karakterden oluşmalı ve en az bir büyük harf, bir küçük harf, bir rakam ve bir özel karakter içermelidir.", "saving": "Kaydediliyor..." }, - "settingsEmailConnectionHost": "E-posta bağlantı ana bilgisayarı - HELO/EHLO selamlamasında kullanılacak ana bilgisayar adı", "emailSent": "E-posta başarı ile gönderildi.", "failedToSendEmail": "E-posta gönderimi başarısız.", - "settingsTestEmail": "Test E-postası gönder.", "settingsTestEmailSuccess": "Test E-postası başarı ile gönderildi.", "settingsTestEmailFailed": "Test E-postası gönderimi başarısız.", "settingsTestEmailFailedWithReason": "Test E-postası gönderimi başarısız: {{reason}}", "settingsTestEmailUnknownError": "Bilinmeyen hata.", - "settingsEmailRequiredFields": "E-posta sunucusu ve portu gereklidir.", "statusMsg": { "paused": "İzleme duraklatıldı.", "up": "Siteniz yayında.", "down": "Siteniz kapalı.", "pending": "Sırada.." }, - "settingsURLTitle": "Durum Sayfasında IP/URL'yi İzle", - "settingsURLDescription": "Genel Durum sayfasında monitörün IP adresini veya URL'sini görüntüleyin. Devre dışı bırakılırsa, hassas bilgileri korumak için yalnızca monitör adı gösterilecektir.", - "settingsURLSelectTitle": "Durum sayfasında IP/URL'yi görüntüle", - "settingsURLEnabled": "Etkinleştirilmiş", - "settingURLDisabled": "Devre dışı bırakılmış", "uptimeGeneralInstructions": { "http": "", "ping": "", @@ -490,73 +438,73 @@ "port": "" }, "common": { - "appName": "", + "appName": "Checkmate", "monitoringAgentName": "", "buttons": { "toggleTheme": "" }, "toasts": { "networkError": "", - "checkConnection": "", - "unknownError": "" + "checkConnection": "Lütfen bağlantınızı kontrol edin", + "unknownError": "Bilinmeyen hata" } }, "auth": { "common": { "navigation": { - "continue": "", - "back": "" + "continue": "Devam et", + "back": "Geri" }, "inputs": { "email": { - "label": "", + "label": "Eposta", "placeholder": "", "errors": { - "empty": "", + "empty": "Devam etmek için eposta adresinizi girin", "invalid": "" } }, "password": { - "label": "", + "label": "Parola", "rules": { "length": { "beginning": "", - "highlighted": "" + "highlighted": "8 karakter uzunluğunda" }, "special": { - "beginning": "", - "highlighted": "" + "beginning": "En az içermelidir: ", + "highlighted": "bir özel karakter" }, "number": { - "beginning": "", - "highlighted": "" + "beginning": "En az içermelidir: ", + "highlighted": "bir sayı" }, "uppercase": { - "beginning": "", - "highlighted": "" + "beginning": "En az içermelidir: ", + "highlighted": "Bir büyük harf" }, "lowercase": { - "beginning": "", - "highlighted": "" + "beginning": "En az içermelidir: ", + "highlighted": "Bir küçük harf" }, "match": { "beginning": "", - "highlighted": "" + "highlighted": "eşleşmelidir" } }, "errors": { - "empty": "", - "length": "", - "uppercase": "", - "lowercase": "", - "number": "", - "special": "", - "incorrect": "" + "empty": "Lütfen parolanızı girin", + "length": "Parolanız en az 8 karakter uzunluğunda olmalıdır", + "uppercase": "Parola en az 1 büyük harf içermelidir", + "lowercase": "Parola en az 1 küçük harf içermelidir", + "number": "Parola en az 1 sayı içermelidir", + "special": "Parola en az 1 özel karakter içermelidir", + "incorrect": "Parolanız sistemdeki ile uyuşmuyor" } }, "passwordConfirm": { - "label": "", - "placeholder": "", + "label": "Parolayı onayla", + "placeholder": "Onaylamak için parolayı yeniden gir", "errors": { "empty": "", "different": "" @@ -572,78 +520,85 @@ } }, "lastName": { - "label": "", - "placeholder": "", + "label": "Soyadı", + "placeholder": "Ellis", "errors": { - "empty": "", - "length": "", + "empty": "Lütfen soyadınızı girin", + "length": "Soyadı en çok 50 karakter olmalıdır", "pattern": "" } } }, "errors": { "validation": "" + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + } } }, "login": { "heading": "", "subheadings": { - "stepOne": "", - "stepTwo": "" + "stepOne": "Eposta adresinizi girin", + "stepTwo": "Parolanızı girin" }, "links": { - "forgotPassword": "", - "register": "", - "forgotPasswordLink": "", - "registerLink": "" + "forgotPassword": "Parolanızı mı unuttunuz?", + "register": "Hesabınız yok mu?", + "forgotPasswordLink": "Parolayı sıfırla", + "registerLink": "Buradan kayıt olun" }, "toasts": { - "success": "", - "incorrectPassword": "" + "success": "Tekrar hoşgeldiniz! Başarıyla sisteme giriş yaptınız.", + "incorrectPassword": "Hatalı parola" }, "errors": { "password": { - "incorrect": "" + "incorrect": "Giriş yaptığınız parola sistemdekiyle uyuşmuyor" } } }, "registration": { "heading": { - "superAdmin": "", - "user": "" + "superAdmin": "Süper yönetici oluştur", + "user": "Kayıt ol" }, "subheadings": { - "stepOne": "", - "stepTwo": "", - "stepThree": "" + "stepOne": "Kişisel bilgilerinizi girin", + "stepTwo": "Eposta adresinizi girin", + "stepThree": "Parolanızı oluturun" }, "description": { - "superAdmin": "", + "superAdmin": "Başlamak için süper yönetici hesabı oluşturun", "user": "" }, "gettingStartedButton": { - "superAdmin": "", + "superAdmin": "Süper yönetici hesabı oluşturun", "user": "" }, "termsAndPolicies": "", "links": { - "login": "" + "login": "Bir hesabınız mı var? Giriş yapın" }, "toasts": { - "success": "" + "success": "Hoşgeldiniz! Hesabınız başarıyla oluşturuldu." } }, "forgotPassword": { - "heading": "", + "heading": "Parolanızı mı kaybettiniz?", "subheadings": { - "stepOne": "", - "stepTwo": "", + "stepOne": "Endişelenmeyin, sıfırlamak için gerekli bilgileri göndereceğiz.", + "stepTwo": "Parola sıfırlama için gerekli bilgileri hesabına ilettik.", "stepThree": "", "stepFour": "" }, "buttons": { - "openEmail": "", - "resetPassword": "" + "openEmail": "Eposta uygulamasını aç", + "resetPassword": "Parolayı sıfırla" }, "imageAlts": { "passwordKey": "", @@ -670,75 +625,211 @@ "reconnected": "", "stillUnreachable": "" }, - "alertBox": "", + "alertBox": "Sunucu bağlantı hatası", "description": "", "retryButton": { - "default": "", - "processing": "" + "default": "Bağlantıyı tekrarla", + "processing": "Bağlanıyor..." } } }, "createNotifications": { - "title": "", + "title": "Bildirim kanalları oluştur", "nameSettings": { - "title": "", - "description": "", - "nameLabel": "", - "namePlaceholder": "" + "title": "İsim", + "description": "Entegrasyon için açıklama", + "nameLabel": "İsim", + "namePlaceholder": "örn. Slack bildirimleri" }, "typeSettings": { - "title": "", + "title": "Tür", "description": "", - "typeLabel": "" + "typeLabel": "Tür" }, "emailSettings": { - "title": "", - "description": "", - "emailLabel": "", - "emailPlaceholder": "" + "title": "Eposta", + "description": "Hedef eposta adresi", + "emailLabel": "Eposta adresi", + "emailPlaceholder": "örn. john@example.com" }, "slackSettings": { - "title": "", - "description": "", - "webhookLabel": "", - "webhookPlaceholder": "" + "title": "Slack", + "description": "Slack webhook'u burada tanımlayın", + "webhookLabel": "Slack webhook adresi", + "webhookPlaceholder": "https://hooks.slack.com/services/...\n" }, "pagerdutySettings": { - "title": "", - "description": "", - "integrationKeyLabel": "", - "integrationKeyPlaceholder": "" - }, - "genericWebhookSettings": { - "title": "", - "description": "", - "webhookLabel": "", - "webhookPlaceholder": "" + "title": "PagerDuty", + "description": "PagerDuty entegrasyonunu burada yapın", + "integrationKeyLabel": "Entegrasyon anahtarı", + "integrationKeyPlaceholder": "1234567890" }, "discordSettings": { - "title": "", + "title": "Discord", + "description": "Discord webhook'u buradan ayarlayın", + "webhookLabel": "Discord Webhook adresi", + "webhookPlaceholder": "https://sunucu-adı.com/webhook\n" + }, + "webhookSettings": { + "title": "Webhook", "description": "", "webhookLabel": "", - "webhookPlaceholder": "" + "webhookPlaceholder": "https://sunucu-adı.com/webhook\n" } }, "notificationConfig": { - "title": "", + "title": "Bildirimler", "description": "" }, "monitorStatus": { "checkingEvery": "", "withCaptureAgent": "", - "up": "", - "down": "", - "paused": "" + "up": "ayakta", + "down": "çalışmıyor", + "paused": "beklemede" }, "advancedMatching": "", - "settingsEmailTLSServername": "", - "settingsEmailIgnoreTLS": "", - "settingsEmailRequireTLS": "", - "settingsEmailRejectUnauthorized": "", - "settingsEmailSecure": "", - "settingsEmailPool": "", - "sendTestNotifications": "" + "sendTestNotifications": "Bildirimleri dene", + "selectAll": "Tümünü seç", + "showAdminLoginLink": "", + "logsPage": { + "title": "", + "description": "", + "tabs": { + "queue": "", + "logs": "" + }, + "toast": { + "fetchLogsSuccess": "" + }, + "logLevelSelect": { + "title": "", + "values": { + "all": "", + "info": "", + "warn": "", + "error": "", + "debug": "" + } + } + }, + "queuePage": { + "title": "", + "refreshButton": "", + "flushButton": "", + "jobTable": { + "title": "", + "idHeader": "", + "urlHeader": "", + "typeHeader": "", + "activeHeader": "", + "lockedAtHeader": "", + "runCountHeader": "", + "failCountHeader": "", + "lastRunHeader": "", + "lastFinishedAtHeader": "", + "lastRunTookHeader": "" + }, + "metricsTable": { + "title": "", + "metricHeader": "", + "valueHeader": "" + }, + "failedJobTable": { + "title": "", + "monitorIdHeader": "", + "monitorUrlHeader": "", + "failCountHeader": "", + "failedAtHeader": "", + "failReasonHeader": "" + } + }, + "export": { + "title": "", + "success": "", + "failed": "" + }, + "monitorActions": { + "title": "", + "import": "", + "export": "" + }, + "settingsPage": { + "aboutSettings": { + "labelDevelopedBy": "", + "labelVersion": "", + "title": "" + }, + "demoMonitorsSettings": { + "buttonAddMonitors": "", + "description": "", + "title": "" + }, + "emailSettings": { + "buttonSendTestEmail": "", + "description": "", + "descriptionTransport": "", + "labelAddress": "", + "labelConnectionHost": "", + "labelHost": "", + "labelIgnoreTLS": "", + "labelPassword": "", + "labelPasswordSet": "", + "labelPool": "", + "labelPort": "", + "labelRejectUnauthorized": "", + "labelRequireTLS": "", + "labelSecure": "", + "labelTLSServername": "", + "labelUser": "", + "linkTransport": "", + "placeholderUser": "", + "title": "" + }, + "pageSpeedSettings": { + "description": "", + "labelApiKeySet": "", + "labelApiKey": "", + "title": "" + }, + "saveButtonLabel": "", + "statsSettings": { + "clearAllStatsButton": "", + "clearAllStatsDescription": "", + "clearAllStatsDialogConfirm": "", + "clearAllStatsDialogDescription": "", + "clearAllStatsDialogTitle": "", + "description": "", + "labelTTL": "", + "labelTTLOptional": "", + "title": "" + }, + "systemResetSettings": { + "buttonRemoveAllMonitors": "", + "description": "", + "dialogConfirm": "", + "dialogDescription": "", + "dialogTitle": "", + "title": "" + }, + "timezoneSettings": { + "description": "", + "label": "", + "title": "" + }, + "title": "", + "uiSettings": { + "description": "", + "labelLanguage": "", + "labelTheme": "", + "title": "" + }, + "urlSettings": { + "description": "", + "label": "", + "selectDisabled": "", + "selectEnabled": "", + "title": "" + } + } } diff --git a/client/src/locales/zh-TW.json b/client/src/locales/zh-TW.json index 6e864b7ee..21f53a7bf 100644 --- a/client/src/locales/zh-TW.json +++ b/client/src/locales/zh-TW.json @@ -3,40 +3,11 @@ "title": "名稱", "distributedStatusHeaderText": "實時、真實設備覆蓋", "distributedStatusSubHeaderText": "由全球數百萬個設備提供支持,您可以按全球區域、國家或城市查看系統效能", - "settingsGeneralSettings": "一般設定", - "settingsDisplayTimezone": "顯示時區", - "settingsDisplayTimezoneDescription": "您公開顯示的儀表板的時區。", - "settingsAppearance": "外觀", - "settingsAppearanceDescription": "", - "settingsThemeMode": "主題", - "settingsLanguage": "語言", - "settingsEnabled": "啟動", "settingsDisabled": "", - "settingsHistoryAndMonitoring": "", - "settingsHistoryAndMonitoringDescription": "", - "settingsTTLLabel": "", - "settingsTTLOptionalLabel": "", - "settingsClearAllStats": "", - "settingsClearAllStatsButton": "", - "settingsClearAllStatsDialogTitle": "", - "settingsClearAllStatsDialogDescription": "", - "settingsClearAllStatsDialogConfirm": "", - "settingsDemoMonitors": "", - "settingsDemoMonitorsDescription": "", - "settingsAddDemoMonitors": "", - "settingsAddDemoMonitorsButton": "", - "settingsRemoveAllMonitors": "", - "settingsRemoveAllMonitorsButton": "", - "settingsRemoveAllMonitorsDialogTitle": "", - "settingsRemoveAllMonitorsDialogConfirm": "", - "settingsAbout": "", - "settingsDevelopedBy": "", - "settingsSave": "", "settingsSuccessSaved": "", "settingsFailedToSave": "", "settingsStatsCleared": "", "settingsFailedToClearStats": "", - "settingsDemoMonitorsAdded": "", "settingsFailedToAddDemoMonitors": "", "settingsMonitorsDeleted": "", "settingsFailedToDeleteMonitors": "", @@ -362,16 +333,10 @@ "noFileSelected": "", "fallbackPage": "" }, - "settingsSystemReset": "", - "settingsSystemResetDescription": "", "DeleteAccountTitle": "", "DeleteAccountButton": "", "publicLink": "", "maskedPageSpeedKeyPlaceholder": "", - "pageSpeedApiKeyFieldTitle": "", - "pageSpeedApiKeyFieldLabel": "", - "pageSpeedApiKeyFieldDescription": "", - "pageSpeedApiKeyFieldResetLabel": "", "reset": "", "ignoreTLSError": "", "tlsErrorIgnored": "", @@ -433,22 +398,13 @@ "password": "", "team": "", "logOut": "", - "notifications": "" + "notifications": "", + "logs": "" }, - "settingsEmail": "", - "settingsEmailDescription": "", - "settingsEmailHost": "", - "settingsEmailPort": "", - "settingsEmailAddress": "", - "settingsEmailPassword": "", "settingsEmailUser": "", - "settingsEmailFieldResetLabel": "", "state": "", "statusBreadCrumbsStatusPages": "", "statusBreadCrumbsDetails": "", - "settingsThemeModeLight": "", - "settingsThemeModeDark": "", - "settingsClearAllStatsDialogCancel": "", "commonSaving": "", "navControls": "", "incidentsPageTitle": "", @@ -463,26 +419,18 @@ "passwordRequirements": "", "saving": "" }, - "settingsEmailConnectionHost": "", "emailSent": "", "failedToSendEmail": "", - "settingsTestEmail": "", "settingsTestEmailSuccess": "", "settingsTestEmailFailed": "", "settingsTestEmailFailedWithReason": "", "settingsTestEmailUnknownError": "", - "settingsEmailRequiredFields": "", "statusMsg": { "paused": "", "up": "", "down": "", "pending": "" }, - "settingsURLTitle": "", - "settingsURLDescription": "", - "settingsURLSelectTitle": "", - "settingsURLEnabled": "", - "settingURLDisabled": "", "uptimeGeneralInstructions": { "http": "", "ping": "", @@ -583,6 +531,13 @@ }, "errors": { "validation": "" + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + } } }, "login": { @@ -709,13 +664,13 @@ "integrationKeyLabel": "", "integrationKeyPlaceholder": "" }, - "genericWebhookSettings": { + "discordSettings": { "title": "", "description": "", "webhookLabel": "", "webhookPlaceholder": "" }, - "discordSettings": { + "webhookSettings": { "title": "", "description": "", "webhookLabel": "", @@ -734,11 +689,147 @@ "paused": "" }, "advancedMatching": "", - "settingsEmailTLSServername": "", - "settingsEmailIgnoreTLS": "", - "settingsEmailRequireTLS": "", - "settingsEmailRejectUnauthorized": "", - "settingsEmailSecure": "", - "settingsEmailPool": "", - "sendTestNotifications": "" + "sendTestNotifications": "", + "selectAll": "", + "showAdminLoginLink": "", + "logsPage": { + "title": "", + "description": "", + "tabs": { + "queue": "", + "logs": "" + }, + "toast": { + "fetchLogsSuccess": "" + }, + "logLevelSelect": { + "title": "", + "values": { + "all": "", + "info": "", + "warn": "", + "error": "", + "debug": "" + } + } + }, + "queuePage": { + "title": "", + "refreshButton": "", + "flushButton": "", + "jobTable": { + "title": "", + "idHeader": "", + "urlHeader": "", + "typeHeader": "", + "activeHeader": "", + "lockedAtHeader": "", + "runCountHeader": "", + "failCountHeader": "", + "lastRunHeader": "", + "lastFinishedAtHeader": "", + "lastRunTookHeader": "" + }, + "metricsTable": { + "title": "", + "metricHeader": "", + "valueHeader": "" + }, + "failedJobTable": { + "title": "", + "monitorIdHeader": "", + "monitorUrlHeader": "", + "failCountHeader": "", + "failedAtHeader": "", + "failReasonHeader": "" + } + }, + "export": { + "title": "", + "success": "", + "failed": "" + }, + "monitorActions": { + "title": "", + "import": "", + "export": "" + }, + "settingsPage": { + "aboutSettings": { + "labelDevelopedBy": "", + "labelVersion": "", + "title": "" + }, + "demoMonitorsSettings": { + "buttonAddMonitors": "", + "description": "", + "title": "" + }, + "emailSettings": { + "buttonSendTestEmail": "", + "description": "", + "descriptionTransport": "", + "labelAddress": "", + "labelConnectionHost": "", + "labelHost": "", + "labelIgnoreTLS": "", + "labelPassword": "", + "labelPasswordSet": "", + "labelPool": "", + "labelPort": "", + "labelRejectUnauthorized": "", + "labelRequireTLS": "", + "labelSecure": "", + "labelTLSServername": "", + "labelUser": "", + "linkTransport": "", + "placeholderUser": "", + "title": "" + }, + "pageSpeedSettings": { + "description": "", + "labelApiKeySet": "", + "labelApiKey": "", + "title": "" + }, + "saveButtonLabel": "", + "statsSettings": { + "clearAllStatsButton": "", + "clearAllStatsDescription": "", + "clearAllStatsDialogConfirm": "", + "clearAllStatsDialogDescription": "", + "clearAllStatsDialogTitle": "", + "description": "", + "labelTTL": "", + "labelTTLOptional": "", + "title": "" + }, + "systemResetSettings": { + "buttonRemoveAllMonitors": "", + "description": "", + "dialogConfirm": "", + "dialogDescription": "", + "dialogTitle": "", + "title": "" + }, + "timezoneSettings": { + "description": "", + "label": "", + "title": "" + }, + "title": "", + "uiSettings": { + "description": "", + "labelLanguage": "", + "labelTheme": "", + "title": "" + }, + "urlSettings": { + "description": "", + "label": "", + "selectDisabled": "", + "selectEnabled": "", + "title": "" + } + } } diff --git a/client/vite.config.js b/client/vite.config.js index dc84ce055..838cc5142 100644 --- a/client/vite.config.js +++ b/client/vite.config.js @@ -1,12 +1,20 @@ -import { defineConfig } from "vite"; +import { defineConfig, loadEnv } from "vite"; import react from "@vitejs/plugin-react"; import svgr from "vite-plugin-svgr"; +import { execSync } from "child_process"; -// https://vitejs.dev/config/ -export default defineConfig({ - base: "/", - plugins: [svgr(), react()], - optimizeDeps: { - include: ["@mui/material/Tooltip", "@emotion/styled"], - }, +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd(), ""); + let version = 2.2; + + return { + base: "/", + plugins: [svgr(), react()], + optimizeDeps: { + include: ["@mui/material/Tooltip", "@emotion/styled"], + }, + define: { + __APP_VERSION__: JSON.stringify(version), + }, + }; }); diff --git a/docker/.gitignore b/docker/.gitignore index 81ba730c9..9a6df883a 100755 --- a/docker/.gitignore +++ b/docker/.gitignore @@ -10,4 +10,5 @@ prod/redis/data/* dist/docker-compose-test.yaml /dist-mono/mongo/data/* /dist-mono/redis/data/* +/dist-arm/mongo/data/* *.env diff --git a/docker/dist-arm/docker-compose.yaml b/docker/dist-arm/docker-compose.yaml index f66640f71..921d3b8c1 100644 --- a/docker/dist-arm/docker-compose.yaml +++ b/docker/dist-arm/docker-compose.yaml @@ -1,6 +1,6 @@ services: server: - image: ghcr.io/bluewave-labs/checkmate:backend-dist-mono-multiarch + image: ghcr.io/bluewave-labs/checkmate-backend-mono-multiarch:latest restart: always ports: - "52345:52345" @@ -12,16 +12,7 @@ services: - CLIENT_HOST=http://localhost - JWT_SECRET=my_secret depends_on: - - redis - mongodb - redis: - image: redis:7 - container_name: checkmate-redis - ports: - - "6379:6379" - volumes: - - ./redis/data:/data - restart: unless-stopped mongodb: image: mongo:4.4.18 container_name: checkmate-mongodb diff --git a/docker/dist-mono/docker-compose.yaml b/docker/dist-mono/docker-compose.yaml index fd5d54ddc..0dd97aafb 100755 --- a/docker/dist-mono/docker-compose.yaml +++ b/docker/dist-mono/docker-compose.yaml @@ -12,19 +12,7 @@ services: - CLIENT_HOST=http://localhost - JWT_SECRET=my_secret depends_on: - - redis - mongodb - redis: - image: ghcr.io/bluewave-labs/checkmate-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: ghcr.io/bluewave-labs/checkmate-mongo:latest restart: always diff --git a/docker/dist/docker-compose.yaml b/docker/dist/docker-compose.yaml index 580f21e97..ff2f1b7ea 100755 --- a/docker/dist/docker-compose.yaml +++ b/docker/dist/docker-compose.yaml @@ -16,7 +16,6 @@ services: ports: - "52345:52345" depends_on: - - redis - mongodb environment: - DB_CONNECTION_STRING=mongodb://mongodb:27017/uptime_db?replicaSet=rs0 @@ -25,17 +24,6 @@ services: - JWT_SECRET=my_secret # volumes: # - /var/run/docker.sock:/var/run/docker.sock:ro - redis: - image: ghcr.io/bluewave-labs/checkmate-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: ghcr.io/bluewave-labs/checkmate-mongo:latest restart: always diff --git a/docker/prod/docker-compose.yaml b/docker/prod/docker-compose.yaml index 9cc4a82cb..980a0e2f5 100755 --- a/docker/prod/docker-compose.yaml +++ b/docker/prod/docker-compose.yaml @@ -21,6 +21,8 @@ services: volumes: - ./certbot/www/:/var/www/certbot/:rw - ./certbot/conf/:/etc/letsencrypt/:rw + entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --webroot -w /var/www/certbot; sleep 12h & wait $${!}; done;'" + server: image: ghcr.io/bluewave-labs/checkmate:backend-demo restart: always @@ -29,21 +31,9 @@ services: env_file: - server.env depends_on: - - redis - mongodb volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - redis: - image: ghcr.io/bluewave-labs/checkmate:redis-demo - restart: always - volumes: - - ./redis/data:/data - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 30s - timeout: 10s - retries: 5 - start_period: 5s mongodb: image: ghcr.io/bluewave-labs/checkmate:mongo-demo restart: always diff --git a/server/controllers/authController.js b/server/controllers/authController.js index df4034aef..1d8c2af0e 100755 --- a/server/controllers/authController.js +++ b/server/controllers/authController.js @@ -82,7 +82,7 @@ class AuthController { } const newUser = await this.db.insertUser({ ...req.body }, req.file); - logger.info({ + this.logger.info({ message: this.stringService.authCreateUser, service: SERVICE_NAME, details: newUser._id, @@ -111,7 +111,7 @@ class AuthController { }); }); } catch (error) { - logger.warn({ + this.logger.warn({ message: error.message, service: SERVICE_NAME, method: "registerUser", @@ -412,8 +412,7 @@ class AuthController { // 1. Find all the monitors associated with the team ID if superadmin const result = await this.db.getMonitorsByTeamId({ - query: {}, - params: { teamId: user.teamId.toString() }, + teamId: user.teamId.toString(), }); if (user.role.includes("superadmin")) { @@ -424,13 +423,6 @@ class AuthController { await this.jobQueue.deleteJob(monitor); }) )); - - // 3. Delete team - await this.db.deleteTeam(user.teamId); - // 4. Delete all other team members - await this.db.deleteAllOtherUsers(); - // 5. Delete each monitor - await this.db.deleteMonitorsByUserId(user._id); } // 6. Delete the user by id await this.db.deleteUser(user._id); diff --git a/server/controllers/checkController.js b/server/controllers/checkController.js index 387839beb..8f9d0fbb5 100755 --- a/server/controllers/checkController.js +++ b/server/controllers/checkController.js @@ -8,6 +8,9 @@ import { deleteChecksParamValidation, deleteChecksByTeamIdParamValidation, updateChecksTTLBodyValidation, + ackCheckBodyValidation, + ackAllChecksParamValidation, + ackAllChecksBodyValidation, } from "../validation/joi.js"; import jwt from "jsonwebtoken"; import { getTokenFromHeaders } from "../utils/utils.js"; @@ -55,13 +58,15 @@ class CheckController { try { const { monitorId } = req.params; - let { type, sortOrder, dateRange, filter, page, rowsPerPage, status } = req.query; + let { type, sortOrder, dateRange, filter, ack, page, rowsPerPage, status } = + req.query; const result = await this.db.getChecksByMonitor({ monitorId, type, sortOrder, dateRange, filter, + ack, page, rowsPerPage, status, @@ -85,13 +90,14 @@ class CheckController { return; } try { - let { sortOrder, dateRange, filter, page, rowsPerPage } = req.query; + let { sortOrder, dateRange, filter, ack, page, rowsPerPage } = req.query; const { teamId } = req.user; const checkData = await this.db.getChecksByTeam({ sortOrder, dateRange, filter, + ack, page, rowsPerPage, teamId, @@ -105,6 +111,55 @@ class CheckController { } }; + ackCheck = async (req, res, next) => { + try { + await ackCheckBodyValidation.validateAsync(req.body); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } + + try { + const { checkId } = req.params; + const { ack } = req.body; + const { teamId } = req.user; + + const updatedCheck = await this.db.ackCheck(checkId, teamId, ack); + + return res.success({ + msg: this.stringService.checkUpdateStatus, + data: updatedCheck, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "ackCheck")); + } + }; + + ackAllChecks = async (req, res, next) => { + try { + await ackAllChecksParamValidation.validateAsync(req.params); + await ackAllChecksBodyValidation.validateAsync(req.body); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } + + try { + const { monitorId, path } = req.params; + const { ack } = req.body; + const { teamId } = req.user; + + const updatedChecks = await this.db.ackAllChecks(monitorId, teamId, ack, path); + + return res.success({ + msg: this.stringService.checkUpdateStatus, + data: updatedChecks, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "ackAllChecks")); + } + }; + deleteChecks = async (req, res, next) => { try { await deleteChecksParamValidation.validateAsync(req.params); diff --git a/server/controllers/inviteController.js b/server/controllers/inviteController.js index 3b2212769..3799c58b9 100755 --- a/server/controllers/inviteController.js +++ b/server/controllers/inviteController.js @@ -77,11 +77,16 @@ class InviteController { name: firstname, link: `${clientHost}/register/${inviteToken.token}`, }); - await this.emailService.sendEmail( + const result = await this.emailService.sendEmail( req.body.email, "Welcome to Uptime Monitor", html ); + if (!result) { + return res.error({ + msg: "Failed to send invite e-mail... Please verify your settings.", + }); + } } catch (error) { logger.warn({ message: error.message, diff --git a/server/controllers/logController.js b/server/controllers/logController.js new file mode 100644 index 000000000..5381fd3fa --- /dev/null +++ b/server/controllers/logController.js @@ -0,0 +1,23 @@ +import { handleError } from "./controllerUtils.js"; + +const SERVICE_NAME = "JobQueueController"; + +class LogController { + constructor(logger) { + this.logger = logger; + } + + getLogs = async (req, res, next) => { + try { + const logs = await this.logger.getLogs(); + res.success({ + msg: "Logs fetched successfully", + data: logs, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getLogs")); + return; + } + }; +} +export default LogController; diff --git a/server/controllers/monitorController.js b/server/controllers/monitorController.js index e1d087c74..d32ef463e 100755 --- a/server/controllers/monitorController.js +++ b/server/controllers/monitorController.js @@ -681,7 +681,7 @@ class MonitorController { explain, }); return res.success({ - msg: "OK", // TODO + msg: "OK", data: result, }); } catch (error) { @@ -698,6 +698,42 @@ class MonitorController { next(handleError(error, SERVICE_NAME, "seedDb")); } }; + + exportMonitorsToCSV = async (req, res, next) => { + try { + const { teamId } = req.user; + + const monitors = await this.db.getMonitorsByTeamId({ teamId }); + if (!monitors || monitors.length === 0) { + return res.success({ + msg: this.stringService.noMonitorsFound, + data: null, + }); + } + const csvData = monitors?.filteredMonitors?.map((monitor) => ({ + name: monitor.name, + description: monitor.description, + type: monitor.type, + url: monitor.url, + interval: monitor.interval, + port: monitor.port, + ignoreTlsErrors: monitor.ignoreTlsErrors, + isActive: monitor.isActive, + })); + + const csv = pkg.unparse(csvData); + + return res.file({ + data: csv, + headers: { + "Content-Type": "text/csv", + "Content-Disposition": "attachment; filename=monitors.csv", + }, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "exportMonitorsToCSV")); + } + }; } export default MonitorController; diff --git a/server/controllers/queueController.js b/server/controllers/queueController.js index b1a7badee..968106a5a 100755 --- a/server/controllers/queueController.js +++ b/server/controllers/queueController.js @@ -25,7 +25,7 @@ class JobQueueController { try { const jobs = await this.jobQueue.getJobs(); return res.success({ - msg: this.stringService.queueGetMetrics, + msg: this.stringService.queueGetJobs, data: jobs, }); } catch (error) { @@ -34,6 +34,20 @@ class JobQueueController { } }; + getAllMetrics = async (req, res, next) => { + try { + const jobs = await this.jobQueue.getJobs(); + const metrics = await this.jobQueue.getMetrics(); + return res.success({ + msg: this.stringService.queueGetAllMetrics, + data: { jobs, metrics }, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getAllMetrics")); + return; + } + }; + addJob = async (req, res, next) => { try { await this.jobQueue.addJob(Math.random().toString(36).substring(7)); diff --git a/server/db/models/Check.js b/server/db/models/Check.js index 39c5281d2..8bb6cd028 100755 --- a/server/db/models/Check.js +++ b/server/db/models/Check.js @@ -64,6 +64,23 @@ const BaseCheckSchema = mongoose.Schema({ default: Date.now, expires: 60 * 60 * 24 * 30, // 30 days }, + /** + * Acknowledgment of the check. + * + * @type {Boolean} + */ + ack: { + type: Boolean, + default: false, + }, + /** + * Resolution date of the check (when the check was resolved). + * + * @type {Date} + */ + ackAt: { + type: Date, + }, }); /** diff --git a/server/db/models/Monitor.js b/server/db/models/Monitor.js index 38aeff0c3..c71ea5f18 100755 --- a/server/db/models/Monitor.js +++ b/server/db/models/Monitor.js @@ -147,6 +147,7 @@ MonitorSchema.pre("findOneAndDelete", async function (next) { MonitorSchema.pre("deleteMany", async function (next) { const filter = this.getFilter(); const monitors = await this.model.find(filter).select(["_id", "type"]).lean(); + for (const monitor of monitors) { if (monitor.type === "pagespeed") { await PageSpeedCheck.deleteMany({ monitorId: monitor._id }); @@ -155,6 +156,10 @@ MonitorSchema.pre("deleteMany", async function (next) { } else { await Check.deleteMany({ monitorId: monitor._id }); } + await StatusPage.updateMany( + { monitors: monitor._id }, + { $pull: { monitors: monitor._id } } + ); await MonitorStats.deleteMany({ monitorId: monitor._id.toString() }); } next(); diff --git a/server/db/models/NetworkCheck.js b/server/db/models/NetworkCheck.js new file mode 100644 index 000000000..eca69eef9 --- /dev/null +++ b/server/db/models/NetworkCheck.js @@ -0,0 +1,46 @@ +import mongoose from "mongoose"; +import { BaseCheckSchema } from "./Check.js"; + +const networkInterfaceSchema = mongoose.Schema({ + name: { type: String, required: true }, + bytes_sent: { type: Number, default: 0 }, + bytes_recv: { type: Number, default: 0 }, + packets_sent: { type: Number, default: 0 }, + packets_recv: { type: Number, default: 0 }, + err_in: { type: Number, default: 0 }, + err_out: { type: Number, default: 0 }, + drop_in: { type: Number, default: 0 }, + drop_out: { type: Number, default: 0 }, + fifo_in: { type: Number, default: 0 }, + fifo_out: { type: Number, default: 0 }, +}); + +const captureSchema = mongoose.Schema({ + version: { type: String, default: "" }, + mode: { type: String, default: "" }, +}); + +const NetworkCheckSchema = mongoose.Schema( + { + ...BaseCheckSchema.obj, + data: { + type: [networkInterfaceSchema], + default: () => [], + }, + capture: { + type: captureSchema, + default: () => ({}), + }, + errors: { + type: mongoose.Schema.Types.Mixed, + default: null, + }, + }, + { timestamps: true } +); + +NetworkCheckSchema.index({ createdAt: 1 }); +NetworkCheckSchema.index({ monitorId: 1, createdAt: 1 }); +NetworkCheckSchema.index({ monitorId: 1, createdAt: -1 }); + +export default mongoose.model("NetworkCheck", NetworkCheckSchema); diff --git a/server/db/models/StatusPage.js b/server/db/models/StatusPage.js index e4c81707f..92cbab1cd 100755 --- a/server/db/models/StatusPage.js +++ b/server/db/models/StatusPage.js @@ -70,6 +70,10 @@ const StatusPageSchema = mongoose.Schema( type: Boolean, default: true, }, + showAdminLoginLink: { + type: Boolean, + default: false, + }, }, { timestamps: true } ); diff --git a/server/db/models/User.js b/server/db/models/User.js index 74ee1166e..f180cfb86 100755 --- a/server/db/models/User.js +++ b/server/db/models/User.js @@ -1,6 +1,9 @@ import mongoose from "mongoose"; import bcrypt from "bcryptjs"; import logger from "../../utils/logger.js"; +import Monitor from "./Monitor.js"; +import Team from "./Team.js"; +import Notification from "./Notification.js"; const UserSchema = mongoose.Schema( { @@ -74,6 +77,25 @@ UserSchema.pre("findOneAndUpdate", function (next) { next(); }); +UserSchema.pre("findOneAndDelete", async function (next) { + try { + const userToDelete = await this.model.findOne(this.getFilter()); + if (!userToDelete) return next(); + if (userToDelete.role.includes("superadmin")) { + await Team.deleteOne({ _id: userToDelete.teamId }); + await Monitor.deleteMany({ userId: userToDelete._id }); + await this.model.deleteMany({ + teamId: userToDelete.teamId, + _id: { $ne: userToDelete._id }, + }); + await Notification.deleteMany({ teamId: userToDelete.teamId }); + } + next(); + } catch (error) { + next(error); + } +}); + UserSchema.methods.comparePassword = async function (submittedPassword) { const res = await bcrypt.compare(submittedPassword, this.password); return res; diff --git a/server/db/mongo/modules/checkModule.js b/server/db/mongo/modules/checkModule.js index f409fd3d2..b7d9f4fb2 100755 --- a/server/db/mongo/modules/checkModule.js +++ b/server/db/mongo/modules/checkModule.js @@ -63,18 +63,26 @@ const getChecksByMonitor = async ({ sortOrder, dateRange, filter, + ack, page, rowsPerPage, status, }) => { try { - status = typeof status !== "undefined" ? false : undefined; + status = status === "true" ? true : status === "false" ? false : undefined; page = parseInt(page); rowsPerPage = parseInt(rowsPerPage); + + const ackStage = + ack === "true" + ? { ack: true } + : { $or: [{ ack: false }, { ack: { $exists: false } }] }; + // Match const matchStage = { - monitorId: ObjectId.createFromHexString(monitorId), + monitorId: new ObjectId(monitorId), ...(typeof status !== "undefined" && { status }), + ...(typeof ack !== "undefined" && ackStage), ...(dateRangeLookup[dateRange] && { createdAt: { $gte: dateRangeLookup[dateRange], @@ -153,6 +161,7 @@ const getChecksByTeam = async ({ sortOrder, dateRange, filter, + ack, page, rowsPerPage, teamId, @@ -160,9 +169,16 @@ const getChecksByTeam = async ({ try { page = parseInt(page); rowsPerPage = parseInt(rowsPerPage); + + const ackStage = + ack === "true" + ? { ack: true } + : { $or: [{ ack: false }, { ack: { $exists: false } }] }; + const matchStage = { - teamId: ObjectId.createFromHexString(teamId), + teamId: new ObjectId(teamId), status: false, + ...(typeof ack !== "undefined" && ackStage), ...(dateRangeLookup[dateRange] && { createdAt: { $gte: dateRangeLookup[dateRange], @@ -236,6 +252,58 @@ const getChecksByTeam = async ({ } }; +/** + * Update the acknowledgment status of a check + * @async + * @param {string} checkId - The ID of the check to update + * @param {string} teamId - The ID of the team + * @param {boolean} ack - The acknowledgment status to set + * @returns {Promise} + * @throws {Error} + */ +const ackCheck = async (checkId, teamId, ack) => { + try { + const updatedCheck = await Check.findOneAndUpdate( + { _id: checkId, teamId: teamId }, + { $set: { ack, ackAt: new Date() } }, + { new: true } + ); + + if (!updatedCheck) { + throw new Error("Check not found"); + } + + return updatedCheck; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "ackCheck"; + throw error; + } +}; + +/** + * Update the acknowledgment status of all checks for a monitor or team + * @async + * @param {string} id - The monitor ID or team ID + * @param {boolean} ack - The acknowledgment status to set + * @param {string} path - The path type ('monitor' or 'team') + * @returns {Promise} + * @throws {Error} + */ +const ackAllChecks = async (monitorId, teamId, ack, path) => { + try { + const updatedChecks = await Check.updateMany( + path === "monitor" ? { monitorId } : { teamId }, + { $set: { ack, ackAt: new Date() } } + ); + return updatedChecks.modifiedCount; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "ackAllChecks"; + throw error; + } +}; + /** * Delete all checks for a monitor * @async @@ -317,6 +385,8 @@ export { createChecks, getChecksByMonitor, getChecksByTeam, + ackCheck, + ackAllChecks, deleteChecks, deleteChecksByTeamId, updateChecksTTL, diff --git a/server/db/mongo/modules/diagnosticModule.js b/server/db/mongo/modules/diagnosticModule.js index 7a51ad193..e59830518 100755 --- a/server/db/mongo/modules/diagnosticModule.js +++ b/server/db/mongo/modules/diagnosticModule.js @@ -19,7 +19,7 @@ const getMonitorsByTeamIdExecutionStats = async (req) => { order = "asc"; } // Build match stage - const matchStage = { teamId: ObjectId.createFromHexString(req.params.teamId) }; + const matchStage = { teamId: new ObjectId(req.params.teamId) }; if (type !== undefined) { matchStage.type = Array.isArray(type) ? { $in: type } : type; } diff --git a/server/db/mongo/modules/monitorModule.js b/server/db/mongo/modules/monitorModule.js index 32ed8b146..bc31915c2 100755 --- a/server/db/mongo/modules/monitorModule.js +++ b/server/db/mongo/modules/monitorModule.js @@ -523,7 +523,7 @@ const getMonitorsByTeamId = async ({ order = "asc"; } // Build match stage - const matchStage = { teamId: ObjectId.createFromHexString(teamId) }; + const matchStage = { teamId: new ObjectId(teamId) }; if (type !== undefined) { matchStage.type = Array.isArray(type) ? { $in: type } : type; } @@ -563,8 +563,7 @@ const getMonitorsByTeamId = async ({ const getMonitorsAndSummaryByTeamId = async ({ type, explain, teamId }) => { try { - const parsedTeamId = ObjectId.createFromHexString(teamId); - const matchStage = { teamId: parsedTeamId }; + const matchStage = { teamId: new ObjectId(teamId) }; if (type !== undefined) { matchStage.type = Array.isArray(type) ? { $in: type } : type; } @@ -606,9 +605,8 @@ const getMonitorsWithChecksByTeamId = async ({ field = "name"; order = "asc"; } - const parsedTeamId = ObjectId.createFromHexString(teamId); // Build match stage - const matchStage = { teamId: parsedTeamId }; + const matchStage = { teamId: new ObjectId(teamId) }; if (type !== undefined) { matchStage.type = Array.isArray(type) ? { $in: type } : type; } diff --git a/server/db/mongo/modules/monitorModuleQueries.js b/server/db/mongo/modules/monitorModuleQueries.js index 35ddb8478..ab45eebb6 100755 --- a/server/db/mongo/modules/monitorModuleQueries.js +++ b/server/db/mongo/modules/monitorModuleQueries.js @@ -4,7 +4,7 @@ const buildUptimeDetailsPipeline = (monitorId, dates, dateString) => { return [ { $match: { - monitorId: ObjectId.createFromHexString(monitorId), + monitorId: new ObjectId(monitorId), createdAt: { $gte: dates.start, $lte: dates.end }, }, }, @@ -407,6 +407,16 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { ], }, }, + { $unwind: "$checks" }, + { $sort: { "checks._id": 1 } }, + { + $group: { + _id: "$_id", + checks: { $push: "$checks" }, + aggregateData: { $first: "$aggregateData" }, + upChecks: { $first: "$upChecks" }, + }, + }, { $project: { aggregateData: { @@ -415,12 +425,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { upChecks: { $arrayElemAt: ["$upChecks", 0], }, - checks: { - $sortArray: { - input: "$checks", - sortBy: { _id: 1 }, - }, - }, + checks: 1, }, }, ]; @@ -734,7 +739,7 @@ const buildGetMonitorsByTeamIdPipeline = (req) => { order = "asc"; } // Build the match stage - const matchStage = { teamId: ObjectId.createFromHexString(req.params.teamId) }; + const matchStage = { teamId: new ObjectId(req.params.teamId) }; if (type !== undefined) { matchStage.type = Array.isArray(type) ? { $in: type } : type; } diff --git a/server/db/mongo/modules/networkCheckModule.js b/server/db/mongo/modules/networkCheckModule.js new file mode 100644 index 000000000..53c55c7e3 --- /dev/null +++ b/server/db/mongo/modules/networkCheckModule.js @@ -0,0 +1,46 @@ +import NetworkCheck from "../../models/NetworkCheck.js"; + +const SERVICE_NAME = "networkCheckModule"; + +/** + * Creates and saves a new network check document to the database. + * @async + * @param {object} networkCheckData - The data for the new network check. This should conform to the NetworkCheckSchema. + * @param {string} networkCheckData.monitorId - The ID of the monitor associated with this check. + * @returns {Promise} A promise that resolves to the newly created network check document. + * @throws {Error} Throws an error if the database operation fails. + */ +const createNetworkCheck = async (networkCheckData) => { + try { + const networkCheck = await new NetworkCheck(networkCheckData); + await networkCheck.save(); + return networkCheck; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createNetworkCheck"; + throw error; + } +}; + +/** + * Retrieves a list of network checks for a specific monitor, sorted by most recent. + * @async + * @param {string} monitorId - The ID of the monitor to retrieve checks for. + * @param {number} [limit=100] - The maximum number of checks to return. Defaults to 100. + * @returns {Promise>} A promise that resolves to an array of network check documents. + * @throws {Error} Throws an error if the database operation fails. + */ +const getNetworkChecksByMonitorId = async (monitorId, limit = 100) => { + try { + const networkChecks = await NetworkCheck.find({ monitorId }) + .sort({ createdAt: -1 }) + .limit(limit); + return networkChecks; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getNetworkChecksByMonitorId"; + throw error; + } +}; + +export { createNetworkCheck, getNetworkChecksByMonitorId }; diff --git a/server/db/mongo/modules/statusPageModule.js b/server/db/mongo/modules/statusPageModule.js index d6331fcf8..8a682d567 100755 --- a/server/db/mongo/modules/statusPageModule.js +++ b/server/db/mongo/modules/statusPageModule.js @@ -90,6 +90,45 @@ const getStatusPage = async (url) => { const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); try { + const preliminaryStatusPage = await StatusPage.findOne({ url }); + if (!preliminaryStatusPage) { + const error = new Error(stringService.statusPageNotFound); + error.status = 404; + throw error; + } + + if (!preliminaryStatusPage.monitors || preliminaryStatusPage.monitors.length === 0) { + const { + _id, + color, + companyName, + isPublished, + logo, + originalMonitors, + showCharts, + showUptimePercentage, + timezone, + showAdminLoginLink, + url, + } = preliminaryStatusPage; + return { + statusPage: { + _id, + color, + companyName, + isPublished, + logo, + originalMonitors, + showCharts, + showUptimePercentage, + timezone, + showAdminLoginLink, + url, + }, + monitors: [], + }; + } + const statusPageQuery = await StatusPage.aggregate([ { $match: { url: url } }, { @@ -134,6 +173,9 @@ const getStatusPage = async (url) => { }, }, }, + { $match: { "monitors.orderIndex": { $ne: -1 } } }, + { $sort: { "monitors.orderIndex": 1 } }, + { $group: { _id: "$_id", @@ -153,22 +195,10 @@ const getStatusPage = async (url) => { showCharts: 1, showUptimePercentage: 1, timezone: 1, + showAdminLoginLink: 1, url: 1, }, - monitors: { - $filter: { - input: { - $sortArray: { - input: "$monitors", - sortBy: { orderIndex: 1 }, - }, - }, - as: "monitor", - cond: { - $ne: ["$$monitor.orderIndex", -1], - }, - }, - }, + monitors: 1, }, }, ]); diff --git a/server/index.js b/server/index.js index 99be6de7a..4b74ef46c 100755 --- a/server/index.js +++ b/server/index.js @@ -36,6 +36,9 @@ import StatusPageController from "./controllers/statusPageController.js"; import QueueRoutes from "./routes/queueRoute.js"; import QueueController from "./controllers/queueController.js"; +import LogRoutes from "./routes/logRoutes.js"; +import LogController from "./controllers/logController.js"; + import NotificationRoutes from "./routes/notificationRoute.js"; import NotificationController from "./controllers/notificationController.js"; @@ -294,6 +297,8 @@ const startApp = async () => { ServiceRegistry.get(StringService.SERVICE_NAME) ); + const logController = new LogController(logger); + const statusPageController = new StatusPageController( ServiceRegistry.get(MongoDB.SERVICE_NAME), ServiceRegistry.get(StringService.SERVICE_NAME) @@ -320,6 +325,7 @@ const startApp = async () => { maintenanceWindowController ); const queueRoutes = new QueueRoutes(queueController); + const logRoutes = new LogRoutes(logController); const statusPageRoutes = new StatusPageRoutes(statusPageController); const notificationRoutes = new NotificationRoutes(notificationController); @@ -372,6 +378,7 @@ const startApp = async () => { app.use("/api/v1/checks", verifyJWT, checkRoutes.getRouter()); app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRoutes.getRouter()); app.use("/api/v1/queue", verifyJWT, queueRoutes.getRouter()); + app.use("/api/v1/logs", verifyJWT, logRoutes.getRouter()); app.use("/api/v1/status-page", statusPageRoutes.getRouter()); app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter()); app.use("/api/v1/diagnostic", verifyJWT, diagnosticRoutes.getRouter()); diff --git a/server/locales/en.json b/server/locales/en.json index a693c6eef..74296d0a5 100755 --- a/server/locales/en.json +++ b/server/locales/en.json @@ -123,6 +123,7 @@ "monitorCertificate": "Got monitor certificate successfully", "monitorDemoAdded": "Successfully added demo monitors", "queueGetMetrics": "Got metrics successfully", + "queueGetJobs": "Got jobs successfully", "queueAddJob": "Job added successfully", "queueObliterate": "Queue obliterated", "jobQueueDeleteJobSuccess": "Job removed successfully", diff --git a/server/middleware/responseHandler.js b/server/middleware/responseHandler.js index a58e2e845..5169e7160 100755 --- a/server/middleware/responseHandler.js +++ b/server/middleware/responseHandler.js @@ -16,7 +16,12 @@ const responseHandler = (req, res, next) => { * @param {*} [options.data=null] - Response data payload * @returns {Object} Express response object */ - res.success = ({ status = 200, msg = "OK", data = null }) => { + res.success = ({ status = 200, msg = "OK", data = null, headers = {} }) => { + // Set custom headers if provided + Object.entries(headers).forEach(([key, value]) => { + res.set(key, value); + }); + return res.status(status).json({ success: true, msg: msg, @@ -40,6 +45,21 @@ const responseHandler = (req, res, next) => { data, }); }; + + /** + * Sends a raw file response (for CSV, PDF, etc.) + * @param {Object} options + * @param {Buffer|string} options.data - The file content + * @param {Object} options.headers - Headers to set (e.g. Content-Type, Content-Disposition) + * @param {number} [options.status=200] - HTTP status code + */ + res.file = ({ data, headers = {}, status = 200 }) => { + Object.entries(headers).forEach(([key, value]) => { + res.setHeader(key, value); + }); + return res.status(status).send(data); + }; + next(); }; diff --git a/server/middleware/verifyOwnership.js b/server/middleware/verifyOwnership.js index ca2476f54..2040d54ee 100755 --- a/server/middleware/verifyOwnership.js +++ b/server/middleware/verifyOwnership.js @@ -1,14 +1,20 @@ import logger from "../utils/logger.js"; import ServiceRegistry from "../service/serviceRegistry.js"; import StringService from "../service/stringService.js"; +import { ObjectId } from "mongodb"; + const SERVICE_NAME = "verifyOwnership"; const verifyOwnership = (Model, paramName) => { const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); return async (req, res, next) => { const userId = req.user._id; - const documentId = req.params[paramName]; + let documentId = req.params[paramName]; + try { + if (typeof documentId === "string") { + documentId = ObjectId.createFromHexString(documentId); + } const doc = await Model.findById(documentId); //If the document is not found, return a 404 error if (!doc) { @@ -35,7 +41,7 @@ const verifyOwnership = (Model, paramName) => { // If the userID does not match the document's userID, return a 403 error if (userId.toString() !== doc.userId.toString()) { - const error = new Error(stringService.verifyOwnerUnauthorized); + const error = new Error("Unauthorized"); error.status = 403; throw error; } diff --git a/server/middleware/verifyTeamAccess.js b/server/middleware/verifyTeamAccess.js new file mode 100644 index 000000000..60647bdc2 --- /dev/null +++ b/server/middleware/verifyTeamAccess.js @@ -0,0 +1,38 @@ +const SERVICE_NAME = "verifyTeamAccess"; + +const verifyTeamAccess = (Model, paramName) => { + return async (req, res, next) => { + try { + const documentId = req.params[paramName]; + const doc = await Model.findById(documentId); + + if (!doc) { + const error = new Error("Document not found"); + error.status = 404; + throw error; + } + + if (!req?.user?.teamId || !doc.teamId) { + const error = new Error("Missing team information"); + error.status = 400; + throw error; + } + + if (req.user.teamId.toString() === doc.teamId.toString()) { + next(); + return; + } + + const error = new Error("Unauthorized"); + error.status = 403; + throw error; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "verifyTeamAccess"; + next(error); + return; + } + }; +}; + +export { verifyTeamAccess }; diff --git a/server/routes/checkRoute.js b/server/routes/checkRoute.js index aedca991a..7f91dbe31 100755 --- a/server/routes/checkRoute.js +++ b/server/routes/checkRoute.js @@ -1,7 +1,9 @@ import { Router } from "express"; import { verifyOwnership } from "../middleware/verifyOwnership.js"; +import { verifyTeamAccess } from "../middleware/verifyTeamAccess.js"; import { isAllowed } from "../middleware/isAllowed.js"; import Monitor from "../db/models/Monitor.js"; +import Check from "../db/models/Check.js"; class CheckRoutes { constructor(checkController) { @@ -20,6 +22,14 @@ class CheckRoutes { this.router.get("/:monitorId", this.checkController.getChecksByMonitor); + this.router.put( + "/check/:checkId", + verifyTeamAccess(Check, "checkId"), + this.checkController.ackCheck + ); + + this.router.put("/:path/:monitorId?", this.checkController.ackAllChecks); + this.router.post( "/:monitorId", verifyOwnership(Monitor, "monitorId"), diff --git a/server/routes/logRoutes.js b/server/routes/logRoutes.js new file mode 100755 index 000000000..867826dde --- /dev/null +++ b/server/routes/logRoutes.js @@ -0,0 +1,18 @@ +import { Router } from "express"; +import { isAllowed } from "../middleware/isAllowed.js"; +class LogRoutes { + constructor(logController) { + this.router = Router(); + this.logController = logController; + this.initRoutes(); + } + initRoutes() { + this.router.get("/", isAllowed(["admin", "superadmin"]), this.logController.getLogs); + } + + getRouter() { + return this.router; + } +} + +export default LogRoutes; diff --git a/server/routes/maintenanceWindowRoute.js b/server/routes/maintenanceWindowRoute.js index 2665c2bb8..36f982e25 100755 --- a/server/routes/maintenanceWindowRoute.js +++ b/server/routes/maintenanceWindowRoute.js @@ -1,7 +1,8 @@ import { Router } from "express"; import { verifyOwnership } from "../middleware/verifyOwnership.js"; +import { verifyTeamAccess } from "../middleware/verifyTeamAccess.js"; import Monitor from "../db/models/Monitor.js"; - +import MaintenanceWindow from "../db/models/MaintenanceWindow.js"; class MaintenanceWindowRoutes { constructor(maintenanceWindowController) { this.router = Router(); @@ -24,9 +25,17 @@ class MaintenanceWindowRoutes { this.router.get("/:id", this.maintenanceWindowController.getMaintenanceWindowById); - this.router.put("/:id", this.maintenanceWindowController.editMaintenanceWindow); + this.router.put( + "/:id", + verifyTeamAccess(MaintenanceWindow, "id"), + this.maintenanceWindowController.editMaintenanceWindow + ); - this.router.delete("/:id", this.maintenanceWindowController.deleteMaintenanceWindow); + this.router.delete( + "/:id", + verifyTeamAccess(MaintenanceWindow, "id"), + this.maintenanceWindowController.deleteMaintenanceWindow + ); } getRouter() { diff --git a/server/routes/monitorRoute.js b/server/routes/monitorRoute.js index 0dabab407..168f8b88d 100755 --- a/server/routes/monitorRoute.js +++ b/server/routes/monitorRoute.js @@ -2,7 +2,9 @@ import { Router } from "express"; import { isAllowed } from "../middleware/isAllowed.js"; import multer from "multer"; import { fetchMonitorCertificate } from "../controllers/controllerUtils.js"; - +import Monitor from "../db/models/Monitor.js"; +import { verifyOwnership } from "../middleware/verifyOwnership.js"; +import { verifyTeamAccess } from "../middleware/verifyTeamAccess.js"; const upload = multer({ storage: multer.memoryStorage(), // Store file in memory as Buffer }); @@ -17,6 +19,11 @@ class MonitorRoutes { initRoutes() { this.router.get("/", this.monitorController.getAllMonitors); this.router.get("/uptime", this.monitorController.getAllMonitorsWithUptimeStats); + this.router.get( + "/export", + isAllowed(["admin", "superadmin"]), + this.monitorController.exportMonitorsToCSV + ); this.router.get("/stats/:monitorId", this.monitorController.getMonitorStatsById); this.router.get( "/hardware/details/:monitorId", @@ -57,6 +64,7 @@ class MonitorRoutes { this.router.delete( "/:monitorId", + verifyOwnership(Monitor, "monitorId"), isAllowed(["admin", "superadmin"]), this.monitorController.deleteMonitor ); @@ -69,6 +77,7 @@ class MonitorRoutes { this.router.put( "/:monitorId", + verifyTeamAccess(Monitor, "monitorId"), isAllowed(["admin", "superadmin"]), this.monitorController.editMonitor ); diff --git a/server/routes/notificationRoute.js b/server/routes/notificationRoute.js index 164c0afcc..09becd16c 100755 --- a/server/routes/notificationRoute.js +++ b/server/routes/notificationRoute.js @@ -1,6 +1,8 @@ import { Router } from "express"; import { verifyJWT } from "../middleware/verifyJWT.js"; - +import { verifyOwnership } from "../middleware/verifyOwnership.js"; +import { verifyTeamAccess } from "../middleware/verifyTeamAccess.js"; +import Notification from "../db/models/Notification.js"; class NotificationRoutes { constructor(notificationController) { this.router = Router(); @@ -18,10 +20,18 @@ class NotificationRoutes { this.router.get("/team", this.notificationController.getNotificationsByTeamId); - this.router.delete("/:id", this.notificationController.deleteNotification); + this.router.delete( + "/:id", + verifyOwnership(Notification, "id"), + this.notificationController.deleteNotification + ); this.router.get("/:id", this.notificationController.getNotificationById); - this.router.put("/:id", this.notificationController.editNotification); + this.router.put( + "/:id", + verifyTeamAccess(Notification, "id"), + this.notificationController.editNotification + ); } getRouter() { diff --git a/server/routes/queueRoute.js b/server/routes/queueRoute.js index dc9b40917..c40d21528 100755 --- a/server/routes/queueRoute.js +++ b/server/routes/queueRoute.js @@ -19,6 +19,12 @@ class QueueRoutes { this.queueController.getJobs ); + this.router.get( + "/all-metrics", + isAllowed(["admin", "superadmin"]), + this.queueController.getAllMetrics + ); + this.router.post( "/jobs", isAllowed(["admin", "superadmin"]), diff --git a/server/service/PulseQueue/PulseQueue.js b/server/service/PulseQueue/PulseQueue.js index 0452985a0..50a959559 100644 --- a/server/service/PulseQueue/PulseQueue.js +++ b/server/service/PulseQueue/PulseQueue.js @@ -57,6 +57,9 @@ class PulseQueue { job.unique({ "data.monitor._id": monitor._id }); job.attrs.jobId = monitorId.toString(); job.repeatEvery(`${intervalInSeconds} seconds`); + if (monitor.isActive === false) { + job.disable(); + } await job.save(); }; @@ -136,6 +139,8 @@ class PulseQueue { const jobs = await this.pulse.jobs(); const metrics = jobs.reduce( (acc, job) => { + acc.totalRuns += job.attrs.runCount || 0; + acc.totalFailures += job.attrs.failCount || 0; acc.jobs++; if (job.attrs.failCount > 0 && job.attrs.failedAt >= job.attrs.lastFinishedAt) { acc.failingJobs++; @@ -147,13 +152,22 @@ class PulseQueue { acc.jobsWithFailures.push({ monitorId: job.attrs.data.monitor._id, monitorUrl: job.attrs.data.monitor.url, + monitorType: job.attrs.data.monitor.type, + failedAt: job.attrs.failedAt, failCount: job.attrs.failCount, failReason: job.attrs.failReason, }); } return acc; }, - { jobs: 0, activeJobs: 0, failingJobs: 0, jobsWithFailures: [] } + { + jobs: 0, + activeJobs: 0, + failingJobs: 0, + jobsWithFailures: [], + totalRuns: 0, + totalFailures: 0, + } ); return metrics; }; @@ -164,10 +178,18 @@ class PulseQueue { return { monitorId: job.attrs.data.monitor._id, monitorUrl: job.attrs.data.monitor.url, + monitorType: job.attrs.data.monitor.type, + active: !job.attrs.disabled, lockedAt: job.attrs.lockedAt, runCount: job.attrs.runCount || 0, failCount: job.attrs.failCount || 0, failReason: job.attrs.failReason, + lastRunAt: job.attrs.lastRunAt, + lastFinishedAt: job.attrs.lastFinishedAt, + lastRunTook: job.attrs.lockedAt + ? null + : job.attrs.lastFinishedAt - job.attrs.lastRunAt, + lastFailedAt: job.attrs.failedAt, }; }); }; diff --git a/server/service/emailService.js b/server/service/emailService.js index 89bd4d50f..433fd0871 100755 --- a/server/service/emailService.js +++ b/server/service/emailService.js @@ -137,6 +137,17 @@ class EmailService { }; this.transporter = this.nodemailer.createTransport(emailConfig); + try { + await this.transporter.verify(); + } catch (error) { + this.logger.warn({ + message: "Email transporter verification failed", + service: SERVICE_NAME, + method: "verifyTransporter", + }); + return false; + } + try { const info = await this.transporter.sendMail({ to: to, diff --git a/server/service/stringService.js b/server/service/stringService.js index b02254255..6f9ad8cd8 100755 --- a/server/service/stringService.js +++ b/server/service/stringService.js @@ -276,6 +276,10 @@ class StringService { return this.translationService.getTranslation("queueGetMetrics"); } + get queueGetJobs() { + return this.translationService.getTranslation("queueGetJobs"); + } + get queueAddJob() { return this.translationService.getTranslation("queueAddJob"); } @@ -432,6 +436,18 @@ class StringService { get testEmailSubject() { return this.translationService.getTranslation("testEmailSubject"); } + + get verifyOwnerNotFound() { + return this.translationService.getTranslation("verifyOwnerNotFound"); + } + + get verifyOwnerUnauthorized() { + return this.translationService.getTranslation("verifyOwnerUnauthorized"); + } + + get dbUserNotFound() { + return this.translationService.getTranslation("dbUserNotFound"); + } } export default StringService; diff --git a/server/utils/logger.js b/server/utils/logger.js index 155adc957..49b875d96 100755 --- a/server/utils/logger.js +++ b/server/utils/logger.js @@ -4,6 +4,8 @@ dotenv.config(); class Logger { constructor() { + this.logCache = []; + this.maxCacheSize = 1000; const consoleFormat = format.printf( ({ level, message, service, method, details, timestamp, stack }) => { if (message instanceof Object) { @@ -72,11 +74,9 @@ class Logger { * @param {Object} config.details - Additional details. */ info(config) { - this.logger.info(config.message, { - service: config.service, - method: config.method, - details: config.details, - }); + const logEntry = this.buildLogEntry("info", config); + this.cacheLog(logEntry); + this.logger.info(logEntry); } /** @@ -88,11 +88,9 @@ class Logger { * @param {Object} config.details - Additional details. */ warn(config) { - this.logger.warn(config.message, { - service: config.service, - method: config.method, - details: config.details, - }); + const logEntry = this.buildLogEntry("warn", config); + this.cacheLog(logEntry); + this.logger.warn(logEntry); } /** @@ -104,12 +102,9 @@ class Logger { * @param {Object} config.details - Additional details. */ error(config) { - this.logger.error(config.message, { - service: config.service, - method: config.method, - details: config.details, - stack: config.stack, - }); + const logEntry = this.buildLogEntry("error", config); + this.cacheLog(logEntry); + this.logger.error(logEntry); } /** * Logs a debug message. @@ -120,12 +115,32 @@ class Logger { * @param {Object} config.details - Additional details. */ debug(config) { - this.logger.debug(config.message, { + const logEntry = this.buildLogEntry("debug", config); + this.cacheLog(logEntry); + this.logger.debug(logEntry); + } + + cacheLog(entry) { + this.logCache.push(entry); + if (this.logCache.length > this.maxCacheSize) { + this.logCache.shift(); + } + } + + getLogs() { + return this.logCache; + } + + buildLogEntry(level, config) { + return { + level, + message: config.message, service: config.service, method: config.method, details: config.details, stack: config.stack, - }); + timestamp: new Date().toISOString(), + }; } } diff --git a/server/validation/joi.js b/server/validation/joi.js index 448a844f1..d44bb14eb 100755 --- a/server/validation/joi.js +++ b/server/validation/joi.js @@ -289,6 +289,19 @@ const createCheckBodyValidation = joi.object({ message: joi.string().required(), }); +const ackCheckBodyValidation = joi.object({ + ack: joi.boolean(), +}); + +const ackAllChecksParamValidation = joi.object({ + monitorId: joi.string().optional(), + path: joi.string().valid("monitor", "team").required(), +}); + +const ackAllChecksBodyValidation = joi.object({ + ack: joi.boolean(), +}); + const getChecksParamValidation = joi.object({ monitorId: joi.string().required(), }); @@ -299,6 +312,7 @@ const getChecksQueryValidation = joi.object({ limit: joi.number(), dateRange: joi.string().valid("recent", "hour", "day", "week", "month", "all"), filter: joi.string().valid("all", "down", "resolve"), + ack: joi.boolean(), page: joi.number(), rowsPerPage: joi.number(), status: joi.boolean(), @@ -311,9 +325,9 @@ const getTeamChecksQueryValidation = joi.object({ limit: joi.number(), dateRange: joi.string().valid("hour", "day", "week", "month", "all"), filter: joi.string().valid("all", "down", "resolve"), + ack: joi.boolean(), page: joi.number(), rowsPerPage: joi.number(), - status: joi.boolean(), }); const deleteChecksParamValidation = joi.object({ @@ -463,6 +477,7 @@ const createStatusPageBodyValidation = joi.object({ isPublished: joi.boolean(), showCharts: joi.boolean().optional(), showUptimePercentage: joi.boolean(), + showAdminLoginLink: joi.boolean().optional(), }); const imageValidation = joi @@ -653,6 +668,9 @@ export { getChecksQueryValidation, getTeamChecksParamValidation, getTeamChecksQueryValidation, + ackCheckBodyValidation, + ackAllChecksParamValidation, + ackAllChecksBodyValidation, deleteChecksParamValidation, deleteChecksByTeamIdParamValidation, updateChecksTTLBodyValidation,