diff --git a/Client/src/Components/Inputs/Search/index.jsx b/Client/src/Components/Inputs/Search/index.jsx index 268c83572..b1e5d74ae 100644 --- a/Client/src/Components/Inputs/Search/index.jsx +++ b/Client/src/Components/Inputs/Search/index.jsx @@ -51,12 +51,14 @@ const Search = ({ filteredBy, secondaryLabel, value, + inputValue, handleInputChange, handleChange, sx, multiple = false, isAdorned = true, error, + disabled, }) => { const theme = useTheme(); @@ -64,7 +66,8 @@ const Search = ({ { handleInputChange(newValue); }} @@ -73,6 +76,7 @@ const Search = ({ }} fullWidth freeSolo + disabled={disabled} disableClearable options={options} getOptionLabel={(option) => option[filteredBy]} @@ -178,12 +182,14 @@ Search.propTypes = { options: PropTypes.array.isRequired, filteredBy: PropTypes.string.isRequired, secondaryLabel: PropTypes.string, - value: PropTypes.string.isRequired, + value: PropTypes.array, + inputValue: PropTypes.string.isRequired, handleInputChange: PropTypes.func.isRequired, handleChange: PropTypes.func, isAdorned: PropTypes.bool, sx: PropTypes.object, error: PropTypes.string, + disabled: PropTypes.bool, }; export default Search; diff --git a/Client/src/Pages/Maintenance/CreateMaintenance/index.jsx b/Client/src/Pages/Maintenance/CreateMaintenance/index.jsx index bd9400978..bb898f143 100644 --- a/Client/src/Pages/Maintenance/CreateMaintenance/index.jsx +++ b/Client/src/Pages/Maintenance/CreateMaintenance/index.jsx @@ -1,4 +1,4 @@ -import { Box, Button, Stack, Typography } from "@mui/material"; +import { Box, Button, duration, Stack, Typography } from "@mui/material"; import { useSelector } from "react-redux"; import { useTheme } from "@emotion/react"; import { useEffect, useState } from "react"; @@ -9,6 +9,8 @@ import { DatePicker } from "@mui/x-date-pickers/DatePicker"; import { MobileTimePicker } from "@mui/x-date-pickers/MobileTimePicker"; import { maintenanceWindowValidation } from "../../../Validation/validation"; import { createToast } from "../../../Utils/toastUtils"; +import LoadingButton from "@mui/lab/LoadingButton"; + import dayjs from "dayjs"; import Select from "../../../Components/Inputs/Select"; import Field from "../../../Components/Inputs/Field"; @@ -23,14 +25,40 @@ import { MS_PER_MINUTE, MS_PER_HOUR, MS_PER_DAY, + MS_PER_WEEK, } from "../../../Utils/timeUtils"; import { useNavigate, useParams } from "react-router-dom"; +const getDurationAndUnit = (durationInMs) => { + if (durationInMs % MS_PER_DAY === 0) { + return { + duration: (durationInMs / MS_PER_DAY).toString(), + durationUnit: "days", + }; + } else if (durationInMs % MS_PER_HOUR === 0) { + return { + duration: (durationInMs / MS_PER_HOUR).toString(), + durationUnit: "hours", + }; + } else if (durationInMs % MS_PER_MINUTE === 0) { + return { + duration: (durationInMs / MS_PER_MINUTE).toString(), + durationUnit: "minutes", + }; + } else { + return { + duration: (durationInMs / MS_PER_SECOND).toString(), + durationUnit: "seconds", + }; + } +}; + const MS_LOOKUP = { seconds: MS_PER_SECOND, minutes: MS_PER_MINUTE, hours: MS_PER_HOUR, days: MS_PER_DAY, + weeks: MS_PER_WEEK, }; const REPEAT_LOOKUP = { @@ -39,6 +67,12 @@ const REPEAT_LOOKUP = { weekly: MS_PER_DAY * 7, }; +const REVERSE_REPEAT_LOOKUP = { + 0: "none", + [MS_PER_DAY]: "daily", + [MS_PER_WEEK]: "weekly", +}; + const repeatConfig = [ { _id: 0, name: "Don't repeat", value: "none" }, { @@ -104,34 +138,34 @@ const CreateMaintenance = () => { limit: -1, types: ["http", "ping", "pagespeed"], }); - setMonitors(response.data.data.monitors); + const monitors = response.data.data.monitors; + setMonitors(monitors); if (maintenanceWindowId === undefined) { return; } - const maintenanceWindow = await networkService.getMaintenanceWindowById( - { - authToken: authToken, - maintenanceWindowId: maintenanceWindowId, - } - ); - logger.info(maintenanceWindow); - // const { name, start, end, repeat, monitors } = - // maintenanceWindow.data.data.maintenanceWindow; - // const startDate = dayjs(start); - // const startTime = dayjs(start); - // const duration = dayjs(end).diff(dayjs(start), "seconds"); - // const durationUnit = "seconds"; - // setForm({ - // name, - // startDate, - // startTime, - // duration, - // durationUnit, - // repeat, - // monitors, - // }); + const res = await networkService.getMaintenanceWindowById({ + authToken: authToken, + maintenanceWindowId: maintenanceWindowId, + }); + const maintenanceWindow = res.data.data; + const { name, start, end, repeat, monitorId } = maintenanceWindow; + const startTime = dayjs(start); + const endTime = dayjs(end); + const durationInMs = endTime.diff(startTime, "milliseconds").toString(); + const { duration, durationUnit } = getDurationAndUnit(durationInMs); + const monitor = monitors.find((monitor) => monitor._id === monitorId); + setForm({ + ...form, + name, + repeat: REVERSE_REPEAT_LOOKUP[repeat], + startDate: startTime, + startTime, + duration, + durationUnit, + monitors: monitor ? [monitor] : [], + }); } catch (error) { createToast({ body: "Failed to fetch data" }); logger.error("Failed to fetch monitors", error); @@ -201,17 +235,13 @@ const CreateMaintenance = () => { newErrors[err.path[0]] = err.message; }); setErrors(newErrors); - console.log(error); + logger.error(error); return; } // Build timestamp for maintenance window from startDate and startTime - const start = dayjs(form.startDate); - start + const start = dayjs(form.startDate) .set("hour", form.startTime.hour()) - .set("minute", form.startTime.minute()) - .set("second", form.startTime.second()) - .set("millisecond", form.startTime.millisecond()); - + .set("minute", form.startTime.minute()); // Build end timestamp for maintenance window const MS_MULTIPLIER = MS_LOOKUP[form.durationUnit]; const durationInMs = form.duration * MS_MULTIPLIER; @@ -232,18 +262,32 @@ const CreateMaintenance = () => { submit.expiry = end; } - try { - await networkService.createMaintenanceWindow({ - authToken: authToken, - maintenanceWindow: submit, - }); + const requestConfig = { authToken: authToken, maintenanceWindow: submit }; + if (maintenanceWindowId !== undefined) { + requestConfig.maintenanceWindowId = maintenanceWindowId; + } + const request = + maintenanceWindowId === undefined + ? networkService.createMaintenanceWindow(requestConfig) + : networkService.editMaintenanceWindow(requestConfig); + + try { + setIsLoading(true); + await request; createToast({ body: "Successfully created maintenance window", }); navigate("/maintenance"); } catch (error) { - console.log(error); + createToast({ + body: `Failed to ${ + maintenanceWindowId === undefined ? "create" : "edit" + } maintenance window`, + }); + logger.error(error); + } finally { + setIsLoading(false); } }; @@ -252,7 +296,10 @@ const CreateMaintenance = () => { { - Create a{" "} + {`${maintenanceWindowId === undefined ? "Create a" : "Edit"}`}{" "} { options={monitors ? monitors : []} filteredBy="name" secondaryLabel={"type"} - value={search} + inputValue={search} + value={form.monitors} handleInputChange={handleSearch} handleChange={handleSelectMonitors} error={errors["monitors"]} + disabled={maintenanceWindowId !== undefined} /> @@ -499,14 +548,19 @@ const CreateMaintenance = () => { > Cancel - + {`${ + maintenanceWindowId === undefined + ? "Create maintenance" + : "Edit maintenance" + }`} + diff --git a/Client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx b/Client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx index bd9d2e316..5f71cdc39 100644 --- a/Client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx +++ b/Client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx @@ -46,6 +46,10 @@ const ActionsMenu = ({ isAdmin, maintenanceWindow, updateCallback }) => { setIsOpen(false); }; + const handleEdit = () => { + navigate(`/maintenance/create/${maintenanceWindow._id}`); + }; + const openMenu = (event) => { event.preventDefault(); event.stopPropagation(); @@ -106,7 +110,7 @@ const ActionsMenu = ({ isAdmin, maintenanceWindow, updateCallback }) => { onClick={(e) => { closeMenu(e); e.stopPropagation(); - console.log("Edit"); + handleEdit(); }} > Edit diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index 91c838070..573651f58 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -717,6 +717,33 @@ class NetworkService { ); } + /** + * ************************************ + * Edits a maintenance window + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {Object} config.maintenanceWindowId - The maintenance window id. + * @param {Object} config.maintenanceWindow - The maintenance window object to be sent in the request body. + * @returns {Promise} The response from the axios POST request. + * + */ + + async editMaintenanceWindow(config) { + return this.axiosInstance.put( + `/maintenance-window/${config.maintenanceWindowId}`, + config.maintenanceWindow, + { + headers: { + Authorization: `Bearer ${config.authToken}`, + "Content-Type": "application/json", + }, + } + ); + } + /** * ************************************ * Get maintenance window by id diff --git a/Client/src/Utils/timeUtils.js b/Client/src/Utils/timeUtils.js index c42c215c0..87f85e37a 100644 --- a/Client/src/Utils/timeUtils.js +++ b/Client/src/Utils/timeUtils.js @@ -8,6 +8,7 @@ export const MS_PER_SECOND = 1000; export const MS_PER_MINUTE = 60 * MS_PER_SECOND; export const MS_PER_HOUR = 60 * MS_PER_MINUTE; export const MS_PER_DAY = 24 * MS_PER_HOUR; +export const MS_PER_WEEK = MS_PER_DAY * 7; export const formatDuration = (ms) => { const seconds = Math.floor(ms / 1000); diff --git a/Server/controllers/maintenanceWindowController.js b/Server/controllers/maintenanceWindowController.js index 72028f622..51e0d74bd 100644 --- a/Server/controllers/maintenanceWindowController.js +++ b/Server/controllers/maintenanceWindowController.js @@ -1,5 +1,7 @@ const { createMaintenanceWindowBodyValidation, + editMaintenanceWindowByIdParamValidation, + editMaintenanceByIdWindowBodyValidation, getMaintenanceWindowByIdParamValidation, getMaintenanceWindowsByMonitorIdParamValidation, getMaintenanceWindowsByTeamIdQueryValidation, @@ -186,7 +188,7 @@ const editMaintenanceWindow = async (req, res, next) => { return; } try { - await req.db.editMaintenanceWindow(req.params.id, req.body); + await req.db.editMaintenanceWindowById(req.params.id, req.body); return res.status(201).json({ success: true, msg: successMessages.MAINTENANCE_WINDOW_EDIT, diff --git a/Server/db/mongo/modules/maintenanceWindowModule.js b/Server/db/mongo/modules/maintenanceWindowModule.js index 6fb3e7317..0f7f7bd0a 100644 --- a/Server/db/mongo/modules/maintenanceWindowModule.js +++ b/Server/db/mongo/modules/maintenanceWindowModule.js @@ -206,6 +206,12 @@ const editMaintenanceWindowById = async ( maintenanceWindowData ) => { try { + const editedMaintenanceWindow = MaintenanceWindow.findByIdAndUpdate( + maintenanceWindowId, + maintenanceWindowData, + { new: true } + ); + return editedMaintenanceWindow; } catch (error) { error.service = SERVICE_NAME; error.method = "editMaintenanceWindowById"; diff --git a/Server/validation/joi.js b/Server/validation/joi.js index 3b07c5bcc..3133da80a 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -405,6 +405,7 @@ const editMaintenanceByIdWindowBodyValidation = joi.object({ start: joi.date(), end: joi.date(), expiry: joi.date(), + monitors: joi.array(), }); //****************************************