From 41170f16bc2f1a84f33d15ae26505d06a134d58d Mon Sep 17 00:00:00 2001 From: "Gorkem Cetin (BWL)" <167266851+gorkem-bwl@users.noreply.github.com> Date: Sat, 1 Mar 2025 15:49:01 -0500 Subject: [PATCH 01/41] Update CONTRIBUTING.md --- CONTRIBUTING.md | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d96c99d0b..8db035b8a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -77,7 +77,7 @@ Once it's filed: This section guides you through submitting an enhancement suggestion for the application, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community understand your enhancements and find related suggestions. - Make sure that you are using the latest version. -- Make sure you run the code locally. The Checkmate [documentation](https://bluewavelabs.gitbook.io/checkmate) covers it. +- Make sure you run the code locally. The Checkmate [documentation](https://docs.checkmate.so) covers it. - Find out if the functionality is already covered, maybe by an individual configuration. - Perform a [search](/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. - Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library. @@ -92,14 +92,7 @@ Enhancement suggestions are tracked as [GitHub issues](/issues). - You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. - **Explain why this enhancement would be useful** to most CONTRIBUTING.md users. You may also want to point out the other projects that solved it better and which could serve as inspiration. -We have a Figma file that includes: - -- All the dashboard elements and components -- The design guideline for the app - -You can see it [here](https://www.figma.com/design/RPSfaw66HjzSwzntKcgDUV/Uptime-Genie?node-id=0-1&t=WqOFv9jqNTFGItpL-1). Since it is read-only, we encourage you to copy to your own Figma page, then work on it. - -[This document](https://docs.google.com/document/d/1Gy3LiimGUNoSiWAMbwyK3SeMADcCMjCLu6cQYoawtSE/edit#heading=h.1lj2lgut6m7h) outlines the process every developer should follow for managing the issues lifecycle. Also make sure you read the [document about how to make a good pull request](/PULLREQUESTS.md). +Also make sure you read the [document about how to make a good pull request](/PULLREQUESTS.md). ## Attribution This guide is based on the **contributing.md**. [Make your own](https://contributing.md/)! From 8c64221f88654509e74d4baa8831f3f4cedf432d Mon Sep 17 00:00:00 2001 From: Cahit Atilgan Date: Tue, 13 May 2025 16:21:30 +0200 Subject: [PATCH 02/41] fix: configuring gmail smtp relay without any authentication --- server/service/emailService.js | 12 +++++++++++- server/service/settingsService.js | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/server/service/emailService.js b/server/service/emailService.js index 94d19f0f4..03e9cdebe 100755 --- a/server/service/emailService.js +++ b/server/service/emailService.js @@ -96,9 +96,10 @@ class EmailService { systemEmailUser, systemEmailAddress, systemEmailPassword, + systemEmailConnectionHost } = await this.settingsService.getDBSettings(); - const emailConfig = { + const baseEmailConfig = { host: systemEmailHost, port: systemEmailPort, secure: true, @@ -109,6 +110,15 @@ class EmailService { connectionTimeout: 5000, }; + const emailConfig = systemEmailPort === "465" ? baseEmailConfig : { + ...baseEmailConfig, + name: systemEmailConnectionHost, + secure: false, + pool: true, + tls: { rejectUnauthorized: false }, + auth: null + }; + this.transporter = this.nodemailer.createTransport(emailConfig); const buildHtml = async (template, context) => { diff --git a/server/service/settingsService.js b/server/service/settingsService.js index 290e645e9..bde361f80 100755 --- a/server/service/settingsService.js +++ b/server/service/settingsService.js @@ -7,6 +7,7 @@ const envConfig = { systemEmailUser: process.env.SYSTEM_EMAIL_USER, systemEmailAddress: process.env.SYSTEM_EMAIL_ADDRESS, systemEmailPassword: process.env.SYSTEM_EMAIL_PASSWORD, + systemEmailConnectionHost: process.env.SYSTEM_EMAIL_CONNECTION_HOST, jwtSecret: process.env.JWT_SECRET, jwtTTL: process.env.TOKEN_TTL, clientHost: process.env.CLIENT_HOST, From 12925c515aceffce215258605e5ca52303b1cbde Mon Sep 17 00:00:00 2001 From: Cahit Atilgan Date: Tue, 13 May 2025 18:07:53 +0200 Subject: [PATCH 03/41] fix: configuring gmail smtp relay without any authentication --- server/service/emailService.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/server/service/emailService.js b/server/service/emailService.js index 03e9cdebe..bdd727813 100755 --- a/server/service/emailService.js +++ b/server/service/emailService.js @@ -110,14 +110,19 @@ class EmailService { connectionTimeout: 5000, }; - const emailConfig = systemEmailPort === "465" ? baseEmailConfig : { + const isSmtps = Number(systemEmailPort) === 465; + + const emailConfig = !isSmtps ? { ...baseEmailConfig, name: systemEmailConnectionHost, secure: false, pool: true, tls: { rejectUnauthorized: false }, - auth: null - }; + } : baseEmailConfig; + + if (!isSmtps) { + delete emailConfig.auth; + } this.transporter = this.nodemailer.createTransport(emailConfig); From 73ddd54d20e93dba22fd4050ff40ce23d2ee336c Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 13 May 2025 12:58:01 -0700 Subject: [PATCH 04/41] add migration for settings doc --- client/src/Pages/Settings/SettingsAbout.jsx | 2 +- server/db/models/AppSettings.js | 4 ++++ server/service/settingsService.js | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/client/src/Pages/Settings/SettingsAbout.jsx b/client/src/Pages/Settings/SettingsAbout.jsx index b2b5df308..5551d8e7c 100644 --- a/client/src/Pages/Settings/SettingsAbout.jsx +++ b/client/src/Pages/Settings/SettingsAbout.jsx @@ -15,7 +15,7 @@ const SettingsAbout = () => { {t("settingsAbout")} - Checkmate {2.0} + Checkmate {2.1} {t("settingsDevelopedBy")} diff --git a/server/db/models/AppSettings.js b/server/db/models/AppSettings.js index 06ba8c1df..22e586131 100755 --- a/server/db/models/AppSettings.js +++ b/server/db/models/AppSettings.js @@ -34,6 +34,10 @@ const AppSettingsSchema = mongoose.Schema( unique: true, default: true, }, + version: { + type: Number, + default: 1, + }, }, { timestamps: true, diff --git a/server/service/settingsService.js b/server/service/settingsService.js index 290e645e9..bc314abff 100755 --- a/server/service/settingsService.js +++ b/server/service/settingsService.js @@ -59,6 +59,9 @@ class SettingsService { } async getDBSettings() { + // Remove any old settings + await this.appSettings.deleteMany({ version: { $exists: false } }); + let settings = await this.appSettings .findOne({ singleton: true }) .select("-__v -_id -createdAt -updatedAt -singleton") From a114bd82fe0b329ed14baec57495410a0cdc6c88 Mon Sep 17 00:00:00 2001 From: "Gorkem Cetin (BWL)" <167266851+gorkem-bwl@users.noreply.github.com> Date: Tue, 13 May 2025 16:50:50 -0400 Subject: [PATCH 05/41] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0830cc3b7..b1330a17a 100644 --- a/README.md +++ b/README.md @@ -30,11 +30,11 @@ You can see the latest build of [Checkmate](https://checkmate-demo.bluewavelabs. ## 🔗 User's guide -Usage instructions can be found [here](https://docs.checkmate.so/). It's still WIP and some of the information there might be outdated as we continuously add features weekly. Rest assured, we are doing our best! :) +Usage instructions can be found [here](https://docs.checkmate.so/checkmate-2.1). It's still WIP and some of the information there might be outdated as we continuously add features weekly. Rest assured, we are doing our best! :) ## 🛠️ Installation -See installation instructions in [Checkmate documentation portal](https://docs.checkmate.so/quickstart). Alternatively, you can also use [Coolify](https://coolify.io/) or [Elestio](https://elest.io/open-source/checkmate) for a one-click Docker deployment. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions. +See installation instructions in [Checkmate documentation portal](https://docs.checkmate.so/checkmate-2.1/users-guide/quickstart). Alternatively, you can also use [Coolify](https://coolify.io/) or [Elestio](https://elest.io/open-source/checkmate) for a one-click Docker deployment. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions. ## 🏁 Translations @@ -118,7 +118,7 @@ Here's how you can contribute: 0. Star this repo :) 1. Check [Contributor's guideline](https://github.com/bluewave-labs/Checkmate/blob/develop/CONTRIBUTING.md). First timers are encouraged to check `good-first-issue` tag. -2. Check [project structure](https://docs.checkmate.so/developers-guide/general-project-structure) and [high level overview](https://bluewavelabs.gitbook.io/checkmate/developers-guide/high-level-overview). +2. Check [project structure](https://docs.checkmate.so/checkmate-2.1/developers-guide/general-project-structure) and [high level overview](https://bluewavelabs.gitbook.io/checkmate/developers-guide/high-level-overview). 3. Read a detailed structure of [Checkmate](https://deepwiki.com/bluewave-labs/Checkmate) if you would like to deep dive into the architecture. 4. Open an issue if you believe you've encountered a bug. 5. Check for good-first-issue's if you are a newcomer. From bf0d073aa8e5c283db0ccbf3d2a435b5b1986378 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 13 May 2025 13:51:11 -0700 Subject: [PATCH 06/41] remove version from returned settings --- server/controllers/settingsController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/controllers/settingsController.js b/server/controllers/settingsController.js index b557e8dd6..21f0050ba 100755 --- a/server/controllers/settingsController.js +++ b/server/controllers/settingsController.js @@ -13,6 +13,7 @@ class SettingsController { getAppSettings = async (req, res, next) => { const dbSettings = await this.settingsService.getDBSettings(); const sanitizedSettings = { ...dbSettings }; + delete sanitizedSettings.version; const returnSettings = { pagespeedKeySet: false, @@ -27,7 +28,6 @@ class SettingsController { returnSettings.emailPasswordSet = true; delete sanitizedSettings.systemEmailPassword; } - returnSettings.settings = sanitizedSettings; return res.success({ msg: this.stringService.getAppSettings, From 6dc5894b9a421fd106569d666a575ff5201f6778 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 13 May 2025 15:28:47 -0700 Subject: [PATCH 07/41] build multi-arch images for dist --- docker/dist/build_images.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docker/dist/build_images.sh b/docker/dist/build_images.sh index e0d83a5d8..ccfa9cc85 100755 --- a/docker/dist/build_images.sh +++ b/docker/dist/build_images.sh @@ -11,11 +11,12 @@ declare -A services=( ["bluewaveuptime/uptime_server"]="./docker/dist/server.Dockerfile" ) -# Loop through each service and build the corresponding image for service in "${!services[@]}"; do - docker build -f "${services[$service]}" -t "$service" . - - # Check if the build succeeded + docker buildx build \ + --platform linux/amd64,linux/arm64 \ + -f "${services[$service]}" \ + -t "$service" \ + if [ $? -ne 0 ]; then echo "Error building $service image. Exiting..." exit 1 From 077fae2147adde973322ff5f135b70e80c7fc9c4 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 13 May 2025 15:32:28 -0700 Subject: [PATCH 08/41] update workflow --- .github/workflows/distribution-deploy.yml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/distribution-deploy.yml b/.github/workflows/distribution-deploy.yml index b96a0f3d7..55d7c5e4c 100644 --- a/.github/workflows/distribution-deploy.yml +++ b/.github/workflows/distribution-deploy.yml @@ -11,6 +11,9 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Log in to GitHub Container Registry uses: docker/login-action@v3 with: @@ -20,7 +23,8 @@ jobs: - name: Build Client Docker image run: | - docker build \ + docker buildx build \ + --platform linux/amd64,linux/arm64 \ -t ghcr.io/bluewave-labs/checkmate:frontend-dist \ -f ./docker/dist/client.Dockerfile \ --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \ @@ -36,6 +40,9 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Log in to GitHub Container Registry uses: docker/login-action@v3 with: @@ -45,7 +52,8 @@ jobs: - name: Build Server Docker image run: | - docker build \ + docker buildx build \ + --platform linux/amd64,linux/arm64 \ -t ghcr.io/bluewave-labs/checkmate:backend-dist \ -f ./docker/dist/server.Dockerfile \ --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \ @@ -56,7 +64,8 @@ jobs: - name: Build Mongo Docker image run: | - docker build \ + docker buildx build \ + --platform linux/amd64,linux/arm64 \ -t ghcr.io/bluewave-labs/checkmate:mongo-dist \ -f ./docker/dist/mongoDB.Dockerfile \ --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \ @@ -67,7 +76,8 @@ jobs: - name: Build Redis Docker image run: | - docker build \ + docker buildx build \ + --platform linux/amd64,linux/arm64 \ -t ghcr.io/bluewave-labs/checkmate:redis-dist \ -f ./docker/dist/redis.Dockerfile \ --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \ From be20fb0dbb7b68a8cf1debb36f89bce4dbfcdb09 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Wed, 14 May 2025 16:08:18 +0200 Subject: [PATCH 09/41] fix: TypeError in hardware notifications for null disk values --- server/service/notificationService.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/server/service/notificationService.js b/server/service/notificationService.js index 624056694..4b1ff6160 100755 --- a/server/service/notificationService.js +++ b/server/service/notificationService.js @@ -257,9 +257,7 @@ class NotificationService { const alerts = { cpu: cpuThreshold !== -1 && cpuUsage > cpuThreshold ? true : false, memory: memoryThreshold !== -1 && memoryUsage > memoryThreshold ? true : false, - disk: disk.some((d) => diskThreshold !== -1 && d.usage_percent > diskThreshold) - ? true - : false, + disk: disk?.some(d => diskThreshold !== -1 && d.usage_percent > diskThreshold) ?? false, }; const notifications = await this.db.getNotificationsByMonitorId( From f6e460d97a67cf8a0b3828f29059dce3c46a6b15 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Wed, 14 May 2025 19:37:47 +0200 Subject: [PATCH 10/41] fix: typeof number check and more safety --- server/service/notificationService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/service/notificationService.js b/server/service/notificationService.js index 4b1ff6160..e4f343c80 100755 --- a/server/service/notificationService.js +++ b/server/service/notificationService.js @@ -257,7 +257,7 @@ class NotificationService { const alerts = { cpu: cpuThreshold !== -1 && cpuUsage > cpuThreshold ? true : false, memory: memoryThreshold !== -1 && memoryUsage > memoryThreshold ? true : false, - disk: disk?.some(d => diskThreshold !== -1 && d.usage_percent > diskThreshold) ?? false, + disk: disk?.some(d => diskThreshold !== -1 && typeof d?.usage_percent === "number" && d?.usage_percent > diskThreshold) ?? false, }; const notifications = await this.db.getNotificationsByMonitorId( From a0f41b03baccffc5758c9db810ba684626e1ccd1 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 14 May 2025 12:00:54 -0700 Subject: [PATCH 11/41] add build platform target --- docker/dist/client.Dockerfile | 4 ++-- docker/dist/mongoDB.Dockerfile | 2 +- docker/dist/redis.Dockerfile | 2 +- docker/dist/server.Dockerfile | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docker/dist/client.Dockerfile b/docker/dist/client.Dockerfile index 6491f4bbb..80819434f 100755 --- a/docker/dist/client.Dockerfile +++ b/docker/dist/client.Dockerfile @@ -1,4 +1,4 @@ -FROM node:20-alpine AS build +FROM --platform=$BUILDPLATFORM node:20-alpine AS build ENV NODE_OPTIONS="--max-old-space-size=4096" @@ -21,7 +21,7 @@ COPY ./client . RUN npm run build -FROM nginx:1.27.1-alpine +FROM --platform=$BUILDPLATFORM nginx:1.27.1-alpine COPY ./docker/dist/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf COPY --from=build /app/dist /usr/share/nginx/html diff --git a/docker/dist/mongoDB.Dockerfile b/docker/dist/mongoDB.Dockerfile index 969a320c2..b44cfa837 100755 --- a/docker/dist/mongoDB.Dockerfile +++ b/docker/dist/mongoDB.Dockerfile @@ -1,3 +1,3 @@ -FROM mongo +FROM --platform=$BUILDPLATFORM mongo EXPOSE 27017 CMD ["mongod"] diff --git a/docker/dist/redis.Dockerfile b/docker/dist/redis.Dockerfile index af68ec61e..144efa66b 100755 --- a/docker/dist/redis.Dockerfile +++ b/docker/dist/redis.Dockerfile @@ -1,2 +1,2 @@ -FROM redis +FROM --platform=$BUILDPLATFORM redis EXPOSE 6379 \ No newline at end of file diff --git a/docker/dist/server.Dockerfile b/docker/dist/server.Dockerfile index d9d99a09d..c2c017477 100755 --- a/docker/dist/server.Dockerfile +++ b/docker/dist/server.Dockerfile @@ -1,4 +1,4 @@ -FROM node:20-alpine +FROM --platform=$BUILDPLATFORM node:20-alpine WORKDIR /app From 8a0725276bcbb1de88060d10dd1a300b70745be3 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 14 May 2025 12:10:16 -0700 Subject: [PATCH 12/41] remove buildx --- .github/workflows/distribution-deploy.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/distribution-deploy.yml b/.github/workflows/distribution-deploy.yml index 55d7c5e4c..69f3afd73 100644 --- a/.github/workflows/distribution-deploy.yml +++ b/.github/workflows/distribution-deploy.yml @@ -23,8 +23,7 @@ jobs: - name: Build Client Docker image run: | - docker buildx build \ - --platform linux/amd64,linux/arm64 \ + docker build \ -t ghcr.io/bluewave-labs/checkmate:frontend-dist \ -f ./docker/dist/client.Dockerfile \ --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \ @@ -52,8 +51,7 @@ jobs: - name: Build Server Docker image run: | - docker buildx build \ - --platform linux/amd64,linux/arm64 \ + docker build \ -t ghcr.io/bluewave-labs/checkmate:backend-dist \ -f ./docker/dist/server.Dockerfile \ --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \ @@ -64,8 +62,7 @@ jobs: - name: Build Mongo Docker image run: | - docker buildx build \ - --platform linux/amd64,linux/arm64 \ + docker build \ -t ghcr.io/bluewave-labs/checkmate:mongo-dist \ -f ./docker/dist/mongoDB.Dockerfile \ --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \ @@ -76,8 +73,7 @@ jobs: - name: Build Redis Docker image run: | - docker buildx build \ - --platform linux/amd64,linux/arm64 \ + docker build \ -t ghcr.io/bluewave-labs/checkmate:redis-dist \ -f ./docker/dist/redis.Dockerfile \ --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \ From 8585500e7c1155208b769983b9707e2c2ffc17f0 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 14 May 2025 12:16:09 -0700 Subject: [PATCH 13/41] remove build platform target --- docker/dist/client.Dockerfile | 4 ++-- docker/dist/mongoDB.Dockerfile | 2 +- docker/dist/redis.Dockerfile | 2 +- docker/dist/server.Dockerfile | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docker/dist/client.Dockerfile b/docker/dist/client.Dockerfile index 80819434f..6491f4bbb 100755 --- a/docker/dist/client.Dockerfile +++ b/docker/dist/client.Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM node:20-alpine AS build +FROM node:20-alpine AS build ENV NODE_OPTIONS="--max-old-space-size=4096" @@ -21,7 +21,7 @@ COPY ./client . RUN npm run build -FROM --platform=$BUILDPLATFORM nginx:1.27.1-alpine +FROM nginx:1.27.1-alpine COPY ./docker/dist/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf COPY --from=build /app/dist /usr/share/nginx/html diff --git a/docker/dist/mongoDB.Dockerfile b/docker/dist/mongoDB.Dockerfile index b44cfa837..969a320c2 100755 --- a/docker/dist/mongoDB.Dockerfile +++ b/docker/dist/mongoDB.Dockerfile @@ -1,3 +1,3 @@ -FROM --platform=$BUILDPLATFORM mongo +FROM mongo EXPOSE 27017 CMD ["mongod"] diff --git a/docker/dist/redis.Dockerfile b/docker/dist/redis.Dockerfile index 144efa66b..af68ec61e 100755 --- a/docker/dist/redis.Dockerfile +++ b/docker/dist/redis.Dockerfile @@ -1,2 +1,2 @@ -FROM --platform=$BUILDPLATFORM redis +FROM redis EXPOSE 6379 \ No newline at end of file diff --git a/docker/dist/server.Dockerfile b/docker/dist/server.Dockerfile index c2c017477..d9d99a09d 100755 --- a/docker/dist/server.Dockerfile +++ b/docker/dist/server.Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM node:20-alpine +FROM node:20-alpine WORKDIR /app From 3c48eacc9894a73cf80abcf692e5cd7a74de191a Mon Sep 17 00:00:00 2001 From: Br0wnHammer Date: Thu, 15 May 2025 02:38:26 +0530 Subject: [PATCH 14/41] Fix: Port Validation Uptime --- client/src/Validation/validation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/Validation/validation.js b/client/src/Validation/validation.js index be6cb19f7..096db1a1a 100644 --- a/client/src/Validation/validation.js +++ b/client/src/Validation/validation.js @@ -171,7 +171,7 @@ const monitorValidation = joi.object({ .max(65535) .when("type", { is: "port", - then: joi.required().messages({ + then: joi.number().messages({ "number.base": "Port must be a number.", "number.min": "Port must be at least 1.", "number.max": "Port must be at most 65535.", From edad05b63b6b1b19744ff184bb61b6f55a2fb04e Mon Sep 17 00:00:00 2001 From: Anurag123Chauhan Date: Thu, 15 May 2025 10:48:04 +0530 Subject: [PATCH 15/41] Changed error msg of invalid of Docker container ID --- client/src/Validation/validation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/Validation/validation.js b/client/src/Validation/validation.js index 096db1a1a..8ed37492b 100644 --- a/client/src/Validation/validation.js +++ b/client/src/Validation/validation.js @@ -161,7 +161,7 @@ const monitorValidation = joi.object({ .messages({ "string.empty": "This field is required.", "string.uri": "The URL you provided is not valid.", - "string.invalidUrl": "Please enter a valid URL with optional port", + "string.invalidUrl": "Please enter a valid 64-character Docker container ID", "string.pattern.base": "Please enter a valid container ID.", }), port: joi From 0f91d3b6e101d7b1a9940dc6a2f7dc96fe227dbf Mon Sep 17 00:00:00 2001 From: Anurag123Chauhan Date: Thu, 15 May 2025 11:03:05 +0530 Subject: [PATCH 16/41] fix: separate Docker ID and URL validation error messages --- client/src/Validation/validation.js | 109 +++++++++++++++------------- 1 file changed, 57 insertions(+), 52 deletions(-) diff --git a/client/src/Validation/validation.js b/client/src/Validation/validation.js index 8ed37492b..847226a1f 100644 --- a/client/src/Validation/validation.js +++ b/client/src/Validation/validation.js @@ -111,59 +111,64 @@ const monitorValidation = joi.object({ .trim() .regex(/^[a-z0-9]{64}$/), otherwise: joi - .string() - .trim() - .custom((value, helpers) => { - // Regex from https://gist.github.com/dperini/729294 - var urlRegex = new RegExp( - "^" + - // protocol identifier (optional) - // short syntax // still required - "(?:(?:https?|ftp):\\/\\/)?" + - // user:pass BasicAuth (optional) - "(?:" + - // IP address dotted notation octets - // excludes loopback network 0.0.0.0 - // excludes reserved space >= 224.0.0.0 - // excludes network & broadcast addresses - // (first & last IP address of each class) - "(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" + - "(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" + - "(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))" + - "|" + - // host & domain names, may end with dot - // can be replaced by a shortest alternative - // (?![-_])(?:[-\\w\\u00a1-\\uffff]{0,63}[^-_]\\.)+ - "(?:" + - "(?:" + - "[a-z0-9\\u00a1-\\uffff]" + - "[a-z0-9\\u00a1-\\uffff_-]{0,62}" + - ")?" + - "[a-z0-9\\u00a1-\\uffff]\\." + - ")+" + - // TLD identifier name, may end with dot - "(?:[a-z\\u00a1-\\uffff]{2,}\\.?)" + - ")" + - // port number (optional) - "(?::\\d{2,5})?" + - // resource path (optional) - "(?:[/?#]\\S*)?" + - "$", - "i" - ); - if (!urlRegex.test(value)) { - return helpers.error("string.invalidUrl"); - } + .string() + .trim() + .custom((value, helpers) => { + // Get the current validation context + const { type } = helpers.state.ancestors[0]; + + // Regex from https://gist.github.com/dperini/729294 + var urlRegex = new RegExp( + "^" + + // protocol identifier (optional) + // short syntax // still required + "(?:(?:https?|ftp):\\/\\/)?" + + // user:pass BasicAuth (optional) + "(?:" + + // IP address dotted notation octets + // excludes loopback network 0.0.0.0 + // excludes reserved space >= 224.0.0.0 + // excludes network & broadcast addresses + // (first & last IP address of each class) + "(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" + + "(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" + + "(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))" + + "|" + + // host & domain names, may end with dot + // can be replaced by a shortest alternative + // (?![-_])(?:[-\\w\\u00a1-\\uffff]{0,63}[^-_]\\.)+ + "(?:" + + "(?:" + + "[a-z0-9\\u00a1-\\uffff]" + + "[a-z0-9\\u00a1-\\uffff_-]{0,62}" + + ")?" + + "[a-z0-9\\u00a1-\\uffff]\\." + + ")+" + + // TLD identifier name, may end with dot + "(?:[a-z\\u00a1-\\uffff]{2,}\\.?)" + + ")" + + // port number (optional) + "(?::\\d{2,5})?" + + // resource path (optional) + "(?:[/?#]\\S*)?" + + "$", + "i" + ); + if (!urlRegex.test(value)) { + // Use different error key based on monitor type + return helpers.error(type === "docker" ? "string.invalidDockerID" : "string.invalidUrl"); + } - return value; - }), - }) - .messages({ - "string.empty": "This field is required.", - "string.uri": "The URL you provided is not valid.", - "string.invalidUrl": "Please enter a valid 64-character Docker container ID", - "string.pattern.base": "Please enter a valid container ID.", - }), + return value; + }), + }) + .messages({ + "string.empty": "This field is required.", + "string.uri": "The URL you provided is not valid.", + "string.invalidUrl": "Please enter a valid URL.", + "string.invalidDockerID": "Please enter a valid 64-character Docker container ID.", + "string.pattern.base": "Please enter a valid container ID.", + }), port: joi .number() .integer() From 18b439bad6eb585327f0ec4a5586507f64d97ea8 Mon Sep 17 00:00:00 2001 From: Anurag123Chauhan Date: Thu, 15 May 2025 11:05:45 +0530 Subject: [PATCH 17/41] added msg --- client/src/Validation/validation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/Validation/validation.js b/client/src/Validation/validation.js index 847226a1f..7cba122e0 100644 --- a/client/src/Validation/validation.js +++ b/client/src/Validation/validation.js @@ -165,7 +165,7 @@ const monitorValidation = joi.object({ .messages({ "string.empty": "This field is required.", "string.uri": "The URL you provided is not valid.", - "string.invalidUrl": "Please enter a valid URL.", + "string.invalidUrl": "Please enter a valid URL with optional port", "string.invalidDockerID": "Please enter a valid 64-character Docker container ID.", "string.pattern.base": "Please enter a valid container ID.", }), From 9da9ef693960b9b329e0be4e2ecddce0fb62677a Mon Sep 17 00:00:00 2001 From: Anurag123Chauhan Date: Thu, 15 May 2025 11:16:02 +0530 Subject: [PATCH 18/41] recommended changes --- client/src/Validation/validation.js | 661 ++++++++++++++-------------- 1 file changed, 330 insertions(+), 331 deletions(-) diff --git a/client/src/Validation/validation.js b/client/src/Validation/validation.js index 7cba122e0..8a14738e2 100644 --- a/client/src/Validation/validation.js +++ b/client/src/Validation/validation.js @@ -4,119 +4,120 @@ import dayjs from "dayjs"; const THRESHOLD_COMMON_BASE_MSG = "Threshold must be a number."; const nameSchema = joi - .string() - .max(50) - .trim() - .pattern(/^[\p{L}\p{M}''\- ]+$/u) - .messages({ - "string.empty": "Name is required", - "string.max": "Name must be less than 50 characters", - "string.pattern.base": - "Name must contain only letters, spaces, apostrophes, or hyphens", - }); + .string() + .max(50) + .trim() + .pattern(/^[\p{L}\p{M}''\- ]+$/u) + .messages({ + "string.empty": "Name is required", + "string.max": "Name must be less than 50 characters", + "string.pattern.base": + "Name must contain only letters, spaces, apostrophes, or hyphens", + }); const lastnameSchema = joi - .string() - .max(50) - .trim() - .pattern(/^[\p{L}\p{M}''\- ]+$/u) - .messages({ - "string.empty": "Surname is required", - "string.max": "Surname must be less than 50 characters", - "string.pattern.base": "Surname must contain only letters, spaces, apostrophes, or hyphens" - }); + .string() + .max(50) + .trim() + .pattern(/^[\p{L}\p{M}''\- ]+$/u) + .messages({ + "string.empty": "Surname is required", + "string.max": "Surname must be less than 50 characters", + "string.pattern.base": "Surname must contain only letters, spaces, apostrophes, or hyphens" + }); const passwordSchema = joi - .string() - .trim() - .min(8) - .custom((value, helpers) => { - if (!/[A-Z]/.test(value)) { - return helpers.error("uppercase"); - } - return value; - }) - .custom((value, helpers) => { - if (!/[a-z]/.test(value)) { - return helpers.error("lowercase"); - } - return value; - }) - .custom((value, helpers) => { - if (!/\d/.test(value)) { - return helpers.error("number"); - } - return value; - }) - .custom((value, helpers) => { - if (!/[!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/]/.test(value)) { - return helpers.error("special"); - } - return value; - }) - .messages({ - "string.empty": "Password is required", - "string.min": "Password must be at least 8 characters long", - uppercase: "Password must contain at least one uppercase letter", - lowercase: "Password must contain at least one lowercase letter", - number: "Password must contain at least one number", - special: "Password must contain at least one special character", - }); + .string() + .trim() + .min(8) + .custom((value, helpers) => { + if (!/[A-Z]/.test(value)) { + return helpers.error("uppercase"); + } + return value; + }) + .custom((value, helpers) => { + if (!/[a-z]/.test(value)) { + return helpers.error("lowercase"); + } + return value; + }) + .custom((value, helpers) => { + if (!/\d/.test(value)) { + return helpers.error("number"); + } + return value; + }) + .custom((value, helpers) => { + if (!/[!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/]/.test(value)) { + return helpers.error("special"); + } + return value; + }) + .messages({ + "string.empty": "Password is required", + "string.min": "Password must be at least 8 characters long", + uppercase: "Password must contain at least one uppercase letter", + lowercase: "Password must contain at least one lowercase letter", + number: "Password must contain at least one number", + special: "Password must contain at least one special character", + }); const credentials = joi.object({ - firstName: nameSchema, - lastName: lastnameSchema, - email: joi - .string() - .trim() - .email({ tlds: { allow: false } }) - .custom((value, helpers) => { - const lowercasedValue = value.toLowerCase(); - if (value !== lowercasedValue) { - return helpers.message("Email must be in lowercase"); - } - return lowercasedValue; - }) - .messages({ - "string.empty": "authRegisterEmailRequired", - "string.email": "authRegisterEmailInvalid", - }), - password: passwordSchema, - newPassword: passwordSchema, - confirm: joi - .string() - .trim() - .custom((value, helpers) => { - const { password } = helpers.prefs.context; - if (value !== password) { - return helpers.error("different"); - } - return value; - }) - .messages({ - "string.empty": "This field can't be empty", - different: "Passwords do not match", - }), - role: joi.array(), - teamId: joi.string().allow("").optional(), - inviteToken: joi.string().allow(""), + firstName: nameSchema, + lastName: lastnameSchema, + email: joi + .string() + .trim() + .email({ tlds: { allow: false } }) + .custom((value, helpers) => { + const lowercasedValue = value.toLowerCase(); + if (value !== lowercasedValue) { + return helpers.message("Email must be in lowercase"); + } + return lowercasedValue; + }) + .messages({ + "string.empty": "authRegisterEmailRequired", + "string.email": "authRegisterEmailInvalid", + }), + password: passwordSchema, + newPassword: passwordSchema, + confirm: joi + .string() + .trim() + .custom((value, helpers) => { + const { password } = helpers.prefs.context; + if (value !== password) { + return helpers.error("different"); + } + return value; + }) + .messages({ + "string.empty": "This field can't be empty", + different: "Passwords do not match", + }), + role: joi.array(), + teamId: joi.string().allow("").optional(), + inviteToken: joi.string().allow(""), }); const monitorValidation = joi.object({ - url: joi - .when("type", { - is: "docker", - then: joi - .string() - .trim() - .regex(/^[a-z0-9]{64}$/), - otherwise: joi + url: joi + .when("type", { + is: "docker", + then: joi + .string() + .trim() + .regex(/^[a-z0-9]{64}$/) + .messages({ + "string.empty": "This field is required.", + "string.pattern.base": "Please enter a valid 64-character Docker container ID.", + }), + otherwise: joi .string() .trim() .custom((value, helpers) => { - // Get the current validation context - const { type } = helpers.state.ancestors[0]; - // Regex from https://gist.github.com/dperini/729294 var urlRegex = new RegExp( "^" + @@ -155,253 +156,251 @@ const monitorValidation = joi.object({ "i" ); if (!urlRegex.test(value)) { - // Use different error key based on monitor type - return helpers.error(type === "docker" ? "string.invalidDockerID" : "string.invalidUrl"); + return helpers.error("string.invalidUrl"); } return value; + }) + .messages({ + "string.empty": "This field is required.", + "string.uri": "The URL you provided is not valid.", + "string.invalidUrl": "Please enter a valid URL with optional port", }), + }), + port: joi + .number() + .integer() + .min(1) + .max(65535) + .when("type", { + is: "port", + then: joi.number().messages({ + "number.base": "Port must be a number.", + "number.min": "Port must be at least 1.", + "number.max": "Port must be at most 65535.", + "any.required": "Port is required for port monitors.", + }), + otherwise: joi.optional(), + }), + name: joi.string().trim().max(50).allow("").messages({ + "string.max": "This field should not exceed the 50 characters limit.", + }), + type: joi.string().trim().messages({ "string.empty": "This field is required." }), + ignoreTlsErrors: joi.boolean(), + interval: joi.number().messages({ + "number.base": "Frequency must be a number.", + "any.required": "Frequency is required.", + }), + expectedValue: joi.string().allow(""), + jsonPath: joi.string().allow(""), + matchMethod: joi.string(), +}); + +const imageValidation = joi.object({ + type: joi.string().valid("image/jpeg", "image/png").messages({ + "any.only": "Invalid file format.", + "string.empty": "File type required.", + }), + size: joi + .number() + .max(3 * 1024 * 1024) + .messages({ + "number.base": "File size must be a number.", + "number.max": "File size must be less than 3 MB.", + "number.empty": "File size required.", + }), +}); + +const logoImageValidation = joi + .object({ + src: joi.string(), + name: joi.string(), + type: joi + .string() + .valid("image/jpeg", "image/png") + .allow(null) // Allow null and empty string + .messages({ + "any.only": "Invalid file format.", + "string.empty": "File type required.", + }) + .optional(), + size: joi + .number() + .max(3000000) + .allow(null) // Allow null and empty string + .messages({ + "number.base": "File size must be a number.", + "number.max": "File size must be less than 3MB.", + "number.empty": "File size required.", + }) + .optional(), + }) + .allow(null) + .optional(); // Make entire object optional + +const statusPageValidation = joi.object({ + type: joi.string().valid("uptime", "distributed").required(), + isPublished: joi.bool(), + companyName: joi + .string() + .trim() + .messages({ "string.empty": "Company name is required." }), + url: joi + .string() + .pattern(/^[a-zA-Z0-9_-]+$/) // Only allow alphanumeric, underscore, and hyphen + .required() + .messages({ + "string.pattern.base": + "URL can only contain letters, numbers, underscores, and hyphens", + }), + timezone: joi.string().trim().messages({ "string.empty": "Timezone is required." }), + color: joi.string().trim().messages({ "string.empty": "Color is required." }), + theme: joi.string(), + monitors: joi.array().min(1).required().messages({ + "string.pattern.base": "Must be a valid monitor ID", + "array.base": "Monitors must be an array", + "array.min": "At least one monitor is required", + "array.empty": "At least one monitor is required", + "any.required": "At least one monitor is required", + }), + subMonitors: joi.array().optional(), + logo: logoImageValidation, + showUptimePercentage: joi.boolean(), + showCharts: joi.boolean(), +}); + +const settingsValidation = joi.object({ + checkTTL: joi.number().required().messages({ + "string.empty": "Please enter a value", + "number.base": "Please enter a valid number", + "any.required": "Please enter a value", + }), + pagespeedApiKey: joi.string().allow("").optional(), + language: joi.string().required(), + systemEmailHost: joi.string().allow(""), + systemEmailPort: joi.number().allow(null, ""), + systemEmailAddress: joi.string().allow(""), + systemEmailPassword: joi.string().allow(""), + systemEmailUser: joi.string().allow(""), +}); + +const dayjsValidator = (value, helpers) => { + if (!dayjs(value).isValid()) { + return helpers.error("any.invalid"); + } + return value; +}; + +const maintenanceWindowValidation = joi.object({ + repeat: joi.string(), + startDate: joi.custom(dayjsValidator, "Day.js date validation"), + startTime: joi.custom(dayjsValidator, "Day.js date validation"), + duration: joi.number().integer().min(0), + durationUnit: joi.string(), + name: joi.string(), + monitors: joi.array().min(1), +}); + +const advancedSettingsValidation = joi.object({ + apiBaseUrl: joi.string().uri({ allowRelative: true }).trim().messages({ + "string.empty": "API base url is required.", + "string.uri": "The URL you provided is not valid.", + }), + logLevel: joi.string().valid("debug", "none", "error", "warn").allow(""), + systemEmailHost: joi.string().allow(""), + systemEmailPort: joi.number().allow(null, ""), + systemEmailAddress: joi.string().allow(""), + systemEmailPassword: joi.string().allow(""), + jwtTTLNum: joi.number().messages({ + "number.base": "JWT TTL is required.", + }), + jwtTTLUnits: joi + .string() + .trim() + .custom((value, helpers) => { + if (!["days", "hours"].includes(value)) { + return helpers.message("JWT TTL unit is required."); + } + return value; + }), + dbType: joi.string().trim().messages({ + "string.empty": "DB type is required.", + }), + redisHost: joi.string().trim().messages({ + "string.empty": "Redis host is required.", + }), + redisPort: joi.number().allow(null, ""), + pagespeedApiKey: joi.string().allow(""), +}); + +const infrastructureMonitorValidation = joi.object({ + url: joi + .string() + .trim() + .custom((value, helpers) => { + const urlRegex = + /^(https?:\/\/)?(([0-9]{1,3}\.){3}[0-9]{1,3}|[\da-z\.-]+)(\.[a-z\.]{2,6})?(:(\d+))?([\/\w \.-]*)*\/?$/i; + + if (!urlRegex.test(value)) { + return helpers.error("string.invalidUrl"); + } + + return value; }) .messages({ "string.empty": "This field is required.", "string.uri": "The URL you provided is not valid.", "string.invalidUrl": "Please enter a valid URL with optional port", - "string.invalidDockerID": "Please enter a valid 64-character Docker container ID.", - "string.pattern.base": "Please enter a valid container ID.", }), - port: joi - .number() - .integer() - .min(1) - .max(65535) - .when("type", { - is: "port", - then: joi.number().messages({ - "number.base": "Port must be a number.", - "number.min": "Port must be at least 1.", - "number.max": "Port must be at most 65535.", - "any.required": "Port is required for port monitors.", - }), - otherwise: joi.optional(), - }), - name: joi.string().trim().max(50).allow("").messages({ - "string.max": "This field should not exceed the 50 characters limit.", - }), - type: joi.string().trim().messages({ "string.empty": "This field is required." }), - ignoreTlsErrors: joi.boolean(), - interval: joi.number().messages({ - "number.base": "Frequency must be a number.", - "any.required": "Frequency is required.", - }), - expectedValue: joi.string().allow(""), - jsonPath: joi.string().allow(""), - matchMethod: joi.string(), -}); - -const imageValidation = joi.object({ - type: joi.string().valid("image/jpeg", "image/png").messages({ - "any.only": "Invalid file format.", - "string.empty": "File type required.", - }), - size: joi - .number() - .max(3 * 1024 * 1024) - .messages({ - "number.base": "File size must be a number.", - "number.max": "File size must be less than 3 MB.", - "number.empty": "File size required.", - }), -}); - -const logoImageValidation = joi - .object({ - src: joi.string(), - name: joi.string(), - type: joi - .string() - .valid("image/jpeg", "image/png") - .allow(null) // Allow null and empty string - .messages({ - "any.only": "Invalid file format.", - "string.empty": "File type required.", - }) - .optional(), - size: joi - .number() - .max(3000000) - .allow(null) // Allow null and empty string - .messages({ - "number.base": "File size must be a number.", - "number.max": "File size must be less than 3MB.", - "number.empty": "File size required.", - }) - .optional(), - }) - .allow(null) - .optional(); // Make entire object optional - -const statusPageValidation = joi.object({ - type: joi.string().valid("uptime", "distributed").required(), - isPublished: joi.bool(), - companyName: joi - .string() - .trim() - .messages({ "string.empty": "Company name is required." }), - url: joi - .string() - .pattern(/^[a-zA-Z0-9_-]+$/) // Only allow alphanumeric, underscore, and hyphen - .required() - .messages({ - "string.pattern.base": - "URL can only contain letters, numbers, underscores, and hyphens", - }), - timezone: joi.string().trim().messages({ "string.empty": "Timezone is required." }), - color: joi.string().trim().messages({ "string.empty": "Color is required." }), - theme: joi.string(), - monitors: joi.array().min(1).required().messages({ - "string.pattern.base": "Must be a valid monitor ID", - "array.base": "Monitors must be an array", - "array.min": "At least one monitor is required", - "array.empty": "At least one monitor is required", - "any.required": "At least one monitor is required", - }), - subMonitors: joi.array().optional(), - logo: logoImageValidation, - showUptimePercentage: joi.boolean(), - showCharts: joi.boolean(), -}); -const settingsValidation = joi.object({ - checkTTL: joi.number().required().messages({ - "string.empty": "Please enter a value", - "number.base": "Please enter a valid number", - "any.required": "Please enter a value", - }), - pagespeedApiKey: joi.string().allow("").optional(), - language: joi.string().required(), - systemEmailHost: joi.string().allow(""), - systemEmailPort: joi.number().allow(null, ""), - systemEmailAddress: joi.string().allow(""), - systemEmailPassword: joi.string().allow(""), - systemEmailUser: joi.string().allow(""), -}); - -const dayjsValidator = (value, helpers) => { - if (!dayjs(value).isValid()) { - return helpers.error("any.invalid"); - } - return value; -}; - -const maintenanceWindowValidation = joi.object({ - repeat: joi.string(), - startDate: joi.custom(dayjsValidator, "Day.js date validation"), - startTime: joi.custom(dayjsValidator, "Day.js date validation"), - duration: joi.number().integer().min(0), - durationUnit: joi.string(), - name: joi.string(), - monitors: joi.array().min(1), -}); - -const advancedSettingsValidation = joi.object({ - apiBaseUrl: joi.string().uri({ allowRelative: true }).trim().messages({ - "string.empty": "API base url is required.", - "string.uri": "The URL you provided is not valid.", - }), - logLevel: joi.string().valid("debug", "none", "error", "warn").allow(""), - systemEmailHost: joi.string().allow(""), - systemEmailPort: joi.number().allow(null, ""), - systemEmailAddress: joi.string().allow(""), - systemEmailPassword: joi.string().allow(""), - jwtTTLNum: joi.number().messages({ - "number.base": "JWT TTL is required.", - }), - jwtTTLUnits: joi - .string() - .trim() - .custom((value, helpers) => { - if (!["days", "hours"].includes(value)) { - return helpers.message("JWT TTL unit is required."); - } - return value; - }), - dbType: joi.string().trim().messages({ - "string.empty": "DB type is required.", - }), - redisHost: joi.string().trim().messages({ - "string.empty": "Redis host is required.", - }), - redisPort: joi.number().allow(null, ""), - pagespeedApiKey: joi.string().allow(""), -}); - -const infrastructureMonitorValidation = joi.object({ - url: joi - .string() - .trim() - .custom((value, helpers) => { - const urlRegex = - /^(https?:\/\/)?(([0-9]{1,3}\.){3}[0-9]{1,3}|[\da-z\.-]+)(\.[a-z\.]{2,6})?(:(\d+))?([\/\w \.-]*)*\/?$/i; - - if (!urlRegex.test(value)) { - return helpers.error("string.invalidUrl"); - } - - return value; - }) - .messages({ - "string.empty": "This field is required.", - "string.uri": "The URL you provided is not valid.", - "string.invalidUrl": "Please enter a valid URL with optional port", - }), - name: joi.string().trim().max(50).allow("").messages({ - "string.max": "This field should not exceed the 50 characters limit.", - }), - secret: joi.string().trim().messages({ "string.empty": "This field is required." }), - usage_cpu: joi.number().messages({ - "number.base": THRESHOLD_COMMON_BASE_MSG, - }), - cpu: joi.boolean(), - memory: joi.boolean(), - disk: joi.boolean(), - temperature: joi.boolean(), - usage_memory: joi.number().messages({ - "number.base": THRESHOLD_COMMON_BASE_MSG, - }), - usage_disk: joi.number().messages({ - "number.base": THRESHOLD_COMMON_BASE_MSG, - }), - usage_temperature: joi.number().messages({ - "number.base": "Temperature must be a number.", - }), + name: joi.string().trim().max(50).allow("").messages({ + "string.max": "This field should not exceed the 50 characters limit.", + }), + secret: joi.string().trim().messages({ "string.empty": "This field is required." }), + usage_cpu: joi.number().messages({ + "number.base": THRESHOLD_COMMON_BASE_MSG, + }), + cpu: joi.boolean(), + memory: joi.boolean(), + disk: joi.boolean(), + temperature: joi.boolean(), + usage_memory: joi.number().messages({ + "number.base": THRESHOLD_COMMON_BASE_MSG, + }), + usage_disk: joi.number().messages({ + "number.base": THRESHOLD_COMMON_BASE_MSG, + }), + usage_temperature: joi.number().messages({ + "number.base": "Temperature must be a number.", + }), // usage_system: joi.number().messages({ // "number.base": "System load must be a number.", // }), // usage_swap: joi.number().messages({ // "number.base": "Swap used must be a number.", // }), - interval: joi.number().messages({ - "number.base": "Frequency must be a number.", - "any.required": "Frequency is required.", - }), - notifications: joi.array().items( - joi.object({ - type: joi.string().valid("email").required(), - address: joi - .string() - .email({ tlds: { allow: false } }) - .required(), - }) - ), + interval: joi.number().messages({ + "number.base": "Frequency must be a number.", + "any.required": "Frequency is required.", + }), + notifications: joi.array().items( + joi.object({ + type: joi.string().valid("email").required(), + address: joi + .string() + .email({ tlds: { allow: false } }) + .required(), + }) + ), }); export { - credentials, - imageValidation, - monitorValidation, - settingsValidation, - maintenanceWindowValidation, - advancedSettingsValidation, - infrastructureMonitorValidation, - statusPageValidation, - logoImageValidation, + credentials, + imageValidation, + monitorValidation, + settingsValidation, + maintenanceWindowValidation, + advancedSettingsValidation, + infrastructureMonitorValidation, + statusPageValidation, + logoImageValidation, }; From a3a381aad14a1e7da542e6e7064ad41ca830aea7 Mon Sep 17 00:00:00 2001 From: Cahit Atilgan Date: Thu, 15 May 2025 14:27:08 +0200 Subject: [PATCH 19/41] fix: removed env variable, configured a form field for editing email connect host --- client/src/Pages/Settings/SettingsEmail.jsx | 9 +++++++++ client/src/Validation/validation.js | 1 + client/src/locales/gb.json | 1 + server/db/models/AppSettings.js | 3 +++ server/service/settingsService.js | 1 - 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/client/src/Pages/Settings/SettingsEmail.jsx b/client/src/Pages/Settings/SettingsEmail.jsx index 694219614..46430e2e5 100644 --- a/client/src/Pages/Settings/SettingsEmail.jsx +++ b/client/src/Pages/Settings/SettingsEmail.jsx @@ -122,6 +122,15 @@ const SettingsEmail = ({ )} + + {t("systemEmailConnectionHost")} + + + + {isAdmin && ( + + )} + + + ); +}; + +MonitorDetailsControlHeader.propTypes = { + path: PropTypes.string, + isLoading: PropTypes.bool, + isAdmin: PropTypes.bool, + monitor: PropTypes.object, + triggerUpdate: PropTypes.func, +}; + +export default MonitorDetailsControlHeader; diff --git a/client/src/Components/MonitorDetailsControlHeader/skeleton.jsx b/client/src/Components/MonitorDetailsControlHeader/skeleton.jsx new file mode 100644 index 000000000..64dc0547f --- /dev/null +++ b/client/src/Components/MonitorDetailsControlHeader/skeleton.jsx @@ -0,0 +1,23 @@ +import { Stack, Skeleton } from "@mui/material"; + +const SkeletonLayout = () => { + return ( + + + + + ); +}; + +export default SkeletonLayout; diff --git a/client/src/Components/MonitorDetailsControlHeader/status.jsx b/client/src/Components/MonitorDetailsControlHeader/status.jsx new file mode 100644 index 000000000..910a22210 --- /dev/null +++ b/client/src/Components/MonitorDetailsControlHeader/status.jsx @@ -0,0 +1,44 @@ +// Components +import Stack from "@mui/material/Stack"; +import PulseDot from "../../Components/Animated/PulseDot"; +import Typography from "@mui/material/Typography"; +import Dot from "../../Components/Dot"; +// Utils +import { formatDurationRounded } from "../../Utils/timeUtils"; +import PropTypes from "prop-types"; +import { useTheme } from "@emotion/react"; +import useUtils from "../../Pages/Uptime/Monitors/Hooks/useUtils"; + +const Status = ({ monitor }) => { + const theme = useTheme(); + const { statusColor, determineState } = useUtils(); + + return ( + + {monitor?.name} + + + + {monitor?.url?.replace(/^https?:\/\//, "") || "..."} + + + + Checking every {formatDurationRounded(monitor?.interval)}. + + + + ); +}; + +Status.propTypes = { + monitor: PropTypes.object, +}; + +export default Status; From 54db96a03e9c29642b7b3cebcfde011a72a11fb5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 15 May 2025 10:49:38 -0700 Subject: [PATCH 26/41] add trigger to useFetchUptimeMonitorDetails --- client/src/Hooks/useFetchUptimeMonitorDetails.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/src/Hooks/useFetchUptimeMonitorDetails.js b/client/src/Hooks/useFetchUptimeMonitorDetails.js index c8cb81e65..3c0ac7ee6 100644 --- a/client/src/Hooks/useFetchUptimeMonitorDetails.js +++ b/client/src/Hooks/useFetchUptimeMonitorDetails.js @@ -3,13 +3,12 @@ import { networkService } from "../main"; import { useNavigate } from "react-router-dom"; import { createToast } from "../Utils/toastUtils"; -export const useFetchUptimeMonitorDetails = ({ monitorId, dateRange }) => { +export const useFetchUptimeMonitorDetails = ({ monitorId, dateRange, trigger }) => { const [networkError, setNetworkError] = useState(false); const [isLoading, setIsLoading] = useState(true); const [monitor, setMonitor] = useState(undefined); const [monitorStats, setMonitorStats] = useState(undefined); const navigate = useNavigate(); - useEffect(() => { const fetchMonitors = async () => { try { @@ -29,7 +28,7 @@ export const useFetchUptimeMonitorDetails = ({ monitorId, dateRange }) => { } }; fetchMonitors(); - }, [dateRange, monitorId, navigate]); + }, [dateRange, monitorId, navigate, trigger]); return [monitor, monitorStats, isLoading, networkError]; }; From f9fcd4ff3a037f163b342b0e42b26f23f670b178 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 15 May 2025 10:50:21 -0700 Subject: [PATCH 27/41] remove pause from uptime config page --- client/src/Pages/Uptime/Configure/index.jsx | 49 --------------------- 1 file changed, 49 deletions(-) diff --git a/client/src/Pages/Uptime/Configure/index.jsx b/client/src/Pages/Uptime/Configure/index.jsx index 5afbc4a27..d52780eea 100644 --- a/client/src/Pages/Uptime/Configure/index.jsx +++ b/client/src/Pages/Uptime/Configure/index.jsx @@ -158,23 +158,6 @@ const Configure = () => { } }; - const handlePause = async () => { - try { - const action = await dispatch(pauseUptimeMonitor({ monitorId })); - if (pauseUptimeMonitor.fulfilled.match(action)) { - const monitor = action.payload.data; - setMonitor(monitor); - const state = action?.payload?.data.isActive === false ? "paused" : "resumed"; - createToast({ body: `Monitor ${state} successfully.` }); - } else if (pauseUptimeMonitor.rejected.match(action)) { - throw new Error(action.error.message); - } - } catch (error) { - logger.error("Error pausing monitor: " + monitorId); - createToast({ body: "Failed to pause monitor" }); - } - }; - const handleSubmit = async (event) => { event.preventDefault(); const action = await dispatch(updateUptimeMonitor({ monitor: monitor })); @@ -329,38 +312,6 @@ const Configure = () => { ml: "auto", }} > -