//Components import { Box, Button, ButtonGroup, FormControlLabel, Stack, Switch, Tooltip, Typography, } from "@mui/material"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import TextInput from "../../../Components/Inputs/TextInput"; import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments"; import Radio from "../../../Components/Inputs/Radio"; import Select from "../../../Components/Inputs/Select"; import ConfigBox from "../../../Components/ConfigBox"; import NotificationsConfig from "../../../Components/NotificationConfig"; import Checkbox from "../../../Components/Inputs/Checkbox"; import Dialog from "../../../Components/Dialog"; import PulseDot from "../../../Components/Animated/PulseDot"; import SkeletonLayout from "./skeleton"; // Utils import { useTheme } from "@emotion/react"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { monitorValidation } from "../../../Validation/validation"; import { createToast } from "../../../Utils/toastUtils"; import { PauseOutlined as PauseOutlinedIcon, PlayArrowOutlined as PlayArrowOutlinedIcon, } from "@mui/icons-material"; import { useMonitorUtils } from "../../../Hooks/useMonitorUtils"; import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications"; import { useParams } from "react-router-dom"; import { useCreateMonitor, useDeleteMonitor, useUpdateMonitor, usePauseMonitor, useFetchMonitorById, } from "../../../Hooks/monitorHooks"; /** * Parses a URL string and returns a URL object. * * @param {string} url - The URL string to parse. * @returns {URL} - The parsed URL object if valid, otherwise an empty string. */ const parseUrl = (url) => { try { return new URL(url); } catch (error) { return null; } }; /** * Create page renders monitor creation or configuration views. * @component */ const UptimeCreate = () => { const { monitorId } = useParams(); const isCreate = typeof monitorId === "undefined"; // States const [monitor, setMonitor] = useState({ type: "http", matchMethod: "equal", expectedValue: "", jsonPath: "", notifications: [], interval: 60000, ignoreTlsErrors: false, ...(isCreate ? { url: "", name: "" } : { port: undefined }), }); const [errors, setErrors] = useState({}); const [https, setHttps] = useState(true); const [isOpen, setIsOpen] = useState(false); const [useAdvancedMatching, setUseAdvancedMatching] = useState(false); const [updateTrigger, setUpdateTrigger] = useState(false); const triggerUpdate = () => { setUpdateTrigger(!updateTrigger); }; // Hooks const [notifications, notificationsAreLoading, notificationsError] = useGetNotificationsByTeamId(); const { determineState, statusColor } = useMonitorUtils(); // Network const [isLoading] = useFetchMonitorById({ monitorId, setMonitor, updateTrigger: true, }); const [createMonitor, isCreating] = useCreateMonitor(); const [pauseMonitor, isPausing] = usePauseMonitor({}); const [deleteMonitor, isDeleting] = useDeleteMonitor(); const [updateMonitor, isUpdating] = useUpdateMonitor(); // Setup const theme = useTheme(); const { t } = useTranslation(); // Constants const MS_PER_MINUTE = 60000; const FREQUENCIES = [ { _id: 1, name: t("time.oneMinute") }, { _id: 2, name: t("time.twoMinutes") }, { _id: 3, name: t("time.threeMinutes") }, { _id: 4, name: t("time.fourMinutes") }, { _id: 5, name: t("time.fiveMinutes") }, ]; const CRUMBS = [ { name: "uptime", path: "/uptime" }, ...(isCreate ? [{ name: "create", path: `/uptime/create` }] : [ { name: "details", path: `/uptime/${monitorId}` }, { name: "configure", path: `/uptime/configure/${monitorId}` }, ]), ]; const matchMethodOptions = [ { _id: "equal", name: t("matchMethodOptions.equal") }, { _id: "include", name: t("matchMethodOptions.include") }, { _id: "regex", name: t("matchMethodOptions.regex") }, ]; const expectedValuePlaceholders = { regex: t("matchMethodOptions.regexPlaceholder"), equal: t("matchMethodOptions.equalPlaceholder"), include: t("matchMethodOptions.includePlaceholder"), }; const monitorTypeMaps = { http: { label: t("monitorType.http.label"), placeholder: t("monitorType.http.placeholder"), namePlaceholder: t("monitorType.http.namePlaceholder"), }, ping: { label: t("monitorType.ping.label"), placeholder: t("monitorType.ping.placeholder"), namePlaceholder: t("monitorType.ping.namePlaceholder"), }, docker: { label: t("monitorType.docker.label"), placeholder: t("monitorType.docker.placeholder"), namePlaceholder: t("monitorType.docker.namePlaceholder"), }, port: { label: t("monitorType.port.label"), placeholder: t("monitorType.port.placeholder"), namePlaceholder: t("monitorType.port.namePlaceholder"), }, }; // Handlers const onSubmit = async (event) => { event.preventDefault(); const { notifications, ...rest } = monitor; let form = {}; if (isCreate) { form = { ...rest, url: monitor.type === "http" ? `http${https ? "s" : ""}://` + monitor.url : monitor.url, port: monitor.type === "port" ? monitor.port : undefined, name: monitor.name || monitor.url.substring(0, 50), type: monitor.type, interval: monitor.interval, }; } else { form = { _id: monitor._id, url: monitor.url, name: monitor.name || monitor.url.substring(0, 50), type: monitor.type, matchMethod: monitor.matchMethod, expectedValue: monitor.expectedValue, jsonPath: monitor.jsonPath, interval: monitor.interval, teamId: monitor.teamId, userId: monitor.userId, port: monitor.type === "port" ? monitor.port : undefined, ignoreTlsErrors: monitor.ignoreTlsErrors, }; } if (!useAdvancedMatching) { form.matchMethod = isCreate ? undefined : ""; form.expectedValue = isCreate ? undefined : ""; form.jsonPath = isCreate ? undefined : ""; } 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; } form = { ...form, description: monitor.name || monitor.url, notifications: monitor.notifications, }; if (isCreate) { await createMonitor({ monitor: form, redirect: "/uptime" }); } else { await updateMonitor({ monitor: form, redirect: "/uptime" }); } }; const onChange = (event) => { let { name, value, checked } = event.target; if (name === "ignoreTlsErrors") { value = checked; } if (name === "useAdvancedMatching") { setUseAdvancedMatching(checked); return; } if (name === "interval") { value = value * MS_PER_MINUTE; } setMonitor((prev) => ({ ...prev, [name]: value })); const { error } = monitorValidation.validate( { type: monitor.type, [name]: value }, { abortEarly: false } ); setErrors((prev) => ({ ...prev, ...(error ? { [name]: error.details[0].message } : { [name]: undefined }), })); }; const handlePause = async () => { await pauseMonitor({ monitorId, triggerUpdate }); }; const handleRemove = async (event) => { event.preventDefault(); await deleteMonitor({ monitor, redirect: "/uptime" }); }; const isBusy = isLoading || isCreating || isDeleting || isUpdating || isPausing; const displayInterval = monitor?.interval / MS_PER_MINUTE || 1; const parsedUrl = parseUrl(monitor?.url); const protocol = parsedUrl?.protocol?.replace(":", "") || ""; useEffect(() => { if (!isCreate) { if (monitor.matchMethod) { setUseAdvancedMatching(true); } else { setUseAdvancedMatching(false); } } }, [monitor, isCreate]); if (Object.keys(monitor).length === 0) { return ; } return ( {!isCreate ? monitor.name : t("createYour") + " "} {isCreate && ( {t("monitor")} )} {!isCreate && ( {monitor.url?.replace(/^https?:\/\//, "") || "..."} {t("editing")} )} {!isCreate && ( )} {isCreate && ( {t("distributedUptimeCreateChecks")} {t("distributedUptimeCreateChecksDescription")} {monitor.type === "http" ? ( ) : ( "" )} {errors["type"] ? ( {errors["type"]} ) : ( "" )} )} {t("settingsGeneralSettings")} {isCreate ? t(`uptimeGeneralInstructions.${monitor.type}`) : t("distributedUptimeCreateSelectURL")} ) } helperText={errors["url"]} onChange={onChange} disabled={!isCreate} /> {t("notificationConfig.title")} {t("notificationConfig.description")} {t("ignoreTLSError")} {t("ignoreTLSErrorDescription")} } label={t("tlsErrorIgnored")} /> {t("distributedUptimeCreateAdvancedSettings")} {t("uptimeCreate")} {t("uptimeCreateJsonPath") + " "} jmespath.org {" " + t("uptimeCreateJsonPathQuery")} )} {!isCreate && ( setIsOpen(false)} confirmationButtonLabel={t("delete")} onConfirm={handleRemove} isLoading={isLoading} /> )} ); }; export default UptimeCreate;