import { useState, useEffect } from "react"; import { Box, Button, ButtonGroup, Stack, Typography } from "@mui/material"; import LoadingButton from "@mui/lab/LoadingButton"; import { useSelector, useDispatch } from "react-redux"; import { monitorValidation } from "../../../Validation/validation"; import { createUptimeMonitor } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; import { checkEndpointResolution } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; import { useNavigate, useParams } from "react-router-dom"; import { useTheme } from "@emotion/react"; import { createToast } from "../../../Utils/toastUtils"; import { logger } from "../../../Utils/Logger"; import { ConfigBox } from "../styled"; import Radio from "../../../Components/Inputs/Radio"; import Field from "../../../Components/Inputs/Field"; import Select from "../../../Components/Inputs/Select"; import Checkbox from "../../../Components/Inputs/Checkbox"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import { getUptimeMonitorById } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; import "./index.css"; const CreateMonitor = () => { const MS_PER_MINUTE = 60000; const { user, authToken } = useSelector((state) => state.auth); const { monitors, isLoading } = useSelector((state) => state.uptimeMonitors); const dispatch = useDispatch(); const navigate = useNavigate(); const theme = useTheme(); const idMap = { "monitor-url": "url", "monitor-name": "name", "monitor-checks-http": "type", "monitor-checks-ping": "type", "monitor-checks-docker": "type", "notify-email-default": "notification-email", }; const monitorTypeMaps = { http: { label: "URL to monitor", placeholder: "google.com", namePlaceholder: "Google", }, ping: { label: "IP address to monitor", placeholder: "1.1.1.1", namePlaceholder: "Google", }, docker: { label: "Container ID", placeholder: "abc123", namePlaceholder: "My Container", }, }; const { monitorId } = useParams(); const [monitor, setMonitor] = useState({ url: "", name: "", type: "http", notifications: [], interval: 1, }); const [https, setHttps] = useState(true); const [errors, setErrors] = useState({}); useEffect(() => { const fetchMonitor = async () => { if (monitorId) { const action = await dispatch(getUptimeMonitorById({ authToken, monitorId })); if (action.payload.success) { const data = action.payload.data; const { name, ...rest } = data; //data.name is read-only if (rest.type === "http") { const url = new URL(rest.url); rest.url = url.host; } rest.name = `${name} (Clone)`; rest.interval /= MS_PER_MINUTE; setMonitor({ ...rest, }); } else { navigate("/not-found", { replace: true }); createToast({ body: "There was an error cloning the monitor.", }); } } }; fetchMonitor(); }, [monitorId, authToken, monitors]); const handleChange = (event, name) => { const { value, id } = event.target; if (!name) name = idMap[id]; if (name.includes("notification-")) { name = name.replace("notification-", ""); let hasNotif = monitor.notifications.some( (notification) => notification.type === name ); setMonitor((prev) => { const notifs = [...prev.notifications]; if (hasNotif) { return { ...prev, notifications: notifs.filter((notif) => notif.type !== name), }; } else { return { ...prev, notifications: [ ...notifs, name === "email" ? { type: name, address: value } : // TODO - phone number { type: name, phone: value }, ], }; } }); } else { setMonitor((prev) => ({ ...prev, [name]: value, })); const { error } = monitorValidation.validate( { [name]: value }, { abortEarly: false } ); setErrors((prev) => { const updatedErrors = { ...prev }; if (error) updatedErrors[name] = error.details[0].message; else delete updatedErrors[name]; return updatedErrors; }); } }; const handleCreateMonitor = async (event) => { event.preventDefault(); //obj to submit let form = { url: //preprending protocol for url monitor.type === "http" ? `http${https ? "s" : ""}://` + monitor.url : 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: "Error validation data." }); } else { if (monitor.type === "http") { const checkEndpointAction = await dispatch( checkEndpointResolution({ authToken, monitorURL: form.url }) ); if (checkEndpointAction.meta.requestStatus === "rejected") { createToast({ body: "The endpoint you entered doesn't resolve. Check the URL again.", }); setErrors({ url: "The entered URL is not reachable." }); return; } } form = { ...form, description: form.name, teamId: user.teamId, userId: user._id, notifications: monitor.notifications, }; const action = await dispatch(createUptimeMonitor({ authToken, monitor: form })); if (action.meta.requestStatus === "fulfilled") { createToast({ body: "Monitor created successfully!" }); navigate("/monitors"); } else { createToast({ body: "Failed to create monitor." }); } } }; //select values const frequencies = [ { _id: 1, name: "1 minute" }, { _id: 2, name: "2 minutes" }, { _id: 3, name: "3 minutes" }, { _id: 4, name: "4 minutes" }, { _id: 5, name: "5 minutes" }, ]; return ( Create your{" "} monitor General settings Here you can select the URL of the host, together with the type of monitor. Checks to perform You can always add or remove checks after adding your site. handleChange(event)} /> {monitor.type === "http" ? ( ) : ( "" )} handleChange(event)} /> handleChange(event)} /> {errors["type"] ? ( {errors["type"]} ) : ( "" )} Incident notifications When there is an incident, notify users. When there is a new incident, logger.warn("disabled")} isDisabled={true} /> notification.type === "email" )} value={user?.email} onChange={(event) => handleChange(event)} /> logger.warn("disabled")} isDisabled={true} /> {monitor.notifications.some( (notification) => notification.type === "emails" ) ? ( logger.warn("disabled")} /> You can separate multiple emails with a comma ) : ( "" )} Advanced settings