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
diff --git a/README.md b/README.md
index 7f7f0520d..757e70493 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,10 @@ 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/), [Elestio](https://elest.io/open-source/checkmate) or [Pikapods](https://www.pikapods.com/) to quickly spin off a Checkmate instance. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions.
+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/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md) or [Pikapods](https://www.pikapods.com/) to quickly spin off a Checkmate instance. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions.
+
## 🏁 Translations
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..97f320e68
--- /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.3"
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.
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..79c77a49e
--- /dev/null
+++ b/charts/helm/checkmate/values.yaml
@@ -0,0 +1,41 @@
+client:
+ image: ghcr.io/bluewave-labs/checkmate-client:v2.3
+ port: 80
+ ingress:
+ enabled: true
+ host: change_me
+
+server:
+ image: ghcr.io/bluewave-labs/checkmate-backend:v2.3
+ port: 52345
+ protocol: change_me
+ ingress:
+ enabled: true
+ host: change_me
+
+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
diff --git a/client/src/Components/Fallback/index.jsx b/client/src/Components/Fallback/index.jsx
index e5a212c00..dd6043697 100644
--- a/client/src/Components/Fallback/index.jsx
+++ b/client/src/Components/Fallback/index.jsx
@@ -88,15 +88,18 @@ const Fallback = ({
display: "flex",
borderStyle: "dashed",
height: {
- sm: "50vh",
- md: "70vh",
- lg: "60vh",
- xl: "55vh",
+ sm: "55vh",
+ md: "50vh",
+ lg: "70vh",
+ xl: "58vh",
},
width: {
sm: "90%",
- md: "50%",
- lg: "40%",
+ md: "70%",
+ lg: "42%",
+ },
+ minHeight: {
+ sm: theme.spacing(280),
},
padding: theme.spacing(10),
}}
@@ -144,11 +147,11 @@ const Fallback = ({
{/* TODO - display a different fallback if user is not an admin*/}
{isAdmin && (
- <>
+
)}
diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js
index d59d88c74..4f0e9c049 100644
--- a/client/src/Hooks/monitorHooks.js
+++ b/client/src/Hooks/monitorHooks.js
@@ -205,6 +205,10 @@ const useFetchStatsByMonitorId = ({
const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
+ if (typeof monitorId === "undefined") {
+ setIsLoading(false);
+ return;
+ }
const fetchMonitor = async () => {
try {
setIsLoading(true);
diff --git a/client/src/Pages/PageSpeed/Configure/index.css b/client/src/Pages/PageSpeed/Configure/index.css
deleted file mode 100644
index 89a7c3466..000000000
--- a/client/src/Pages/PageSpeed/Configure/index.css
+++ /dev/null
@@ -1,9 +0,0 @@
-.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/Configure/index.jsx b/client/src/Pages/PageSpeed/Configure/index.jsx
deleted file mode 100644
index 71e1e111e..000000000
--- a/client/src/Pages/PageSpeed/Configure/index.jsx
+++ /dev/null
@@ -1,351 +0,0 @@
-// Components
-import { Box, Stack, Tooltip, Typography, Button } 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";
-
-// Utils
-import { useState } from "react";
-import { useTheme } from "@emotion/react";
-import { useParams } from "react-router";
-import { monitorValidation } from "../../../Validation/validation";
-import { useTranslation } from "react-i18next";
-import { useMonitorUtils } from "../../../Hooks/useMonitorUtils";
-import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
-import {
- useFetchMonitorById,
- useDeleteMonitor,
- useUpdateMonitor,
- usePauseMonitor,
-} from "../../../Hooks/monitorHooks";
-const PageSpeedConfigure = () => {
- // Redux state
-
- // Local state
- const [monitor, setMonitor] = useState({});
- const [errors, setErrors] = useState({});
- const [isOpen, setIsOpen] = useState(false);
- const [updateTrigger, setUpdateTrigger] = useState(false);
-
- // Utils
- const theme = useTheme();
- const { t } = useTranslation();
- const MS_PER_MINUTE = 60000;
- const { monitorId } = useParams();
- const { statusColor, pagespeedStatusMsg, determineState } = useMonitorUtils();
-
- const [notifications, notificationsAreLoading, notificationsError] =
- useGetNotificationsByTeamId();
-
- const [isLoading] = useFetchMonitorById({ monitorId, setMonitor, updateTrigger });
- const [deleteMonitor, isDeleting] = useDeleteMonitor();
- const [updateMonitor, isUpdating] = useUpdateMonitor();
- const [pauseMonitor, isPausing] = 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" },
- ];
-
- // Handlers
- const triggerUpdate = () => {
- setUpdateTrigger(!updateTrigger);
- };
-
- const onChange = (event) => {
- let { value, name } = event.target;
-
- if (name === "interval") {
- value = value * MS_PER_MINUTE;
- }
- setMonitor((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 handlePause = async () => {
- await pauseMonitor({ monitorId, triggerUpdate });
- };
-
- const onSubmit = async (event) => {
- event.preventDefault();
- await updateMonitor({ monitor, redirect: "/pagespeed" });
- };
-
- const handleRemove = async (event) => {
- event.preventDefault();
- await deleteMonitor({ monitor, redirect: "/pagespeed" });
- };
-
- return (
-
- {Object.keys(monitor).length === 0 ? (
-
- ) : (
- <>
-
-
-
-
-
- {monitor.name}
-
-
-
-
-
-
-
-
- {monitor.url?.replace(/^https?:\/\//, "") || "..."}
-
-
- {t("editing")}
-
-
-
-
-
-
-
-
-
-
-
- {t("settingsGeneralSettings")}
-
-
- {t("pageSpeedConfigureSettingsDescription")}
-
-
- .Mui-disabled)": {
- backgroundColor: theme.palette.tertiary.main,
- },
- }}
- >
-
-
-
-
-
-
- {t("notificationConfig.title")}
-
- {t("notificationConfig.description")}
-
-
-
-
-
-
-
- {t("distributedUptimeCreateAdvancedSettings")}
-
-
-
-
-
-
-
-
-
-
- >
- )}
-
- );
-};
-
-export default PageSpeedConfigure;
diff --git a/client/src/Pages/PageSpeed/Create/index.jsx b/client/src/Pages/PageSpeed/Create/index.jsx
index 5cc4a1a54..78709093a 100644
--- a/client/src/Pages/PageSpeed/Create/index.jsx
+++ b/client/src/Pages/PageSpeed/Create/index.jsx
@@ -1,19 +1,20 @@
-//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";
+// Components
+import { Box, Stack, Tooltip, Typography, Button, ButtonGroup } from "@mui/material";
import ConfigBox from "../../../Components/ConfigBox";
-import Radio from "../../../Components/Inputs/Radio";
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 } from "react";
+import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import { monitorValidation } from "../../../Validation/validation";
import { parseDomainName } from "../../../Utils/monitorUtils";
@@ -21,79 +22,130 @@ 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;
+import { useParams } from "react-router";
+import { useMonitorUtils } from "../../../Hooks/useMonitorUtils";
+import {
+ useCreateMonitor,
+ useFetchMonitorById,
+ useDeleteMonitor,
+ useUpdateMonitor,
+ usePauseMonitor,
+} from "../../../Hooks/monitorHooks";
-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 PageSpeedSetup = () => {
+ const { monitorId } = useParams();
+ const isCreate = typeof monitorId === "undefined";
+ const CRUMBS = [
+ { name: "pagespeed", path: "/pagespeed" },
+ ...(isCreate
+ ? [{ name: "create", path: `/pagespeed/create` }]
+ : [
+ { name: "details", path: `/pagespeed/${monitorId}` },
+ { name: "configure", path: `/pagespeed/configure/${monitorId}` },
+ ]),
+ ];
+ // States
+ const [monitor, setMonitor] = useState(
+ isCreate
+ ? {
+ url: "",
+ name: "",
+ type: "pagespeed",
+ notifications: [],
+ interval: 180000,
+ }
+ : {}
+ );
const [https, setHttps] = useState(true);
const [errors, setErrors] = useState({});
- const { user } = useSelector((state) => state.auth);
- const [notifications, notificationsAreLoading, error] = useGetNotificationsByTeamId();
+ const [isOpen, setIsOpen] = useState(false);
+ const [updateTrigger, setUpdateTrigger] = useState(false);
// Setup
+ const { t } = useTranslation();
const theme = useTheme();
+ // Constants
+ const MS_PER_MINUTE = 60000;
+ const FREQUENCIES = [
+ { _id: 3, name: t("time.threeMinutes") },
+ { _id: 5, name: t("time.fiveMinutes") },
+ { _id: 10, name: t("time.tenMinutes") },
+ { _id: 20, name: t("time.twentyMinutes") },
+ { _id: 60, name: t("time.oneHour") },
+ { _id: 1440, name: t("time.oneDay") },
+ { _id: 10080, name: t("time.oneWeek") },
+ ];
+
+ const { user } = useSelector((state) => state.auth);
+ const { statusColor, pagespeedStatusMsg, determineState } = useMonitorUtils();
+ const [notifications, notificationsAreLoading, notificationsError] =
+ useGetNotificationsByTeamId();
+
+ // Hooks for API actions
+ const [isLoading] = useFetchMonitorById({ monitorId, setMonitor, updateTrigger });
const [createMonitor, isCreating] = useCreateMonitor();
+ const [deleteMonitor, isDeleting] = useDeleteMonitor();
+ const [updateMonitor, isUpdating] = useUpdateMonitor();
+ const [pauseMonitor, isPausing] = usePauseMonitor();
// 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,
- };
+ if (isCreate) {
+ let form = {
+ url: `http${https ? "s" : ""}://` + monitor.url,
+ name: monitor.name === "" ? monitor.url : monitor.name,
+ type: monitor.type,
+ interval: monitor.interval,
+ };
- const { error } = monitorValidation.validate(form, {
- abortEarly: false,
- });
+ 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: t("checkFormError") });
+ return;
+ }
- 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" });
+ } else {
+ const monitorParams = {
+ url: monitor.url,
+ name: monitor.name === "" ? monitor.url : monitor.name,
+ type: monitor.type,
+ interval: monitor.interval,
+ };
+ const { error } = monitorValidation.validate(monitorParams, { abortEarly: false });
+ if (error) {
+ const newErrors = {};
+ error.details.forEach((err) => {
+ newErrors[err.path[0]] = err.message;
+ });
+ setErrors(newErrors);
+ createToast({ body: t("checkFormError") });
+ return;
+ }
+ await updateMonitor({ monitor, redirect: "/pagespeed" });
}
-
- form = {
- ...form,
- description: form.name,
- notifications: monitor.notifications,
- };
-
- await createMonitor({ monitor: form, redirect: "/pagespeed" });
};
const handleChange = (event) => {
- const { value, name } = event.target;
+ let { value, name } = event.target;
+
+ if (name === "interval") {
+ value = value * MS_PER_MINUTE;
+ }
+
setMonitor({
...monitor,
[name]: value,
@@ -103,7 +155,6 @@ const CreatePageSpeed = () => {
{ [name]: value },
{ abortEarly: false }
);
-
setErrors((prev) => ({
...prev,
...(error ? { [name]: error.details[0].message } : { [name]: undefined }),
@@ -111,59 +162,179 @@ const CreatePageSpeed = () => {
};
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 { name, value } = event.target;
+ if (name === "url" && monitor.name === "") {
+ setMonitor((prev) => ({ ...prev, name: parseDomainName(value) }));
}
};
- const { t } = useTranslation();
+ const triggerUpdate = () => {
+ setUpdateTrigger(!updateTrigger);
+ };
+
+ const handlePause = async () => {
+ await pauseMonitor({ monitorId, triggerUpdate });
+ };
+
+ const handleRemove = async (event) => {
+ event.preventDefault();
+ await deleteMonitor({ monitor, redirect: "/pagespeed" });
+ };
+
+ const isBusy = isLoading || isCreating || isDeleting || isUpdating || isPausing;
+
+ if (Object.keys(monitor).length === 0) {
+ return ;
+ }
return (
-
-
- {t("createYour")}{" "}
-
-
- {t("pageSpeedMonitor")}
-
-
+
+
+
+ {!isCreate ? monitor.name : t("createYour") + " "}
+
+ {isCreate ? (
+
+ {t("pageSpeedMonitor")}
+
+ ) : (
+ <>>
+ )}
+
+ {!isCreate && (
+
+
+
+
+
+
+
+ {monitor.url?.replace(/^https?:\/\//, "") || "..."}
+
+
+ {t("editing")}
+
+
+ )}
+
+ {!isCreate && (
+
+
+
+
+ )}
+
{
>
{t("settingsGeneralSettings")}
- {t("distributedUptimeCreateSelectURL")}
+
+ {t("pageSpeedConfigureSettingsDescription")}
+
-
+ .Mui-disabled)": {
+ backgroundColor: theme.palette.tertiary.main,
+ },
+ }}
+ >
}
- placeholder="google.com"
- value={monitor.url}
+ label={!isCreate ? t("url") : t("urlMonitor")}
+ startAdornment={isCreate ? : undefined}
+ placeholder="random.website.com"
+ value={monitor.url || ""}
onChange={handleChange}
- onBlur={handleBlur}
- error={errors["url"] ? true : false}
+ onBlur={isCreate ? handleBlur : undefined}
+ error={!!errors["url"]}
helperText={errors["url"]}
+ disabled={!isCreate}
/>
-
-
-
- {t("distributedUptimeCreateChecks")}
-
-
- {t("distributedUptimeCreateChecksDescription")}
-
-
-
-
-
-
-
-
-
+ {isCreate && (
+
+
+
+ {t("distributedUptimeCreateChecks")}
+
+
+ {t("distributedUptimeCreateChecksDescription")}
+
+
+
+
+
+
+
+
+
+
+ {errors["type"] ? (
+
+
+ {errors["type"]}
+
+
+ ) : (
+ ""
+ )}
- {errors["type"] ? (
-
-
- {errors["type"]}
-
-
- ) : (
- ""
- )}
-
-
+
+ )}
{
@@ -280,33 +463,47 @@ const CreatePageSpeed = () => {
{t("distributedUptimeCreateAdvancedSettings")}
-
+
+ {!isCreate && (
+