// React, Redux, Router import { useTheme } from "@emotion/react"; import { useNavigate, useParams } from "react-router-dom"; import { useState, useEffect } from "react"; import { useSelector, useDispatch } from "react-redux"; // Utility and Network import { infrastructureMonitorValidation } from "../../../Validation/validation"; import { createInfrastructureMonitor, updateInfrastructureMonitor } from "../../../Features/InfrastructureMonitors/infrastructureMonitorsSlice"; import { useHardwareMonitorsFetch } from "../Details/Hooks/useHardwareMonitorsFetch"; import { capitalizeFirstLetter } from "../../../Utils/stringUtils"; import { useTranslation } from "react-i18next"; // MUI import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material"; //Components import Breadcrumbs from "../../../Components/Breadcrumbs"; import Link from "../../../Components/Link"; import ConfigBox from "../../../Components/ConfigBox"; import TextInput from "../../../Components/Inputs/TextInput"; import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments"; import { createToast } from "../../../Utils/toastUtils"; import Checkbox from "../../../Components/Inputs/Checkbox"; import Select from "../../../Components/Inputs/Select"; import { CustomThreshold } from "./Components/CustomThreshold"; const SELECT_VALUES = [ { _id: 0.25, name: "15 seconds" }, { _id: 0.5, name: "30 seconds" }, { _id: 1, name: "1 minute" }, { _id: 2, name: "2 minutes" }, { _id: 5, name: "5 minutes" }, { _id: 10, name: "10 minutes" }, ]; const METRICS = ["cpu", "memory", "disk", "temperature"]; const METRIC_PREFIX = "usage_"; const MS_PER_MINUTE = 60000; const hasAlertError = (errors) => { return Object.keys(errors).filter((k) => k.startsWith(METRIC_PREFIX)).length > 0; }; const getAlertError = (errors) => { return Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX)) ? errors[Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX))] : null; }; const CreateInfrastructureMonitor = () => { const theme = useTheme(); const { user } = useSelector((state) => state.auth); const monitorState = useSelector((state) => state.infrastructureMonitor); const dispatch = useDispatch(); const navigate = useNavigate(); const { monitorId } = useParams(); const { t } = useTranslation(); // Determine if we are creating or editing const isCreate = typeof monitorId === "undefined"; // Fetch monitor details if editing const { monitor, isLoading, networkError } = useHardwareMonitorsFetch({ monitorId }); // State const [errors, setErrors] = useState({}); const [https, setHttps] = useState(false); const [infrastructureMonitor, setInfrastructureMonitor] = useState({ url: "", name: "", notifications: [], notify_email: false, interval: 0.25, cpu: false, usage_cpu: "", memory: false, usage_memory: "", disk: false, usage_disk: "", temperature: false, usage_temperature: "", secret: "", }); // Populate form fields if editing useEffect(() => { if (isCreate || !monitor) return; setInfrastructureMonitor({ url: monitor.url.replace(/^https?:\/\//, ""), name: monitor.name || "", notifications: monitor.notifications?.filter(n => typeof n === "object") || [], notify_email: (monitor.notifications?.length ?? 0) > 0, interval: monitor.interval / MS_PER_MINUTE, cpu: monitor.thresholds?.usage_cpu !== undefined, usage_cpu: monitor.thresholds?.usage_cpu ? monitor.thresholds.usage_cpu * 100 : "", memory: monitor.thresholds?.usage_memory !== undefined, usage_memory: monitor.thresholds?.usage_memory ? monitor.thresholds.usage_memory * 100 : "", disk: monitor.thresholds?.usage_disk !== undefined, usage_disk: monitor.thresholds?.usage_disk ? monitor.thresholds.usage_disk * 100 : "", temperature: monitor.thresholds?.usage_temperature !== undefined, usage_temperature: monitor.thresholds?.usage_temperature ? monitor.thresholds.usage_temperature * 100 : "", secret: monitor.secret || "", }); setHttps(monitor.url.startsWith("https")); }, [isCreate, monitor]); // Handlers const handleCreateInfrastructureMonitor = async (event) => { event.preventDefault(); const formattedNotifications = infrastructureMonitor.notifications.map((n) => typeof n === "string" ? { type: "email", address: n } : n ); if (infrastructureMonitor.notify_email) { formattedNotifications.push({ type: "email", address: user.email }); } // Build the form let form = { url: `http${https ? "s" : ""}://` + infrastructureMonitor.url, name: infrastructureMonitor.name === "" ? infrastructureMonitor.url : infrastructureMonitor.name, interval: infrastructureMonitor.interval * MS_PER_MINUTE, cpu: infrastructureMonitor.cpu, ...(infrastructureMonitor.cpu ? { usage_cpu: infrastructureMonitor.usage_cpu } : {}), memory: infrastructureMonitor.memory, ...(infrastructureMonitor.memory ? { usage_memory: infrastructureMonitor.usage_memory } : {}), disk: infrastructureMonitor.disk, ...(infrastructureMonitor.disk ? { usage_disk: infrastructureMonitor.usage_disk } : {}), temperature: infrastructureMonitor.temperature, ...(infrastructureMonitor.temperature ? { usage_temperature: infrastructureMonitor.usage_temperature } : {}), secret: infrastructureMonitor.secret, notifications: formattedNotifications, }; const { error } = infrastructureMonitorValidation.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; } // Build the thresholds for the form const { cpu, usage_cpu, memory, usage_memory, disk, usage_disk, temperature, usage_temperature, ...rest } = form; const thresholds = { ...(infrastructureMonitor.cpu ? { usage_cpu: parseFloat(infrastructureMonitor.usage_cpu) / 100 } : {}), ...(infrastructureMonitor.memory ? { usage_memory: parseFloat(infrastructureMonitor.usage_memory) / 100 } : {}), ...(infrastructureMonitor.disk ? { usage_disk: parseFloat(infrastructureMonitor.usage_disk) / 100 } : {}), ...(infrastructureMonitor.temperature ? { usage_temperature: parseFloat(infrastructureMonitor.usage_temperature) / 100 } : {}), }; form = { ...rest, description: form.name, teamId: user.teamId, userId: user._id, type: "hardware", notifications: infrastructureMonitor.notifications, thresholds, }; // Handle create or update const action = isCreate ? await dispatch(createInfrastructureMonitor({ monitor: form })) : await dispatch(updateInfrastructureMonitor({ monitorId, monitor: form })); if (action.meta.requestStatus === "fulfilled") { createToast({ body: isCreate ? "Infrastructure monitor created successfully!" : "Infrastructure monitor updated successfully!", }); navigate("/infrastructure"); } else { createToast({ body: "Failed to save monitor." }); } }; const handleChange = (event) => { const { value, name } = event.target; setInfrastructureMonitor({ ...infrastructureMonitor, [name]: value, }); const { error } = infrastructureMonitorValidation.validate( { [name]: value }, { abortEarly: false } ); setErrors((prev) => ({ ...prev, ...(error ? { [name]: error.details[0].message } : { [name]: undefined }), })); }; const handleCheckboxChange = (event) => { const { name } = event.target; const { checked } = event.target; setInfrastructureMonitor({ ...infrastructureMonitor, [name]: checked, }); }; const handleNotifications = (event, type) => { const { value, checked } = event.target; let notifications = [...infrastructureMonitor.notifications]; if (checked) { if (!notifications.some((n) => n.type === type && n.address === value)) { notifications.push({ type, address: value }); } } else { notifications = notifications.filter((n) => !(n.type === type && n.address === value)); } setInfrastructureMonitor((prev) => ({ ...prev, notifications, notify_email: checked, })); }; return ( {t(isCreate? "infrastructureCreateYour": "infrastructureEditYour")}{" "} {t("monitor")} {t("settingsGeneralSettings")} {t("infrastructureCreateGeneralSettingsDescription")} {t("infrastructureServerRequirement")}{" "} } placeholder={"localhost:59232/api/v1/metrics"} label={t("infrastructureServerUrlLabel")} https={https} value={infrastructureMonitor.url} onChange={handleChange} error={errors["url"] ? true : false} helperText={errors["url"]} disabled={!isCreate} /> {isCreate && ( {t("infrastructureProtocol")} )} {t("distributedUptimeCreateIncidentNotification")} {t("distributedUptimeCreateIncidentDescription")} handleNotifications(event, "email")} /> {t("infrastructureCustomizeAlerts")} {t("infrastructureAlertNotificationDescription")} {METRICS.map((metric) => { return ( ); })} {/* Error text */} {hasAlertError(errors) && ( {getAlertError(errors)} )} {t("distributedUptimeCreateAdvancedSettings")}