From 2b4727db0d6bf17c152f0b39ae774996d590f7af Mon Sep 17 00:00:00 2001 From: Jeffer Marcelino Date: Sun, 29 Jun 2025 15:34:50 +0200 Subject: [PATCH 01/22] feat: add checkmate helm chart with basic validation --- charts/helm/checkmate/.helmignore | 17 ++++++++ charts/helm/checkmate/Chart.yaml | 6 +++ .../helm/checkmate/templates/_prechecks.yaml | 12 ++++++ .../templates/client-deployment.yaml | 22 ++++++++++ .../checkmate/templates/client-ingress.yaml | 20 +++++++++ .../checkmate/templates/client-service.yaml | 9 ++++ .../templates/mongodb-deployment.yaml | 27 ++++++++++++ .../helm/checkmate/templates/mongodb-pvc.yaml | 10 +++++ .../checkmate/templates/mongodb-service.yaml | 9 ++++ .../checkmate/templates/redis-deployment.yaml | 26 ++++++++++++ .../helm/checkmate/templates/redis-pvc.yaml | 10 +++++ .../checkmate/templates/redis-service.yaml | 9 ++++ charts/helm/checkmate/templates/secrets.yaml | 19 +++++++++ .../templates/server-deployment.yaml | 30 ++++++++++++++ .../checkmate/templates/server-ingress.yaml | 20 +++++++++ .../checkmate/templates/server-service.yaml | 9 ++++ charts/helm/checkmate/values.yaml | 41 +++++++++++++++++++ 17 files changed, 296 insertions(+) create mode 100644 charts/helm/checkmate/.helmignore create mode 100644 charts/helm/checkmate/Chart.yaml create mode 100644 charts/helm/checkmate/templates/_prechecks.yaml create mode 100644 charts/helm/checkmate/templates/client-deployment.yaml create mode 100644 charts/helm/checkmate/templates/client-ingress.yaml create mode 100644 charts/helm/checkmate/templates/client-service.yaml create mode 100644 charts/helm/checkmate/templates/mongodb-deployment.yaml create mode 100644 charts/helm/checkmate/templates/mongodb-pvc.yaml create mode 100644 charts/helm/checkmate/templates/mongodb-service.yaml create mode 100644 charts/helm/checkmate/templates/redis-deployment.yaml create mode 100644 charts/helm/checkmate/templates/redis-pvc.yaml create mode 100644 charts/helm/checkmate/templates/redis-service.yaml create mode 100644 charts/helm/checkmate/templates/secrets.yaml create mode 100644 charts/helm/checkmate/templates/server-deployment.yaml create mode 100644 charts/helm/checkmate/templates/server-ingress.yaml create mode 100644 charts/helm/checkmate/templates/server-service.yaml create mode 100644 charts/helm/checkmate/values.yaml diff --git a/charts/helm/checkmate/.helmignore b/charts/helm/checkmate/.helmignore new file mode 100644 index 000000000..21846e965 --- /dev/null +++ b/charts/helm/checkmate/.helmignore @@ -0,0 +1,17 @@ +.DS_Store +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +*.swp +*.bak +*.tmp +*.orig +*~ +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/helm/checkmate/Chart.yaml b/charts/helm/checkmate/Chart.yaml new file mode 100644 index 000000000..2ca35c1f6 --- /dev/null +++ b/charts/helm/checkmate/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: checkmate-chart +description: A Helm chart for Checkmate App +type: application +version: 0.1.0 +appVersion: "2.1.0" diff --git a/charts/helm/checkmate/templates/_prechecks.yaml b/charts/helm/checkmate/templates/_prechecks.yaml new file mode 100644 index 000000000..870ab5c77 --- /dev/null +++ b/charts/helm/checkmate/templates/_prechecks.yaml @@ -0,0 +1,12 @@ +{{- if eq .Values.client.ingress.host "change_me" }} + {{- fail "client.ingress.host must be overridden and not set to 'change_me'" }} +{{- end }} + +{{- if eq .Values.server.ingress.host "change_me" }} + {{- fail "server.ingress.host must be overridden and not set to 'change_me'" }} +{{- end }} + +{{- $protocol := .Values.server.protocol }} +{{- if not (or (eq $protocol "http") (eq $protocol "https")) }} + {{- fail "server.protocol must be either 'http' or 'https'" }} +{{- end }} diff --git a/charts/helm/checkmate/templates/client-deployment.yaml b/charts/helm/checkmate/templates/client-deployment.yaml new file mode 100644 index 000000000..6c1f70fd7 --- /dev/null +++ b/charts/helm/checkmate/templates/client-deployment.yaml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: client +spec: + replicas: 1 + selector: + matchLabels: + app: client + template: + metadata: + labels: + app: client + spec: + containers: + - name: client + image: {{ .Values.client.image }} + ports: + - containerPort: {{ .Values.client.port }} + env: + - name: UPTIME_APP_API_BASE_URL + value: "{{ .Values.server.protocol }}://{{ .Values.server.ingress.host }}/api/v1" diff --git a/charts/helm/checkmate/templates/client-ingress.yaml b/charts/helm/checkmate/templates/client-ingress.yaml new file mode 100644 index 000000000..da059a863 --- /dev/null +++ b/charts/helm/checkmate/templates/client-ingress.yaml @@ -0,0 +1,20 @@ +{{- if .Values.client.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: client-ingress + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / +spec: + rules: + - host: {{ .Values.client.ingress.host }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: client + port: + number: {{ .Values.client.port }} +{{- end }} diff --git a/charts/helm/checkmate/templates/client-service.yaml b/charts/helm/checkmate/templates/client-service.yaml new file mode 100644 index 000000000..afb88188c --- /dev/null +++ b/charts/helm/checkmate/templates/client-service.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Service +metadata: + name: client +spec: + selector: + app: client + ports: + - port: {{ .Values.client.port }} diff --git a/charts/helm/checkmate/templates/mongodb-deployment.yaml b/charts/helm/checkmate/templates/mongodb-deployment.yaml new file mode 100644 index 000000000..4c906fe09 --- /dev/null +++ b/charts/helm/checkmate/templates/mongodb-deployment.yaml @@ -0,0 +1,27 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mongodb +spec: + replicas: 1 + selector: + matchLabels: + app: mongodb + template: + metadata: + labels: + app: mongodb + spec: + containers: + - name: mongodb + image: {{ .Values.mongodb.image }} + ports: + - containerPort: {{ .Values.mongodb.port }} + command: ["mongod", "--quiet", "--bind_ip_all"] + volumeMounts: + - name: mongodb-data + mountPath: /data/db + volumes: + - name: mongodb-data + persistentVolumeClaim: + claimName: mongodb-pvc \ No newline at end of file diff --git a/charts/helm/checkmate/templates/mongodb-pvc.yaml b/charts/helm/checkmate/templates/mongodb-pvc.yaml new file mode 100644 index 000000000..435b662e6 --- /dev/null +++ b/charts/helm/checkmate/templates/mongodb-pvc.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: mongodb-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.persistence.mongodbSize }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/mongodb-service.yaml b/charts/helm/checkmate/templates/mongodb-service.yaml new file mode 100644 index 000000000..2911dc3eb --- /dev/null +++ b/charts/helm/checkmate/templates/mongodb-service.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Service +metadata: + name: mongodb +spec: + selector: + app: mongodb + ports: + - port: {{ .Values.mongodb.port }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/redis-deployment.yaml b/charts/helm/checkmate/templates/redis-deployment.yaml new file mode 100644 index 000000000..cc9ffdaf9 --- /dev/null +++ b/charts/helm/checkmate/templates/redis-deployment.yaml @@ -0,0 +1,26 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + containers: + - name: redis + image: {{ .Values.redis.image }} + ports: + - containerPort: {{ .Values.redis.port }} + volumeMounts: + - name: redis-data + mountPath: /data + volumes: + - name: redis-data + persistentVolumeClaim: + claimName: redis-pvc diff --git a/charts/helm/checkmate/templates/redis-pvc.yaml b/charts/helm/checkmate/templates/redis-pvc.yaml new file mode 100644 index 000000000..2f0d81960 --- /dev/null +++ b/charts/helm/checkmate/templates/redis-pvc.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: redis-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.persistence.redisSize }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/redis-service.yaml b/charts/helm/checkmate/templates/redis-service.yaml new file mode 100644 index 000000000..f29fa46de --- /dev/null +++ b/charts/helm/checkmate/templates/redis-service.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Service +metadata: + name: redis +spec: + selector: + app: redis + ports: + - port: {{ .Values.redis.port }} diff --git a/charts/helm/checkmate/templates/secrets.yaml b/charts/helm/checkmate/templates/secrets.yaml new file mode 100644 index 000000000..42e7c6047 --- /dev/null +++ b/charts/helm/checkmate/templates/secrets.yaml @@ -0,0 +1,19 @@ +{{- $secrets := .Values.secrets }} + +{{- if or (not $secrets.JWT_SECRET) (eq $secrets.JWT_SECRET "change_me") }} + {{- fail "secrets.JWT_SECRET must be overridden and cannot be 'change_me'" }} +{{- end }} + +{{- if or (not $secrets.REFRESH_TOKEN_SECRET) (eq $secrets.REFRESH_TOKEN_SECRET "change_me") }} + {{- fail "secrets.REFRESH_TOKEN_SECRET must be overridden and cannot be 'change_me'" }} +{{- end }} + +apiVersion: v1 +kind: Secret +metadata: + name: checkmate-secrets +type: Opaque +stringData: +{{- range $key, $value := := $secrets }} + {{ $key }}: {{ $value | quote }} +{{- end }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/server-deployment.yaml b/charts/helm/checkmate/templates/server-deployment.yaml new file mode 100644 index 000000000..8548495dd --- /dev/null +++ b/charts/helm/checkmate/templates/server-deployment.yaml @@ -0,0 +1,30 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: server +spec: + replicas: 1 + selector: + matchLabels: + app: server + template: + metadata: + labels: + app: server + spec: + containers: + - name: server + image: {{ .Values.server.image }} + ports: + - containerPort: {{ .Values.server.port }} + envFrom: + - secretRef: + name: checkmate-secrets + volumeMounts: + - name: docker-sock + mountPath: /var/run/docker.sock + volumes: + - name: docker-sock + hostPath: + path: /var/run/docker.sock + type: Socket diff --git a/charts/helm/checkmate/templates/server-ingress.yaml b/charts/helm/checkmate/templates/server-ingress.yaml new file mode 100644 index 000000000..acf64f95f --- /dev/null +++ b/charts/helm/checkmate/templates/server-ingress.yaml @@ -0,0 +1,20 @@ +{{- if .Values.server.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: server-ingress + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / +spec: + rules: + - host: {{ .Values.server.ingress.host }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: server + port: + number: {{ .Values.server.port }} +{{- end }} diff --git a/charts/helm/checkmate/templates/server-service.yaml b/charts/helm/checkmate/templates/server-service.yaml new file mode 100644 index 000000000..64494ff90 --- /dev/null +++ b/charts/helm/checkmate/templates/server-service.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Service +metadata: + name: server +spec: + selector: + app: server + ports: + - port: {{ .Values.server.port }} diff --git a/charts/helm/checkmate/values.yaml b/charts/helm/checkmate/values.yaml new file mode 100644 index 000000000..d73e17965 --- /dev/null +++ b/charts/helm/checkmate/values.yaml @@ -0,0 +1,41 @@ +client: + image: bluewaveuptime/uptime_client:latest + port: 80 + ingress: + enabled: true + host: checkmate-chart.local + +server: + image: bluewaveuptime/uptime_server:latest + port: 5000 + protocol: http + ingress: + enabled: true + host: server.checkmate-chart.local + +redis: + image: redis:7.2 + port: 6379 + +mongodb: + image: mongo:7.0 + port: 27017 + +secrets: + JWT_SECRET: change_me + REFRESH_TOKEN_SECRET: change_me + SYSTEM_EMAIL_ADDRESS: test@example.com + SYSTEM_EMAIL_PASSWORD: change_me + SYSTEM_EMAIL_HOST: smtp.example.com + SYSTEM_EMAIL_PORT: "587" + PAGESPEED_API_KEY: change_me + DB_CONNECTION_STRING: mongodb://mongodb:27017/uptime_db + REDIS_HOST: redis + REDIS_PORT: "6379" + DB_TYPE: MongoDB + TOKEN_TTL: 99d + REFRESH_TOKEN_TTL: 99d + +persistence: + mongodbSize: 5Gi + redisSize: 1Gi \ No newline at end of file From edd23469390c62128d6fce64c7341d81d605699b Mon Sep 17 00:00:00 2001 From: Jeffer Marcelino Date: Thu, 3 Jul 2025 18:06:47 +0200 Subject: [PATCH 02/22] fix: change hardcoded protocol and host to be change_me by default --- charts/helm/checkmate/values.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/charts/helm/checkmate/values.yaml b/charts/helm/checkmate/values.yaml index d73e17965..36ce3a246 100644 --- a/charts/helm/checkmate/values.yaml +++ b/charts/helm/checkmate/values.yaml @@ -3,15 +3,15 @@ client: port: 80 ingress: enabled: true - host: checkmate-chart.local + host: change_me server: image: bluewaveuptime/uptime_server:latest port: 5000 - protocol: http + protocol: change_me ingress: enabled: true - host: server.checkmate-chart.local + host: change_me redis: image: redis:7.2 From 6e5550ba32a301cfe5c3b690e8c94bb2842e5f5a Mon Sep 17 00:00:00 2001 From: Jeffer Marcelino Date: Thu, 3 Jul 2025 18:16:42 +0200 Subject: [PATCH 03/22] docs: add INSTALLATION.md with Helm-based Kubernetes deployment steps --- README.md | 9 +++++-- charts/helm/checkmate/INSTALLATION.md | 39 +++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 charts/helm/checkmate/INSTALLATION.md diff --git a/README.md b/README.md index c9c9073ad..5b99e67d4 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,8 @@ Checkmate has been stress-tested with 1000+ active monitors without any particul - [📦 Demo](#-demo) - [🔗 User's guide](#-users-guide) -- [🛠️ Installation](#️-installation) +- [🛠️ Installation](#️-installation) +- [🚀 Deploying Checkmate with Helm](#-deploying-checkmate-with-helm) - [🏁 Translations](#-translations) - [🚀 Performance](#-performance) - [💚 Questions & Ideas](#-questions--ideas) @@ -50,7 +51,11 @@ Usage instructions can be found [here](https://docs.checkmate.so/checkmate-2.1). ## 🛠️ Installation -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. +See installation instructions in [Checkmate documentation portal](https://docs.checkmate.so/checkmate-2.1/users-guide/quickstart). + +For detailed Kubernetes deployment instructions using Helm, see the [INSTALLATION.md](./charts/helm/checkmate/INSTALLATION.md) file. + +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 diff --git a/charts/helm/checkmate/INSTALLATION.md b/charts/helm/checkmate/INSTALLATION.md new file mode 100644 index 000000000..9ca321ad3 --- /dev/null +++ b/charts/helm/checkmate/INSTALLATION.md @@ -0,0 +1,39 @@ +# Kubernetes Installation Guide for Checkmate + +This guide walks you through deploying Checkmate on your Kubernetes cluster using Helm. + +## Prerequisites + +- A running Kubernetes cluster +- Helm CLI installed and configured +- `kubectl` configured to access your cluster + +## Steps + +### 1. Clone the repo and navigate to the Helm chart + +```bash +git clone https://github.com/bluewave-labs/checkmate.git +cd checkmate/charts/helm/checkmate +``` + +### 2. Customize values.yaml +Edit `values.yaml` to update: +- `client.ingress.host` and `server.ingress.host` with your domain names +- `server.protocol` (usually http or https) +- Secrets under the `secrets` section (`JWT_SECRET`, email credentials, API keys, etc.) — replace all change_me values + +### 3. Deploy the Helm chart +```bash +helm install checkmate ./charts/helm/checkmate +``` +This will deploy the client, server, MongoDB, and Redis components. + +### 4. Verify the deployment +Check pods and services: +```bash +kubectl get pods +kubectl get svc +``` + +Once all pods are `Running` and `Ready`, you can access Checkmate via the configured ingress hosts. From f00d7275e5b977b73762c9a7b8c1e0dc53036a86 Mon Sep 17 00:00:00 2001 From: Jesulayomy Date: Sat, 5 Jul 2025 18:26:34 -0400 Subject: [PATCH 04/22] [PageSpeed]: Monitor Config and Create pages consolidated into Setup Component --- client/src/Pages/PageSpeed/Setup/index.css | 9 + client/src/Pages/PageSpeed/Setup/index.jsx | 466 ++++++++++++++++++ client/src/Pages/PageSpeed/Setup/skeleton.jsx | 86 ++++ client/src/Routes/index.jsx | 5 +- 4 files changed, 564 insertions(+), 2 deletions(-) create mode 100644 client/src/Pages/PageSpeed/Setup/index.css create mode 100644 client/src/Pages/PageSpeed/Setup/index.jsx create mode 100644 client/src/Pages/PageSpeed/Setup/skeleton.jsx diff --git a/client/src/Pages/PageSpeed/Setup/index.css b/client/src/Pages/PageSpeed/Setup/index.css new file mode 100644 index 000000000..89a7c3466 --- /dev/null +++ b/client/src/Pages/PageSpeed/Setup/index.css @@ -0,0 +1,9 @@ +.configure-pagespeed button { + height: var(--env-var-height-2); +} + +.configure-pagespeed .field, +.configure-pagespeed .section-disabled, +.configure-pagespeed .select-wrapper { + flex: 1; +} diff --git a/client/src/Pages/PageSpeed/Setup/index.jsx b/client/src/Pages/PageSpeed/Setup/index.jsx new file mode 100644 index 000000000..4a653c58d --- /dev/null +++ b/client/src/Pages/PageSpeed/Setup/index.jsx @@ -0,0 +1,466 @@ +// Components +import { Box, Stack, Tooltip, Typography, Button, ButtonGroup } from "@mui/material"; +import ConfigBox from "../../../Components/ConfigBox"; +import Select from "../../../Components/Inputs/Select"; +import TextInput from "../../../Components/Inputs/TextInput"; +import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline"; +import Breadcrumbs from "../../../Components/Breadcrumbs"; +import PulseDot from "../../../Components/Animated/PulseDot"; +import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded"; +import SkeletonLayout from "./skeleton"; +import NotificationsConfig from "../../../Components/NotificationConfig"; +import Dialog from "../../../Components/Dialog"; +import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments"; +import Radio from "../../../Components/Inputs/Radio"; + + +// Utils +import { useState, useEffect } from "react"; +import { useSelector } from "react-redux"; +import { monitorValidation } from "../../../Validation/validation"; +import { parseDomainName } from "../../../Utils/monitorUtils"; +import { useTranslation } from "react-i18next"; +import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications"; +import { useTheme } from "@emotion/react"; +import { createToast } from "../../../Utils/toastUtils"; + +import { useParams } from "react-router"; +import { useMonitorUtils } from "../../../Hooks/useMonitorUtils"; +import { + useCreateMonitor, + useFetchMonitorById, + useDeleteMonitor, + useUpdateMonitor, + usePauseMonitor, +} from "../../../Hooks/monitorHooks"; + +const MS_PER_MINUTE = 60000; +const SELECT_VALUES = [ + { _id: 3, name: "3 minutes" }, + { _id: 5, name: "5 minutes" }, + { _id: 10, name: "10 minutes" }, + { _id: 20, name: "20 minutes" }, + { _id: 60, name: "1 hour" }, + { _id: 1440, name: "1 day" }, + { _id: 10080, name: "1 week" }, +]; + +const PageSpeedSetup = ({ monitor }) => { + const isConfigure = monitor; + const { t } = useTranslation(); + const theme = useTheme(); + const { monitorId } = useParams(); + const { statusColor, pagespeedStatusMsg, determineState } = useMonitorUtils(); + const [notifications, notificationsAreLoading] = useGetNotificationsByTeamId(); + const [https, setHttps] = useState(true); + const [errors, setErrors] = useState({}); + const [isOpen, setIsOpen] = useState(false); + const [updateTrigger, setUpdateTrigger] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [isDeleting, setIsDeleting] = useState(false); + const [isUpdating, setIsUpdating] = useState(false); + const [isPausing, setIsPausing] = useState(false); + + // Monitor state + const [monitorData, setMonitorData] = useState({ + url: "", + name: "", + type: "pagespeed", + notifications: [], + interval: 3, + }); + + // Hooks for API actions + const [createMonitor, isCreating] = useCreateMonitor(); + const [fetchMonitor] = monitor + ? useFetchMonitorById({ monitorId, setMonitor: setMonitorData, updateTrigger }) + : [null]; + const [deleteMonitor] = useDeleteMonitor(); + const [updateMonitor] = useUpdateMonitor(); + const [pauseMonitor] = usePauseMonitor(); + + const frequencies = [ + { _id: 3, name: "3 minutes" }, + { _id: 5, name: "5 minutes" }, + { _id: 10, name: "10 minutes" }, + { _id: 20, name: "20 minutes" }, + { _id: 60, name: "1 hour" }, + { _id: 1440, name: "1 day" }, + { _id: 10080, name: "1 week" }, + ]; + + // Fetch monitor if in configure mode + useEffect(() => { + if (monitor && monitorId && typeof fetchMonitor === 'function') { + setIsLoading(true); + fetchMonitor().finally(() => setIsLoading(false)); + } + }, [monitor, monitorId, updateTrigger, fetchMonitor]); + + // Handlers + + const handleChange = (event) => { + let { value, name } = event.target; + if (name === "interval") { + value = value * MS_PER_MINUTE; + } + setMonitorData((prev) => ({ + ...prev, + [name]: value + })); + const validation = monitorValidation.validate( + { [name]: value }, + { abortEarly: false } + ); + + setErrors((prev) => { + const updatedErrors = { ...prev }; + if (validation.error) { + updatedErrors[name] = validation.error.details[0].message; + } else { + delete updatedErrors[name]; + } + return updatedErrors; + }); + }; + + const handleBlur = (event) => { + const { name, value } = event.target; + if (name === "url" && monitorData.name === "") { + setMonitorData((prev) => ({ ...prev, name: parseDomainName(value) })); + } + }; + + const onSubmit = async (event) => { + event.preventDefault(); + if (isConfigure) { + setIsUpdating(true); + await updateMonitor({ monitor: monitorData, redirect: "/pagespeed" }); + setIsUpdating(false); + } else { + let form = { + url: `http${https ? "s" : ""}://` + monitorData.url, + name: monitorData.name === "" ? monitorData.url : monitorData.name, + type: monitorData.type, + interval: monitorData.interval * MS_PER_MINUTE, + description: monitorData.name === "" ? monitorData.url : monitorData.name, + notifications: monitorData.notifications, + }; + const { error } = monitorValidation.validate(form, { abortEarly: false }); + if (error) { + const newErrors = {}; + error.details.forEach((err) => { + newErrors[err.path[0]] = err.message; + }); + setErrors(newErrors); + createToast({ body: "Please check the form for errors." }); + return; + } + setIsLoading(true); + await createMonitor({ monitor: form, redirect: "/pagespeed" }); + setIsLoading(false); + } + }; + + const handlePause = async () => { + setIsPausing(true); + await pauseMonitor({ monitorId, triggerUpdate: () => setUpdateTrigger((u) => !u) }); + setIsPausing(false); + }; + + const handleRemove = async (event) => { + event.preventDefault(); + setIsDeleting(true); + await deleteMonitor({ monitor: monitorData, redirect: "/pagespeed" }); + setIsDeleting(false); + }; + + // Loading skeleton + if (isConfigure && (isLoading || !monitorData._id)) { + return ; + } + + return ( + + + + + + {isConfigure ? monitorData.name : t("createYour") + " "} + + { !isConfigure + ? ( + + {t("pageSpeedMonitor")} + + ) + : <> + } + + {isConfigure && ( + + + + + + + + + + {monitorData.url?.replace(/^https?:\/\//, "") || "..."} + + + {t("editing")} + + + + + + + + + )} + + + + {t("settingsGeneralSettings")} + + + {isConfigure ? t("pageSpeedConfigureSettingsDescription") : t("distributedUptimeCreateSelectURL")} + + + + : undefined} + placeholder={isConfigure ? "random.website.com" : "google.com"} + value={monitorData.url || ""} + onChange={handleChange} + onBlur={!isConfigure ? handleBlur : undefined} + error={!!errors["url"]} + helperText={errors["url"]} + disabled={isConfigure} + /> + + + + {!isConfigure && ( + + + + {t("distributedUptimeCreateChecks")} + + + {t("distributedUptimeCreateChecksDescription")} + + + + + + + + + + + {errors["type"] ? ( + + + {errors["type"]} + + + ) : null} + + + )} + + + + {t("notificationConfig.title")} + + {t("notificationConfig.description")} + + + + + + + {t("distributedUptimeCreateAdvancedSettings")} + + + + - - - - - - - - )} - setIsOpen(false)} - confirmationButtonLabel={t("delete")} - onConfirm={handleRemove} - isLoading={isLoading || isDeleting || isUpdating || isPausing} - /> - - ); -}; - -export default PageSpeedConfigure; diff --git a/client/src/Pages/PageSpeed/Configure/skeleton.jsx b/client/src/Pages/PageSpeed/Configure/skeleton.jsx deleted file mode 100644 index 347fcaa65..000000000 --- a/client/src/Pages/PageSpeed/Configure/skeleton.jsx +++ /dev/null @@ -1,86 +0,0 @@ -import { Box, Skeleton, Stack } from "@mui/material"; -import { useTheme } from "@emotion/react"; - -/** - * Renders a skeleton layout. - * - * @returns {JSX.Element} - */ -const SkeletonLayout = () => { - const theme = useTheme(); - - return ( - <> - - - - - - - - - - - - - - - - - - - - ); -}; - -export default SkeletonLayout; diff --git a/client/src/Pages/PageSpeed/Create/index.css b/client/src/Pages/PageSpeed/Create/index.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/client/src/Pages/PageSpeed/Create/index.jsx b/client/src/Pages/PageSpeed/Create/index.jsx deleted file mode 100644 index 5cc4a1a54..000000000 --- a/client/src/Pages/PageSpeed/Create/index.jsx +++ /dev/null @@ -1,312 +0,0 @@ -//Components -import Box from "@mui/material/Box"; -import Stack from "@mui/material/Stack"; -import Typography from "@mui/material/Typography"; -import Button from "@mui/material/Button"; -import ButtonGroup from "@mui/material/ButtonGroup"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import TextInput from "../../../Components/Inputs/TextInput"; -import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments"; -import ConfigBox from "../../../Components/ConfigBox"; -import Radio from "../../../Components/Inputs/Radio"; -import Select from "../../../Components/Inputs/Select"; -import NotificationsConfig from "../../../Components/NotificationConfig"; - -// Utils -import { useState } from "react"; -import { useSelector } from "react-redux"; -import { monitorValidation } from "../../../Validation/validation"; -import { parseDomainName } from "../../../Utils/monitorUtils"; -import { useTranslation } from "react-i18next"; -import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications"; -import { useTheme } from "@emotion/react"; -import { createToast } from "../../../Utils/toastUtils"; -import { useCreateMonitor } from "../../../Hooks/monitorHooks"; - -const MS_PER_MINUTE = 60000; - -const CRUMBS = [ - { name: "pagespeed", path: "/pagespeed" }, - { name: "create", path: `/pagespeed/create` }, -]; - -const SELECT_VALUES = [ - { _id: 3, name: "3 minutes" }, - { _id: 5, name: "5 minutes" }, - { _id: 10, name: "10 minutes" }, - { _id: 20, name: "20 minutes" }, - { _id: 60, name: "1 hour" }, - { _id: 1440, name: "1 day" }, - { _id: 10080, name: "1 week" }, -]; - -const CreatePageSpeed = () => { - // State - const [monitor, setMonitor] = useState({ - url: "", - name: "", - type: "pagespeed", - notifications: [], - interval: 3, - }); - - const [https, setHttps] = useState(true); - const [errors, setErrors] = useState({}); - const { user } = useSelector((state) => state.auth); - const [notifications, notificationsAreLoading, error] = useGetNotificationsByTeamId(); - - // Setup - const theme = useTheme(); - const [createMonitor, isCreating] = useCreateMonitor(); - - // Handlers - const onSubmit = async (event) => { - event.preventDefault(); - let form = { - url: `http${https ? "s" : ""}://` + monitor.url, - name: monitor.name === "" ? monitor.url : monitor.name, - type: monitor.type, - interval: monitor.interval * MS_PER_MINUTE, - }; - - const { error } = monitorValidation.validate(form, { - abortEarly: false, - }); - - if (error) { - const newErrors = {}; - error.details.forEach((err) => { - newErrors[err.path[0]] = err.message; - }); - setErrors(newErrors); - createToast({ body: "Please check the form for errors." }); - return; - } - - form = { - ...form, - description: form.name, - notifications: monitor.notifications, - }; - - await createMonitor({ monitor: form, redirect: "/pagespeed" }); - }; - - const handleChange = (event) => { - const { value, name } = event.target; - setMonitor({ - ...monitor, - [name]: value, - }); - - const { error } = monitorValidation.validate( - { [name]: value }, - { abortEarly: false } - ); - - setErrors((prev) => ({ - ...prev, - ...(error ? { [name]: error.details[0].message } : { [name]: undefined }), - })); - }; - - const handleBlur = (event) => { - const { name } = event.target; - if (name === "url") { - const { value } = event.target; - if (monitor.name !== "") { - return; - } - setMonitor((prev) => ({ - ...prev, - name: parseDomainName(value), - })); - } - }; - - const { t } = useTranslation(); - - return ( - - - - - - {t("createYour")}{" "} - - - {t("pageSpeedMonitor")} - - - - - - {t("settingsGeneralSettings")} - - {t("distributedUptimeCreateSelectURL")} - - - } - placeholder="google.com" - value={monitor.url} - onChange={handleChange} - onBlur={handleBlur} - error={errors["url"] ? true : false} - helperText={errors["url"]} - /> - - - - - - - {t("distributedUptimeCreateChecks")} - - - {t("distributedUptimeCreateChecksDescription")} - - - - - - - - - - - {errors["type"] ? ( - - - {errors["type"]} - - - ) : ( - "" - )} - - - - - - {t("notificationConfig.title")} - - {t("notificationConfig.description")} - - - - - - - {t("distributedUptimeCreateAdvancedSettings")} - - - - + + + + - - )} - - - - {t("notificationConfig.title")} - - {t("notificationConfig.description")} - - - - - - - {t("distributedUptimeCreateAdvancedSettings")} - - - - - - - - - - - - )} - {isConfigure && ( - setIsOpen(false)} - confirmationButtonLabel={t("delete")} - onConfirm={handleRemove} - isLoading={isLoading || isDeleting || isUpdating || isPausing} - /> - )} - - ); + > + : undefined + } + placeholder="random.website.com" + value={monitor.url || ""} + onChange={handleChange} + onBlur={!isConfigure ? handleBlur : undefined} + error={!!errors["url"]} + helperText={errors["url"]} + disabled={isConfigure} + /> + + + + {!isConfigure && ( + + + + {t("distributedUptimeCreateChecks")} + + + {t("distributedUptimeCreateChecksDescription")} + + + + + + + + + + + {errors["type"] ? ( + + + {errors["type"]} + + + ) : ( + "" + )} + + + )} + + + + {t("notificationConfig.title")} + + + {t("notificationConfig.description")} + + + + + + + + {t("distributedUptimeCreateAdvancedSettings")} + + + + { )} - {isConfigure && ( + {!isCreate && ( { onCancel={() => setIsOpen(false)} confirmationButtonLabel={t("delete")} onConfirm={handleRemove} - isLoading={isLoading || isDeleting || isUpdating || isPausing} /> )} From 840069b93519116c593d502912b19f0bb5c01f29 Mon Sep 17 00:00:00 2001 From: Jesulayomy Date: Thu, 10 Jul 2025 21:53:33 -0400 Subject: [PATCH 13/22] [PageSpeed]: Implement conditional check for monitorId inside useFetchMonitorById hook --- client/src/Hooks/monitorHooks.js | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index d59d88c74..b3210c1b4 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -205,18 +205,22 @@ const useFetchStatsByMonitorId = ({ const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => { const [isLoading, setIsLoading] = useState(true); useEffect(() => { - const fetchMonitor = async () => { - try { - setIsLoading(true); - const res = await networkService.getMonitorById({ monitorId: monitorId }); - setMonitor(res.data.data); - } catch (error) { - createToast({ body: error.message }); - } finally { - setIsLoading(false); - } - }; - fetchMonitor(); + if (monitorId !== undefined) { + const fetchMonitor = async () => { + try { + setIsLoading(true); + const res = await networkService.getMonitorById({ monitorId: monitorId }); + setMonitor(res.data.data); + } catch (error) { + createToast({ body: error.message }); + } finally { + setIsLoading(false); + } + }; + fetchMonitor(); + } else { + setIsLoading(false); + } }, [monitorId, setMonitor, updateTrigger]); return [isLoading]; }; From b7b768760825fa0b8240a8741ff2d63830c7ef98 Mon Sep 17 00:00:00 2001 From: Jesulayomy Date: Thu, 10 Jul 2025 21:54:27 -0400 Subject: [PATCH 14/22] [PageSpeed]: Loading states from hooks, unused classnames, conditional calling of hook removed --- client/src/Pages/PageSpeed/Create/index.jsx | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/client/src/Pages/PageSpeed/Create/index.jsx b/client/src/Pages/PageSpeed/Create/index.jsx index 8941d8c9a..7c6042fad 100644 --- a/client/src/Pages/PageSpeed/Create/index.jsx +++ b/client/src/Pages/PageSpeed/Create/index.jsx @@ -84,13 +84,11 @@ const PageSpeedSetup = () => { useGetNotificationsByTeamId(); // Hooks for API actions + const [isLoading] = useFetchMonitorById({ monitorId, setMonitor, updateTrigger }); const [createMonitor, isCreating] = useCreateMonitor(); - const [isLoading] = isCreate - ? [false] - : useFetchMonitorById({ monitorId, setMonitor, updateTrigger }); - const [deleteMonitor] = useDeleteMonitor(); - const [updateMonitor] = useUpdateMonitor(); - const [pauseMonitor] = usePauseMonitor(); + const [deleteMonitor, isDeleting] = useDeleteMonitor(); + const [updateMonitor, isUpdating] = useUpdateMonitor(); + const [pauseMonitor, isPausing] = usePauseMonitor(); // Handlers const onSubmit = async (event) => { @@ -186,7 +184,6 @@ const PageSpeedSetup = () => { return ( { { > - - + ) : ( + <> )} - - - + + {!isCreate && ( + + + + + + - {t("settingsGeneralSettings")} + {monitor.url?.replace(/^https?:\/\//, "") || "..."} - - {t("pageSpeedConfigureSettingsDescription")} + + {t("editing")} - - + )} + + {!isCreate && ( + + + + + )} + + + + + {t("settingsGeneralSettings")} + + + {t("pageSpeedConfigureSettingsDescription")} + + + .Mui-disabled)": { + backgroundColor: theme.palette.tertiary.main, + }, + }} + > + : undefined} + placeholder="random.website.com" + value={monitor.url || ""} + onChange={handleChange} + onBlur={isCreate ? handleBlur : undefined} + error={!!errors["url"]} + helperText={errors["url"]} + disabled={!isCreate} + /> + + + + {isCreate && ( + + + + {t("distributedUptimeCreateChecks")} + + + {t("distributedUptimeCreateChecksDescription")} + + + + + + + + + - - {isCreate && ( - + {errors["type"] ? ( - {t("distributedUptimeCreateChecks")} - - - {t("distributedUptimeCreateChecksDescription")} + {errors["type"]} - - - - - - - - - {errors["type"] ? ( - - - {errors["type"]} - - - ) : ( - "" - )} - - - )} - - - - {t("notificationConfig.title")} - - - {t("notificationConfig.description")} - - - - - - - - {t("distributedUptimeCreateAdvancedSettings")} - - - - - - )} + + + + + {!isCreate && ( Date: Fri, 11 Jul 2025 13:40:29 -0400 Subject: [PATCH 17/22] [PageSpeed]: Use typeof operator for clarity, and to handle undeclared and undefined variables --- client/src/Hooks/monitorHooks.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index b3210c1b4..55c348626 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -205,22 +205,22 @@ const useFetchStatsByMonitorId = ({ const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => { const [isLoading, setIsLoading] = useState(true); useEffect(() => { - if (monitorId !== undefined) { - const fetchMonitor = async () => { - try { - setIsLoading(true); - const res = await networkService.getMonitorById({ monitorId: monitorId }); - setMonitor(res.data.data); - } catch (error) { - createToast({ body: error.message }); - } finally { - setIsLoading(false); - } - }; - fetchMonitor(); - } else { + if (typeof monitorId === "undefined") { setIsLoading(false); - } + return + }; + const fetchMonitor = async () => { + try { + setIsLoading(true); + const res = await networkService.getMonitorById({ monitorId: monitorId }); + setMonitor(res.data.data); + } catch (error) { + createToast({ body: error.message }); + } finally { + setIsLoading(false); + } + }; + fetchMonitor(); }, [monitorId, setMonitor, updateTrigger]); return [isLoading]; }; From f8a7b86ab7a2d66f9f4b594023c652aec0f42854 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 11 Jul 2025 11:58:05 -0700 Subject: [PATCH 18/22] add CODEOWNERS --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..10bf888d9 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @ajhollid @mohicody @Br0wnHammer @Owaiseimdad @karenvicent @shanikauwu1 \ No newline at end of file From a7dd23141ccc022629c6cb73f39f287c126b10c9 Mon Sep 17 00:00:00 2001 From: Jesulayomy Date: Sat, 12 Jul 2025 14:26:17 -0400 Subject: [PATCH 19/22] [PageSpeed]: Formatting monitorHooks --- client/src/Hooks/monitorHooks.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index 55c348626..4f0e9c049 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -207,8 +207,8 @@ const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => { useEffect(() => { if (typeof monitorId === "undefined") { setIsLoading(false); - return - }; + return; + } const fetchMonitor = async () => { try { setIsLoading(true); From 72d34fcc4fbf63bb25197cb0d56ce259e3162dd7 Mon Sep 17 00:00:00 2001 From: Jeffer Marcelino Date: Sun, 13 Jul 2025 19:18:01 +0200 Subject: [PATCH 20/22] chore: update appVersion and images in Helm chart for Checkmate --- charts/helm/checkmate/Chart.yaml | 2 +- charts/helm/checkmate/values.yaml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/charts/helm/checkmate/Chart.yaml b/charts/helm/checkmate/Chart.yaml index 2ca35c1f6..97f320e68 100644 --- a/charts/helm/checkmate/Chart.yaml +++ b/charts/helm/checkmate/Chart.yaml @@ -3,4 +3,4 @@ name: checkmate-chart description: A Helm chart for Checkmate App type: application version: 0.1.0 -appVersion: "2.1.0" +appVersion: "2.3" diff --git a/charts/helm/checkmate/values.yaml b/charts/helm/checkmate/values.yaml index 36ce3a246..79c77a49e 100644 --- a/charts/helm/checkmate/values.yaml +++ b/charts/helm/checkmate/values.yaml @@ -1,13 +1,13 @@ client: - image: bluewaveuptime/uptime_client:latest + image: ghcr.io/bluewave-labs/checkmate-client:v2.3 port: 80 ingress: enabled: true host: change_me server: - image: bluewaveuptime/uptime_server:latest - port: 5000 + image: ghcr.io/bluewave-labs/checkmate-backend:v2.3 + port: 52345 protocol: change_me ingress: enabled: true From f912c5eb5d282965d80b1ca8568f071eb76009d8 Mon Sep 17 00:00:00 2001 From: Br0wnHammer Date: Mon, 14 Jul 2025 01:45:28 +0530 Subject: [PATCH 21/22] Fix: Reset Page when Searching --- client/src/Pages/Uptime/Monitors/index.jsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/client/src/Pages/Uptime/Monitors/index.jsx b/client/src/Pages/Uptime/Monitors/index.jsx index f3342e101..6e6f6caba 100644 --- a/client/src/Pages/Uptime/Monitors/index.jsx +++ b/client/src/Pages/Uptime/Monitors/index.jsx @@ -21,7 +21,7 @@ import MonitorCountHeader from "../../../Components/MonitorCountHeader"; import { Stack, Box, Button, Typography } from "@mui/material"; // Utils -import { useState, useCallback } from "react"; +import { useState, useCallback, useEffect } from "react"; import { useIsAdmin } from "../../../Hooks/useIsAdmin"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; @@ -146,6 +146,12 @@ const UptimeMonitors = () => { monitorUpdateTrigger, }); + useEffect(() => { + if (isSearching) { + setPage(undefined); + } + }, [isSearching]); + const isLoading = monitorsWithSummaryIsLoading || monitorsWithChecksIsLoading; if (networkError) { return ( From a879051454256c9c1c0100ccc21c1ea1d8570054 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 14 Jul 2025 09:38:00 -0700 Subject: [PATCH 22/22] reorder rotues to correctly match --- server/routes/checkRoute.js | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/server/routes/checkRoute.js b/server/routes/checkRoute.js index 4ac1e3730..f215cbe37 100755 --- a/server/routes/checkRoute.js +++ b/server/routes/checkRoute.js @@ -20,17 +20,18 @@ class CheckRoutes { isAllowed(["admin", "superadmin"]), this.checkController.deleteChecksByTeamId ); - - this.router.get("/:monitorId", this.checkController.getChecksByMonitor); - this.router.put( "/check/:checkId", verifyTeamAccess(Check, "checkId"), this.checkController.ackCheck ); + this.router.put( + "/team/ttl", + isAllowed(["admin", "superadmin"]), + this.checkController.updateChecksTTL + ); - this.router.put("/:path/:monitorId?", this.checkController.ackAllChecks); - + this.router.get("/:monitorId", this.checkController.getChecksByMonitor); this.router.post( "/:monitorId", verifyOwnership(Monitor, "monitorId"), @@ -41,12 +42,7 @@ class CheckRoutes { verifyOwnership(Monitor, "monitorId"), this.checkController.deleteChecks ); - - this.router.put( - "/team/ttl", - isAllowed(["admin", "superadmin"]), - this.checkController.updateChecksTTL - ); + this.router.put("/:path/:monitorId?", this.checkController.ackAllChecks); } getRouter() {