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 TextInput from "../../../Components/Inputs/TextInput"; import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments"; 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. } label={monitorTypeMaps[monitor.type].label || "URL to monitor"} https={https} placeholder={monitorTypeMaps[monitor.type].placeholder || ""} value={monitor.url} onChange={handleChange} error={errors["url"] ? true : false} helperText={errors["url"]} /> 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