From 560b7c4a4d0bb5d38e22dea53555bb8e82a92253 Mon Sep 17 00:00:00 2001 From: Rogerio Bordignon Date: Mon, 21 Oct 2024 14:40:22 -0300 Subject: [PATCH 001/411] Add Http Status Label to Incident Table --- Client/src/Components/Label/index.jsx | 41 ++++++++++++------- .../Pages/Incidents/IncidentTable/index.jsx | 33 ++++++++++++--- 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/Client/src/Components/Label/index.jsx b/Client/src/Components/Label/index.jsx index bdc34982a..0c5cd8503 100644 --- a/Client/src/Components/Label/index.jsx +++ b/Client/src/Components/Label/index.jsx @@ -58,7 +58,7 @@ const lightenColor = (color, percent) => { let r = parseInt(color.substring(1, 3), 16); let g = parseInt(color.substring(3, 5), 16); let b = parseInt(color.substring(5, 7), 16); - + const amt = Math.round((255 * percent) / 100); r = r + amt <= 255 ? r + amt : 255; @@ -86,10 +86,7 @@ const lightenColor = (color, percent) => { const ColoredLabel = ({ label, color }) => { const theme = useTheme(); // If an invalid color is passed, default to the labelGray color - if ( - typeof color !== "string" || - !/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(color) - ) { + if (typeof color !== "string" || !/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(color)) { color = theme.palette.border.light; } @@ -119,13 +116,14 @@ ColoredLabel.propTypes = { * @param {Object} props * @param { 'up' | 'down' | 'cannot resolve'} props.status - The status for the label * @param {string} props.text - The text of the label + * @param {boolean} props.hasDot - Whether to show a dot or not * @returns {JSX.Element} * @example * // Render an active label * */ -const StatusLabel = ({ status, text, customStyles }) => { +const StatusLabel = ({ status, text, hasDot = true, customStyles }) => { const theme = useTheme(); const colors = { up: { @@ -148,6 +146,16 @@ const StatusLabel = ({ status, text, customStyles }) => { bgColor: theme.palette.warning.bg, borderColor: theme.palette.warning.light, }, + 400: { + dotColor: theme.palette.warning.main, + bgColor: theme.palette.warning.bg, + borderColor: theme.palette.warning.light, + }, + 500: { + dotColor: theme.palette.warning.main, + bgColor: theme.palette.warning.bg, + borderColor: theme.palette.warning.light, + }, "cannot resolve": { dotColor: theme.palette.unresolved.main, bgColor: theme.palette.unresolved.bg, @@ -156,7 +164,7 @@ const StatusLabel = ({ status, text, customStyles }) => { }; // Look up the color for the status - const { borderColor, bgColor, dotColor } = colors[status]; + const { borderColor, bgColor, dotColor } = colors[status] || colors["cannot resolve"]; return ( { ...customStyles, }} > - + {hasDot && ( + + )} ); }; @@ -185,9 +195,12 @@ StatusLabel.propTypes = { "down", "paused", "pending", + "400", + "500", "cannot resolve", ]), text: PropTypes.string, + hasDot: PropTypes.bool, customStyles: PropTypes.object, }; diff --git a/Client/src/Pages/Incidents/IncidentTable/index.jsx b/Client/src/Pages/Incidents/IncidentTable/index.jsx index ef8356031..8e0464376 100644 --- a/Client/src/Pages/Incidents/IncidentTable/index.jsx +++ b/Client/src/Pages/Incidents/IncidentTable/index.jsx @@ -133,19 +133,31 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => { <> {checks?.length === 0 && selectedMonitor === "0" ? ( - + {mode === "light" ? : } - + No incidents recorded yet. ) : checks?.length === 0 ? ( - + {mode === "light" ? : } - + The monitor you have selected has no recorded incidents yet. @@ -170,6 +182,12 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => { "YYYY-MM-DD HH:mm:ss A", uiTimezone ); + const getGenericStatus = (status) => { + if (typeof status === "number" && status >= 100 && status < 600) { + return Math.floor(status / 100) * 100; + } + return "cannot resolve"; + }; return ( @@ -183,7 +201,12 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => { {formattedDate} - {check.statusCode ? check.statusCode : "N/A"} + {check.message} From 1347ccda8924f334cac0542f62594f153f7c5b67 Mon Sep 17 00:00:00 2001 From: Rogerio Bordignon Date: Mon, 21 Oct 2024 14:48:40 -0300 Subject: [PATCH 002/411] Prettier Adjustment --- Client/src/Components/Label/index.jsx | 146 +++++++------- .../Pages/Incidents/IncidentTable/index.jsx | 180 +++++++++--------- 2 files changed, 163 insertions(+), 163 deletions(-) diff --git a/Client/src/Components/Label/index.jsx b/Client/src/Components/Label/index.jsx index 21b748b1d..f0872980c 100644 --- a/Client/src/Components/Label/index.jsx +++ b/Client/src/Components/Label/index.jsx @@ -124,84 +124,84 @@ ColoredLabel.propTypes = { */ const StatusLabel = ({ status, text, hasDot = true, customStyles }) => { - const theme = useTheme(); - const colors = { - up: { - dotColor: theme.palette.success.main, - bgColor: theme.palette.success.bg, - borderColor: theme.palette.success.light, - }, - down: { - dotColor: theme.palette.error.text, - bgColor: theme.palette.error.bg, - borderColor: theme.palette.error.light, - }, - paused: { - dotColor: theme.palette.warning.main, - bgColor: theme.palette.warning.bg, - borderColor: theme.palette.warning.light, - }, - pending: { - dotColor: theme.palette.warning.main, - bgColor: theme.palette.warning.bg, - borderColor: theme.palette.warning.light, - }, - 400: { - dotColor: theme.palette.warning.main, - bgColor: theme.palette.warning.bg, - borderColor: theme.palette.warning.light, - }, - 500: { - dotColor: theme.palette.warning.main, - bgColor: theme.palette.warning.bg, - borderColor: theme.palette.warning.light, - }, - "cannot resolve": { - dotColor: theme.palette.unresolved.main, - bgColor: theme.palette.unresolved.bg, - borderColor: theme.palette.unresolved.light, - }, - }; + const theme = useTheme(); + const colors = { + up: { + dotColor: theme.palette.success.main, + bgColor: theme.palette.success.bg, + borderColor: theme.palette.success.light, + }, + down: { + dotColor: theme.palette.error.text, + bgColor: theme.palette.error.bg, + borderColor: theme.palette.error.light, + }, + paused: { + dotColor: theme.palette.warning.main, + bgColor: theme.palette.warning.bg, + borderColor: theme.palette.warning.light, + }, + pending: { + dotColor: theme.palette.warning.main, + bgColor: theme.palette.warning.bg, + borderColor: theme.palette.warning.light, + }, + 400: { + dotColor: theme.palette.warning.main, + bgColor: theme.palette.warning.bg, + borderColor: theme.palette.warning.light, + }, + 500: { + dotColor: theme.palette.warning.main, + bgColor: theme.palette.warning.bg, + borderColor: theme.palette.warning.light, + }, + "cannot resolve": { + dotColor: theme.palette.unresolved.main, + bgColor: theme.palette.unresolved.bg, + borderColor: theme.palette.unresolved.light, + }, + }; - // Look up the color for the status - const { borderColor, bgColor, dotColor } = colors[status] || colors["cannot resolve"]; + // Look up the color for the status + const { borderColor, bgColor, dotColor } = colors[status] || colors["cannot resolve"]; - return ( - - {hasDot && ( - - )} - - ); + return ( + + {hasDot && ( + + )} + + ); }; StatusLabel.propTypes = { - status: PropTypes.oneOf([ - "up", - "down", - "paused", - "pending", - "400", - "500", - "cannot resolve", - ]), - text: PropTypes.string, - hasDot: PropTypes.bool, - customStyles: PropTypes.object, + status: PropTypes.oneOf([ + "up", + "down", + "paused", + "pending", + "400", + "500", + "cannot resolve", + ]), + text: PropTypes.string, + hasDot: PropTypes.bool, + customStyles: PropTypes.object, }; export { ColoredLabel, StatusLabel }; diff --git a/Client/src/Pages/Incidents/IncidentTable/index.jsx b/Client/src/Pages/Incidents/IncidentTable/index.jsx index 06f9affdd..e26de1190 100644 --- a/Client/src/Pages/Incidents/IncidentTable/index.jsx +++ b/Client/src/Pages/Incidents/IncidentTable/index.jsx @@ -129,97 +129,97 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => { p: theme.spacing(30), }; - return ( - <> - {checks?.length === 0 && selectedMonitor === "0" ? ( - - - {mode === "light" ? : } - - - No incidents recorded yet. - - - ) : checks?.length === 0 ? ( - - - {mode === "light" ? : } - - - The monitor you have selected has no recorded incidents yet. - - - ) : ( - <> - - - - - Monitor Name - Status - Date & Time - Status Code - Message - - - - {checks.map((check) => { - const status = check.status === true ? "up" : "down"; - const formattedDate = formatDateWithTz( - check.createdAt, - "YYYY-MM-DD HH:mm:ss A", - uiTimezone - ); - const getGenericStatus = (status) => { - if (typeof status === "number" && status >= 100 && status < 600) { - return Math.floor(status / 100) * 100; - } - return "cannot resolve"; - }; + return ( + <> + {checks?.length === 0 && selectedMonitor === "0" ? ( + + + {mode === "light" ? : } + + + No incidents recorded yet. + + + ) : checks?.length === 0 ? ( + + + {mode === "light" ? : } + + + The monitor you have selected has no recorded incidents yet. + + + ) : ( + <> + +
+ + + Monitor Name + Status + Date & Time + Status Code + Message + + + + {checks.map((check) => { + const status = check.status === true ? "up" : "down"; + const formattedDate = formatDateWithTz( + check.createdAt, + "YYYY-MM-DD HH:mm:ss A", + uiTimezone + ); + const getGenericStatus = (status) => { + if (typeof status === "number" && status >= 100 && status < 600) { + return Math.floor(status / 100) * 100; + } + return "cannot resolve"; + }; - return ( - - {monitors[check.monitorId]?.name} - - - - {formattedDate} - - - - {check.message} - - ); - })} - -
-
- {paginationComponent} - - )} - - ); + return ( + + {monitors[check.monitorId]?.name} + + + + {formattedDate} + + + + {check.message} + + ); + })} + + + + {paginationComponent} + + )} + + ); }; IncidentTable.propTypes = { From 9b3f73da0e4c5db34a069650e024788ec95da5a2 Mon Sep 17 00:00:00 2001 From: Caio Cabral Date: Wed, 23 Oct 2024 19:11:06 -0400 Subject: [PATCH 003/411] feat: getting rid of unnecessary ids and changing validation format --- .chores.md | 12 ++ Client/src/App.jsx | 16 +-- Client/src/Components/Check/Check.jsx | 5 +- Client/src/Pages/Auth/SetNewPassword.jsx | 133 ++++++++++++----------- Client/src/Validation/validation.js | 27 +++-- 5 files changed, 112 insertions(+), 81 deletions(-) create mode 100644 .chores.md diff --git a/.chores.md b/.chores.md new file mode 100644 index 000000000..ba0fd4dd7 --- /dev/null +++ b/.chores.md @@ -0,0 +1,12 @@ +- [ ] Take validation out from the component. + - [ ] SetNewPassword + - [ ] Register +- [ ] Create Check with highlight component. Check receives: status and message +- [ ] Split some of these user feedbacks? Like: + - [ ] Must contain at least one special character, + - [ ] Must contain at least one number, + - [ ] Must contain at least one upper character, + - [ ] Must contain at least one lower character +- [ ] Pass mismatching password user feedback as the other messages, for consistency +- [ ] Get rid of toast message +- [ ] Do it on blur diff --git a/Client/src/App.jsx b/Client/src/App.jsx index 98772ecca..6cb2f433a 100644 --- a/Client/src/App.jsx +++ b/Client/src/App.jsx @@ -1,7 +1,10 @@ +import { useEffect } from "react"; import { Routes, Route } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { useDispatch } from "react-redux"; +// import "./App.css"; import "react-toastify/dist/ReactToastify.css"; import { ToastContainer } from "react-toastify"; -// import "./App.css"; import NotFound from "./Pages/NotFound"; import Login from "./Pages/Auth/Login"; import Register from "./Pages/Auth/Register/Register"; @@ -21,21 +24,18 @@ import ProtectedRoute from "./Components/ProtectedRoute"; import Details from "./Pages/Monitors/Details"; import AdvancedSettings from "./Pages/AdvancedSettings"; import Maintenance from "./Pages/Maintenance"; -import withAdminCheck from "./HOC/withAdminCheck"; -import withAdminProp from "./HOC/withAdminProp"; import Configure from "./Pages/Monitors/Configure"; import PageSpeed from "./Pages/PageSpeed"; import CreatePageSpeed from "./Pages/PageSpeed/CreatePageSpeed"; import CreateNewMaintenanceWindow from "./Pages/Maintenance/CreateMaintenance"; import PageSpeedDetails from "./Pages/PageSpeed/Details"; import PageSpeedConfigure from "./Pages/PageSpeed/Configure"; +import withAdminCheck from "./HOC/withAdminCheck"; +import withAdminProp from "./HOC/withAdminProp"; import { ThemeProvider } from "@emotion/react"; import lightTheme from "./Utils/Theme/lightTheme"; import darkTheme from "./Utils/Theme/darkTheme"; -import { useSelector } from "react-redux"; import { CssBaseline } from "@mui/material"; -import { useEffect } from "react"; -import { useDispatch } from "react-redux"; import { getAppSettings } from "./Features/Settings/settingsSlice"; import { logger } from "./Utils/Logger"; // Import the logger import { networkService } from "./main"; @@ -181,16 +181,18 @@ function App() { path="/register" element={} /> + } /> - {/* } /> */} + } /> + } diff --git a/Client/src/Components/Check/Check.jsx b/Client/src/Components/Check/Check.jsx index 7ef51137e..9c1ec57fb 100644 --- a/Client/src/Components/Check/Check.jsx +++ b/Client/src/Components/Check/Check.jsx @@ -20,7 +20,7 @@ import { useTheme } from "@emotion/react"; * * @returns {React.Element} The `Check` component with a check icon and a label, defined by the `text` prop. */ -const Check = ({ text, variant = "info", outlined = false }) => { +const Check = ({ text, noHighlightText, variant = "info", outlined = false }) => { const theme = useTheme(); const colors = { success: theme.palette.success.main, @@ -54,7 +54,7 @@ const Check = ({ text, variant = "info", outlined = false }) => { opacity: 0.8, }} > - {text} + {noHighlightText} {text} ); @@ -62,6 +62,7 @@ const Check = ({ text, variant = "info", outlined = false }) => { Check.propTypes = { text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired, + noHighlightText: PropTypes.string, variant: PropTypes.oneOf(["info", "error", "success"]), outlined: PropTypes.bool, }; diff --git a/Client/src/Pages/Auth/SetNewPassword.jsx b/Client/src/Pages/Auth/SetNewPassword.jsx index 6c8f7418e..9d1497b47 100644 --- a/Client/src/Pages/Auth/SetNewPassword.jsx +++ b/Client/src/Pages/Auth/SetNewPassword.jsx @@ -1,20 +1,20 @@ +import { useState } from "react"; import { useDispatch, useSelector } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import { useParams } from "react-router-dom"; +import { useTheme } from "@emotion/react"; +import { Box, Stack, Typography } from "@mui/material"; +import LoadingButton from "@mui/lab/LoadingButton"; import { setNewPassword } from "../../Features/Auth/authSlice"; import { createToast } from "../../Utils/toastUtils"; -import { Box, Stack, Typography } from "@mui/material"; -import { useTheme } from "@emotion/react"; -import { useParams } from "react-router-dom"; -import { useState } from "react"; import { credentials } from "../../Validation/validation"; -import { useNavigate } from "react-router-dom"; import Check from "../../Components/Check/Check"; import Field from "../../Components/Inputs/Field"; -import LockIcon from "../../assets/icons/lock.svg?react"; -import Background from "../../assets/Images/background-grid.svg?react"; -import Logo from "../../assets/icons/bwu-icon.svg?react"; -import LoadingButton from "@mui/lab/LoadingButton"; -import "./index.css"; import { IconBox } from "./styled"; +import LockIcon from "../../assets/icons/lock.svg?react"; +import Logo from "../../assets/icons/bwu-icon.svg?react"; +import Background from "../../assets/Images/background-grid.svg?react"; +import "./index.css"; const SetNewPassword = () => { const navigate = useNavigate(); @@ -27,11 +27,6 @@ const SetNewPassword = () => { confirm: "", }); - const idMap = { - "register-password-input": "password", - "confirm-password-input": "confirm", - }; - const { isLoading } = useSelector((state) => state.auth); const { token } = useParams(); @@ -41,7 +36,7 @@ const SetNewPassword = () => { const passwordForm = { ...form }; const { error } = credentials.validate(passwordForm, { abortEarly: false, - context: { password: form.password }, + /* context: { password: form.password }, */ }); if (error) { @@ -82,8 +77,8 @@ const SetNewPassword = () => { }; const handleChange = (event) => { - const { value, id } = event.target; - const name = idMap[id]; + //TODO Change from id to name + const { value, name } = event.target; setForm((prev) => ({ ...prev, [name]: value, @@ -91,15 +86,35 @@ const SetNewPassword = () => { const { error } = credentials.validate( { [name]: value }, - { abortEarly: false, context: { password: form.password } } + { + abortEarly: false, + context: { password: form.password }, + } ); - setErrors((prev) => { - const prevErrors = { ...prev }; - if (error) prevErrors[name] = error.details[0].message; - else delete prevErrors[name]; - return prevErrors; - }); + const errors = error.details.map((error) => error.message); + + setErrors((prev) => ({ ...prev, [name]: errors })); + }; + + console.log(errors); + + /* function getCheckStatus() */ + + const feedbacks = { + length: "info" /* !errors.password + ? "info" + : errors.password === "length" || errors.password === "empty" + ? "error" + : "success", */, + special: "info" /* !errors + ? "info" + : errors.password === "length" || errors.password === "empty" + ? "error" + : "success", */, + number: "info", + upper: "success", + lower: "error", }; return ( @@ -189,7 +204,7 @@ const SetNewPassword = () => { > { > { characters long } - variant={ - errors?.password === "Password is required" - ? "error" - : form.password === "" - ? "info" - : form.password.length < 8 - ? "error" - : "success" - } - /> - - Must contain one special - character and a number - - } - variant={ - errors?.password === "Password is required" - ? "error" - : form.password === "" - ? "info" - : !/^(?=.*[!@#$%^&*(),.?":{}|])(?=.*\d).+$/.test(form.password) - ? "error" - : "success" - } + variant={feedbacks.length} /> Must contain at least one - upper and lower character + special character } - variant={ - errors?.password === "Password is required" - ? "error" - : form.password === "" - ? "info" - : !/^(?=.*[A-Z])(?=.*[a-z]).+$/.test(form.password) - ? "error" - : "success" + variant={feedbacks.special} + /> + + Must contain at least one + one number + } + variant={feedbacks.number} + /> + + Must contain at least one + upper character + + } + variant={feedbacks.upper} + /> + + Must contain at least one + lower character + + } + variant={feedbacks.lower} />
diff --git a/Client/src/Validation/validation.js b/Client/src/Validation/validation.js index c620bdf43..82dee8541 100644 --- a/Client/src/Validation/validation.js +++ b/Client/src/Validation/validation.js @@ -17,26 +17,33 @@ const passwordSchema = joi .trim() .min(8) .messages({ - "string.empty": "Password is required", - "string.min": "Password must be at least 8 characters long", + "string.empty": "empty", + "string.min": "length", }) .custom((value, helpers) => { if (!/[A-Z]/.test(value)) { - return helpers.message("Password must contain at least one uppercase letter"); + return helpers.message("uppercase"); } + return value; + }) + .custom((value, helpers) => { if (!/[a-z]/.test(value)) { - return helpers.message("Password must contain at least one lowercase letter"); + return helpers.message("lowercase"); } + return value; + }) + .custom((value, helpers) => { if (!/\d/.test(value)) { - return helpers.message("Password must contain at least one number"); + return helpers.message("number"); } + return value; + }) + .custom((value, helpers) => { if (!/[!@#$%^&*]/.test(value)) { - return helpers.message("Password must contain at least one special character"); + return helpers.message("special"); } - return value; }); - const credentials = joi.object({ firstName: nameSchema, lastName: nameSchema, @@ -61,12 +68,12 @@ const credentials = joi.object({ .string() .trim() .messages({ - "string.empty": "Password confirmation is required", + "string.empty": "empty", }) .custom((value, helpers) => { const { password } = helpers.prefs.context; if (value !== password) { - return helpers.message("Passwords do not match"); + return helpers.message("different"); } return value; }), From 6905f49d3b21f724269a76d49518f845a01224ff Mon Sep 17 00:00:00 2001 From: Caio Cabral Date: Wed, 23 Oct 2024 19:51:52 -0400 Subject: [PATCH 004/411] feat: validation on change working for SetNewPassword --- Client/src/Pages/Auth/SetNewPassword.jsx | 84 ++++++++++++++---------- Client/src/Validation/validation.js | 2 +- 2 files changed, 49 insertions(+), 37 deletions(-) diff --git a/Client/src/Pages/Auth/SetNewPassword.jsx b/Client/src/Pages/Auth/SetNewPassword.jsx index 9d1497b47..9edd13d97 100644 --- a/Client/src/Pages/Auth/SetNewPassword.jsx +++ b/Client/src/Pages/Auth/SetNewPassword.jsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useId, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; import { useParams } from "react-router-dom"; @@ -21,6 +21,9 @@ const SetNewPassword = () => { const dispatch = useDispatch(); const theme = useTheme(); + const passwordId = useId(); + const confirmPasswordId = useId(); + const [errors, setErrors] = useState({}); const [form, setForm] = useState({ password: "", @@ -36,11 +39,10 @@ const SetNewPassword = () => { const passwordForm = { ...form }; const { error } = credentials.validate(passwordForm, { abortEarly: false, - /* context: { password: form.password }, */ + context: { password: form.password }, }); if (error) { - // validation errors const newErrors = {}; error.details.forEach((err) => { newErrors[err.path[0]] = err.message; @@ -77,46 +79,43 @@ const SetNewPassword = () => { }; const handleChange = (event) => { - //TODO Change from id to name const { value, name } = event.target; - setForm((prev) => ({ - ...prev, - [name]: value, - })); + setForm((prev) => ({ ...prev, [name]: value })); - const { error } = credentials.validate( - { [name]: value }, - { - abortEarly: false, - context: { password: form.password }, - } - ); - - const errors = error.details.map((error) => error.message); + const validateValue = { [name]: value }; + const validateOptions = { abortEarly: false, context: { password: form.password } }; + const { error } = credentials.validate(validateValue, validateOptions); + const errors = error?.details.map((error) => error.message); setErrors((prev) => ({ ...prev, [name]: errors })); }; - console.log(errors); + const getFeedbackStatus = (field, criteria) => { + const fieldErrors = errors[field]; + const isFieldEmpty = form[field].length === 0; + const hasError = fieldErrors?.includes(criteria) || fieldErrors?.includes("empty"); + const isCorrect = !isFieldEmpty && !hasError; - /* function getCheckStatus() */ + if (isCorrect) { + return "success"; + } else if (hasError) { + return "error"; + } else { + return "info"; + } + }; const feedbacks = { - length: "info" /* !errors.password - ? "info" - : errors.password === "length" || errors.password === "empty" - ? "error" - : "success", */, - special: "info" /* !errors - ? "info" - : errors.password === "length" || errors.password === "empty" - ? "error" - : "success", */, - number: "info", - upper: "success", - lower: "error", + length: getFeedbackStatus("password", "length"), + special: getFeedbackStatus("password", "special"), + number: getFeedbackStatus("password", "number"), + uppercase: getFeedbackStatus("password", "uppercase"), + lowercase: getFeedbackStatus("password", "lowercase"), + confirm: getFeedbackStatus("confirm", "different"), }; + console.log({ feedbacks }); + return ( { onSubmit={handleSubmit} > { placeholder="••••••••" value={form.password} onChange={handleChange} - error={errors.password} + /* error={errors.password} */ />
{ onSubmit={handleSubmit} > { placeholder="••••••••" value={form.confirm} onChange={handleChange} - error={errors.confirm} + /* error={errors.confirm} */ /> { upper character } - variant={feedbacks.upper} + variant={feedbacks.uppercase} /> { lower character } - variant={feedbacks.lower} + variant={feedbacks.lowercase} + /> + + + Confirm password and password + {" "} + must match + + } + variant={feedbacks.confirm} /> diff --git a/Client/src/Validation/validation.js b/Client/src/Validation/validation.js index 82dee8541..79533ee1b 100644 --- a/Client/src/Validation/validation.js +++ b/Client/src/Validation/validation.js @@ -39,7 +39,7 @@ const passwordSchema = joi return value; }) .custom((value, helpers) => { - if (!/[!@#$%^&*]/.test(value)) { + if (!/[!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/]/.test(value)) { return helpers.message("special"); } return value; From bd059720def99d9328ab69af8628949f4005e7c7 Mon Sep 17 00:00:00 2001 From: Caio Cabral Date: Wed, 23 Oct 2024 19:57:21 -0400 Subject: [PATCH 005/411] feat: sanitizing checks to reduce code repetition --- Client/src/Pages/Auth/SetNewPassword.jsx | 52 ++++++------------------ 1 file changed, 12 insertions(+), 40 deletions(-) diff --git a/Client/src/Pages/Auth/SetNewPassword.jsx b/Client/src/Pages/Auth/SetNewPassword.jsx index 9edd13d97..63ba3af1c 100644 --- a/Client/src/Pages/Auth/SetNewPassword.jsx +++ b/Client/src/Pages/Auth/SetNewPassword.jsx @@ -114,8 +114,6 @@ const SetNewPassword = () => { confirm: getFeedbackStatus("confirm", "different"), }; - console.log({ feedbacks }); - return ( { mb={theme.spacing(12)} > - Must be at least 8 - characters long - - } + noHighlightText={"Must be at least"} + text={"8 characters long"} variant={feedbacks.length} /> - Must contain at least one - special character - - } + noHighlightText={"Must contain at least"} + text={"one special character"} variant={feedbacks.special} /> - Must contain at least one - one number - - } + noHighlightText={"Must contain at least"} + text={"one number"} variant={feedbacks.number} /> - Must contain at least one - upper character - - } + noHighlightText={"Must contain at least"} + text={"one upper character"} variant={feedbacks.uppercase} /> - Must contain at least one - lower character - - } + noHighlightText={"Must contain at least"} + text={"one lower character"} variant={feedbacks.lowercase} /> - - Confirm password and password - {" "} - must match - - } + noHighlightText={"Confirm password and password"} + text={"must match"} variant={feedbacks.confirm} /> From 95a0ad0dbde7dcb8e750d1f7828ab06ad7066b2e Mon Sep 17 00:00:00 2001 From: Rogerio Bordignon Date: Fri, 25 Oct 2024 11:00:29 -0300 Subject: [PATCH 006/411] Refact: create a new HttpStatusLabel component --- .../src/Components/HttpStatusLabel/index.jsx | 65 +++++++++++++++++++ Client/src/Components/Label/index.jsx | 32 +++------ .../Pages/Incidents/IncidentTable/index.jsx | 16 +---- 3 files changed, 78 insertions(+), 35 deletions(-) create mode 100644 Client/src/Components/HttpStatusLabel/index.jsx diff --git a/Client/src/Components/HttpStatusLabel/index.jsx b/Client/src/Components/HttpStatusLabel/index.jsx new file mode 100644 index 000000000..96033807b --- /dev/null +++ b/Client/src/Components/HttpStatusLabel/index.jsx @@ -0,0 +1,65 @@ +import PropTypes from "prop-types"; +import { useTheme } from "@mui/material"; +import { BaseLabel } from "../Label"; + +/** + * @component + * @param {Object} props + * @param {number | 'N/A'} props.status - The http status for the label + * @param {Styles} props.customStyles - CSS Styles passed from parent component + * @returns {JSX.Element} + * @example + * // Render a http status label + * + */ + +const getRoundedStatusCode = (status) => { + if (typeof status === "number" && status >= 100 && status < 600) { + return Math.floor(status / 100) * 100; + } + return status; +}; + +const HttpStatusLabel = ({ status, customStyles }) => { + const theme = useTheme(); + const colors = { + 400: { + dotColor: theme.palette.warning.main, + bgColor: theme.palette.warning.bg, + borderColor: theme.palette.warning.light, + }, + 500: { + dotColor: theme.palette.error.main, + bgColor: theme.palette.error.bg, + borderColor: theme.palette.error.light, + }, + "N/A": { + dotColor: theme.palette.unresolved.main, + bgColor: theme.palette.unresolved.bg, + borderColor: theme.palette.unresolved.light, + }, + }; + + const { borderColor, bgColor, dotColor } = + colors[getRoundedStatusCode(status)] || colors["N/A"]; + + return ( + + ); +}; + +// TODO: Restrict status type to accept only numeric values after "N/A" status in IncidentTable is resolved +HttpStatusLabel.propTypes = { + status: PropTypes.oneOfType([PropTypes.number, PropTypes.oneOf(["N/A"])]).isRequired, + customStyles: PropTypes.object, +}; + +export { HttpStatusLabel }; diff --git a/Client/src/Components/Label/index.jsx b/Client/src/Components/Label/index.jsx index f0872980c..156210c47 100644 --- a/Client/src/Components/Label/index.jsx +++ b/Client/src/Components/Label/index.jsx @@ -116,14 +116,13 @@ ColoredLabel.propTypes = { * @param {Object} props * @param { 'up' | 'down' | 'cannot resolve'} props.status - The status for the label * @param {string} props.text - The text of the label - * @param {boolean} props.hasDot - Whether to show a dot or not * @returns {JSX.Element} * @example * // Render an active label * */ -const StatusLabel = ({ status, text, hasDot = true, customStyles }) => { +const StatusLabel = ({ status, text, customStyles }) => { const theme = useTheme(); const colors = { up: { @@ -176,32 +175,21 @@ const StatusLabel = ({ status, text, hasDot = true, customStyles }) => { ...customStyles, }} > - {hasDot && ( - - )} + ); }; StatusLabel.propTypes = { - status: PropTypes.oneOf([ - "up", - "down", - "paused", - "pending", - "400", - "500", - "cannot resolve", - ]), + status: PropTypes.oneOf(["up", "down", "paused", "pending", "cannot resolve"]), text: PropTypes.string, - hasDot: PropTypes.bool, customStyles: PropTypes.object, }; -export { ColoredLabel, StatusLabel }; +export { BaseLabel, ColoredLabel, StatusLabel }; diff --git a/Client/src/Pages/Incidents/IncidentTable/index.jsx b/Client/src/Pages/Incidents/IncidentTable/index.jsx index e26de1190..214960b3e 100644 --- a/Client/src/Pages/Incidents/IncidentTable/index.jsx +++ b/Client/src/Pages/Incidents/IncidentTable/index.jsx @@ -24,6 +24,7 @@ import { useTheme } from "@emotion/react"; import { formatDateWithTz } from "../../../Utils/timeUtils"; import PlaceholderLight from "../../../assets/Images/data_placeholder.svg?react"; import PlaceholderDark from "../../../assets/Images/data_placeholder_dark.svg?react"; +import { HttpStatusLabel } from "../../../Components/HttpStatusLabel"; const IncidentTable = ({ monitors, selectedMonitor, filter }) => { const uiTimezone = useSelector((state) => state.ui.timezone); @@ -182,13 +183,7 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => { "YYYY-MM-DD HH:mm:ss A", uiTimezone ); - const getGenericStatus = (status) => { - if (typeof status === "number" && status >= 100 && status < 600) { - return Math.floor(status / 100) * 100; - } - return "cannot resolve"; - }; - + return ( {monitors[check.monitorId]?.name} @@ -201,12 +196,7 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => { {formattedDate} - + {check.message} From d51ff3a586f0f1cb6464396c7107475a8344fd39 Mon Sep 17 00:00:00 2001 From: Rogerio Bordignon Date: Fri, 25 Oct 2024 11:21:39 -0300 Subject: [PATCH 007/411] Fix some files typo --- Client/src/Components/HttpStatusLabel/index.jsx | 2 +- Client/src/Components/Label/index.jsx | 12 +----------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/Client/src/Components/HttpStatusLabel/index.jsx b/Client/src/Components/HttpStatusLabel/index.jsx index 96033807b..18db3590d 100644 --- a/Client/src/Components/HttpStatusLabel/index.jsx +++ b/Client/src/Components/HttpStatusLabel/index.jsx @@ -10,7 +10,7 @@ import { BaseLabel } from "../Label"; * @returns {JSX.Element} * @example * // Render a http status label - * + * */ const getRoundedStatusCode = (status) => { diff --git a/Client/src/Components/Label/index.jsx b/Client/src/Components/Label/index.jsx index 156210c47..f7686a8e0 100644 --- a/Client/src/Components/Label/index.jsx +++ b/Client/src/Components/Label/index.jsx @@ -145,16 +145,6 @@ const StatusLabel = ({ status, text, customStyles }) => { bgColor: theme.palette.warning.bg, borderColor: theme.palette.warning.light, }, - 400: { - dotColor: theme.palette.warning.main, - bgColor: theme.palette.warning.bg, - borderColor: theme.palette.warning.light, - }, - 500: { - dotColor: theme.palette.warning.main, - bgColor: theme.palette.warning.bg, - borderColor: theme.palette.warning.light, - }, "cannot resolve": { dotColor: theme.palette.unresolved.main, bgColor: theme.palette.unresolved.bg, @@ -163,7 +153,7 @@ const StatusLabel = ({ status, text, customStyles }) => { }; // Look up the color for the status - const { borderColor, bgColor, dotColor } = colors[status] || colors["cannot resolve"]; + const { borderColor, bgColor, dotColor } = colors[status]; return ( Date: Sat, 26 Oct 2024 19:20:53 -0400 Subject: [PATCH 008/411] feat: Check for confirm password if password changes and confirm is filled, and they do not match anymore --- .chores.md | 2 ++ Client/src/Pages/Auth/SetNewPassword.jsx | 24 ++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/.chores.md b/.chores.md index 1b45b2361..b10e56264 100644 --- a/.chores.md +++ b/.chores.md @@ -11,3 +11,5 @@ - [x] Must contain at least one lower character - [x] Pass mismatching password user feedback as the other messages, for consistency - [ ] Get rid of toast message +- [x] Check for confirm password if password changes and confirm is filled, and they do not match anymore +- [ ] Check for confirm password if password changes, and they now match diff --git a/Client/src/Pages/Auth/SetNewPassword.jsx b/Client/src/Pages/Auth/SetNewPassword.jsx index 63ba3af1c..103dff4d5 100644 --- a/Client/src/Pages/Auth/SetNewPassword.jsx +++ b/Client/src/Pages/Auth/SetNewPassword.jsx @@ -79,15 +79,35 @@ const SetNewPassword = () => { }; const handleChange = (event) => { + //update form state const { value, name } = event.target; setForm((prev) => ({ ...prev, [name]: value })); + //validate const validateValue = { [name]: value }; const validateOptions = { abortEarly: false, context: { password: form.password } }; + if (name === "password" && form.confirm.length > 0) { + validateValue.confirm = form.confirm; + validateOptions.context = value; + } const { error } = credentials.validate(validateValue, validateOptions); - const errors = error?.details.map((error) => error.message); - setErrors((prev) => ({ ...prev, [name]: errors })); + //update errors + //fazer o map com path e message + const errors = error?.details.map((error) => ({ + message: error.message, + path: error.path[0], + })); + const errorsByPath = errors + ? errors.reduce((acc, { path, message }) => { + if (!acc[path]) { + acc[path] = []; + } + acc[path].push(message); + return acc; + }, {}) + : { [name]: [] }; + setErrors((prev) => ({ ...prev, ...errorsByPath })); }; const getFeedbackStatus = (field, criteria) => { From 50c1896cfa3e5299c6817b8398acbe8f2ef5138b Mon Sep 17 00:00:00 2001 From: Shemy Gan Date: Tue, 29 Oct 2024 10:24:03 -0400 Subject: [PATCH 009/411] - Create Gauge component --- Client/src/Components/Charts/Gauge/index.jsx | 77 ++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 Client/src/Components/Charts/Gauge/index.jsx diff --git a/Client/src/Components/Charts/Gauge/index.jsx b/Client/src/Components/Charts/Gauge/index.jsx new file mode 100644 index 000000000..5743049bb --- /dev/null +++ b/Client/src/Components/Charts/Gauge/index.jsx @@ -0,0 +1,77 @@ +import React from "react"; +import { RadialBarChart, RadialBar, ResponsiveContainer } from "recharts"; +import PropTypes from "prop-types"; + +const Gauge = ({ progressValue, displayText, theme }) => { + const myStyle = { fontSize: "12px", fill: theme.palette.text.tertiary }; + const textStyle = { + textAnchor: "middle", + dominantBaseline: "middle", + style: myStyle + }; + const data = [{ value: 100 }]; + return ( + + + + + + {`${progressValue}%`} + + + {`${displayText}`} + + + + ); +}; + +Gauge.propTypes = { + progressValue: PropTypes.string, + displayText: PropTypes.string, + theme: PropTypes.object, +}; +export default Gauge; From fe5f2af6496f81fe706bfe0d49ecb2a94fbf59dd Mon Sep 17 00:00:00 2001 From: Shemy Gan Date: Tue, 29 Oct 2024 14:03:41 -0400 Subject: [PATCH 010/411] - Update based on coderabbitai feedback --- Client/src/Components/Charts/Gauge/index.jsx | 42 ++++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/Client/src/Components/Charts/Gauge/index.jsx b/Client/src/Components/Charts/Gauge/index.jsx index 5743049bb..1c65ba322 100644 --- a/Client/src/Components/Charts/Gauge/index.jsx +++ b/Client/src/Components/Charts/Gauge/index.jsx @@ -3,13 +3,23 @@ import { RadialBarChart, RadialBar, ResponsiveContainer } from "recharts"; import PropTypes from "prop-types"; const Gauge = ({ progressValue, displayText, theme }) => { - const myStyle = { fontSize: "12px", fill: theme.palette.text.tertiary }; - const textStyle = { + const BASE_TEXT_STYLE = { textAnchor: "middle", dominantBaseline: "middle", - style: myStyle }; + const getStyles = (theme) => ({ + text: { + ...BASE_TEXT_STYLE, + style: { fontSize: "12px", fill: theme.palette.text.tertiary }, + }, + }); const data = [{ value: 100 }]; + const commonRadialBarProps = { + minAngle: 15, + clockWise: true, + dataKey: "value", + cornerRadius: 0 + }; return ( { data={data} > { {`${progressValue}%`} {`${displayText}`} @@ -70,8 +70,8 @@ const Gauge = ({ progressValue, displayText, theme }) => { }; Gauge.propTypes = { - progressValue: PropTypes.string, - displayText: PropTypes.string, - theme: PropTypes.object, + progressValue: PropTypes.number.isRequired, + displayText: PropTypes.string.isRequired, + theme: PropTypes.object.isRequired, }; export default Gauge; From 09ba21245c4bc9a20ac3acf79d362127e2a1b95d Mon Sep 17 00:00:00 2001 From: Shemy Gan Date: Tue, 29 Oct 2024 14:55:35 -0400 Subject: [PATCH 011/411] - Update css font/color based on figma --- Client/src/Components/Charts/Gauge/index.jsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Client/src/Components/Charts/Gauge/index.jsx b/Client/src/Components/Charts/Gauge/index.jsx index 1c65ba322..26fa5f73b 100644 --- a/Client/src/Components/Charts/Gauge/index.jsx +++ b/Client/src/Components/Charts/Gauge/index.jsx @@ -10,7 +10,8 @@ const Gauge = ({ progressValue, displayText, theme }) => { const getStyles = (theme) => ({ text: { ...BASE_TEXT_STYLE, - style: { fontSize: "12px", fill: theme.palette.text.tertiary }, + style: { fontSize: "11px", fontWeight: 400, fill: theme.palette.text.tertiary + }, }, }); const data = [{ value: 100 }]; @@ -36,8 +37,8 @@ const Gauge = ({ progressValue, displayText, theme }) => { > { x="50%" y="55%" {...getStyles(theme).text} + style={{fontSize: 9 }} > {`${displayText}`} From 5e54f5c75763e49a3efb630e7baabf71a483f0c1 Mon Sep 17 00:00:00 2001 From: Shemy Gan Date: Tue, 29 Oct 2024 21:35:58 -0400 Subject: [PATCH 012/411] - Move styles to theme - Use different color for above and below 50% --- Client/src/Components/Charts/Gauge/index.jsx | 44 ++++++++------------ Client/src/Utils/Theme/darkTheme.js | 9 ++++ Client/src/Utils/Theme/lightTheme.js | 16 +++++++ 3 files changed, 42 insertions(+), 27 deletions(-) diff --git a/Client/src/Components/Charts/Gauge/index.jsx b/Client/src/Components/Charts/Gauge/index.jsx index 26fa5f73b..5299a3421 100644 --- a/Client/src/Components/Charts/Gauge/index.jsx +++ b/Client/src/Components/Charts/Gauge/index.jsx @@ -2,30 +2,17 @@ import React from "react"; import { RadialBarChart, RadialBar, ResponsiveContainer } from "recharts"; import PropTypes from "prop-types"; +const data = [{ value: 100 }]; +const commonRadialBarProps = { + minAngle: 15, + clockWise: true, + dataKey: "value", + cornerRadius: 0 +}; + const Gauge = ({ progressValue, displayText, theme }) => { - const BASE_TEXT_STYLE = { - textAnchor: "middle", - dominantBaseline: "middle", - }; - const getStyles = (theme) => ({ - text: { - ...BASE_TEXT_STYLE, - style: { fontSize: "11px", fontWeight: 400, fill: theme.palette.text.tertiary - }, - }, - }); - const data = [{ value: 100 }]; - const commonRadialBarProps = { - minAngle: 15, - clockWise: true, - dataKey: "value", - cornerRadius: 0 - }; return ( - + { > 50 + ? theme.palette.primary.main + : theme.palette.percentage.uptimePoor + } + background={{ fill: theme.palette.background.fill }} data={[{ value: progressValue }]} /> { {`${progressValue}%`} {`${displayText}`} diff --git a/Client/src/Utils/Theme/darkTheme.js b/Client/src/Utils/Theme/darkTheme.js index d182f0496..756a8a11b 100644 --- a/Client/src/Utils/Theme/darkTheme.js +++ b/Client/src/Utils/Theme/darkTheme.js @@ -1,4 +1,5 @@ import { createTheme } from "@mui/material"; +import { gaugeText } from "./lightTheme" const text = { primary: "#fafafa", @@ -34,6 +35,14 @@ const darkTheme = createTheme({ primary: { main: "#1570ef" }, secondary: { main: "#2D2D33" }, text: text, + gaugePercentageText: { + ...gaugeText, + fontSize: "11px" + }, + gaugeDisplayText: { + ...gaugeText, + fontSize: "9px" + }, background: background, border: border, info: { diff --git a/Client/src/Utils/Theme/lightTheme.js b/Client/src/Utils/Theme/lightTheme.js index d2bfafaf4..7b00079e5 100644 --- a/Client/src/Utils/Theme/lightTheme.js +++ b/Client/src/Utils/Theme/lightTheme.js @@ -12,6 +12,14 @@ const background = { fill: "#F4F4F4", accent: "#f9fafb", }; + +export const gaugeText = { + fontWeight: 400, + fill: text.tertiary, + textAnchor: "middle", + dominantBaseline: "middle", +} + const border = { light: "#eaecf0", dark: "#d0d5dd" }; const fontFamilyDefault = @@ -32,6 +40,14 @@ const lightTheme = createTheme({ primary: { main: "#1570EF" }, secondary: { main: "#F4F4F4", dark: "#e3e3e3", contrastText: "#475467" }, text: text, + gaugePercentageText: { + ...gaugeText, + fontSize: "11px" + }, + gaugeDisplayText: { + ...gaugeText, + fontSize: "9px" + }, background: background, border: border, info: { From 400150486dd6ddf547f94ad03f491aa1556484eb Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 30 Oct 2024 10:20:42 +0800 Subject: [PATCH 013/411] implement base check --- Server/db/models/Check.js | 127 ++++++++++++++--------------- Server/db/models/HardwareCheck.js | 34 +------- Server/db/models/PageSpeedCheck.js | 18 ++-- 3 files changed, 71 insertions(+), 108 deletions(-) diff --git a/Server/db/models/Check.js b/Server/db/models/Check.js index 320ddefe9..ad50c208a 100644 --- a/Server/db/models/Check.js +++ b/Server/db/models/Check.js @@ -1,6 +1,63 @@ import mongoose from "mongoose"; -import EmailService from "../../service/emailService.js"; -import Notification from "./Notification.js"; + +const BaseCheckSchema = mongoose.Schema({ + /** + * Reference to the associated Monitor document. + * + * @type {mongoose.Schema.Types.ObjectId} + */ + monitorId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Monitor", + immutable: true, + index: true, + }, + /** + * Status of the check (true for up, false for down). + * + * @type {Boolean} + */ + status: { + type: Boolean, + index: true, + }, + /** + * Response time of the check in milliseconds. + * + * @type {Number} + */ + responseTime: { + type: Number, + }, + /** + * HTTP status code received during the check. + * + * @type {Number} + */ + statusCode: { + type: Number, + index: true, + }, + /** + * Message or description of the check result. + * + * @type {String} + */ + message: { + type: String, + }, + /** + * Expiry date of the check, auto-calculated to expire after 30 days. + * + * @type {Date} + */ + + expiry: { + type: Date, + default: Date.now, + expires: 60 * 60 * 24 * 30, // 30 days + }, +}); /** * Check Schema for MongoDB collection. @@ -8,69 +65,7 @@ import Notification from "./Notification.js"; * Represents a check associated with a monitor, storing information * about the status and response of a particular check event. */ -const CheckSchema = mongoose.Schema( - { - /** - * Reference to the associated Monitor document. - * - * @type {mongoose.Schema.Types.ObjectId} - */ - monitorId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Monitor", - immutable: true, - index: true, - }, - /** - * Status of the check (true for up, false for down). - * - * @type {Boolean} - */ - status: { - type: Boolean, - index: true, - }, - /** - * Response time of the check in milliseconds. - * - * @type {Number} - */ - responseTime: { - type: Number, - }, - /** - * HTTP status code received during the check. - * - * @type {Number} - */ - statusCode: { - type: Number, - index: true, - }, - /** - * Message or description of the check result. - * - * @type {String} - */ - message: { - type: String, - }, - /** - * Expiry date of the check, auto-calculated to expire after 30 days. - * - * @type {Date} - */ - - expiry: { - type: Date, - default: Date.now, - expires: 60 * 60 * 24 * 30, // 30 days - }, - }, - { - timestamps: true, // Adds createdAt and updatedAt timestamps - } -); - +const CheckSchema = mongoose.Schema({ ...BaseCheckSchema.obj }, { timestamps: true }); CheckSchema.index({ createdAt: 1 }); export default mongoose.model("Check", CheckSchema); +export { BaseCheckSchema }; diff --git a/Server/db/models/HardwareCheck.js b/Server/db/models/HardwareCheck.js index c307b9804..75e4ac584 100644 --- a/Server/db/models/HardwareCheck.js +++ b/Server/db/models/HardwareCheck.js @@ -1,5 +1,5 @@ import mongoose from "mongoose"; - +import { BaseCheckSchema } from "./Check.js"; const cpuSchema = mongoose.Schema({ physical_core: { type: Number, default: 0 }, logical_core: { type: Number, default: 0 }, @@ -32,35 +32,7 @@ const hostSchema = mongoose.Schema({ const HardwareCheckSchema = mongoose.Schema( { - monitorId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Monitor", - immutable: true, - index: true, - }, - - status: { - type: Boolean, - index: true, - }, - - responseTime: { - type: Number, - }, - - statusCode: { - type: Number, - index: true, - }, - - message: { - type: String, - }, - expiry: { - type: Date, - default: Date.now, - expires: 60 * 60 * 24 * 30, // 30 days - }, + ...BaseCheckSchema.obj, cpu: { type: cpuSchema, default: () => ({}), @@ -81,4 +53,6 @@ const HardwareCheckSchema = mongoose.Schema( { timestamps: true } ); +HardwareCheckSchema.index({ createdAt: 1 }); + export default mongoose.model("HardwareCheck", HardwareCheckSchema); diff --git a/Server/db/models/PageSpeedCheck.js b/Server/db/models/PageSpeedCheck.js index 344cc685d..4ebc60280 100644 --- a/Server/db/models/PageSpeedCheck.js +++ b/Server/db/models/PageSpeedCheck.js @@ -1,5 +1,7 @@ import mongoose from "mongoose"; +import { BaseCheckSchema } from "./Check.js"; import logger from "../../utils/logger.js"; +import { time } from "console"; const AuditSchema = mongoose.Schema({ id: { type: String, required: true }, title: { type: String, required: true }, @@ -46,15 +48,7 @@ const AuditsSchema = mongoose.Schema({ const PageSpeedCheck = mongoose.Schema( { - monitorId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Monitor", - immutable: true, - }, - status: { - type: Boolean, - required: true, - }, + ...BaseCheckSchema.obj, accessibility: { type: Number, required: true, @@ -76,9 +70,7 @@ const PageSpeedCheck = mongoose.Schema( required: true, }, }, - { - timestamps: true, - } + { timestamps: true } ); /** @@ -112,4 +104,6 @@ PageSpeedCheck.pre("save", async function (next) { } }); +PageSpeedCheck.index({ createdAt: 1 }); + export default mongoose.model("PageSpeedCheck", PageSpeedCheck); From b730f076caf0240fa377be7334ca1ab5d1008d82 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 30 Oct 2024 16:05:07 +0000 Subject: [PATCH 014/411] fix(deps): update dependency recharts to v2.13.2 --- Client/package-lock.json | 8 ++++---- Client/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Client/package-lock.json b/Client/package-lock.json index 8d816f4dc..b9c92f3f7 100644 --- a/Client/package-lock.json +++ b/Client/package-lock.json @@ -29,7 +29,7 @@ "react-router": "^6.23.0", "react-router-dom": "^6.23.1", "react-toastify": "^10.0.5", - "recharts": "2.13.1", + "recharts": "2.13.2", "redux-persist": "6.0.0", "vite-plugin-svgr": "^4.2.0" }, @@ -5518,9 +5518,9 @@ } }, "node_modules/recharts": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.13.1.tgz", - "integrity": "sha512-87LdsmgK/MHLmWQfTC6yDysno2cOigi/+2KRCwy0D8NDu1IOdtTGS8lMovA0VIvJ7kf3zdp1IiwznHZWSPJhYw==", + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.13.2.tgz", + "integrity": "sha512-UDLGFmnsBluDIPpQb9uty0ejb+jiVI71vkki8vVsR6ZCJdgjBfKQoQfft4re99CKlTy9qjQApxCLG6TrxJkeAg==", "license": "MIT", "dependencies": { "clsx": "^2.0.0", diff --git a/Client/package.json b/Client/package.json index 8fe31f8da..03c1a77c2 100644 --- a/Client/package.json +++ b/Client/package.json @@ -32,7 +32,7 @@ "react-router": "^6.23.0", "react-router-dom": "^6.23.1", "react-toastify": "^10.0.5", - "recharts": "2.13.1", + "recharts": "2.13.2", "redux-persist": "6.0.0", "vite-plugin-svgr": "^4.2.0" }, From 2eadb9835040b7998c0fce758fda7a434804b656 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 30 Oct 2024 18:29:31 +0000 Subject: [PATCH 015/411] chore(deps): update dependency mocha to v10.8.2 --- Server/package-lock.json | 8 ++++---- Server/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Server/package-lock.json b/Server/package-lock.json index fefc83f9d..e7f12dd70 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -35,7 +35,7 @@ "c8": "10.1.2", "chai": "5.1.2", "esm": "3.2.25", - "mocha": "10.8.1", + "mocha": "10.8.2", "nodemon": "3.1.7", "prettier": "^3.3.3", "sinon": "19.0.2" @@ -4129,9 +4129,9 @@ } }, "node_modules/mocha": { - "version": "10.8.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.1.tgz", - "integrity": "sha512-WxSpEWgF03HfgNKBuysfK40DUaOSVX5zxgLDoieMGO+zyE69iq2eQ1vBypvIJ5mOPKpuVAqWiTbt4Orj7L6wVw==", + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/Server/package.json b/Server/package.json index 512a1e01a..5a8e98562 100644 --- a/Server/package.json +++ b/Server/package.json @@ -38,7 +38,7 @@ "c8": "10.1.2", "chai": "5.1.2", "esm": "3.2.25", - "mocha": "10.8.1", + "mocha": "10.8.2", "nodemon": "3.1.7", "prettier": "^3.3.3", "sinon": "19.0.2" From 5a5c0c610d2e6010a18135ec73a05c434761b374 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 31 Oct 2024 15:01:08 +0800 Subject: [PATCH 016/411] replace cleanup method with better shutdown method --- Server/index.js | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/Server/index.js b/Server/index.js index b83a3d5ce..52a3784be 100644 --- a/Server/index.js +++ b/Server/index.js @@ -8,7 +8,6 @@ import cors from "cors"; import logger from "./utils/logger.js"; import { verifyJWT } from "./middleware/verifyJWT.js"; import { handleErrors } from "./middleware/handleErrors.js"; -import { errorMessages } from "./utils/messages.js"; import authRouter from "./routes/authRoute.js"; import inviteRouter from "./routes/inviteRoute.js"; import monitorRouter from "./routes/monitorRoute.js"; @@ -46,7 +45,6 @@ import NotificationService from "./service/notificationService.js"; import db from "./db/mongo/MongoDB.js"; const SERVICE_NAME = "Server"; -let cleaningUp = false; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -113,11 +111,11 @@ const startApp = async () => { // Create services await db.connect(); - app.listen(PORT, () => { + const server = app.listen(PORT, () => { logger.info({ message: `server started on port:${PORT}` }); }); - const settingsService = new SettingsService(AppSettings); + const settingsService = new SettingsService(AppSettings); await settingsService.loadSettings(); const emailService = new EmailService( settingsService, @@ -142,30 +140,35 @@ const startApp = async () => { Worker ); - const cleanup = async () => { - if (cleaningUp) { - logger.warn({ message: "Already cleaning up" }); - return; - } - cleaningUp = true; + const shutdown = async () => { + logger.info({ message: "Attempting graceful shutdown" }); + setTimeout(() => { + logger.error({ + message: "Could not shut down in time, forcing shutdown", + service: SERVICE_NAME, + method: "shutdown", + }); + process.exit(1); + }, 10000); try { - logger.info({ message: "shutting down gracefully" }); + server.close(); await jobQueue.obliterate(); await db.disconnect(); - logger.info({ message: "shut down gracefully" }); + logger.info({ message: "Graceful shutdown complete" }); + process.exit(0); } catch (error) { logger.error({ message: error.message, service: SERVICE_NAME, - method: "cleanup", + method: "shutdown", stack: error.stack, }); } - process.exit(0); }; - process.on("SIGUSR2", cleanup); - process.on("SIGINT", cleanup); - process.on("SIGTERM", cleanup); + + process.on("SIGUSR2", shutdown); + process.on("SIGINT", shutdown); + process.on("SIGTERM", shutdown); }; startApp().catch((error) => { From 0f30d58125ddbe658a40bd94bdf3d20ef827b8b6 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 31 Oct 2024 15:59:51 +0800 Subject: [PATCH 017/411] Added isShuttigDown var to handle case of multiple kill/interupt signals --- Server/index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Server/index.js b/Server/index.js index 52a3784be..3e083df50 100644 --- a/Server/index.js +++ b/Server/index.js @@ -45,6 +45,7 @@ import NotificationService from "./service/notificationService.js"; import db from "./db/mongo/MongoDB.js"; const SERVICE_NAME = "Server"; +let isShuttingDown = false; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -141,6 +142,10 @@ const startApp = async () => { ); const shutdown = async () => { + if (isShuttingDown) { + return; + } + isShuttingDown = true; logger.info({ message: "Attempting graceful shutdown" }); setTimeout(() => { logger.error({ @@ -149,7 +154,7 @@ const startApp = async () => { method: "shutdown", }); process.exit(1); - }, 10000); + }, 2000); try { server.close(); await jobQueue.obliterate(); From 120296763bf46e3723eeb1136059fe5be777fcbe Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 31 Oct 2024 16:00:29 +0800 Subject: [PATCH 018/411] Attempt to stop active jobs --- Server/service/jobQueue.js | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index 6d46052f1..3793a256a 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -36,10 +36,10 @@ class JobQueue { host: redisHost, port: redisPort, }; - this.connection = connection; this.queue = new Queue(QUEUE_NAME, { connection, }); + this.connection = connection; this.workers = []; this.db = null; this.networkService = null; @@ -406,9 +406,6 @@ class JobQueue { delayed: await this.queue.getDelayedCount(), repeatableJobs: (await this.queue.getRepeatableJobs()).length, }; - this.logger.info({ - message: metrics, - }); return metrics; } catch (error) { this.logger.error({ @@ -424,17 +421,33 @@ class JobQueue { * @async * @returns {Promise} - Returns true if obliteration is successful */ + async obliterate() { try { - let metrics = await this.getMetrics(); - this.logger.info({ message: metrics }); + this.logger.info({ message: "Attempting to obliterate job queue..." }); await this.queue.pause(); const jobs = await this.getJobs(); + // Stop currently active jobs + const redisClient = await this.queue.client; + const activeJobs = await this.queue.getJobs(["active"]); + if (activeJobs.length !== 0) { + this.logger.info({ message: "Attempting to stop active jobs..." }); + } + await Promise.all( + activeJobs.map(async (job) => { + await redisClient.del(`${this.queue.toKey(job.id)}:lock`); + await job.remove(); + }) + ); + + // Remove all repeatable jobs for (const job of jobs) { await this.queue.removeRepeatableByKey(job.key); await this.queue.remove(job.id); } + + // Close workers await Promise.all( this.workers.map(async (worker) => { await worker.close(); @@ -442,9 +455,13 @@ class JobQueue { ); await this.queue.obliterate(); - metrics = await this.getMetrics(); - this.logger.info({ message: metrics }); - this.logger.info({ message: successMessages.JOB_QUEUE_OBLITERATE }); + const metrics = await this.getMetrics(); + this.logger.info({ + message: successMessages.JOB_QUEUE_OBLITERATE, + service: SERVICE_NAME, + method: "obliterate", + details: metrics, + }); return true; } catch (error) { error.service === undefined ? (error.service = SERVICE_NAME) : null; From 3fc79d47ba4e690ca7e771891e0bdf4d8203f48f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 31 Oct 2024 16:01:04 +0800 Subject: [PATCH 019/411] Stringify objects in the details field of logger config --- Server/utils/logger.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Server/utils/logger.js b/Server/utils/logger.js index 60d509a6a..e263085a7 100644 --- a/Server/utils/logger.js +++ b/Server/utils/logger.js @@ -7,6 +7,10 @@ class Logger { if (message instanceof Object) { message = JSON.stringify(message, null, 2); } + + if (details instanceof Object) { + details = JSON.stringify(details, null, 2); + } let msg = `${timestamp} ${level}:`; service && (msg += ` [${service}]`); method && (msg += `(${method})`); From c83e2cb6f2fe042448ec7f27e3c16d6d2ae5f340 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 31 Oct 2024 17:01:27 +0800 Subject: [PATCH 020/411] Add module var for shutdown timeout --- Server/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Server/index.js b/Server/index.js index 3e083df50..e12d83a18 100644 --- a/Server/index.js +++ b/Server/index.js @@ -44,6 +44,7 @@ import NotificationService from "./service/notificationService.js"; import db from "./db/mongo/MongoDB.js"; const SERVICE_NAME = "Server"; +const SHUTDOWN_TIMEOUT = 0; let isShuttingDown = false; const __filename = fileURLToPath(import.meta.url); @@ -154,7 +155,7 @@ const startApp = async () => { method: "shutdown", }); process.exit(1); - }, 2000); + }, SHUTDOWN_TIMEOUT); try { server.close(); await jobQueue.obliterate(); From ce221afa9fe0e8247455633668df8845d66d9908 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 31 Oct 2024 17:02:02 +0800 Subject: [PATCH 021/411] remove active job code, does not accomplish intended purpose --- Server/service/jobQueue.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index 3793a256a..aaab725e5 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -428,19 +428,6 @@ class JobQueue { await this.queue.pause(); const jobs = await this.getJobs(); - // Stop currently active jobs - const redisClient = await this.queue.client; - const activeJobs = await this.queue.getJobs(["active"]); - if (activeJobs.length !== 0) { - this.logger.info({ message: "Attempting to stop active jobs..." }); - } - await Promise.all( - activeJobs.map(async (job) => { - await redisClient.del(`${this.queue.toKey(job.id)}:lock`); - await job.remove(); - }) - ); - // Remove all repeatable jobs for (const job of jobs) { await this.queue.removeRepeatableByKey(job.key); From 686217944d8fd243db820df5ca1adaece7f9e246 Mon Sep 17 00:00:00 2001 From: Caio Cabral Date: Thu, 31 Oct 2024 06:09:59 -0400 Subject: [PATCH 022/411] feat: breaking step components and adding custom hook for password validation --- .chores.md | 18 +- Client/src/Pages/Auth/Register/Register.jsx | 475 ++---------------- .../src/Pages/Auth/Register/StepOne/index.jsx | 121 +++++ .../Pages/Auth/Register/StepThree/index.jsx | 162 ++++++ .../src/Pages/Auth/Register/StepTwo/index.jsx | 104 ++++ Client/src/Pages/Auth/SetNewPassword.jsx | 69 ++- .../Pages/Auth/hooks/useValidatePassword.jsx | 67 +++ 7 files changed, 545 insertions(+), 471 deletions(-) create mode 100644 Client/src/Pages/Auth/Register/StepOne/index.jsx create mode 100644 Client/src/Pages/Auth/Register/StepThree/index.jsx create mode 100644 Client/src/Pages/Auth/Register/StepTwo/index.jsx create mode 100644 Client/src/Pages/Auth/hooks/useValidatePassword.jsx diff --git a/.chores.md b/.chores.md index b10e56264..6d45e9a11 100644 --- a/.chores.md +++ b/.chores.md @@ -1,8 +1,14 @@ - [x] Change schema to fix validations - [x] Refactor to apply Single Responsibility Principle to validation (validation is done on joi only) +- [ ] Validation on change +- [ ] Validation on submit - [ ] Take validation out from the component. - [x] SetNewPassword - [ ] Register + - [ ] Refactor on change to deal with step three specificity + - [ ] Fix Input enter not working + - [ ] PasswordPanel + - [ ] Login - [x] Update check component to accept not highlighted text - [x] Split some of these user feedbacks? Like: - [x] Must contain at least one special character, @@ -11,5 +17,13 @@ - [x] Must contain at least one lower character - [x] Pass mismatching password user feedback as the other messages, for consistency - [ ] Get rid of toast message -- [x] Check for confirm password if password changes and confirm is filled, and they do not match anymore -- [ ] Check for confirm password if password changes, and they now match +- [x] Tests + - [x] Valid Password and Matching Confirmation + - [x] Valid Password, Incorrect Confirmation, then Corrects confirmation + - [x] Password Update to Match Confirm Password + - [x] Short Password with Matching Confirmation + - [x] Invalid Password Format, Correct Confirmation + - [x] Empty Password Field with Confirmation Filled + - [x] Confirm Password Left Blank + - [x] Password Change After Successful Match +- [x] Avoid submission in new password if password or confirm empty, and on error diff --git a/Client/src/Pages/Auth/Register/Register.jsx b/Client/src/Pages/Auth/Register/Register.jsx index c5c6bd85a..b85cf89c0 100644 --- a/Client/src/Pages/Auth/Register/Register.jsx +++ b/Client/src/Pages/Auth/Register/Register.jsx @@ -1,20 +1,19 @@ +import { useState, useEffect } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { useDispatch } from "react-redux"; import PropTypes from "prop-types"; -import { useState, useEffect, useRef } from "react"; -import { useNavigate } from "react-router-dom"; import { useTheme } from "@emotion/react"; import { Box, Button, Stack, Typography } from "@mui/material"; -import { useDispatch } from "react-redux"; +import { StepOne } from "./StepOne"; +import { StepTwo } from "./StepTwo"; +import { StepThree } from "./StepThree"; +import { networkService } from "../../../main"; import { credentials } from "../../../Validation/validation"; import { createToast } from "../../../Utils/toastUtils"; import { register } from "../../../Features/Auth/authSlice"; -import { useParams } from "react-router-dom"; import Background from "../../../assets/Images/background-grid.svg?react"; import Logo from "../../../assets/icons/bwu-icon.svg?react"; import Mail from "../../../assets/icons/mail.svg?react"; -import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; -import Check from "../../../Components/Check/Check"; -import Field from "../../../Components/Inputs/Field"; -import { networkService } from "../../../main"; import "../index.css"; /** @@ -110,389 +109,6 @@ LandingPage.propTypes = { onSignup: PropTypes.func, }; -/** - * Renders the first step of the sign up process. - * - * @param {Object} props - * @param {Object} props.form - Form state object. - * @param {Object} props.errors - Object containing form validation errors. - * @param {Function} props.onSubmit - Callback function to handle form submission. - * @param {Function} props.onChange - Callback function to handle form input changes. - * @param {Function} props.onBack - Callback function to handle "Back" button click. - * @returns {JSX.Element} - */ -const StepOne = ({ form, errors, onSubmit, onChange, onBack }) => { - const theme = useTheme(); - const inputRef = useRef(null); - - useEffect(() => { - if (inputRef.current) { - inputRef.current.focus(); - } - }, []); - - return ( - <> - - - Sign Up - Enter your personal details - - - - - - - - - - - - - - - - ); -}; - -StepOne.propTypes = { - form: PropTypes.object, - errors: PropTypes.object, - onSubmit: PropTypes.func, - onChange: PropTypes.func, - onBack: PropTypes.func, -}; - -/** - * Renders the second step of the sign up process. - * - * @param {Object} props - * @param {Object} props.form - Form state object. - * @param {Object} props.errors - Object containing form validation errors. - * @param {Function} props.onSubmit - Callback function to handle form submission. - * @param {Function} props.onChange - Callback function to handle form input changes. - * @param {Function} props.onBack - Callback function to handle "Back" button click. - * @returns {JSX.Element} - */ -const StepTwo = ({ form, errors, onSubmit, onChange, onBack }) => { - const theme = useTheme(); - const inputRef = useRef(null); - - useEffect(() => { - if (inputRef.current) { - inputRef.current.focus(); - } - }, []); - - return ( - <> - - - Sign Up - Enter your email address - - - - (e.target.value = e.target.value.toLowerCase())} - onChange={onChange} - error={errors.email} - ref={inputRef} - /> - - - - - - - - - ); -}; - -StepTwo.propTypes = { - form: PropTypes.object, - errors: PropTypes.object, - onSubmit: PropTypes.func, - onChange: PropTypes.func, - onBack: PropTypes.func, -}; - -/** - * Renders the third step of the sign up process. - * - * @param {Object} props - * @param {Object} props.form - Form state object. - * @param {Object} props.errors - Object containing form validation errors. - * @param {Function} props.onSubmit - Callback function to handle form submission. - * @param {Function} props.onChange - Callback function to handle form input changes. - * @param {Function} props.onBack - Callback function to handle "Back" button click. - * @returns {JSX.Element} - */ -const StepThree = ({ form, errors, onSubmit, onChange, onBack }) => { - const theme = useTheme(); - const inputRef = useRef(null); - - useEffect(() => { - if (inputRef.current) { - inputRef.current.focus(); - } - }, []); - - return ( - <> - - - Sign Up - Create your password - - - - - - - - - - - Must be at least 8 characters - long - - } - variant={ - errors?.password === "Password is required" - ? "error" - : form.password === "" - ? "info" - : form.password.length < 8 - ? "error" - : "success" - } - /> - - Must contain one special - character and a number - - } - variant={ - errors?.password === "Password is required" - ? "error" - : form.password === "" - ? "info" - : !/^(?=.*[!@#$%^&*(),.?":{}|])(?=.*\d).+$/.test(form.password) - ? "error" - : "success" - } - /> - - Must contain at least one - upper and lower character - - } - variant={ - errors?.password === "Password is required" - ? "error" - : form.password === "" - ? "info" - : !/^(?=.*[A-Z])(?=.*[a-z]).+$/.test(form.password) - ? "error" - : "success" - } - /> - - - - - - - - - ); -}; - -StepThree.propTypes = { - form: PropTypes.object, - errors: PropTypes.object, - onSubmit: PropTypes.func, - onChange: PropTypes.func, - onBack: PropTypes.func, -}; - const Register = ({ isSuperAdmin }) => { const dispatch = useDispatch(); const navigate = useNavigate(); @@ -596,42 +212,45 @@ const Register = ({ isSuperAdmin }) => { // Attempts account registration const handleStepThree = async (e) => { e.preventDefault(); + const { password, confirm } = e.target.elements; + console.log(password.value); + console.log(confirm.value); + // let registerForm = { + // ...form, - let registerForm = { - ...form, - role: isSuperAdmin ? ["superadmin"] : form.role, - inviteToken: token ? token : "", // Add the token to the request for verification - }; - let error = validateForm(registerForm, { - context: { password: form.password }, - }); - if (error) { - handleError(error); - return; - } + // role: isSuperAdmin ? ["superadmin"] : form.role, + // inviteToken: token ? token : "", // Add the token to the request for verification + // }; + // let error = validateForm(registerForm, { + // context: { password: form.password }, + // }); + // if (error) { + // handleError(error); + // return; + // } - delete registerForm.confirm; - const action = await dispatch(register(registerForm)); - if (action.payload.success) { - const authToken = action.payload.data; - localStorage.setItem("token", authToken); - navigate("/"); - createToast({ - body: "Welcome! Your account was created successfully.", - }); - } else { - if (action.payload) { - // dispatch errors - createToast({ - body: action.payload.msg, - }); - } else { - // unknown errors - createToast({ - body: "Unknown error.", - }); - } - } + // delete registerForm.confirm; + // const action = await dispatch(register(registerForm)); + // if (action.payload.success) { + // const authToken = action.payload.data; + // localStorage.setItem("token", authToken); + // navigate("/"); + // createToast({ + // body: "Welcome! Your account was created successfully.", + // }); + // } else { + // if (action.payload) { + // // dispatch errors + // createToast({ + // body: action.payload.msg, + // }); + // } else { + // // unknown errors + // createToast({ + // body: "Unknown error.", + // }); + // } + // } }; const handleChange = (event) => { @@ -732,10 +351,10 @@ const Register = ({ isSuperAdmin }) => { /> ) : step === 3 ? ( setStep(2)} /> ) : ( diff --git a/Client/src/Pages/Auth/Register/StepOne/index.jsx b/Client/src/Pages/Auth/Register/StepOne/index.jsx new file mode 100644 index 000000000..8867b2794 --- /dev/null +++ b/Client/src/Pages/Auth/Register/StepOne/index.jsx @@ -0,0 +1,121 @@ +import { useEffect, useRef } from "react"; +import PropTypes from "prop-types"; +import { useTheme } from "@emotion/react"; +import { Box, Button, Stack, Typography } from "@mui/material"; +import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; +import Field from "../../../../Components/Inputs/Field"; + +StepOne.propTypes = { + form: PropTypes.object, + errors: PropTypes.object, + onSubmit: PropTypes.func, + onChange: PropTypes.func, + onBack: PropTypes.func, +}; + +/** + * Renders the first step of the sign up process. + * + * @param {Object} props + * @param {Object} props.form - Form state object. + * @param {Object} props.errors - Object containing form validation errors. + * @param {Function} props.onSubmit - Callback function to handle form submission. + * @param {Function} props.onChange - Callback function to handle form input changes. + * @param {Function} props.onBack - Callback function to handle "Back" button click. + * @returns {JSX.Element} + */ + +function StepOne({ form, errors, onSubmit, onChange, onBack }) { + const theme = useTheme(); + const inputRef = useRef(null); + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, []); + + return ( + <> + + + Sign Up + Enter your personal details + + + + + + + + + + + + + + + + ); +} + +export { StepOne }; diff --git a/Client/src/Pages/Auth/Register/StepThree/index.jsx b/Client/src/Pages/Auth/Register/StepThree/index.jsx new file mode 100644 index 000000000..cde86c2db --- /dev/null +++ b/Client/src/Pages/Auth/Register/StepThree/index.jsx @@ -0,0 +1,162 @@ +import { useEffect, useRef /* , useState */ } from "react"; +import PropTypes from "prop-types"; +import { useTheme } from "@emotion/react"; +import { Box, Button, Stack, Typography } from "@mui/material"; +import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; +import Field from "../../../../Components/Inputs/Field"; +import Check from "../../../../Components/Check/Check"; +/* import { credentials } from "../../../../Validation/validation"; */ +import { useValidatePassword } from "../../hooks/useValidatePassword"; + +StepThree.propTypes = { + form: PropTypes.object, + errors: PropTypes.object, + onSubmit: PropTypes.func, + /* onChange: PropTypes.func, */ + onBack: PropTypes.func, +}; + +/** + * Renders the third step of the sign up process. + * + * @param {Object} props + * @param {Object} props.form - Form state object. + * @param {Object} props.errors - Object containing form validation errors. + * @param {Function} props.onSubmit - Callback function to handle form submission. + * @param {Function} props.onChange - Callback function to handle form input changes. + * @param {Function} props.onBack - Callback function to handle "Back" button click. + * @returns {JSX.Element} + */ +function StepThree({ onSubmit, onBack }) { + const theme = useTheme(); + const inputRef = useRef(null); + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, []); + + const { handleChange, feedbacks, form, errors } = useValidatePassword(); + + return ( + <> + + + Sign Up + Create your password + + + + + + + + + + + + + + + + + + + + ); +} + +export { StepThree }; diff --git a/Client/src/Pages/Auth/Register/StepTwo/index.jsx b/Client/src/Pages/Auth/Register/StepTwo/index.jsx new file mode 100644 index 000000000..567a82818 --- /dev/null +++ b/Client/src/Pages/Auth/Register/StepTwo/index.jsx @@ -0,0 +1,104 @@ +import { useEffect, useRef } from "react"; +import PropTypes from "prop-types"; +import { useTheme } from "@emotion/react"; +import { Box, Button, Stack, Typography } from "@mui/material"; +import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; +import Field from "../../../../Components/Inputs/Field"; + +StepTwo.propTypes = { + form: PropTypes.object, + errors: PropTypes.object, + onSubmit: PropTypes.func, + onChange: PropTypes.func, + onBack: PropTypes.func, +}; + +/** + * Renders the second step of the sign up process. + * + * @param {Object} props + * @param {Object} props.form - Form state object. + * @param {Object} props.errors - Object containing form validation errors. + * @param {Function} props.onSubmit - Callback function to handle form submission. + * @param {Function} props.onChange - Callback function to handle form input changes. + * @param {Function} props.onBack - Callback function to handle "Back" button click. + * @returns {JSX.Element} + */ +function StepTwo({ form, errors, onSubmit, onChange, onBack }) { + const theme = useTheme(); + const inputRef = useRef(null); + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, []); + + return ( + <> + + + Sign Up + Enter your email address + + + + (e.target.value = e.target.value.toLowerCase())} + onChange={onChange} + error={errors.email} + ref={inputRef} + /> + + + + + + + + + ); +} + +export { StepTwo }; diff --git a/Client/src/Pages/Auth/SetNewPassword.jsx b/Client/src/Pages/Auth/SetNewPassword.jsx index 103dff4d5..1cb858e11 100644 --- a/Client/src/Pages/Auth/SetNewPassword.jsx +++ b/Client/src/Pages/Auth/SetNewPassword.jsx @@ -35,19 +35,12 @@ const SetNewPassword = () => { const handleSubmit = async (e) => { e.preventDefault(); - - const passwordForm = { ...form }; - const { error } = credentials.validate(passwordForm, { + const { error } = credentials.validate(form, { abortEarly: false, context: { password: form.password }, }); if (error) { - const newErrors = {}; - error.details.forEach((err) => { - newErrors[err.path[0]] = err.message; - }); - setErrors(newErrors); createToast({ body: error.details && error.details.length > 0 @@ -55,59 +48,49 @@ const SetNewPassword = () => { : "Error validating data.", }); } else { - delete passwordForm.confirm; - const action = await dispatch(setNewPassword({ token: token, form: passwordForm })); + const action = await dispatch(setNewPassword({ token, form })); if (action.payload.success) { navigate("/new-password-confirmed"); createToast({ body: "Your password was reset successfully.", }); } else { - if (action.payload) { - // dispatch errors - createToast({ - body: action.payload.msg, - }); - } else { - // unknown errors - createToast({ - body: "Unknown error.", - }); - } + const errorMessage = action.payload ? action.payload.msg : "Unknown error"; + createToast({ + body: errorMessage, + }); } } }; const handleChange = (event) => { - //update form state const { value, name } = event.target; setForm((prev) => ({ ...prev, [name]: value })); - //validate const validateValue = { [name]: value }; const validateOptions = { abortEarly: false, context: { password: form.password } }; if (name === "password" && form.confirm.length > 0) { validateValue.confirm = form.confirm; - validateOptions.context = value; + validateOptions.context = { password: value }; + } else if (name === "confirm") { + validateValue.password = form.password; } - const { error } = credentials.validate(validateValue, validateOptions); - //update errors - //fazer o map com path e message + const { error } = credentials.validate(validateValue, validateOptions); const errors = error?.details.map((error) => ({ - message: error.message, path: error.path[0], + message: error.message, })); - const errorsByPath = errors - ? errors.reduce((acc, { path, message }) => { - if (!acc[path]) { - acc[path] = []; - } - acc[path].push(message); - return acc; - }, {}) - : { [name]: [] }; - setErrors((prev) => ({ ...prev, ...errorsByPath })); + const errorsByPath = + errors && + errors.reduce((acc, { path, message }) => { + if (!acc[path]) { + acc[path] = []; + } + acc[path].push(message); + return acc; + }, {}); + setErrors(() => (errorsByPath ? { ...errorsByPath } : {})); }; const getFeedbackStatus = (field, criteria) => { @@ -228,7 +211,7 @@ const SetNewPassword = () => { placeholder="••••••••" value={form.password} onChange={handleChange} - /* error={errors.password} */ + error={errors.password} /> { placeholder="••••••••" value={form.confirm} onChange={handleChange} - /* error={errors.confirm} */ + error={errors.confirm} /> { color="primary" loading={isLoading} onClick={handleSubmit} - disabled={Object.keys(errors).length !== 0} + disabled={ + form.password.length === 0 || + form.confirm.length === 0 || + Object.keys(errors).length !== 0 + } sx={{ width: "100%", maxWidth: 400 }} > Reset password diff --git a/Client/src/Pages/Auth/hooks/useValidatePassword.jsx b/Client/src/Pages/Auth/hooks/useValidatePassword.jsx new file mode 100644 index 000000000..043fc6424 --- /dev/null +++ b/Client/src/Pages/Auth/hooks/useValidatePassword.jsx @@ -0,0 +1,67 @@ +import { useState } from "react"; +import { credentials } from "../../../Validation/validation"; + +function useValidatePassword() { + const [errors, setErrors] = useState({}); + const [form, setForm] = useState({ + password: "", + confirm: "", + }); + + const handleChange = (event) => { + const { value, name } = event.target; + setForm((prev) => ({ ...prev, [name]: value })); + + const validateValue = { [name]: value }; + const validateOptions = { abortEarly: false, context: { password: form.password } }; + if (name === "password" && form.confirm.length > 0) { + validateValue.confirm = form.confirm; + validateOptions.context = { password: value }; + } else if (name === "confirm") { + validateValue.password = form.password; + } + + const { error } = credentials.validate(validateValue, validateOptions); + const errors = error?.details.map((error) => ({ + path: error.path[0], + message: error.message, + })); + const errorsByPath = + errors && + errors.reduce((acc, { path, message }) => { + if (!acc[path]) { + acc[path] = []; + } + acc[path].push(message); + return acc; + }, {}); + setErrors(() => (errorsByPath ? { ...errorsByPath } : {})); + }; + + const getFeedbackStatus = (field, criteria) => { + const fieldErrors = errors[field]; + const isFieldEmpty = form[field].length === 0; + const hasError = fieldErrors?.includes(criteria) || fieldErrors?.includes("empty"); + const isCorrect = !isFieldEmpty && !hasError; + + if (isCorrect) { + return "success"; + } else if (hasError) { + return "error"; + } else { + return "info"; + } + }; + + const feedbacks = { + length: getFeedbackStatus("password", "length"), + special: getFeedbackStatus("password", "special"), + number: getFeedbackStatus("password", "number"), + uppercase: getFeedbackStatus("password", "uppercase"), + lowercase: getFeedbackStatus("password", "lowercase"), + confirm: getFeedbackStatus("confirm", "different"), + }; + + return { handleChange, feedbacks, form, errors }; +} +export { useValidatePassword }; From fd38a9d7eb3383b84eb455bd754db2be7f01786b Mon Sep 17 00:00:00 2001 From: Caio Cabral Date: Thu, 31 Oct 2024 09:30:43 -0400 Subject: [PATCH 023/411] fix: backend validation password --- Client/src/Pages/Auth/Register/Register.jsx | 72 ++++++------ .../Pages/Auth/Register/StepThree/index.jsx | 10 +- Client/src/Pages/Auth/SetNewPassword.jsx | 108 +++++++++--------- .../Pages/Auth/hooks/useValidatePassword.jsx | 51 +++++---- Server/validation/joi.js | 2 +- 5 files changed, 127 insertions(+), 116 deletions(-) diff --git a/Client/src/Pages/Auth/Register/Register.jsx b/Client/src/Pages/Auth/Register/Register.jsx index b85cf89c0..628365bdf 100644 --- a/Client/src/Pages/Auth/Register/Register.jsx +++ b/Client/src/Pages/Auth/Register/Register.jsx @@ -213,44 +213,42 @@ const Register = ({ isSuperAdmin }) => { const handleStepThree = async (e) => { e.preventDefault(); const { password, confirm } = e.target.elements; - console.log(password.value); - console.log(confirm.value); - // let registerForm = { - // ...form, + let registerForm = { + ...form, + password: password.value, + confirm: confirm.value, + role: isSuperAdmin ? ["superadmin"] : form.role, + inviteToken: token ? token : "", // Add the token to the request for verification + }; + let error = validateForm(registerForm, { + context: { password: registerForm.password }, + }); + if (error) { + handleError(error); + return; + } - // role: isSuperAdmin ? ["superadmin"] : form.role, - // inviteToken: token ? token : "", // Add the token to the request for verification - // }; - // let error = validateForm(registerForm, { - // context: { password: form.password }, - // }); - // if (error) { - // handleError(error); - // return; - // } - - // delete registerForm.confirm; - // const action = await dispatch(register(registerForm)); - // if (action.payload.success) { - // const authToken = action.payload.data; - // localStorage.setItem("token", authToken); - // navigate("/"); - // createToast({ - // body: "Welcome! Your account was created successfully.", - // }); - // } else { - // if (action.payload) { - // // dispatch errors - // createToast({ - // body: action.payload.msg, - // }); - // } else { - // // unknown errors - // createToast({ - // body: "Unknown error.", - // }); - // } - // } + delete registerForm.confirm; + const action = await dispatch(register(registerForm)); + if (action.payload.success) { + const authToken = action.payload.data; + localStorage.setItem("token", authToken); + /* navigate("/"); */ + navigate("/monitors"); + createToast({ + body: "Welcome! Your account was created successfully.", + }); + } else { + if (action.payload) { + createToast({ + body: action.payload.msg, + }); + } else { + createToast({ + body: "Unknown error.", + }); + } + } }; const handleChange = (event) => { diff --git a/Client/src/Pages/Auth/Register/StepThree/index.jsx b/Client/src/Pages/Auth/Register/StepThree/index.jsx index cde86c2db..0f1e540f7 100644 --- a/Client/src/Pages/Auth/Register/StepThree/index.jsx +++ b/Client/src/Pages/Auth/Register/StepThree/index.jsx @@ -136,6 +136,10 @@ function StepThree({ onSubmit, onBack }) { "& svg.MuiSvgIcon-root": { mr: theme.spacing(3), }, + ":focus-visible": { + outline: `2px solid ${theme.palette.primary.dark}`, + outlineOffset: "4px", + }, }} props={{ tabIndex: -1 }} > @@ -147,7 +151,11 @@ function StepThree({ onSubmit, onBack }) { variant="contained" color="primary" /* onClick={onSubmit} */ - disabled={errors.email && true} + disabled={ + form.password.length === 0 || + form.confirm.length === 0 || + Object.keys(errors).length !== 0 + } sx={{ width: "30%" }} > Continue diff --git a/Client/src/Pages/Auth/SetNewPassword.jsx b/Client/src/Pages/Auth/SetNewPassword.jsx index 1cb858e11..5f0acef15 100644 --- a/Client/src/Pages/Auth/SetNewPassword.jsx +++ b/Client/src/Pages/Auth/SetNewPassword.jsx @@ -15,6 +15,7 @@ import LockIcon from "../../assets/icons/lock.svg?react"; import Logo from "../../assets/icons/bwu-icon.svg?react"; import Background from "../../assets/Images/background-grid.svg?react"; import "./index.css"; +import { useValidatePassword } from "./hooks/useValidatePassword"; const SetNewPassword = () => { const navigate = useNavigate(); @@ -24,11 +25,12 @@ const SetNewPassword = () => { const passwordId = useId(); const confirmPasswordId = useId(); - const [errors, setErrors] = useState({}); - const [form, setForm] = useState({ - password: "", - confirm: "", - }); + const { form, errors, handleChange, feedbacks } = useValidatePassword(); + // const [errors, setErrors] = useState({}); + // const [form, setForm] = useState({ + // password: "", + // confirm: "", + // }); const { isLoading } = useSelector((state) => state.auth); const { token } = useParams(); @@ -63,59 +65,59 @@ const SetNewPassword = () => { } }; - const handleChange = (event) => { - const { value, name } = event.target; - setForm((prev) => ({ ...prev, [name]: value })); + // const handleChange = (event) => { + // const { value, name } = event.target; + // setForm((prev) => ({ ...prev, [name]: value })); - const validateValue = { [name]: value }; - const validateOptions = { abortEarly: false, context: { password: form.password } }; - if (name === "password" && form.confirm.length > 0) { - validateValue.confirm = form.confirm; - validateOptions.context = { password: value }; - } else if (name === "confirm") { - validateValue.password = form.password; - } + // const validateValue = { [name]: value }; + // const validateOptions = { abortEarly: false, context: { password: form.password } }; + // if (name === "password" && form.confirm.length > 0) { + // validateValue.confirm = form.confirm; + // validateOptions.context = { password: value }; + // } else if (name === "confirm") { + // validateValue.password = form.password; + // } - const { error } = credentials.validate(validateValue, validateOptions); - const errors = error?.details.map((error) => ({ - path: error.path[0], - message: error.message, - })); - const errorsByPath = - errors && - errors.reduce((acc, { path, message }) => { - if (!acc[path]) { - acc[path] = []; - } - acc[path].push(message); - return acc; - }, {}); - setErrors(() => (errorsByPath ? { ...errorsByPath } : {})); - }; + // const { error } = credentials.validate(validateValue, validateOptions); + // const errors = error?.details.map((error) => ({ + // path: error.path[0], + // message: error.message, + // })); + // const errorsByPath = + // errors && + // errors.reduce((acc, { path, message }) => { + // if (!acc[path]) { + // acc[path] = []; + // } + // acc[path].push(message); + // return acc; + // }, {}); + // setErrors(() => (errorsByPath ? { ...errorsByPath } : {})); + // }; - const getFeedbackStatus = (field, criteria) => { - const fieldErrors = errors[field]; - const isFieldEmpty = form[field].length === 0; - const hasError = fieldErrors?.includes(criteria) || fieldErrors?.includes("empty"); - const isCorrect = !isFieldEmpty && !hasError; + // const getFeedbackStatus = (field, criteria) => { + // const fieldErrors = errors[field]; + // const isFieldEmpty = form[field].length === 0; + // const hasError = fieldErrors?.includes(criteria) || fieldErrors?.includes("empty"); + // const isCorrect = !isFieldEmpty && !hasError; - if (isCorrect) { - return "success"; - } else if (hasError) { - return "error"; - } else { - return "info"; - } - }; + // if (isCorrect) { + // return "success"; + // } else if (hasError) { + // return "error"; + // } else { + // return "info"; + // } + // }; - const feedbacks = { - length: getFeedbackStatus("password", "length"), - special: getFeedbackStatus("password", "special"), - number: getFeedbackStatus("password", "number"), - uppercase: getFeedbackStatus("password", "uppercase"), - lowercase: getFeedbackStatus("password", "lowercase"), - confirm: getFeedbackStatus("confirm", "different"), - }; + // const feedbacks = { + // length: getFeedbackStatus("password", "length"), + // special: getFeedbackStatus("password", "special"), + // number: getFeedbackStatus("password", "number"), + // uppercase: getFeedbackStatus("password", "uppercase"), + // lowercase: getFeedbackStatus("password", "lowercase"), + // confirm: getFeedbackStatus("confirm", "different"), + // }; return ( { + const fieldErrors = errors[field]; + const isFieldEmpty = form[field].length === 0; + const hasError = fieldErrors?.includes(criteria) || fieldErrors?.includes("empty"); + const isCorrect = !isFieldEmpty && !hasError; + + if (isCorrect) { + return "success"; + } else if (hasError) { + return "error"; + } else { + return "info"; + } +}; + function useValidatePassword() { const [errors, setErrors] = useState({}); const [form, setForm] = useState({ @@ -38,29 +53,17 @@ function useValidatePassword() { setErrors(() => (errorsByPath ? { ...errorsByPath } : {})); }; - const getFeedbackStatus = (field, criteria) => { - const fieldErrors = errors[field]; - const isFieldEmpty = form[field].length === 0; - const hasError = fieldErrors?.includes(criteria) || fieldErrors?.includes("empty"); - const isCorrect = !isFieldEmpty && !hasError; - - if (isCorrect) { - return "success"; - } else if (hasError) { - return "error"; - } else { - return "info"; - } - }; - - const feedbacks = { - length: getFeedbackStatus("password", "length"), - special: getFeedbackStatus("password", "special"), - number: getFeedbackStatus("password", "number"), - uppercase: getFeedbackStatus("password", "uppercase"), - lowercase: getFeedbackStatus("password", "lowercase"), - confirm: getFeedbackStatus("confirm", "different"), - }; + const feedbacks = useMemo( + () => ({ + length: getFeedbackStatus(form, errors, "password", "length"), + special: getFeedbackStatus(form, errors, "password", "special"), + number: getFeedbackStatus(form, errors, "password", "number"), + uppercase: getFeedbackStatus(form, errors, "password", "uppercase"), + lowercase: getFeedbackStatus(form, errors, "password", "lowercase"), + confirm: getFeedbackStatus(form, errors, "confirm", "different"), + }), + [form, errors] + ); return { handleChange, feedbacks, form, errors }; } diff --git a/Server/validation/joi.js b/Server/validation/joi.js index d1ba107bc..299acebb6 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -118,7 +118,7 @@ const newPasswordValidation = joi.object({ .min(8) .required() .pattern( - /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ + /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/])[A-Za-z0-9!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/]+$/ ), confirm: joi.string(), }); From 97be172dfeb2788401a45ade499a1c04ac9533cd Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 31 Oct 2024 14:35:22 +0000 Subject: [PATCH 024/411] fix(deps): update dependency mongoose to v8.8.0 --- Server/package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Server/package-lock.json b/Server/package-lock.json index e7f12dd70..2e39f34d3 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -4314,9 +4314,9 @@ } }, "node_modules/mongodb": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.9.0.tgz", - "integrity": "sha512-UMopBVx1LmEUbW/QE0Hw18u583PEDVQmUmVzzBRH0o/xtE9DBRA5ZYLOjpLIa03i8FXjzvQECJcqoMvCXftTUA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.10.0.tgz", + "integrity": "sha512-gP9vduuYWb9ZkDM546M+MP2qKVk5ZG2wPF63OvSRuUbqCR+11ZCAE1mOfllhlAG0wcoJY5yDL/rV3OmYEwXIzg==", "license": "Apache-2.0", "dependencies": { "@mongodb-js/saslprep": "^1.1.5", @@ -4369,14 +4369,14 @@ } }, "node_modules/mongoose": { - "version": "8.7.3", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.7.3.tgz", - "integrity": "sha512-Xl6+dzU5ZpEcDoJ8/AyrIdAwTY099QwpolvV73PIytpK13XqwllLq/9XeVzzLEQgmyvwBVGVgjmMrKbuezxrIA==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.8.0.tgz", + "integrity": "sha512-KluvgwnQB1GPOYZZXUHJRjS1TW6xxwTlf/YgjWExuuNanIe3W7VcR7dDXQVCIRk8L7NYge8EnoTcu2grWtN+XQ==", "license": "MIT", "dependencies": { "bson": "^6.7.0", "kareem": "2.6.3", - "mongodb": "6.9.0", + "mongodb": "~6.10.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", From 42ae1af98700b5b3c1778517acbc8ff34ad2b53e Mon Sep 17 00:00:00 2001 From: Shemy Gan Date: Thu, 31 Oct 2024 10:36:09 -0400 Subject: [PATCH 025/411] - Add infrastructure monitor slice --- .../infrastructureMonitorsSlice.js | 405 ++++++++++++++++++ Client/src/store.js | 2 + 2 files changed, 407 insertions(+) create mode 100644 Client/src/Features/InfrastructureMonitors /infrastructureMonitorsSlice.js diff --git a/Client/src/Features/InfrastructureMonitors /infrastructureMonitorsSlice.js b/Client/src/Features/InfrastructureMonitors /infrastructureMonitorsSlice.js new file mode 100644 index 000000000..abcb31adf --- /dev/null +++ b/Client/src/Features/InfrastructureMonitors /infrastructureMonitorsSlice.js @@ -0,0 +1,405 @@ +import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; +import { jwtDecode } from "jwt-decode"; +import { networkService } from "../../main"; +const initialState = { + isLoading: false, + monitorsSummary: [], + success: null, + msg: null, +}; + +export const createInfrastructureMonitor = createAsyncThunk( + "infrastructureMonitors/createMonitor", + async (data, thunkApi) => { + try { + const { authToken, monitor } = data; + const res = await networkService.createMonitor({ + authToken: authToken, + monitor: monitor, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } +); + +export const checkInfrastructureEndpointResolution = createAsyncThunk( + "infrastructureMonitors/CheckEndpoint", + async (data, thunkApi) => { + try { + const { authToken, monitorURL } = data; + + const res = await networkService.checkEndpointResolution({ + authToken: authToken, + monitorURL: monitorURL, + }) + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } +) + +export const getInfrastructureMonitorById = createAsyncThunk( + "infrastructureMonitors/getMonitorById", + async (data, thunkApi) => { + try { + const { authToken, monitorId } = data; + const res = await networkService.getMonitorById({ + authToken: authToken, + monitorId: monitorId, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } +); + +export const getInfrastructureMonitorsByTeamId = createAsyncThunk( + "infrastructureMonitors/getMonitorsByTeamId", + async (token, thunkApi) => { + const user = jwtDecode(token); + try { + const res = await networkService.getMonitorsAndSummaryByTeamId({ + authToken: token, + teamId: user.teamId, + types: ["http", "ping"], + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } +); + +export const updateInfrastructureMonitor = createAsyncThunk( + "infrastructureMonitors/updateMonitor", + async (data, thunkApi) => { + try { + const { authToken, monitor } = data; + const updatedFields = { + name: monitor.name, + description: monitor.description, + interval: monitor.interval, + notifications: monitor.notifications, + }; + const res = await networkService.updateMonitor({ + authToken: authToken, + monitorId: monitor._id, + updatedFields: updatedFields, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } +); + +export const deleteInfrastructureMonitor = createAsyncThunk( + "infrastructureMonitors/deleteMonitor", + async (data, thunkApi) => { + try { + const { authToken, monitor } = data; + const res = await networkService.deleteMonitorById({ + authToken: authToken, + monitorId: monitor._id, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } +); + +export const pauseInfrastructureMonitor = createAsyncThunk( + "infrastructureMonitors/pauseMonitor", + async (data, thunkApi) => { + try { + const { authToken, monitorId } = data; + const res = await networkService.pauseMonitorById({ + authToken: authToken, + monitorId: monitorId, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } +); + +export const deleteInfrastructureMonitorChecksByTeamId = createAsyncThunk( + "infrastructureMonitors/deleteChecksByTeamId", + async (data, thunkApi) => { + try { + const { authToken, teamId } = data; + const res = await networkService.deleteChecksByTeamId({ + authToken: authToken, + teamId: teamId, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } +); + +export const deleteAllInfrastructureMonitors = createAsyncThunk( + "infrastructureMonitors/deleteAllMonitors", + async (data, thunkApi) => { + try { + const { authToken } = data; + const res = await networkService.deleteAllMonitors({ + authToken: authToken, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } +); + +const infrastructureMonitorsSlice = createSlice({ + name: "infrastructureMonitors", + initialState, + reducers: { + clearInfrastructureMonitorState: (state) => { + state.isLoading = false; + state.monitorsSummary = []; + state.success = null; + state.msg = null; + }, + }, + extraReducers: (builder) => { + builder + // ***************************************************** + // Monitors by teamId + // ***************************************************** + + .addCase(getInfrastructureMonitorsByTeamId.pending, (state) => { + state.isLoading = true; + }) + .addCase(getInfrastructureMonitorsByTeamId.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.msg; + state.monitorsSummary = action.payload.data; + }) + .addCase(getInfrastructureMonitorsByTeamId.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Getting infrastructure monitors failed"; + }) + + // ***************************************************** + // Create Monitor + // ***************************************************** + .addCase(createInfrastructureMonitor.pending, (state) => { + state.isLoading = true; + }) + .addCase(createInfrastructureMonitor.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(createInfrastructureMonitor.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to create infrastructure monitor"; + }) + // ***************************************************** + // Resolve Endpoint + // ***************************************************** + .addCase(checkEndpointResolution.pending, (state) => { + state.isLoading = true; + }) + .addCase(checkEndpointResolution.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(checkEndpointResolution.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to check endpoint resolution"; + }) + // ***************************************************** + // Get Monitor By Id + // ***************************************************** + .addCase(getInfrastructureMonitorById.pending, (state) => { + state.isLoading = true; + }) + .addCase(getInfrastructureMonitorById.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(getInfrastructureMonitorById.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload ? action.payload.msg : "Failed to get infrastructure monitor"; + }) + // ***************************************************** + // update Monitor + // ***************************************************** + .addCase(updateInfrastructureMonitor.pending, (state) => { + state.isLoading = true; + }) + .addCase(updateInfrastructureMonitor.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(updateInfrastructureMonitor.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to update infrastructure monitor"; + }) + + // ***************************************************** + // Delete Monitor + // ***************************************************** + .addCase(deleteInfrastructureMonitor.pending, (state) => { + state.isLoading = true; + }) + .addCase(deleteInfrastructureMonitor.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(deleteInfrastructureMonitor.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to delete infrastructure monitor"; + }) + // ***************************************************** + // Delete Monitor checks by Team ID + // ***************************************************** + .addCase(deleteMonitorChecksByTeamId.pending, (state) => { + state.isLoading = true; + }) + .addCase(deleteMonitorChecksByTeamId.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(deleteMonitorChecksByTeamId.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to delete monitor checks"; + }) + // ***************************************************** + // Pause Monitor + // ***************************************************** + .addCase(pauseInfrastructureMonitor.pending, (state) => { + state.isLoading = true; + }) + .addCase(pauseInfrastructureMonitor.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(pauseInfrastructureMonitor.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to pause infrastructure monitor"; + }) + // ***************************************************** + // Delete all Monitors + // ***************************************************** + .addCase(deleteAllInfrastructureMonitors.pending, (state) => { + state.isLoading = true; + }) + .addCase(deleteAllInfrastructureMonitors.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(deleteAllInfrastructureMonitors.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload ? action.payload.msg : "Failed to delete all monitors"; + }); + }, +}); + +export const { setInfrastructureMonitors, clearInfrastructureMonitorState } = infrastructureMonitorsSlice.actions; + +export default infrastructureMonitorsSlice.reducer; diff --git a/Client/src/store.js b/Client/src/store.js index 0b4c46ce8..92ddfad00 100644 --- a/Client/src/store.js +++ b/Client/src/store.js @@ -1,6 +1,7 @@ import { configureStore, combineReducers } from "@reduxjs/toolkit"; import uptimeMonitorsReducer from "./Features/UptimeMonitors/uptimeMonitorsSlice"; +import infrastructureMonitorsReducer from "./Features/InfrastructureMonitors/infrastructureMonitorsSlice"; import pageSpeedMonitorReducer from "./Features/PageSpeedMonitor/pageSpeedMonitorSlice"; import authReducer from "./Features/Auth/authSlice"; import uiReducer from "./Features/UI/uiSlice"; @@ -28,6 +29,7 @@ const persistConfig = { const rootReducer = combineReducers({ uptimeMonitors: uptimeMonitorsReducer, + infrastructureMonitors: infrastructureMonitorsReducer, auth: authReducer, pageSpeedMonitors: pageSpeedMonitorReducer, ui: uiReducer, From 40ef5c9be48be1511c046f8216dfcc41abba6ce9 Mon Sep 17 00:00:00 2001 From: Caio Cabral Date: Thu, 31 Oct 2024 11:46:52 -0400 Subject: [PATCH 026/411] fix: login validation --- .chores.md | 7 +- Client/src/Pages/Auth/Login.jsx | 117 ++++++++++-------- Client/src/Pages/Auth/Register/Register.jsx | 1 - .../Pages/Auth/Register/StepThree/index.jsx | 3 +- Client/src/Pages/Auth/SetNewPassword.jsx | 61 +-------- Server/validation/joi.js | 41 ++---- 6 files changed, 79 insertions(+), 151 deletions(-) diff --git a/.chores.md b/.chores.md index 6d45e9a11..9786ce569 100644 --- a/.chores.md +++ b/.chores.md @@ -4,7 +4,7 @@ - [ ] Validation on submit - [ ] Take validation out from the component. - [x] SetNewPassword - - [ ] Register + - [x] Register - [ ] Refactor on change to deal with step three specificity - [ ] Fix Input enter not working - [ ] PasswordPanel @@ -27,3 +27,8 @@ - [x] Confirm Password Left Blank - [x] Password Change After Successful Match - [x] Avoid submission in new password if password or confirm empty, and on error +- [ ] Update validation for password on backend + +//TODO Apply custom hook to SetNewPassword +// Enter on input triggers back action on steps one and two +// When user registers, we see sign up page for half a second diff --git a/Client/src/Pages/Auth/Login.jsx b/Client/src/Pages/Auth/Login.jsx index c0975f2d0..687abb7e6 100644 --- a/Client/src/Pages/Auth/Login.jsx +++ b/Client/src/Pages/Auth/Login.jsx @@ -130,17 +130,14 @@ const StepOne = ({ form, errors, onSubmit, onChange, onBack }) => { return ( <> Log In Enter your email address - +
{ const navigate = useNavigate(); const inputRef = useRef(null); + console.log({ errors }); + + const errorsMap = { + length: "Must be at least 8 characters long", + uppercase: "Must contain at least one upper character", + lowercase: "Must contain at least one lower character", + number: "Must contain at least one number", + special: "Must contain at least one special character", + }; + useEffect(() => { if (inputRef.current) { inputRef.current.focus(); @@ -235,7 +242,7 @@ const StepTwo = ({ form, errors, onSubmit, onChange, onBack }) => { return ( <> @@ -244,64 +251,66 @@ const StepTwo = ({ form, errors, onSubmit, onChange, onBack }) => { Enter your password - + - - + + + - - - - { if (action.payload.success) { const authToken = action.payload.data; localStorage.setItem("token", authToken); - /* navigate("/"); */ navigate("/monitors"); createToast({ body: "Welcome! Your account was created successfully.", diff --git a/Client/src/Pages/Auth/Register/StepThree/index.jsx b/Client/src/Pages/Auth/Register/StepThree/index.jsx index 0f1e540f7..b225f09e8 100644 --- a/Client/src/Pages/Auth/Register/StepThree/index.jsx +++ b/Client/src/Pages/Auth/Register/StepThree/index.jsx @@ -1,11 +1,10 @@ -import { useEffect, useRef /* , useState */ } from "react"; +import { useEffect, useRef } from "react"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { Box, Button, Stack, Typography } from "@mui/material"; import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; import Field from "../../../../Components/Inputs/Field"; import Check from "../../../../Components/Check/Check"; -/* import { credentials } from "../../../../Validation/validation"; */ import { useValidatePassword } from "../../hooks/useValidatePassword"; StepThree.propTypes = { diff --git a/Client/src/Pages/Auth/SetNewPassword.jsx b/Client/src/Pages/Auth/SetNewPassword.jsx index 5f0acef15..92d6973f1 100644 --- a/Client/src/Pages/Auth/SetNewPassword.jsx +++ b/Client/src/Pages/Auth/SetNewPassword.jsx @@ -1,4 +1,4 @@ -import { useId, useState } from "react"; +import { useId } from "react"; import { useDispatch, useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; import { useParams } from "react-router-dom"; @@ -26,11 +26,6 @@ const SetNewPassword = () => { const confirmPasswordId = useId(); const { form, errors, handleChange, feedbacks } = useValidatePassword(); - // const [errors, setErrors] = useState({}); - // const [form, setForm] = useState({ - // password: "", - // confirm: "", - // }); const { isLoading } = useSelector((state) => state.auth); const { token } = useParams(); @@ -65,60 +60,6 @@ const SetNewPassword = () => { } }; - // const handleChange = (event) => { - // const { value, name } = event.target; - // setForm((prev) => ({ ...prev, [name]: value })); - - // const validateValue = { [name]: value }; - // const validateOptions = { abortEarly: false, context: { password: form.password } }; - // if (name === "password" && form.confirm.length > 0) { - // validateValue.confirm = form.confirm; - // validateOptions.context = { password: value }; - // } else if (name === "confirm") { - // validateValue.password = form.password; - // } - - // const { error } = credentials.validate(validateValue, validateOptions); - // const errors = error?.details.map((error) => ({ - // path: error.path[0], - // message: error.message, - // })); - // const errorsByPath = - // errors && - // errors.reduce((acc, { path, message }) => { - // if (!acc[path]) { - // acc[path] = []; - // } - // acc[path].push(message); - // return acc; - // }, {}); - // setErrors(() => (errorsByPath ? { ...errorsByPath } : {})); - // }; - - // const getFeedbackStatus = (field, criteria) => { - // const fieldErrors = errors[field]; - // const isFieldEmpty = form[field].length === 0; - // const hasError = fieldErrors?.includes(criteria) || fieldErrors?.includes("empty"); - // const isCorrect = !isFieldEmpty && !hasError; - - // if (isCorrect) { - // return "success"; - // } else if (hasError) { - // return "error"; - // } else { - // return "info"; - // } - // }; - - // const feedbacks = { - // length: getFeedbackStatus("password", "length"), - // special: getFeedbackStatus("password", "special"), - // number: getFeedbackStatus("password", "number"), - // uppercase: getFeedbackStatus("password", "uppercase"), - // lowercase: getFeedbackStatus("password", "lowercase"), - // confirm: getFeedbackStatus("confirm", "different"), - // }; - return ( (value, helpers) => { // Auth //**************************************** +const passwordPattern = + /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/])[A-Za-z0-9!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/]+$/; + const loginValidation = joi.object({ email: joi .string() @@ -30,13 +33,7 @@ const loginValidation = joi.object({ } return lowercasedValue; }), - password: joi - .string() - .min(8) - .required() - .pattern( - /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ - ), + password: joi.string().min(8).required().pattern(passwordPattern), }); const registrationBodyValidation = joi.object({ @@ -59,13 +56,7 @@ const registrationBodyValidation = joi.object({ } return lowercasedValue; }), - password: joi - .string() - .min(8) - .required() - .pattern( - /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ - ), + password: joi.string().min(8).required().pattern(passwordPattern), profileImage: joi.any(), role: joi .array() @@ -84,18 +75,8 @@ const editUserBodyValidation = joi.object({ firstName: joi.string().pattern(/^[A-Za-z]+$/), lastName: joi.string().pattern(/^[A-Za-z]+$/), profileImage: joi.any(), - newPassword: joi - .string() - .min(8) - .pattern( - /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ - ), - password: joi - .string() - .min(8) - .pattern( - /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ - ), + newPassword: joi.string().min(8).pattern(passwordPattern), + password: joi.string().min(8).pattern(passwordPattern), deleteProfileImage: joi.boolean(), role: joi.array(), }); @@ -113,13 +94,7 @@ const recoveryTokenValidation = joi.object({ const newPasswordValidation = joi.object({ recoveryToken: joi.string().required(), - password: joi - .string() - .min(8) - .required() - .pattern( - /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/])[A-Za-z0-9!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/]+$/ - ), + password: joi.string().min(8).required().pattern(passwordPattern), confirm: joi.string(), }); From 5f1817fdbbec39d10d361a4147d0b7a5b2ca619c Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 31 Oct 2024 17:51:45 +0000 Subject: [PATCH 027/411] fix(deps): update dependency bullmq to v5.22.0 --- Server/package-lock.json | 8 ++++---- Server/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Server/package-lock.json b/Server/package-lock.json index e7f12dd70..e644872e0 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -12,7 +12,7 @@ "@sendgrid/mail": "^8.1.3", "axios": "^1.7.2", "bcrypt": "^5.1.1", - "bullmq": "5.21.2", + "bullmq": "5.22.0", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", @@ -1164,9 +1164,9 @@ "license": "MIT" }, "node_modules/bullmq": { - "version": "5.21.2", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.21.2.tgz", - "integrity": "sha512-LPuNoGaDc5CON2X6h4cJ2iVfd+B+02xubFU+IB/fyJHd+/HqUZRqnlYryUCAuhVHBhUKtA6oyVdJxqSa62i+og==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.22.0.tgz", + "integrity": "sha512-nwjJSQt/kpO4bIfAznyKKz3+m5OZ6YSaz2Vg7oNoZWTD5wCnJJJy6b9iWM5QIF0bADhDWyorLCO0hU3de+iKMA==", "license": "MIT", "dependencies": { "cron-parser": "^4.6.0", diff --git a/Server/package.json b/Server/package.json index 5a8e98562..12ceaa407 100644 --- a/Server/package.json +++ b/Server/package.json @@ -15,7 +15,7 @@ "@sendgrid/mail": "^8.1.3", "axios": "^1.7.2", "bcrypt": "^5.1.1", - "bullmq": "5.21.2", + "bullmq": "5.22.0", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", From b36a5d028e6534dceebdfd000b3fe49958dc3093 Mon Sep 17 00:00:00 2001 From: Caio Cabral Date: Thu, 31 Oct 2024 14:43:09 -0400 Subject: [PATCH 028/411] feat: fixing PAssword Panel --- .chores.md | 34 ------- .../TabPanels/Account/PasswordPanel.jsx | 99 +++++++++++++------ .../Pages/Auth/Register/StepThree/index.jsx | 1 - 3 files changed, 68 insertions(+), 66 deletions(-) delete mode 100644 .chores.md diff --git a/.chores.md b/.chores.md deleted file mode 100644 index 9786ce569..000000000 --- a/.chores.md +++ /dev/null @@ -1,34 +0,0 @@ -- [x] Change schema to fix validations -- [x] Refactor to apply Single Responsibility Principle to validation (validation is done on joi only) -- [ ] Validation on change -- [ ] Validation on submit -- [ ] Take validation out from the component. - - [x] SetNewPassword - - [x] Register - - [ ] Refactor on change to deal with step three specificity - - [ ] Fix Input enter not working - - [ ] PasswordPanel - - [ ] Login -- [x] Update check component to accept not highlighted text -- [x] Split some of these user feedbacks? Like: - - [x] Must contain at least one special character, - - [x] Must contain at least one number, - - [x] Must contain at least one upper character, - - [x] Must contain at least one lower character -- [x] Pass mismatching password user feedback as the other messages, for consistency -- [ ] Get rid of toast message -- [x] Tests - - [x] Valid Password and Matching Confirmation - - [x] Valid Password, Incorrect Confirmation, then Corrects confirmation - - [x] Password Update to Match Confirm Password - - [x] Short Password with Matching Confirmation - - [x] Invalid Password Format, Correct Confirmation - - [x] Empty Password Field with Confirmation Filled - - [x] Confirm Password Left Blank - - [x] Password Change After Successful Match -- [x] Avoid submission in new password if password or confirm empty, and on error -- [ ] Update validation for password on backend - -//TODO Apply custom hook to SetNewPassword -// Enter on input triggers back action on steps one and two -// When user registers, we see sign up page for half a second diff --git a/Client/src/Components/TabPanels/Account/PasswordPanel.jsx b/Client/src/Components/TabPanels/Account/PasswordPanel.jsx index 700e27fb9..c2e4c1cf8 100644 --- a/Client/src/Components/TabPanels/Account/PasswordPanel.jsx +++ b/Client/src/Components/TabPanels/Account/PasswordPanel.jsx @@ -29,13 +29,21 @@ const PasswordPanel = () => { "edit-confirm-password": "confirm", }; + const errorsMap = { + length: "Must be at least 8 characters long", + uppercase: "Must contain at least one upper character", + lowercase: "Must contain at least one lower character", + number: "Must contain at least one number", + special: "Must contain at least one special character", + different: "Confirm password and password must match", + }; + const [localData, setLocalData] = useState({ password: "", newPassword: "", confirm: "", }); const [errors, setErrors] = useState({}); - const handleChange = (event) => { const { value, id } = event.target; const name = idToName[id]; @@ -110,20 +118,32 @@ const PasswordPanel = () => { onSubmit={handleSubmit} noValidate spellCheck="false" - gap={theme.spacing(20)} + gap={theme.spacing(26)} + maxWidth={"80ch"} + marginInline={"auto"} > - - - Current password - - - { error={errors.jwtTTLUnits} /> - { value={localSettings.dbType} onChange={handleChange} onBlur={handleBlur} - error={errors.dbType} + error={errors.dbType ? true : false} + helperText={errors.dbType} /> - { value={localSettings.redisHost} onChange={handleChange} onBlur={handleBlur} - error={errors.redisHost} + error={errors.redisHost ? true : false} + helperText={errors.redisHost} /> - { value={localSettings.redisPort?.toString()} onChange={handleChange} onBlur={handleBlur} - error={errors.redisPort} + error={errors.redisPort ? true : false} + helperText={errors.redisPort} /> - { value={localSettings.pagespeedApiKey} onChange={handleChange} onBlur={handleBlur} - error={errors.pagespeedApiKey} + error={errors.pagespeedApiKey ? true : false} + helperText={errors.pagespeedApiKey} />
From bdfe91c6616929db9885071e0f45e0664937611e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 26 Nov 2024 14:17:36 +0800 Subject: [PATCH 311/411] integrate into ForgotPassword page --- Client/src/Pages/AdvancedSettings/index.jsx | 1 - Client/src/Pages/Auth/ForgotPassword.jsx | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Client/src/Pages/AdvancedSettings/index.jsx b/Client/src/Pages/AdvancedSettings/index.jsx index 60742c51b..35f0e4c85 100644 --- a/Client/src/Pages/AdvancedSettings/index.jsx +++ b/Client/src/Pages/AdvancedSettings/index.jsx @@ -1,7 +1,6 @@ import { useTheme } from "@emotion/react"; import { Box, Stack, Typography } from "@mui/material"; import TextInput from "../../Components/Inputs/TextInput"; -import Field from "../../Components/Inputs/Field"; import Link from "../../Components/Link"; import "./index.css"; import { useDispatch, useSelector } from "react-redux"; diff --git a/Client/src/Pages/Auth/ForgotPassword.jsx b/Client/src/Pages/Auth/ForgotPassword.jsx index 675bd0239..1fd5f1b2f 100644 --- a/Client/src/Pages/Auth/ForgotPassword.jsx +++ b/Client/src/Pages/Auth/ForgotPassword.jsx @@ -7,7 +7,7 @@ import { useEffect, useState } from "react"; import { credentials } from "../../Validation/validation"; import { useNavigate } from "react-router-dom"; import { IconBox } from "./styled"; -import Field from "../../Components/Inputs/Field"; +import TextInput from "../../Components/Inputs/TextInput"; import Logo from "../../assets/icons/bwu-icon.svg?react"; import Key from "../../assets/icons/key.svg?react"; import Background from "../../assets/Images/background-grid.svg?react"; @@ -160,7 +160,7 @@ const ForgotPassword = () => { spellCheck={false} onSubmit={handleSubmit} > - { placeholder="Enter your email" value={form.email} onChange={handleChange} - error={errors.email} + error={errors.email ? true : false} + helperText={errors.email} /> Date: Tue, 26 Nov 2024 14:20:59 +0800 Subject: [PATCH 312/411] Integrate into SetNewPassword --- Client/src/Pages/Auth/Login.jsx | 1 - Client/src/Pages/Auth/SetNewPassword.jsx | 15 +++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Client/src/Pages/Auth/Login.jsx b/Client/src/Pages/Auth/Login.jsx index 05bf6d0b9..eb9b8fd0e 100644 --- a/Client/src/Pages/Auth/Login.jsx +++ b/Client/src/Pages/Auth/Login.jsx @@ -9,7 +9,6 @@ import { createToast } from "../../Utils/toastUtils"; import { networkService } from "../../main"; import TextInput from "../../Components/Inputs/TextInput"; import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments"; -import Field from "../../Components/Inputs/Field"; import Background from "../../assets/Images/background-grid.svg?react"; import Logo from "../../assets/icons/bwu-icon.svg?react"; import Mail from "../../assets/icons/mail.svg?react"; diff --git a/Client/src/Pages/Auth/SetNewPassword.jsx b/Client/src/Pages/Auth/SetNewPassword.jsx index 77e621acd..ed165720d 100644 --- a/Client/src/Pages/Auth/SetNewPassword.jsx +++ b/Client/src/Pages/Auth/SetNewPassword.jsx @@ -10,6 +10,9 @@ import { createToast } from "../../Utils/toastUtils"; import { credentials } from "../../Validation/validation"; import Check from "../../Components/Check/Check"; import Field from "../../Components/Inputs/Field"; +import TextInput from "../../Components/Inputs/TextInput"; +import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments"; + import { IconBox } from "./styled"; import LockIcon from "../../assets/icons/lock.svg?react"; import Logo from "../../assets/icons/bwu-icon.svg?react"; @@ -147,7 +150,7 @@ const SetNewPassword = () => { spellCheck={false} onSubmit={handleSubmit} > - { placeholder="••••••••" value={form.password} onChange={handleChange} - error={errors.password} + error={errors.password ? true : false} + helperText={errors.password} + endAdornment={} />
{ spellCheck={false} onSubmit={handleSubmit} > - { placeholder="••••••••" value={form.confirm} onChange={handleChange} - error={errors.confirm} + error={errors.confirm ? true : false} + helperText={errors.confirm} + endAdornment={} /> Date: Tue, 26 Nov 2024 14:23:46 +0800 Subject: [PATCH 313/411] Remove unused imports, integrate into CreateMaintenance --- .../src/Pages/Auth/Register/StepTwo/index.jsx | 1 - Client/src/Pages/Auth/SetNewPassword.jsx | 1 - .../CreateMonitor/CustomThreshold/index.jsx | 1 - .../Infrastructure/CreateMonitor/index.jsx | 10 ---------- .../Maintenance/CreateMaintenance/index.jsx | 17 +++++++++-------- 5 files changed, 9 insertions(+), 21 deletions(-) diff --git a/Client/src/Pages/Auth/Register/StepTwo/index.jsx b/Client/src/Pages/Auth/Register/StepTwo/index.jsx index c78ecf718..c8b6ff267 100644 --- a/Client/src/Pages/Auth/Register/StepTwo/index.jsx +++ b/Client/src/Pages/Auth/Register/StepTwo/index.jsx @@ -4,7 +4,6 @@ import { useTheme } from "@emotion/react"; import { Box, Button, Stack, Typography } from "@mui/material"; import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; import TextInput from "../../../../Components/Inputs/TextInput"; -import Field from "../../../../Components/Inputs/Field"; StepTwo.propTypes = { form: PropTypes.object, diff --git a/Client/src/Pages/Auth/SetNewPassword.jsx b/Client/src/Pages/Auth/SetNewPassword.jsx index ed165720d..e69e65a33 100644 --- a/Client/src/Pages/Auth/SetNewPassword.jsx +++ b/Client/src/Pages/Auth/SetNewPassword.jsx @@ -9,7 +9,6 @@ import { setNewPassword } from "../../Features/Auth/authSlice"; import { createToast } from "../../Utils/toastUtils"; import { credentials } from "../../Validation/validation"; import Check from "../../Components/Check/Check"; -import Field from "../../Components/Inputs/Field"; import TextInput from "../../Components/Inputs/TextInput"; import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments"; diff --git a/Client/src/Pages/Infrastructure/CreateMonitor/CustomThreshold/index.jsx b/Client/src/Pages/Infrastructure/CreateMonitor/CustomThreshold/index.jsx index 71f392d6b..c7b9f0c76 100644 --- a/Client/src/Pages/Infrastructure/CreateMonitor/CustomThreshold/index.jsx +++ b/Client/src/Pages/Infrastructure/CreateMonitor/CustomThreshold/index.jsx @@ -1,5 +1,4 @@ import { Box, Stack, Typography } from "@mui/material"; -import Field from "../../../../Components/Inputs/Field"; import TextInput from "../../../../Components/Inputs/TextInput"; import Checkbox from "../../../../Components/Inputs/Checkbox"; import { useTheme } from "@emotion/react"; diff --git a/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx b/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx index fd638f076..1775b2645 100644 --- a/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx +++ b/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx @@ -11,7 +11,6 @@ import { useNavigate } from "react-router-dom"; import { useTheme } from "@emotion/react"; import { createToast } from "../../../Utils/toastUtils"; import { ConfigBox } from "../../Monitors/styled"; -import Field from "../../../Components/Inputs/Field"; import TextInput from "../../../Components/Inputs/TextInput"; import Select from "../../../Components/Inputs/Select"; import Checkbox from "../../../Components/Inputs/Checkbox"; @@ -356,15 +355,6 @@ const CreateInfrastructureMonitor = () => { onBlur={(e) => handleBlur(e, "interval")} items={frequencies} /> - {/* */} { }; const handleSubmit = async () => { - if (hasValidationErrors(form, maintenanceWindowValidation, setErrors)) - return; + if (hasValidationErrors(form, maintenanceWindowValidation, setErrors)) return; // Build timestamp for maintenance window from startDate and startTime const start = dayjs(form.startDate) .set("hour", form.startTime.hour()) @@ -467,14 +466,15 @@ const CreateMaintenance = () => { direction="row" spacing={theme.spacing(8)} > - { handleFormChange("duration", event.target.value); }} - error={errors["duration"]} + error={errors["duration"] ? true : false} + helperText={errors["duration"]} />