diff --git a/client/src/Components/v2/common/controls/HeaderMonitorControls.tsx b/client/src/Components/v2/common/controls/HeaderMonitorControls.tsx index 17576a8b2..0db84940b 100644 --- a/client/src/Components/v2/common/controls/HeaderMonitorControls.tsx +++ b/client/src/Components/v2/common/controls/HeaderMonitorControls.tsx @@ -1,7 +1,7 @@ import Stack from "@mui/material/Stack"; import { Icon, MonitorStatus } from "@/Components/v2/design-elements"; import { Button } from "@/Components/v2/inputs"; -import { Settings, Pause, Play, Mail, Bug } from "lucide-react"; +import { Settings, Pause, Play, Mail, Bug, Trash } from "lucide-react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; @@ -9,10 +9,28 @@ import { useTheme } from "@mui/material/styles"; import { usePost } from "@/Hooks/UseApi"; import type { Monitor } from "@/Types/Monitor.js"; +interface BaseHeaderProps { + monitor: Monitor; +} + +const BaseHeader = ({ monitor, children }: React.PropsWithChildren) => { + const theme = useTheme(); + return ( + + + {children} + + ); +}; interface HeaderMonitorControlsProps { path: string; - monitor?: Monitor; + monitor?: Monitor | null; isAdmin: boolean; refetch: Function; } @@ -36,13 +54,7 @@ export const HeaderMonitorControls = ({ return null; } return ( - - + )} - + + ); +}; + +interface HeaderDeleteControlsProps { + monitor?: Monitor | null; + isAdmin: boolean; + refetch: Function; +} + +export const HeaderDeleteControls = ({ + monitor, + isAdmin, + refetch, +}: HeaderDeleteControlsProps) => { + const { t } = useTranslation(); + const navigate = useNavigate(); + const theme = useTheme(); + const { + post, + loading: isPosting, + // error: postError, + } = usePost(); + + if (!monitor) { + return null; + } + return ( + + + {isAdmin && ( + + )} + {isAdmin && ( + + )} + + ); }; diff --git a/client/src/Pages/CreateMonitor/index.tsx b/client/src/Pages/CreateMonitor/index.tsx index c38f4eb05..37741746b 100644 --- a/client/src/Pages/CreateMonitor/index.tsx +++ b/client/src/Pages/CreateMonitor/index.tsx @@ -14,6 +14,7 @@ import Link from "@mui/material/Link"; import Divider from "@mui/material/Divider"; import IconButton from "@mui/material/IconButton"; import { Trash2 } from "lucide-react"; +import { HeaderDeleteControls } from "@/Components/v2/common"; import { BasePage, ConfigBox } from "@/Components/v2/design-elements"; import { @@ -138,7 +139,7 @@ const CreateMonitorPage = () => { ? "hardware" : "http"; - const { data: existingMonitor } = useGet( + const { data: existingMonitor, refetch: refetchMonitor } = useGet( isEditMode ? `/monitors/${monitorId}` : null ); @@ -206,6 +207,11 @@ const CreateMonitorPage = () => { component="form" onSubmit={handleSubmit(onSubmit, onError)} > + {/* Monitor Type Selection - only shown for uptime monitors */} {showTypeSelector && ( { checked={field.value ?? false} onChange={(e) => field.onChange(e.target.checked)} /> - {t("pages.createMonitor.form.advanced.option.advancedMatching.label")} + + {t( + "pages.createMonitor.form.advanced.option.advancedMatching.label" + )} + )} /> diff --git a/client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx b/client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx deleted file mode 100644 index 64f82cec2..000000000 --- a/client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx +++ /dev/null @@ -1,87 +0,0 @@ -import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; -import { Box, Stack, Typography } from "@mui/material"; -import { CustomThreshold } from "./CustomThreshold/index.jsx"; -import { capitalizeFirstLetter } from "../../../../Utils/stringUtils.js"; -import { useTheme } from "@emotion/react"; -import { useTranslation } from "react-i18next"; -import PropTypes from "prop-types"; -const CustomAlertsSection = ({ - errors, - onChange, - infrastructureMonitor, - handleCheckboxChange, -}) => { - const theme = useTheme(); - const { t } = useTranslation(); - - const METRICS = ["cpu", "memory", "disk", "temperature"]; - const METRIC_PREFIX = "usage_"; - const hasAlertError = (errors) => { - return Object.keys(errors).filter((k) => k.startsWith(METRIC_PREFIX)).length > 0; - }; - const getAlertError = (errors) => { - const errorKey = Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX)); - return errorKey ? errors[errorKey] : null; - }; - return ( - - - - {t("infrastructureCustomizeAlerts")} - - - {t("infrastructureAlertNotificationDescription")} - - - - {METRICS.map((metric) => { - return ( - - ); - })} - - {hasAlertError(errors) && ( - - {getAlertError(errors)} - - )} - - - ); -}; - -CustomAlertsSection.propTypes = { - errors: PropTypes.object.isRequired, - onChange: PropTypes.func.isRequired, - infrastructureMonitor: PropTypes.object.isRequired, - handleCheckboxChange: PropTypes.func.isRequired, -}; - -export default CustomAlertsSection; diff --git a/client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx b/client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx deleted file mode 100644 index d783f7985..000000000 --- a/client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx +++ /dev/null @@ -1,127 +0,0 @@ -import { Box, Stack, Typography } from "@mui/material"; -import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; -import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx"; -import { useTheme } from "@emotion/react"; -import PropTypes from "prop-types"; - -/** - * CustomThreshold Component - * - * A reusable component that renders a checkbox with an associated numeric input field - * and an optional unit label. The input field can be enabled/disabled based on checkbox state. - * - * @component - * @param {Object} props - Component properties - * @param {string} [props.checkboxId] - Optional unique identifier for the checkbox - * @param {string} [props.checkboxName] - Optional name attribute for the checkbox - * @param {string} props.checkboxLabel - Label text for the checkbox - * @param {boolean} props.isChecked - Current checked state of the checkbox - * @param {Function} props.onCheckboxChange - Callback function when checkbox is toggled - * @param {string} props.fieldId - Unique identifier for the input field - * @param {string} [props.fieldName] - Optional name attribute for the input field - * @param {string} props.fieldValue - Current value of the input field - * @param {Function} props.onFieldChange - Callback function when input field value changes - * @param {Function} props.onFieldBlur - Callback function when input field loses focus - * @param {string} props.alertUnit - Unit label displayed next to the input field - * @param {Object} props.errors - Object containing validation errors for the field - * @param {Object} props.infrastructureMonitor - Infrastructure monitor configuration object - * - * @returns {React.ReactElement} Rendered CustomThreshold component - * - * @example - * - */ -export const CustomThreshold = ({ - checkboxId, - checkboxName, - checkboxLabel, - onCheckboxChange, - isChecked, - fieldId, - fieldName, - fieldValue, - onFieldChange, - onFieldBlur, - alertUnit, - errors, -}) => { - const theme = useTheme(); - return ( - - - - - - - - - {alertUnit} - - - - ); -}; - -CustomThreshold.propTypes = { - checkboxId: PropTypes.string, - checkboxName: PropTypes.string, - checkboxLabel: PropTypes.string.isRequired, - isChecked: PropTypes.bool.isRequired, - onCheckboxChange: PropTypes.func.isRequired, - fieldId: PropTypes.string.isRequired, - fieldName: PropTypes.string, - fieldValue: PropTypes.string.isRequired, - onFieldChange: PropTypes.func.isRequired, - onFieldBlur: PropTypes.func, - alertUnit: PropTypes.string.isRequired, - infrastructureMonitor: PropTypes.object.isRequired, - errors: PropTypes.object.isRequired, -}; diff --git a/client/src/Pages/Infrastructure/Create/Components/DiskSelection.jsx b/client/src/Pages/Infrastructure/Create/Components/DiskSelection.jsx deleted file mode 100644 index 50c4ed980..000000000 --- a/client/src/Pages/Infrastructure/Create/Components/DiskSelection.jsx +++ /dev/null @@ -1,90 +0,0 @@ -import React from "react"; -import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; -import { Box, Stack, Typography } from "@mui/material"; -import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx"; -import { useTheme } from "@emotion/react"; -import { useTranslation } from "react-i18next"; -import PropTypes from "prop-types"; - -const DiskSelection = ({ availableDisks, selectedDisks, onChange }) => { - const theme = useTheme(); - const { t } = useTranslation(); - - const handleDiskChange = (event, mountpoint) => { - const isChecked = event.target.checked; - let newSelectedDisks = []; - - if (isChecked) { - newSelectedDisks = [...selectedDisks, mountpoint]; - } else { - newSelectedDisks = selectedDisks.filter((disk) => disk !== mountpoint); - } - - onChange(newSelectedDisks); - }; - - return ( - - - - {t("v1.infrastructure.disk_selection_title")} - - - {t("v1.infrastructure.disk_selection_description")} - - - - - {!availableDisks || availableDisks.length === 0 ? ( - - {t("v1.infrastructure.disk_selection_info")} - - ) : ( - availableDisks.map((disk) => { - const identifier = disk.mountpoint || disk.device; - return ( - - - handleDiskChange(e, identifier)} - /> - - - ); - }) - )} - - - ); -}; - -DiskSelection.propTypes = { - availableDisks: PropTypes.arrayOf( - PropTypes.shape({ - mountpoint: PropTypes.string.isRequired, - }) - ), - selectedDisks: PropTypes.arrayOf(PropTypes.string).isRequired, - onChange: PropTypes.func.isRequired, -}; - -export default DiskSelection; diff --git a/client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx b/client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx deleted file mode 100644 index 2bde5f321..000000000 --- a/client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx +++ /dev/null @@ -1,83 +0,0 @@ -import { useState } from "react"; -import { Box, Button } from "@mui/material"; -import { useTheme } from "@emotion/react"; -import { useTranslation } from "react-i18next"; -import Icon from "@/Components/v1/Icon"; -import Dialog from "@/Components/v1/Dialog/index.jsx"; -import PropTypes from "prop-types"; - -const MonitorActionButtons = ({ monitor, isBusy, handlePause, handleRemove }) => { - const theme = useTheme(); - const { t } = useTranslation(); - const [isOpen, setIsOpen] = useState(false); - - return ( - - - - setIsOpen(false)} - confirmationButtonLabel={t("delete")} - onConfirm={handleRemove} - /> - - ); -}; - -MonitorActionButtons.propTypes = { - monitor: PropTypes.object.isRequired, - isBusy: PropTypes.bool.isRequired, - handlePause: PropTypes.func.isRequired, - handleRemove: PropTypes.func.isRequired, -}; - -export default MonitorActionButtons; diff --git a/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx b/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx deleted file mode 100644 index 8fa6f4ff1..000000000 --- a/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Box, Stack, Tooltip, Typography } from "@mui/material"; -import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils.js"; -import { useTheme } from "@emotion/react"; -import { useTranslation } from "react-i18next"; -import PulseDot from "@/Components/v1/Animated/PulseDot.jsx"; -import PropTypes from "prop-types"; -const MonitorStatusHeader = ({ monitor, infrastructureMonitor }) => { - const theme = useTheme(); - const { t } = useTranslation(); - const { statusColor, pagespeedStatusMsg, determineState } = useMonitorUtils(); - return ( - - - - - - - - {infrastructureMonitor.url?.replace(/^https?:\/\//, "") || "..."} - - - {t("editing")} - - - ); -}; - -MonitorStatusHeader.propTypes = { - monitor: PropTypes.object.isRequired, - infrastructureMonitor: PropTypes.object.isRequired, -}; - -export default MonitorStatusHeader; diff --git a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx deleted file mode 100644 index a38ac9fc6..000000000 --- a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx +++ /dev/null @@ -1,99 +0,0 @@ -import { useState, useCallback } from "react"; -const useInfrastructureMonitorForm = () => { - const [infrastructureMonitor, setInfrastructureMonitor] = useState({ - url: "", - name: "", - notifications: [], - notify_email: false, - interval: 0.25, - statusWindowSize: 5, - statusWindowThreshold: 60, - cpu: false, - usage_cpu: "", - memory: false, - usage_memory: "", - disk: false, - usage_disk: "", - temperature: false, - usage_temperature: "", - secret: "", - selectedDisks: [], - }); - - const onChangeForm = (name, value) => { - setInfrastructureMonitor({ - ...infrastructureMonitor, - [name]: value, - }); - }; - const handleCheckboxChange = (event) => { - setInfrastructureMonitor({ - ...infrastructureMonitor, - [event.target.name]: event.target.checked, - }); - }; - const initializeInfrastructureMonitorForCreate = useCallback((globalSettings) => { - const gt = globalSettings?.data?.settings?.globalThresholds || {}; - setInfrastructureMonitor((prev) => ({ - ...prev, - url: "", - name: "", - notifications: [], - interval: 0.25, - cpu: gt.cpu !== undefined, - usage_cpu: gt.cpu !== undefined ? gt.cpu.toString() : "", - memory: gt.memory !== undefined, - usage_memory: gt.memory !== undefined ? gt.memory.toString() : "", - disk: gt.disk !== undefined, - usage_disk: gt.disk !== undefined ? gt.disk.toString() : "", - temperature: gt.temperature !== undefined, - usage_temperature: gt.temperature !== undefined ? gt.temperature.toString() : "", - secret: "", - selectedDisks: [], - })); - }, []); - - const initializeInfrastructureMonitorForUpdate = useCallback((monitor) => { - const MS_PER_MINUTE = 60000; - const { thresholds = {} } = monitor; - setInfrastructureMonitor((prev) => ({ - ...prev, - url: monitor.url.replace(/^https?:\/\//, ""), - name: monitor.name || "", - notifications: monitor.notifications || [], - interval: monitor.interval / MS_PER_MINUTE, - statusWindowSize: monitor.statusWindowSize, - statusWindowThreshold: monitor.statusWindowThreshold, - cpu: thresholds.usage_cpu !== undefined, - usage_cpu: - thresholds.usage_cpu !== undefined ? (thresholds.usage_cpu * 100).toString() : "", - memory: thresholds.usage_memory !== undefined, - usage_memory: - thresholds.usage_memory !== undefined - ? (thresholds.usage_memory * 100).toString() - : "", - disk: thresholds.usage_disk !== undefined, - usage_disk: - thresholds.usage_disk !== undefined - ? (thresholds.usage_disk * 100).toString() - : "", - temperature: thresholds.usage_temperature !== undefined, - usage_temperature: - thresholds.usage_temperature !== undefined - ? (thresholds.usage_temperature * 100).toString() - : "", - secret: monitor.secret || "", - selectedDisks: monitor.selectedDisks || [], - })); - }, []); - return { - infrastructureMonitor, - setInfrastructureMonitor, - onChangeForm, - handleCheckboxChange, - initializeInfrastructureMonitorForCreate, - initializeInfrastructureMonitorForUpdate, - }; -}; - -export default useInfrastructureMonitorForm; diff --git a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx deleted file mode 100644 index 04279cac4..000000000 --- a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx +++ /dev/null @@ -1,84 +0,0 @@ -import { useCreateMonitor, useUpdateMonitor } from "../../../../Hooks/monitorHooks.js"; -const useInfrastructureSubmit = () => { - const [createMonitor, isCreating] = useCreateMonitor(); - const [updateMonitor, isUpdating] = useUpdateMonitor(); - const buildForm = (infrastructureMonitor, https) => { - const MS_PER_MINUTE = 60000; - - let form = { - url: `http${https ? "s" : ""}://` + infrastructureMonitor.url, - name: - infrastructureMonitor.name === "" - ? infrastructureMonitor.url - : infrastructureMonitor.name, - interval: infrastructureMonitor.interval * MS_PER_MINUTE, - statusWindowSize: infrastructureMonitor.statusWindowSize, - statusWindowThreshold: infrastructureMonitor.statusWindowThreshold, - 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 } - : {}), - ...(infrastructureMonitor.temperature - ? { usage_temperature: infrastructureMonitor.usage_temperature } - : {}), - secret: infrastructureMonitor.secret, - selectedDisks: infrastructureMonitor.selectedDisks, - }; - return form; - }; - const submitInfrastructureForm = async ( - infrastructureMonitor, - form, - isCreate, - monitorId - ) => { - const { - cpu, - usage_cpu, - memory, - usage_memory, - disk, - usage_disk, - temperature, - usage_temperature, - selectedDisks, - ...rest - } = form; - - const thresholds = { - ...(cpu ? { usage_cpu: usage_cpu / 100 } : {}), - ...(memory ? { usage_memory: usage_memory / 100 } : {}), - ...(disk ? { usage_disk: usage_disk / 100 } : {}), - ...(temperature ? { usage_temperature: usage_temperature / 100 } : {}), - }; - - const finalForm = { - ...(isCreate ? {} : { id: monitorId }), - ...rest, - description: form.name, - type: "hardware", - notifications: infrastructureMonitor.notifications, - selectedDisks, - thresholds, - }; - // Handle create or update - isCreate - ? await createMonitor({ monitor: finalForm, redirect: "/infrastructure" }) - : await updateMonitor({ monitor: finalForm, redirect: "/infrastructure" }); - }; - return { - buildForm, - submitInfrastructureForm, - isCreating, - isUpdating, - }; -}; -export default useInfrastructureSubmit; diff --git a/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx b/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx deleted file mode 100644 index f7665e65f..000000000 --- a/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx +++ /dev/null @@ -1,37 +0,0 @@ -import { useState } from "react"; -import { infrastructureMonitorValidation } from "../../../../Validation/validation.js"; -import { createToast } from "../../../../Utils/toastUtils.jsx"; -const useValidateInfrastructureForm = () => { - const [errors, setErrors] = useState({}); - - const validateField = (name, value) => { - const { error } = infrastructureMonitorValidation.validate( - { [name]: value }, - { abortEarly: false } - ); - setErrors((prev) => ({ - ...prev, - ...(error ? { [name]: error.details[0].message } : { [name]: undefined }), - })); - }; - - const validateForm = (form) => { - const { error } = infrastructureMonitorValidation.validate(form, { - abortEarly: false, - }); - - if (error) { - const newErrors = {}; - error.details.forEach((err) => { - newErrors[err.path[0]] = err.message; - }); - console.log(newErrors); - setErrors(newErrors); - createToast({ body: "Please check the form for errors." }); - return error; - } - return null; - }; - return { errors, validateField, validateForm }; -}; -export default useValidateInfrastructureForm; diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx deleted file mode 100644 index 936d87ab0..000000000 --- a/client/src/Pages/Infrastructure/Create/index.jsx +++ /dev/null @@ -1,409 +0,0 @@ -//Components -import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; -import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; -import FieldWrapper from "@/Components/v1/Inputs/FieldWrapper/index.jsx"; -import Link from "@/Components/v1/Link/index.jsx"; -import Select from "@/Components/v1/Inputs/Select/index.jsx"; -import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; -import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material"; -import { HttpAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; -import MonitorStatusHeader from "./Components/MonitorStatusHeader.jsx"; -import MonitorActionButtons from "./Components/MonitorActionButtons.jsx"; -import CustomAlertsSection from "./Components/CustomAlertsSection.jsx"; -import DiskSelection from "./Components/DiskSelection.jsx"; -// Utils -import NotificationsConfig from "@/Components/v1/NotificationConfig/index.jsx"; -import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications.js"; -import { networkService } from "../../../Utils/NetworkService.js"; -import { useParams } from "react-router-dom"; -import { useState, useEffect } from "react"; -import { useTheme } from "@emotion/react"; -import { useTranslation } from "react-i18next"; -import { - useDeleteMonitor, - useFetchGlobalSettings, - useFetchMonitorById, - usePauseMonitor, -} from "@/Hooks/monitorHooks.js"; -import useInfrastructureMonitorForm from "./hooks/useInfrastructureMonitorForm.jsx"; -import useValidateInfrastructureForm from "./hooks/useValidateInfrastructureForm.jsx"; -import useInfrastructureSubmit from "./hooks/useInfrastructureSubmit.jsx"; - -const CreateInfrastructureMonitor = () => { - const { monitorId } = useParams(); - const isCreate = typeof monitorId === "undefined"; - - const theme = useTheme(); - const { t } = useTranslation(); - - // State - const [monitor, setMonitor] = useState(null); - const [https, setHttps] = useState(false); - const [updateTrigger, setUpdateTrigger] = useState(false); - const [availableDisks, setAvailableDisks] = useState([]); - - // Fetch monitor details if editing - const [isLoading] = useFetchMonitorById({ - monitorId, - setMonitor, - updateTrigger: true, - }); - const [deleteMonitor, isDeleting] = useDeleteMonitor(); - const [globalSettings, globalSettingsLoading] = useFetchGlobalSettings(); - const [notifications, notificationsAreLoading] = useGetNotificationsByTeamId(); - const [pauseMonitor, isPausing] = usePauseMonitor(); - const { - infrastructureMonitor, - setInfrastructureMonitor, - onChangeForm, - handleCheckboxChange, - initializeInfrastructureMonitorForCreate, - initializeInfrastructureMonitorForUpdate, - } = useInfrastructureMonitorForm(); - const { errors, validateField, validateForm } = useValidateInfrastructureForm(); - const { buildForm, submitInfrastructureForm, isCreating, isUpdating } = - useInfrastructureSubmit(); - - const FREQUENCIES = [ - { _id: 0.25, name: t("time.fifteenSeconds") }, - { _id: 0.5, name: t("time.thirtySeconds") }, - { _id: 1, name: t("time.oneMinute") }, - { _id: 2, name: t("time.twoMinutes") }, - { _id: 5, name: t("time.fiveMinutes") }, - { _id: 10, name: t("time.tenMinutes") }, - ]; - const CRUMBS = [ - { name: "Infrastructure monitors", path: "/infrastructure" }, - ...(isCreate - ? [{ name: "Create", path: "/infrastructure/create" }] - : [ - { name: "Details", path: `/infrastructure/${monitorId}` }, - { name: "Configure", path: `/infrastructure/configure/${monitorId}` }, - ]), - ]; - // Populate form fields if editing - useEffect(() => { - if (isCreate) { - if (globalSettingsLoading) return; - setHttps(false); - initializeInfrastructureMonitorForCreate(globalSettings); - } else if (monitor) { - setHttps(monitor.url.startsWith("https")); - initializeInfrastructureMonitorForUpdate(monitor); - const fetchLastCheck = async () => { - try { - let disks = monitor?.recentChecks?.[0]?.disk || []; - - if (disks.length > 0) { - setAvailableDisks(disks); - return; - } - - const response = await networkService.getChecksByMonitor({ - monitorId, - dateRange: "all", - rowsPerPage: 1, - sortOrder: "desc", - }); - disks = response?.data?.data?.checks?.[0]?.disk || []; - setAvailableDisks(disks); - } catch (error) { - setAvailableDisks([]); - } - }; - - fetchLastCheck(); - } - }, [ - isCreate, - monitor, - globalSettings, - globalSettingsLoading, - initializeInfrastructureMonitorForCreate, - initializeInfrastructureMonitorForUpdate, - ]); - - // Handlers - const onSubmit = async (event) => { - event.preventDefault(); - const form = buildForm(infrastructureMonitor, https); - // When editing, exclude URL from validation since it's disabled and can't be changed - const formToValidate = isCreate ? form : { ...form, url: monitor.url }; - const error = validateForm(formToValidate); - if (error) { - return; - } - submitInfrastructureForm(infrastructureMonitor, form, isCreate, monitorId); - }; - - const triggerUpdate = () => { - setUpdateTrigger(!updateTrigger); - }; - - const onChange = (event) => { - const { value, name } = event.target; - onChangeForm(name, value); - validateField(name, value); - }; - - const handlePause = async () => { - await pauseMonitor({ monitorId, triggerUpdate }); - }; - - const handleRemove = async (event) => { - event.preventDefault(); - await deleteMonitor({ monitor, redirect: "/infrastructure" }); - }; - - const isBusy = - isLoading || - isUpdating || - isCreating || - isDeleting || - isPausing || - notificationsAreLoading; - - return ( - - - - - - - - {!isCreate ? infrastructureMonitor.name : t("createYour") + " "} - - {isCreate ? ( - - {t("monitor")} - - ) : ( - <> - )} - - {!isCreate && monitor && ( - - )} - - {!isCreate && monitor && ( - - )} - - - - - {t("settingsGeneralSettings")} - - - {t("infrastructureCreateGeneralSettingsDescription")} - - - {t("infrastructureServerRequirement")}{" "} - - - - - } - placeholder={"localhost:59232/api/v1/metrics"} - label={t("infrastructureServerUrlLabel")} - https={https} - value={infrastructureMonitor.url} - onChange={onChange} - error={errors["url"] ? true : false} - helperText={errors["url"]} - disabled={!isCreate} - /> - {isCreate && ( - - - - - - - )} - - - - - - - - {t("notificationConfig.title")} - - {t("notificationConfig.description")} - - - - - - - {t("createMonitorPage.incidentConfigTitle")} - - - {t("createMonitorPage.incidentConfigDescription")} - - - - - - - - - - {monitorId && ( - - onChangeForm("selectedDisks", newSelectedDisks) - } - /> - )} - - - - - {t("distributedUptimeCreateAdvancedSettings")} - - - - - - - - - - - {!isCreate && ( - setIsOpen(false)} - confirmationButtonLabel={t("delete")} - onConfirm={handleRemove} - /> - )} - - ); -}; - -export default PageSpeedSetup; diff --git a/client/src/Pages/PageSpeed/Create/skeleton.jsx b/client/src/Pages/PageSpeed/Create/skeleton.jsx deleted file mode 100644 index 347fcaa65..000000000 --- a/client/src/Pages/PageSpeed/Create/skeleton.jsx +++ /dev/null @@ -1,86 +0,0 @@ -import { Box, Skeleton, Stack } from "@mui/material"; -import { useTheme } from "@emotion/react"; - -/** - * Renders a skeleton layout. - * - * @returns {JSX.Element} - */ -const SkeletonLayout = () => { - const theme = useTheme(); - - return ( - <> - - - - - - - - - - - - - - - - - - - - ); -}; - -export default SkeletonLayout; diff --git a/client/src/Pages/Uptime/Create/index.css b/client/src/Pages/Uptime/Create/index.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/client/src/Pages/Uptime/Create/index.jsx b/client/src/Pages/Uptime/Create/index.jsx deleted file mode 100644 index 01fc9eb7a..000000000 --- a/client/src/Pages/Uptime/Create/index.jsx +++ /dev/null @@ -1,826 +0,0 @@ -//Components -import { - Box, - Button, - ButtonGroup, - FormControlLabel, - Stack, - Switch, - Tooltip, - Typography, -} from "@mui/material"; -import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; -import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; -import { HttpAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; -import Radio from "@/Components/v1/Inputs/Radio/index.jsx"; -import Select from "@/Components/v1/Inputs/Select/index.jsx"; -import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; -import NotificationsConfig from "@/Components/v1/NotificationConfig/index.jsx"; -import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx"; -import Dialog from "@/Components/v1/Dialog/index.jsx"; -import PulseDot from "@/Components/v1/Animated/PulseDot.jsx"; -import SkeletonLayout from "./skeleton.jsx"; - -// Utils -import PropTypes from "prop-types"; -import { useTheme } from "@emotion/react"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { monitorValidation } from "../../../Validation/validation.js"; -import { createToast } from "../../../Utils/toastUtils.jsx"; -import Icon from "@/Components/v1/Icon"; -import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js"; -import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications.js"; -import { useParams } from "react-router-dom"; -import { - useCreateMonitor, - useDeleteMonitor, - useUpdateMonitor, - usePauseMonitor, - useFetchMonitorById, - useFetchMonitorGames, -} from "../../../Hooks/monitorHooks.js"; - -/** - * Create page renders monitor creation or configuration views. - * @component - */ -const UptimeCreate = ({ isClone = false }) => { - const { monitorId } = useParams(); - const isCreate = typeof monitorId === "undefined" || isClone; - - // States - const [monitor, setMonitor] = useState({ - type: "http", - statusWindowSize: 5, - statusWindowThreshold: 60, - 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 [games, setGames] = useState({}); - const triggerUpdate = () => { - setUpdateTrigger(!updateTrigger); - }; - - // Hooks - const [notifications, notificationsAreLoading, notificationsError] = - useGetNotificationsByTeamId(); - const { determineState, statusColor } = useMonitorUtils(); - // Fetch monitor details - const [isFetchingMonitor] = useFetchMonitorById({ - monitorId, - setMonitor, - updateTrigger, - }); - - // Fetch games - const [isFetchingGames] = useFetchMonitorGames({ - setGames, - updateTrigger, - }); - - // Combine the loading states - const isLoading = isFetchingMonitor || isFetchingGames; - - 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: 0.25, name: t("time.fifteenSeconds") }, - { _id: 0.5, name: t("time.thirtySeconds") }, - { _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") }, - { _id: 10, name: t("time.tenMinutes") }, - { _id: 15, name: t("time.fifteenMinutes") }, - { _id: 30, name: t("time.thirtyMinutes") }, - ]; - - const GAMELIST = Object.entries(games).map(([key, value]) => ({ - _id: key, - name: value.name, - })); - - 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"), - }, - game: { - label: t("monitorType.game.label"), - placeholder: t("monitorType.game.placeholder"), - namePlaceholder: t("monitorType.game.namePlaceholder"), - }, - }; - - // Handlers - const onSubmit = async (event) => { - event.preventDefault(); - const { notifications, ...rest } = monitor; - - let form = {}; - if (isCreate) { - form = { - url: - monitor.type === "http" && !isClone - ? `http${https ? "s" : ""}://` + monitor.url - : monitor.url, - name: monitor.name || monitor.url.substring(0, 50), - statusWindowSize: monitor.statusWindowSize, - statusWindowThreshold: monitor.statusWindowThreshold, - type: monitor.type, - - port: - monitor.type === "port" || monitor.type === "game" ? monitor.port : undefined, - interval: monitor.interval, - matchMethod: monitor.matchMethod, - expectedValue: monitor.expectedValue, - jsonPath: monitor.jsonPath, - ignoreTlsErrors: monitor.ignoreTlsErrors, - gameId: monitor.gameId || undefined, - }; - } else { - form = { - id: monitor.id, - url: monitor.url, - name: monitor.name || monitor.url.substring(0, 50), - statusWindowSize: monitor.statusWindowSize, - statusWindowThreshold: monitor.statusWindowThreshold, - 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.type === "game" ? monitor.port : undefined, - ignoreTlsErrors: monitor.ignoreTlsErrors, - gameId: monitor.gameId || undefined, - }; - } - 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 })); - - if (name === "type") { - setErrors({}); - return; - } - - const { error } = monitorValidation.validate( - { type: monitor.type, [name]: value }, - { abortEarly: false } - ); - - setErrors((prev) => ({ - ...prev, - ...(error && error.details[0].path[0] === name - ? { [name]: error.details[0].message } - : { [name]: undefined }), - })); - }; - - const handleRemove = async (event) => { - event.preventDefault(); - const TEMP_MONITOR = { id: monitor.id }; - await deleteMonitor({ monitor: TEMP_MONITOR, redirect: "/uptime" }); - }; - - const isBusy = isLoading || isCreating || isDeleting || isUpdating || isPausing; - const displayInterval = monitor?.interval / MS_PER_MINUTE || 1; - const parsedUrl = monitor?.url; - const protocol = parsedUrl?.protocol?.replace(":", "") || ""; - - useEffect(() => { - if (!isCreate || isClone) { - 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} - /> -