diff --git a/Client/src/App.jsx b/Client/src/App.jsx index 82c8203a8..245d8f153 100644 --- a/Client/src/App.jsx +++ b/Client/src/App.jsx @@ -41,6 +41,8 @@ import { logger } from "./Utils/Logger"; // Import the logger import { networkService } from "./main"; import { Infrastructure } from "./Pages/Infrastructure"; import InfrastructureDetails from "./Pages/Infrastructure/Details"; +import ConfigureInfrastructureMonitor from "./Pages/Infrastructure/Configure"; + function App() { const AdminCheckedRegister = withAdminCheck(Register); const MonitorsWithAdminProp = withAdminProp(Monitors); @@ -138,6 +140,10 @@ function App() { path="infrastructure/:monitorId" element={} /> + } + /> { try { - const { authToken, monitor } = data; + const { authToken, monitorId } = data; const res = await networkService.deleteMonitorById({ authToken: authToken, - monitorId: monitor._id, + monitorId: monitorId, }); return res.data; } catch (error) { @@ -236,6 +246,9 @@ const infrastructureMonitorsSlice = createSlice({ state.success = null; state.msg = null; }, + resetInfrastructureMonitorFormAction: (state) => { + state.formAction = initialState.formAction; + }, }, extraReducers: (builder) => { builder @@ -245,6 +258,7 @@ const infrastructureMonitorsSlice = createSlice({ .addCase(getInfrastructureMonitorsByTeamId.pending, (state) => { state.isLoading = true; + state.success = false; }) .addCase(getInfrastructureMonitorsByTeamId.fulfilled, (state, action) => { state.isLoading = false; @@ -264,6 +278,7 @@ const infrastructureMonitorsSlice = createSlice({ // ***************************************************** .addCase(createInfrastructureMonitor.pending, (state) => { state.isLoading = true; + state.success = false; }) .addCase(createInfrastructureMonitor.fulfilled, (state, action) => { state.isLoading = false; @@ -282,6 +297,7 @@ const infrastructureMonitorsSlice = createSlice({ // ***************************************************** .addCase(checkInfrastructureEndpointResolution.pending, (state) => { state.isLoading = true; + state.success = false; }) .addCase(checkInfrastructureEndpointResolution.fulfilled, (state, action) => { state.isLoading = false; @@ -299,12 +315,15 @@ const infrastructureMonitorsSlice = createSlice({ // Get Monitor By Id // ***************************************************** .addCase(getInfrastructureMonitorById.pending, (state) => { + state.formAction = FormAction.GET; state.isLoading = true; + state.success = false; }) .addCase(getInfrastructureMonitorById.fulfilled, (state, action) => { state.isLoading = false; state.success = action.payload.success; state.msg = action.payload.msg; + state.selectedInfraMonitor = action.payload.data; }) .addCase(getInfrastructureMonitorById.rejected, (state, action) => { state.isLoading = false; @@ -318,6 +337,7 @@ const infrastructureMonitorsSlice = createSlice({ // ***************************************************** .addCase(updateInfrastructureMonitor.pending, (state) => { state.isLoading = true; + state.success = false; }) .addCase(updateInfrastructureMonitor.fulfilled, (state, action) => { state.isLoading = false; @@ -336,7 +356,9 @@ const infrastructureMonitorsSlice = createSlice({ // Delete Monitor // ***************************************************** .addCase(deleteInfrastructureMonitor.pending, (state) => { + state.formAction = FormAction.DELETE; state.isLoading = true; + state.success = false; }) .addCase(deleteInfrastructureMonitor.fulfilled, (state, action) => { state.isLoading = false; @@ -355,6 +377,7 @@ const infrastructureMonitorsSlice = createSlice({ // ***************************************************** .addCase(deleteInfrastructureMonitorChecksByTeamId.pending, (state) => { state.isLoading = true; + state.success = false; }) .addCase(deleteInfrastructureMonitorChecksByTeamId.fulfilled, (state, action) => { state.isLoading = false; @@ -373,11 +396,13 @@ const infrastructureMonitorsSlice = createSlice({ // ***************************************************** .addCase(pauseInfrastructureMonitor.pending, (state) => { state.isLoading = true; + state.success = false; }) .addCase(pauseInfrastructureMonitor.fulfilled, (state, action) => { state.isLoading = false; state.success = action.payload.success; state.msg = action.payload.msg; + state.selectedInfraMonitor = action.payload.data; }) .addCase(pauseInfrastructureMonitor.rejected, (state, action) => { state.isLoading = false; @@ -391,6 +416,7 @@ const infrastructureMonitorsSlice = createSlice({ // ***************************************************** .addCase(deleteAllInfrastructureMonitors.pending, (state) => { state.isLoading = true; + state.success = false; }) .addCase(deleteAllInfrastructureMonitors.fulfilled, (state, action) => { state.isLoading = false; @@ -405,7 +431,7 @@ const infrastructureMonitorsSlice = createSlice({ }, }); -export const { setInfrastructureMonitors, clearInfrastructureMonitorState } = +export const { clearInfrastructureMonitorState, resetInfrastructureMonitorFormAction } = infrastructureMonitorsSlice.actions; export default infrastructureMonitorsSlice.reducer; diff --git a/Client/src/Pages/Infrastructure/Configure/index.jsx b/Client/src/Pages/Infrastructure/Configure/index.jsx new file mode 100644 index 000000000..6dfa07988 --- /dev/null +++ b/Client/src/Pages/Infrastructure/Configure/index.jsx @@ -0,0 +1,490 @@ +import { useEffect, useState } from "react"; +import { Box, Stack, Tooltip, Typography } from "@mui/material"; +import LoadingButton from "@mui/lab/LoadingButton"; +import { useSelector, useDispatch } from "react-redux"; +import { infrastructureMonitorValidation } from "../../../Validation/validation"; +import { parseDomainName } from "../../../Utils/monitorUtils"; +import { + getInfrastructureMonitorById, + pauseInfrastructureMonitor, + deleteInfrastructureMonitor, + FormAction, +} from "../../../Features/InfrastructureMonitors/infrastructureMonitorsSlice"; +import { useNavigate, useParams } from "react-router-dom"; +import { useTheme } from "@emotion/react"; +import { createToast } from "../../../Utils/toastUtils"; +import Link from "../../../Components/Link"; +import { ConfigBox } from "../../Monitors/styled"; +import TextInput from "../../../Components/Inputs/TextInput"; +import Select from "../../../Components/Inputs/Select"; +import Checkbox from "../../../Components/Inputs/Checkbox"; +import Breadcrumbs from "../../../Components/Breadcrumbs"; +import { buildErrors } from "../../../Validation/error"; +import { capitalizeFirstLetter } from "../../../Utils/stringUtils"; +import { CustomThreshold } from "../CreateMonitor/CustomThreshold"; +import useUtils from "../../Monitors/utils"; +import PulseDot from "../../../Components/Animated/PulseDot"; +import PauseIcon from "../../../assets/icons/pause-icon.svg?react"; +import ResumeIcon from "../../../assets/icons/resume-icon.svg?react"; +import Dialog from "../../../Components/Dialog"; +import { MS_PER_MINUTE } from "../../../Utils/timeUtils"; +import { HARDWARE_MONITOR_TYPES, THRESHOLD_FIELD_PREFIX } from "../constants"; + +const ConfigureInfrastructureMonitor = () => { + const { user, authToken } = useSelector((state) => state.auth); + const { monitorId } = useParams(); + const { isLoading, selectedInfraMonitor, success, formAction } = useSelector( + (state) => state.infrastructureMonitors + ); + const [infrastructureMonitor, setInfrastructureMonitor] = useState(null); + const [isOpen, setIsOpen] = useState(false); + const { statusColor, statusMsg, determineState } = useUtils(); + const [errors, setErrors] = useState({}); + const dispatch = useDispatch(); + const navigate = useNavigate(); + const theme = useTheme(); + const idMap = { + "notify-email-default": "notification-email", + }; + + const alertErrKeyLen = Object.keys(errors).filter((k) => + k.startsWith(THRESHOLD_FIELD_PREFIX) + ).length; + + useEffect(() => { + if (!infrastructureMonitor) { + dispatch(getInfrastructureMonitorById({ authToken, monitorId })); + } + }, [monitorId, authToken, dispatch]); + + useEffect(() => { + if (formAction === FormAction.GET && !isLoading && success === false) + navigate("/not-found", { replace: true }); + }, [success, isLoading, navigate]); + + useEffect(() => { + if (selectedInfraMonitor) { + setInfrastructureMonitor(selectedInfraMonitor); + } + }, [selectedInfraMonitor]); + + useEffect(() => { + if (formAction === FormAction.DELETE && !isLoading) { + if (success) { + navigate("/infrastructure", { replace: true }); + } else { + createToast({ body: "Failed to delete monitor." }); + } + } + }, [formAction, isLoading, success, navigate]); + + const handlePause = () => { + dispatch(pauseInfrastructureMonitor({ authToken, monitorId })); + }; + + const handleRemove = () => { + dispatch(deleteInfrastructureMonitor({ authToken, monitorId })); + }; + + const handleCustomAlertCheckChange = (event) => { + const { value, id } = event.target; + setInfrastructureMonitor((prev) => { + const newState = { + [id]: prev[id] == undefined && value == "on" ? true : !prev[id], + }; + return { + ...prev, + ...newState, + [THRESHOLD_FIELD_PREFIX + id]: newState[id] + ? prev[THRESHOLD_FIELD_PREFIX + id] + : "", + }; + }); + // Remove the error if unchecked + setErrors((prev) => { + return buildErrors(prev, [THRESHOLD_FIELD_PREFIX + id]); + }); + }; + + const handleBlur = (event, appendID) => { + event.preventDefault(); + const { value, id } = event.target; + + let name = idMap[id] ?? id; + if (name === "url" && infrastructureMonitor?.name === "") { + setInfrastructureMonitor((prev) => ({ + ...prev, + name: parseDomainName(value), + })); + } + + if (id?.startsWith("notify-email-")) return; + const { error } = infrastructureMonitorValidation.validate( + { [id ?? appendID]: value }, + { + abortEarly: false, + } + ); + setErrors((prev) => { + return buildErrors(prev, id ?? appendID, error); + }); + }; + + const handleChange = (event, appendedId) => { + event.preventDefault(); + const { value, id } = event.target; + let name = appendedId ?? idMap[id] ?? id; + if (name.includes("notification-")) { + name = name.replace("notification-", ""); + let hasNotif = infrastructureMonitor.notifications.some( + (notification) => notification.type === name + ); + setInfrastructureMonitor((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 { + setInfrastructureMonitor((prev) => ({ + ...prev, + [name]: value, + })); + } + }; + + // TODO: add update infrastructure monitor functionalityf + const handleUpdateInfrastructureMonitor = () => { + console.log("Update infrastructure monitor..."); + }; + + //select values + const frequencies = [ + { _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" }, + ]; + + return ( + infrastructureMonitor && ( + + + + + + + {infrastructureMonitor.name} + + + + + + + + + {infrastructureMonitor.url?.replace(/^https?:\/\//, "") || "..."} + + + Editting... + + + + + + {infrastructureMonitor?.isActive ? ( + <> + + Pause + + ) : ( + <> + + Resume + + )} + + setIsOpen(true)} + > + Remove + + + + + + + General settings + + Here you can select the URL of the host, together with the friendly name + and authorization secret to connect to the server agent. + + + The server you are monitoring must be running the{" "} + + + + + + + + + + + + + Incident notifications + + When there is an incident, notify users. + + + + When there is a new incident, + notification.type === "email" + )} + value={user?.email} + onChange={(e) => handleChange(e)} + onBlur={handleBlur} + /> + + + + + + Customize alerts + + Send a notification to user(s) when thresholds exceed a specified + percentage. + + + + {HARDWARE_MONITOR_TYPES.map((type, idx) => ( + + ))} + {alertErrKeyLen > 0 && ( + + { + errors[ + THRESHOLD_FIELD_PREFIX + + HARDWARE_MONITOR_TYPES.filter( + (type) => errors[THRESHOLD_FIELD_PREFIX + type] + )[0] + ] + } + + )} + + + + + Advanced settings + + +