From 4fe31b27a910d46656bcb6517620178a98b79f49 Mon Sep 17 00:00:00 2001 From: Sam Windsor <121157983+windsywinds@users.noreply.github.com> Date: Thu, 27 Jun 2024 16:53:52 +1200 Subject: [PATCH 01/10] fix: add css for password input --- Client/src/Pages/Login/index.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Client/src/Pages/Login/index.css b/Client/src/Pages/Login/index.css index 68c1df5e2..41020cf67 100644 --- a/Client/src/Pages/Login/index.css +++ b/Client/src/Pages/Login/index.css @@ -45,6 +45,12 @@ font-size: var(--env-var-font-size-medium); } +#login-password-input { + width: 308px; + height: 11px; + font-size: var(--env-var-font-size-medium); +} + .login-form-password-options { display: flex; align-items: center; From 3b88ea01d0dca5a43c73e408e4eed3eef4f566b0 Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Thu, 27 Jun 2024 13:35:12 -0400 Subject: [PATCH 02/10] Fixed some small styling issues and validation schema --- .../ProfileSettings/ProfilePanel.jsx | 11 ++- Client/src/Pages/Settings/index.css | 1 - Client/src/Validation/validation.js | 72 +++++++++---------- 3 files changed, 40 insertions(+), 44 deletions(-) diff --git a/Client/src/Components/TabPanels/ProfileSettings/ProfilePanel.jsx b/Client/src/Components/TabPanels/ProfileSettings/ProfilePanel.jsx index 2675aedd9..14a3ba1b0 100644 --- a/Client/src/Components/TabPanels/ProfileSettings/ProfilePanel.jsx +++ b/Client/src/Components/TabPanels/ProfileSettings/ProfilePanel.jsx @@ -153,7 +153,7 @@ const ProfilePanel = () => {
- First Name + First name @@ -178,7 +178,7 @@ const ProfilePanel = () => {
- Last Name + Last name @@ -232,18 +232,17 @@ const ProfilePanel = () => {
- Your Photo + Your photo This photo will be displayed in your profile page. - {/* TODO - Update placeholder values with redux data */} { + if (!/[A-Z]/.test(value)) { + return helpers.message( + "*Password must contain at least one uppercase letter." + ); + } + if (!/[a-z]/.test(value)) { + return helpers.message( + "*Password must contain at least one lowercase letter." + ); + } + if (!/\d/.test(value)) { + return helpers.message("*Password must contain at least one number."); + } + if (!/[!@#$%^&*]/.test(value)) { + return helpers.message( + "*Password must contain at least one special character." + ); + } + + return value; + }); + const editPasswordValidation = joi.object({ // TBD - validation for current password ? - password : joi - .string().trim() - .messages({ - "string.empty": "*Current password is required.", - }), - newpassword: joi - .string() - .trim() - .min(8) - .messages({ - "string.empty": "*Password is required.", - "string.min": "*Password must be at least 8 characters long.", - }) - .custom((value, helpers) => { - if (!/[A-Z]/.test(value)) { - return helpers.message( - "*Password must contain at least one uppercase letter." - ); - } - if (!/[a-z]/.test(value)) { - return helpers.message( - "*Password must contain at least one lowercase letter." - ); - } - if (!/\d/.test(value)) { - return helpers.message("*Password must contain at least one number."); - } - if (!/[!@#$%^&*]/.test(value)) { - return helpers.message( - "*Password must contain at least one special character." - ); - } - - return value; - }), + password: passwordSchema, + newpassword: passwordSchema, confirm: joi .string() .trim() @@ -126,8 +124,8 @@ const editPasswordValidation = joi.object({ "string.empty": "*Password confirmation is required.", }) .custom((value, helpers) => { - const { password } = helpers.prefs.context; - if (value !== password) { + const { newpassword } = helpers.prefs.context; + if (value !== newpassword) { return helpers.message("*Passwords do not match."); } return value; From 7e0442e4ebd0133c2ba8319ddad2a23760639b3c Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 27 Jun 2024 13:19:23 -0700 Subject: [PATCH 03/10] removed dangling file: --- README.md | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 59e79159c..c54f8f66e 100644 --- a/README.md +++ b/README.md @@ -817,6 +817,18 @@ curl --request GET \ > | -------------- | ----- | > | Method | POST | +##### Body + +> | Name | Type | Notes | Accepted Values | +> | ----------- | --------- | ---------------------- | ---------------------- | +> | userId | `string` | UserId of current user | | +> | name | `string` | Monitor name | | +> | description | `string` | Monitor Description | | +> | type | `string` | Valid email address | `"ping"`|`"http"` | +> | url | `string` | URL of service or IP | | +> | isActive | `boolean` | | | +> | interval | `number` | In ms | | + ###### Response Payload > | Type | Notes | @@ -830,12 +842,15 @@ curl --request POST \ --url http://localhost:5000/api/v1/monitors \ --header \ --header 'Content-Type: application/json' \ - --data '{"userId": "6645079aae0b439371913972", - "name": "Inserting a new Monitor", - "description": "Description", - "url": "https://monitor0.com", + --header 'User-Agent: insomnia/2023.5.8' \ + --data '{ + "userId": "66675891cb17336d84c25d9f", + "name": "Ping Google", + "description": "Google", + "type": "ping", + "url": "8.8.8.8", "isActive": true, - "interval": 60000}' + "interval": 5000}' ``` ##### Sample Response From 081eb1727aaa1a5a5219745f1e3b7779a55b5a38 Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Thu, 27 Jun 2024 16:29:50 -0400 Subject: [PATCH 04/10] Refactored useStates --- Client/src/Components/Checkbox/Checkbox.css | 1 - .../Checkbox/CustomizableCheckbox.jsx | 4 +- Client/src/Components/ConfigBox/index.jsx | 4 +- Client/src/Components/RadioButton/index.jsx | 3 +- .../Flexibile/FlexibileTextField.jsx | 10 +- Client/src/Pages/CreateNewMonitor/index.jsx | 310 +++++++++--------- Client/src/Pages/Monitors/index.jsx | 1 + 7 files changed, 168 insertions(+), 165 deletions(-) diff --git a/Client/src/Components/Checkbox/Checkbox.css b/Client/src/Components/Checkbox/Checkbox.css index 27c6310cc..e78fff6da 100644 --- a/Client/src/Components/Checkbox/Checkbox.css +++ b/Client/src/Components/Checkbox/Checkbox.css @@ -33,5 +33,4 @@ font-size: var(--env-var-font-size-medium); color: var(--env-var-color-5); font-weight: 500; - cursor: pointer; } diff --git a/Client/src/Components/Checkbox/CustomizableCheckbox.jsx b/Client/src/Components/Checkbox/CustomizableCheckbox.jsx index 807562f26..e39ea134b 100644 --- a/Client/src/Components/Checkbox/CustomizableCheckbox.jsx +++ b/Client/src/Components/Checkbox/CustomizableCheckbox.jsx @@ -10,9 +10,9 @@ const CustomizableCheckbox = (props) => { onChange={props.handleChange} className="custom-checkbox" type="checkbox" - id="checkbox" + id={props.id} /> - +
); }; diff --git a/Client/src/Components/ConfigBox/index.jsx b/Client/src/Components/ConfigBox/index.jsx index 7cf547232..28776752f 100644 --- a/Client/src/Components/ConfigBox/index.jsx +++ b/Client/src/Components/ConfigBox/index.jsx @@ -3,10 +3,10 @@ import React from "react"; const ConfigBox = ({ leftLayout, rightLayout }) => { return ( -
+
{leftLayout} {rightLayout} -
+ ); }; diff --git a/Client/src/Components/RadioButton/index.jsx b/Client/src/Components/RadioButton/index.jsx index e9c874bee..40f3fc230 100644 --- a/Client/src/Components/RadioButton/index.jsx +++ b/Client/src/Components/RadioButton/index.jsx @@ -11,6 +11,7 @@ * /> * * @param {Object} props - The component props. + * @param {string} props.id - The id of the radio button. * @param {string} props.title - The title of the radio button. * @param {string} [props.desc] - The description of the radio button. * @param {string} [props.size="small"] - The size of the radio button. @@ -28,7 +29,7 @@ function RadioButton(props) { } + control={} onChange={props.onChange} label={
diff --git a/Client/src/Components/TextFields/Flexibile/FlexibileTextField.jsx b/Client/src/Components/TextFields/Flexibile/FlexibileTextField.jsx index f7b05ab12..9f2998ef9 100644 --- a/Client/src/Components/TextFields/Flexibile/FlexibileTextField.jsx +++ b/Client/src/Components/TextFields/Flexibile/FlexibileTextField.jsx @@ -1,11 +1,19 @@ import "./flexibileTextField.css"; import React from "react"; -const FlexibileTextField = ({ title, type, placeholder, value, onChange }) => { +const FlexibileTextField = ({ + id, + title, + type, + placeholder, + value, + onChange, +}) => { return (
{title}
{ - const portOptions = [ - { label: "Port 1", value: "Port1" }, - { label: "Port 2", value: "Port2" }, - // Add more Ports as needed - ]; + //General Settings Form + const [generalSettings, setGeneralSettings] = useState({ url: "", name: "" }); + //Checks Form + const [checks, setChecks] = useState({ monitor: "", port: "" }); + //Incidents Form + const [notifications, setNotifications] = useState({ + viaSms: false, + viaEmail: false, + viaOther: false, + email: "", + }); + //Advanced Settings Form + const [advancedSettings, setAdvancedSettings] = useState({ + frequency: "", + retries: "", + codes: "", + redirects: "", + }); + //Proxy Settings Form + const [proxy, setProxy] = useState({ + enabled: false, + protocol: "", + address: "", + proxy_port: "", + }); - const frequencyOptions = [ - { label: "1 minute", value: "1" }, - { label: "5 minutes", value: "5" }, - // Add more Ports as needed - ]; - - const [incidentEmail, setIncidentEmail] = useState(""); - - const [notifyViaSMS, setNotifyViaSMS] = useState(false); - const [notifyViaEmail, setNotifyViaEmail] = useState(false); - const [notifyViaCurrentEmail, setNotifyViaCurrentEmail] = useState(false); - - const [maxRetries, setMaxRetries] = useState(0); - const [acceptCode, setAcceptCode] = useState(0); - const [maxRedirects, setMaxRedirects] = useState(0); - - const [proxy, setProxy] = useState(false); - - const [proxyAddress, setProxyAddress] = useState(""); - const [proxyPort, setProxyPort] = useState(""); - - const [selectedMonitorType, setSelectedMonitorType] = useState(""); - - const [urlToMonitor, setUrlToMonitor] = useState(""); - const [friendlyName, setFriendlyName] = useState(""); - - const [port, setPort] = useState("Select a port to check"); - const [frequncy, setFrequency] = useState("1 minute"); - - const handlePortOptionChange = (newValue) => { - setPort(newValue); + const handleChange = (event, id, setState, checkbox) => { + const { value } = event.target; + setState((prev) => ({ + ...prev, + [id]: checkbox ? true : value, + })); + }; + const handleCheck = (id, setState) => { + setState((prev) => ({ + ...prev, + [id]: !prev[id], + })); }; - const handleFrequencyOptionChange = (newValue) => { - setPort(newValue); - }; + console.log({ + ...generalSettings, + ...checks, + ...notifications, + ...advancedSettings, + ...proxy, + }); - const handleUrlToMonitor = (event) => { - setUrlToMonitor(event.target.value); - console.log(urlToMonitor); - }; - - const handleFriendlyName = (event) => { - setFriendlyName(event.target.value); - console.log(friendlyName); - }; - - const handleIncidentEmail = (event) => { - setIncidentEmail(event.target.value); - console.log(incidentEmail); - }; - - const handleNotifyViaSMS = (event) => { - setNotifyViaSMS(event.target.checked); - }; - - const handleNotifyViaEmail = (event) => { - setNotifyViaEmail(event.target.checked); - }; - - const handleNotifyViaCurrentEmail = (event) => { - setNotifyViaCurrentEmail(event.target.checked); - }; - - const handleSetMaxRetries = (event) => { - setMaxRetries(event.target.value); - console.log(maxRetries); - }; - - const handleSetAcceptCode = (event) => { - setAcceptCode(event.target.value); - console.log(acceptCode); - }; - - const handleSetMaxRedirects = (event) => { - setMaxRedirects(event.target.value); - console.log(maxRedirects); - }; - - const handleEnableProxy = (event) => { - setProxy(event.target.value); - console.log(proxy); - }; - - const handleproxyAddress = (event) => { - setProxyAddress(event.target.value); - console.log(proxyAddress); - }; - - const handleproxyPort = (event) => { - setProxyPort(event.target.value); - console.log(proxyPort); - }; - - const handleMonitorTypeChange = (value) => { - setSelectedMonitorType(value); - console.log(value); - }; - - const handleCreateNewMonitor = () => { - console.log("Created"); - }; + const ports = ["Port 1", "Port 2", "Port 3"]; + const frequencies = [1, 2, 3, 4, 5]; return (
@@ -134,17 +76,25 @@ const CreateNewMonitor = () => { rightLayout={
+ handleChange(event, "url", setGeneralSettings) + } />
+ handleChange(event, "name", setGeneralSettings) + } />
} @@ -163,37 +113,48 @@ const CreateNewMonitor = () => { rightLayout={
handleMonitorTypeChange("http")} + checked={checks.monitor === "http"} + onChange={(event) => handleChange(event, "monitor", setChecks)} />
handleMonitorTypeChange("ping")} + checked={checks.monitor === "ping"} + onChange={(event) => handleChange(event, "monitor", setChecks)} />
handleMonitorTypeChange("port")} + checked={checks.monitor === "port"} + onChange={(event) => handleChange(event, "monitor", setChecks)} />
- +
} @@ -217,29 +178,35 @@ const CreateNewMonitor = () => {
handleCheck("viaSms", setNotifications)} />
handleCheck("viaEmail", setNotifications)} />
handleCheck("viaOther", setNotifications)} />
+ handleChange(event, "email", setNotifications) + } />
@@ -258,35 +225,49 @@ const CreateNewMonitor = () => {
Check frequency
- + handleChange(event, "frequency", setAdvancedSettings) + } + > + {frequencies.map((freq) => ( + + {freq} {freq === 1 ? "minute" : "minutes"} + + ))} + +
+ + handleChange(event, "retries", setAdvancedSettings) + } />
-
- Maximum retries before the service is marked as down -
+ handleChange(event, "codes", setAdvancedSettings) + } />
-
Accepted status codes
-
-
Maximum redirects
- + handleChange(event, "redirects", setAdvancedSettings) + } />
} @@ -302,19 +283,32 @@ const CreateNewMonitor = () => { rightLayout={
handleCheck("enabled", setProxy)} />
-
Proxy protocol
- + handleChange(event, "protocol", setProxy)} + />
-
Proxy address
- + handleChange(event, "address", setProxy)} + />
-
Proxy port
- + handleChange(event, "proxy_port", setProxy)} + />
} /> @@ -326,7 +320,7 @@ const CreateNewMonitor = () => { level="primary" label="Create new monitor" sx={{ width: "210px", fontSize: "var(--env-var-font-size-medium)" }} - onClick={() => handleCreateNewMonitor()} + // onClick={() => handleCreateNewMonitor()} />
diff --git a/Client/src/Pages/Monitors/index.jsx b/Client/src/Pages/Monitors/index.jsx index 00df4ee24..94bd6e74f 100644 --- a/Client/src/Pages/Monitors/index.jsx +++ b/Client/src/Pages/Monitors/index.jsx @@ -4,6 +4,7 @@ import { useEffect } from "react"; import { useSelector } from "react-redux"; import { useDispatch } from "react-redux"; import { getMonitorsByUserId } from "../../Features/Monitors/monitorsSlice"; +import CreateNewMonitor from "../CreateNewMonitor"; const Monitors = () => { const monitorState = useSelector((state) => state.monitors); From 57be438ea7a9a07cf74dcc03e163d8d80e317fbe Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 27 Jun 2024 13:42:18 -0700 Subject: [PATCH 05/10] fixed monitor page --- Client/src/Pages/Monitors/index.jsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Client/src/Pages/Monitors/index.jsx b/Client/src/Pages/Monitors/index.jsx index 94bd6e74f..5f7b4b9af 100644 --- a/Client/src/Pages/Monitors/index.jsx +++ b/Client/src/Pages/Monitors/index.jsx @@ -4,7 +4,6 @@ import { useEffect } from "react"; import { useSelector } from "react-redux"; import { useDispatch } from "react-redux"; import { getMonitorsByUserId } from "../../Features/Monitors/monitorsSlice"; -import CreateNewMonitor from "../CreateNewMonitor"; const Monitors = () => { const monitorState = useSelector((state) => state.monitors); @@ -17,8 +16,7 @@ const Monitors = () => { return (
- {/* */} - +
); }; From 16006934cdd435f324bf54d896a0c7b393fda34b Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Thu, 27 Jun 2024 16:44:56 -0400 Subject: [PATCH 06/10] Commented out unfinished features --- Client/src/Components/ConfigBox/index.jsx | 4 +-- Client/src/Pages/CreateNewMonitor/index.jsx | 38 ++++++++++----------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Client/src/Components/ConfigBox/index.jsx b/Client/src/Components/ConfigBox/index.jsx index 28776752f..7cf547232 100644 --- a/Client/src/Components/ConfigBox/index.jsx +++ b/Client/src/Components/ConfigBox/index.jsx @@ -3,10 +3,10 @@ import React from "react"; const ConfigBox = ({ leftLayout, rightLayout }) => { return ( -
+
{leftLayout} {rightLayout} - +
); }; diff --git a/Client/src/Pages/CreateNewMonitor/index.jsx b/Client/src/Pages/CreateNewMonitor/index.jsx index 669f1975c..2ca0f0392 100644 --- a/Client/src/Pages/CreateNewMonitor/index.jsx +++ b/Client/src/Pages/CreateNewMonitor/index.jsx @@ -11,7 +11,7 @@ const CreateNewMonitor = () => { //General Settings Form const [generalSettings, setGeneralSettings] = useState({ url: "", name: "" }); //Checks Form - const [checks, setChecks] = useState({ monitor: "", port: "" }); + const [checks, setChecks] = useState({ type: "", port: "" }); //Incidents Form const [notifications, setNotifications] = useState({ viaSms: false, @@ -48,19 +48,15 @@ const CreateNewMonitor = () => { })); }; - console.log({ - ...generalSettings, - ...checks, - ...notifications, - ...advancedSettings, - ...proxy, - }); + const handleCreateNewMonitor = () => { + + } const ports = ["Port 1", "Port 2", "Port 3"]; const frequencies = [1, 2, 3, 4, 5]; return ( -
+
Create new monitor
{ onChange={(event) => handleChange(event, "monitor", setChecks)} />
- { ))} -
+
*/}
} /> -
+ {/* TODO */} + {/*
{
} - /> + /> */}
{ ))}
- { onChange={(event) => handleChange(event, "redirects", setAdvancedSettings) } - /> + /> */} } /> -
+ {/* TODO */} + {/*
{ /> } - /> + /> */}
@@ -320,10 +320,10 @@ const CreateNewMonitor = () => { level="primary" label="Create new monitor" sx={{ width: "210px", fontSize: "var(--env-var-font-size-medium)" }} - // onClick={() => handleCreateNewMonitor()} + onClick={() => handleCreateNewMonitor()} />
- + ); }; From f3e222ac3a0aaa069851c807729be061a480a3df Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Thu, 27 Jun 2024 17:26:00 -0400 Subject: [PATCH 07/10] Added some basic validation --- Client/src/Pages/CreateNewMonitor/index.jsx | 135 +++++++++++++++----- Client/src/Validation/validation.js | 7 + 2 files changed, 109 insertions(+), 33 deletions(-) diff --git a/Client/src/Pages/CreateNewMonitor/index.jsx b/Client/src/Pages/CreateNewMonitor/index.jsx index 2ca0f0392..8f59f5225 100644 --- a/Client/src/Pages/CreateNewMonitor/index.jsx +++ b/Client/src/Pages/CreateNewMonitor/index.jsx @@ -5,34 +5,42 @@ import React, { useState } from "react"; import RadioButton from "../../Components/RadioButton"; import CustomizableCheckBox from "../../Components/Checkbox/CustomizableCheckbox"; import Button from "../../Components/Button"; -import { MenuItem, Select } from "@mui/material"; +import { MenuItem, Select, Typography } from "@mui/material"; +import { useSelector } from "react-redux"; +import { createMonitorValidation } from "../../Validation/validation"; const CreateNewMonitor = () => { + const { user } = useSelector((state) => state.auth); + const [errors, setErrors] = useState({}); + //General Settings Form const [generalSettings, setGeneralSettings] = useState({ url: "", name: "" }); //Checks Form - const [checks, setChecks] = useState({ type: "", port: "" }); - //Incidents Form - const [notifications, setNotifications] = useState({ - viaSms: false, - viaEmail: false, - viaOther: false, - email: "", + const [checks, setChecks] = useState({ + type: "", + // port: "" }); + //Incidents Form + // const [notifications, setNotifications] = useState({ + // viaSms: false, + // viaEmail: false, + // viaOther: false, + // email: "", + // }); //Advanced Settings Form const [advancedSettings, setAdvancedSettings] = useState({ frequency: "", - retries: "", - codes: "", - redirects: "", + // retries: "", + // codes: "", + // redirects: "", }); //Proxy Settings Form - const [proxy, setProxy] = useState({ - enabled: false, - protocol: "", - address: "", - proxy_port: "", - }); + // const [proxy, setProxy] = useState({ + // enabled: false, + // protocol: "", + // address: "", + // proxy_port: "", + // }); const handleChange = (event, id, setState, checkbox) => { const { value } = event.target; @@ -40,19 +48,59 @@ const CreateNewMonitor = () => { ...prev, [id]: checkbox ? true : value, })); + + const validation = createMonitorValidation.validate( + { [id]: value }, + { abortEarly: false } + ); + + setErrors((prev) => { + const updatedErrors = { ...prev }; + + if (validation.error) { + updatedErrors[id] = validation.error.details[0].message; + } else { + delete updatedErrors[id]; + } + return updatedErrors; + }); }; - const handleCheck = (id, setState) => { - setState((prev) => ({ - ...prev, - [id]: !prev[id], - })); + // const handleCheck = (id, setState) => { + // setState((prev) => ({ + // ...prev, + // [id]: !prev[id], + // })); + // }; + + const handleCreateNewMonitor = (event) => { + event.preventDefault(); + //obj to submit + const monitor = { + ...generalSettings, + ...advancedSettings, + userId: user._id, + ...checks, + }; + + const { error } = createMonitorValidation.validate(monitor, { + abortEarly: false, + }); + + if (error) { + const newErrors = {}; + error.details.forEach((err) => { + newErrors[err.path[0]] = err.message; + }); + setErrors(newErrors); + } else { + //TODO - submit logic + } }; - const handleCreateNewMonitor = () => { + console.log(errors); - } - - const ports = ["Port 1", "Port 2", "Port 3"]; + //select values + // const ports = ["Port 1", "Port 2", "Port 3"]; const frequencies = [1, 2, 3, 4, 5]; return ( @@ -81,6 +129,13 @@ const CreateNewMonitor = () => { handleChange(event, "url", setGeneralSettings) } /> + {errors["url"] ? ( + + {errors["url"]} + + ) : ( + "" + )}
{ handleChange(event, "name", setGeneralSettings) } /> + {errors["name"] ? ( + + {errors["name"]} + + ) : ( + "" + )} } /> @@ -113,8 +175,8 @@ const CreateNewMonitor = () => { title="HTTP/website monitoring" desc="Use HTTP(s) to monitor your website or API endpoint." value="http" - checked={checks.monitor === "http"} - onChange={(event) => handleChange(event, "monitor", setChecks)} + checked={checks.type === "http"} + onChange={(event) => handleChange(event, "type", setChecks)} />
{ title="Ping monitoring" desc="Check whether your server is available or not." value="ping" - checked={checks.monitor === "ping"} - onChange={(event) => handleChange(event, "monitor", setChecks)} + checked={checks.type === "ping"} + onChange={(event) => handleChange(event, "type", setChecks)} />
{/* TODO */} @@ -132,8 +194,8 @@ const CreateNewMonitor = () => { title="Port monitoring" desc="Monitor a specific service on your server." value="port" - checked={checks.monitor === "port"} - onChange={(event) => handleChange(event, "monitor", setChecks)} + checked={checks.type === "port"} + onChange={(event) => handleChange(event, "type", setChecks)} />
@@ -153,6 +215,13 @@ const CreateNewMonitor = () => { ))}
*/} + {errors["type"] ? ( + + {errors["type"]} + + ) : ( + "" + )} } /> @@ -320,7 +389,7 @@ const CreateNewMonitor = () => { level="primary" label="Create new monitor" sx={{ width: "210px", fontSize: "var(--env-var-font-size-medium)" }} - onClick={() => handleCreateNewMonitor()} + onClick={handleCreateNewMonitor} /> diff --git a/Client/src/Validation/validation.js b/Client/src/Validation/validation.js index d8bfd8ed1..802965870 100644 --- a/Client/src/Validation/validation.js +++ b/Client/src/Validation/validation.js @@ -132,7 +132,14 @@ const editPasswordValidation = joi.object({ }), }); +const createMonitorValidation = joi.object({ + url: joi.string().trim().messages({ "string.empty": "*Url is required." }), + name: joi.string().trim().messages({ "string.empty": "*Name is required." }), + type: joi.string().trim().messages({ "string.empty": "*Type is required." }), +}); + export { + createMonitorValidation, registerValidation, loginValidation, recoveryValidation, From 7a066733daa30c0766bdf194b2000fbc3cea23e2 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 27 Jun 2024 15:27:32 -0700 Subject: [PATCH 08/10] Added async thunk for creating monitors, connected CreateMonitor to backend --- Client/src/Features/Monitors/monitorsSlice.js | 42 +++++++++++++++++++ Client/src/Pages/CreateNewMonitor/index.jsx | 25 +++++++---- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/Client/src/Features/Monitors/monitorsSlice.js b/Client/src/Features/Monitors/monitorsSlice.js index 19d536797..c2a4ad044 100644 --- a/Client/src/Features/Monitors/monitorsSlice.js +++ b/Client/src/Features/Monitors/monitorsSlice.js @@ -1,6 +1,7 @@ import axios from "axios"; import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import { jwtDecode } from "jwt-decode"; +const BASE_URL = import.meta.env.VITE_APP_API_BASE_URL; const initialState = { isLoading: false, @@ -9,6 +10,28 @@ const initialState = { msg: null, }; +export const createMonitor = createAsyncThunk( + "monitors/createMonitor", + async (data, thunkApi) => { + try { + const { authToken, monitor } = data; + + const res = await axios.post(`${BASE_URL}/monitors`, monitor, { + headers: { + Authorization: `Bearer ${authToken}`, + "Content-Type": "application/json", + }, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + return thunkApi.rejectWithValue(error.message); + } + } +); + export const getMonitors = createAsyncThunk( "monitors/getMonitors", async (token, thunkApi) => { @@ -99,6 +122,25 @@ const monitorsSlice = createSlice({ state.msg = action.payload ? action.payload.msg : "Getting montiors failed"; + }) + + // ***************************************************** + // Create Monitor + // ***************************************************** + .addCase(createMonitor.pending, (state) => { + state.isLoading = true; + }) + .addCase(createMonitor.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(createMonitor.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to create monitor"; }); }, }); diff --git a/Client/src/Pages/CreateNewMonitor/index.jsx b/Client/src/Pages/CreateNewMonitor/index.jsx index 8f59f5225..127ec9766 100644 --- a/Client/src/Pages/CreateNewMonitor/index.jsx +++ b/Client/src/Pages/CreateNewMonitor/index.jsx @@ -6,11 +6,15 @@ import RadioButton from "../../Components/RadioButton"; import CustomizableCheckBox from "../../Components/Checkbox/CustomizableCheckbox"; import Button from "../../Components/Button"; import { MenuItem, Select, Typography } from "@mui/material"; -import { useSelector } from "react-redux"; +import { useSelector, useDispatch } from "react-redux"; import { createMonitorValidation } from "../../Validation/validation"; +import { createMonitor } from "../../Features/Monitors/monitorsSlice"; const CreateNewMonitor = () => { - const { user } = useSelector((state) => state.auth); + const MS_PER_MINUTE = 60000; + const { user, authToken } = useSelector((state) => state.auth); + const dispatch = useDispatch(); + const [errors, setErrors] = useState({}); //General Settings Form @@ -29,7 +33,7 @@ const CreateNewMonitor = () => { // }); //Advanced Settings Form const [advancedSettings, setAdvancedSettings] = useState({ - frequency: "", + frequency: 1, // retries: "", // codes: "", // redirects: "", @@ -75,10 +79,8 @@ const CreateNewMonitor = () => { const handleCreateNewMonitor = (event) => { event.preventDefault(); //obj to submit - const monitor = { + let monitor = { ...generalSettings, - ...advancedSettings, - userId: user._id, ...checks, }; @@ -93,12 +95,17 @@ const CreateNewMonitor = () => { }); setErrors(newErrors); } else { - //TODO - submit logic + monitor = { + ...monitor, + description: monitor.name, + userId: user._id, + // ...advancedSettings, // TODO frequency should be interval, then we can use spread + interval: advancedSettings.frequency * MS_PER_MINUTE, + }; + dispatch(createMonitor({ authToken, monitor })); } }; - console.log(errors); - //select values // const ports = ["Port 1", "Port 2", "Port 3"]; const frequencies = [1, 2, 3, 4, 5]; From 018b01f53c09130b8a3084e72e52163cfd955ae9 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 27 Jun 2024 15:45:05 -0700 Subject: [PATCH 09/10] Redirected user to Monitors page after adding a new monitor --- Client/src/Pages/CreateNewMonitor/index.jsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Client/src/Pages/CreateNewMonitor/index.jsx b/Client/src/Pages/CreateNewMonitor/index.jsx index 127ec9766..19dd1386a 100644 --- a/Client/src/Pages/CreateNewMonitor/index.jsx +++ b/Client/src/Pages/CreateNewMonitor/index.jsx @@ -9,11 +9,13 @@ import { MenuItem, Select, Typography } from "@mui/material"; import { useSelector, useDispatch } from "react-redux"; import { createMonitorValidation } from "../../Validation/validation"; import { createMonitor } from "../../Features/Monitors/monitorsSlice"; +import { useNavigate } from "react-router-dom"; const CreateNewMonitor = () => { const MS_PER_MINUTE = 60000; const { user, authToken } = useSelector((state) => state.auth); const dispatch = useDispatch(); + const navigate = useNavigate(); const [errors, setErrors] = useState({}); @@ -76,7 +78,7 @@ const CreateNewMonitor = () => { // })); // }; - const handleCreateNewMonitor = (event) => { + const handleCreateNewMonitor = async (event) => { event.preventDefault(); //obj to submit let monitor = { @@ -102,7 +104,14 @@ const CreateNewMonitor = () => { // ...advancedSettings, // TODO frequency should be interval, then we can use spread interval: advancedSettings.frequency * MS_PER_MINUTE, }; - dispatch(createMonitor({ authToken, monitor })); + try { + const action = await dispatch(createMonitor({ authToken, monitor })); + if (action.meta.requestStatus === "fulfilled") { + navigate("/monitors"); + } + } catch (error) { + alert(error); + } } }; From 62d545df065a9f02bc2f77c493a878a894ac3605 Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Thu, 27 Jun 2024 19:19:36 -0400 Subject: [PATCH 10/10] Added account parent route and fixed links --- Client/src/App.jsx | 13 ++ Client/src/Components/NavBar/index.jsx | 4 +- .../PasswordPanel.jsx | 2 +- .../ProfilePanel.jsx | 2 +- .../TeamPanel.jsx | 2 +- Client/src/Pages/Account/index.css | 200 +++++++++++++++++ Client/src/Pages/Account/index.jsx | 84 ++++++++ Client/src/Pages/Settings/index.css | 203 ------------------ Client/src/Pages/Settings/index.jsx | 83 +------ 9 files changed, 304 insertions(+), 289 deletions(-) rename Client/src/Components/TabPanels/{ProfileSettings => Account}/PasswordPanel.jsx (99%) rename Client/src/Components/TabPanels/{ProfileSettings => Account}/ProfilePanel.jsx (99%) rename Client/src/Components/TabPanels/{ProfileSettings => Account}/TeamPanel.jsx (99%) create mode 100644 Client/src/Pages/Account/index.css create mode 100644 Client/src/Pages/Account/index.jsx diff --git a/Client/src/App.jsx b/Client/src/App.jsx index 03ecf303b..4a05c1de3 100644 --- a/Client/src/App.jsx +++ b/Client/src/App.jsx @@ -6,6 +6,7 @@ import Register from "./Pages/Register"; import HomeLayout from "./Layouts/HomeLayout"; import Demo from "./Pages/Demo/Demo"; import PlayGround from "./Pages/PlayGround/PlayGround"; +import Account from "./Pages/Account"; import Monitors from "./Pages/Monitors"; import CreateNewMonitor from "./Pages/CreateNewMonitor"; import Incidents from "./Pages/Incidents"; @@ -53,6 +54,18 @@ function App() { path="settings" element={} /> + } + /> + } + /> + } + /> } /> diff --git a/Client/src/Components/NavBar/index.jsx b/Client/src/Components/NavBar/index.jsx index c3ef60091..1e28f0aff 100644 --- a/Client/src/Components/NavBar/index.jsx +++ b/Client/src/Components/NavBar/index.jsx @@ -66,10 +66,10 @@ function NavBar() { setAnchorElUser(null); switch (setting) { case "Profile": - console.log("Profile"); + navigate("/account/profile"); break; case "Team": - console.log("Team"); + navigate("/account/team"); break; case "Invite Colleagues": console.log("Invite Colleagues"); diff --git a/Client/src/Components/TabPanels/ProfileSettings/PasswordPanel.jsx b/Client/src/Components/TabPanels/Account/PasswordPanel.jsx similarity index 99% rename from Client/src/Components/TabPanels/ProfileSettings/PasswordPanel.jsx rename to Client/src/Components/TabPanels/Account/PasswordPanel.jsx index 56eb9a12a..31664e2a2 100644 --- a/Client/src/Components/TabPanels/ProfileSettings/PasswordPanel.jsx +++ b/Client/src/Components/TabPanels/Account/PasswordPanel.jsx @@ -87,7 +87,7 @@ const PasswordPanel = () => { }; return ( - +
{ }; return ( - +
diff --git a/Client/src/Components/TabPanels/ProfileSettings/TeamPanel.jsx b/Client/src/Components/TabPanels/Account/TeamPanel.jsx similarity index 99% rename from Client/src/Components/TabPanels/ProfileSettings/TeamPanel.jsx rename to Client/src/Components/TabPanels/Account/TeamPanel.jsx index f639ec155..23ea3e6ba 100644 --- a/Client/src/Components/TabPanels/ProfileSettings/TeamPanel.jsx +++ b/Client/src/Components/TabPanels/Account/TeamPanel.jsx @@ -184,7 +184,7 @@ const TeamPanel = () => { return count; }; return ( - +
.announcement-tile { + width: 100%; +} +.edit-password-form__wrapper + > .announcement-tile + .announcement-content-subject { + margin-bottom: 2px; +} +#edit-new-password + div > button > svg, +#edit-confirm-password + div > button > svg, +#edit-current-password + div > button > svg { + color: var(--env-var-color-29); +} +.edit-password-form__wrapper .announcement-tile { + margin: 0; +} +.edit-password-form__wrapper .announcement-tile .announcement-close { + margin-left: auto; +} +.MuiBox-root > .announcement-tile { + border-color: var(--env-var-color-27); + background-color: var(--lighter-env-var-color-27); +} +.MuiBox-root > .announcement-tile .announcement-content-body { + color: var(--env-var-color-28); +} +.MuiBox-root > .announcement-tile .announcement-close svg, +.edit-password-form__wrapper > .announcement-tile .announcement-close svg { + display: none; +} +#org-name-flex-container .Mui-disabled { + color: var(--env-var-color-2) !important; +} + +.edit-team-form__wrapper td { + padding: 10px 0; + padding-top: 20px; + vertical-align: text-top; +} +.edit-team-form__wrapper th { + padding: var(--env-var-spacing-1) 0; +} +.edit-team-form__wrapper th:first-of-type { + padding-left: 20px; +} +.edit-team-form__wrapper td { + color: var(--env-var-color-25); +} + +#select-actions, +#select-role { + padding: 5px 0; + padding-left: 15px; + padding-right: 50px; + line-height: 1.75; +} +.MuiInputBase-root:has(#select-actions) fieldset, +.MuiInputBase-root:has(#select-role) fieldset { + border-color: #a1a7b1; + margin: -1px; + border-width: 1px; +} +.MuiInputBase-root:has(#select-actions):hover fieldset, +.MuiInputBase-root:has(#select-role):hover fieldset { + border-color: var(--env-var-color-2); +} + +.MuiStack-root:has(.MuiBox-root > .members-query) .MuiBox-root { + cursor: pointer; +} +.members-query { + display: inline-flex; + justify-content: center; + align-items: center; + margin-left: 6px; + padding: 2px; + font-size: 12px; + font-weight: 500; + color: var(--env-var-color-3); + background-color: #e0e9fd; + min-width: 22px; + min-height: 22px; + border-radius: 50%; +} + +/* for testing, will remove later */ +@media only screen and (max-width: 1600px) { + .edit-profile-form__wrapper .MuiStack-root:not(.row-stack), + .edit-password-form__wrapper .MuiStack-root:not(.row-stack), + .edit-organization-form__wrapper .MuiStack-root:not(.row-stack) { + margin-right: 30px; + } +} + +.account .MuiStack-root:has(p.MuiTypography-root.input-error) { + position: relative; +} +.account .text-field-icon { + position: absolute; + top: 50%; + transform: translateY(-50%); + width: 18px !important; + height: 18px !important; + opacity: 0.8; +} +.account p.MuiTypography-root.input-error { + color: var(--env-var-color-24); + opacity: 0.8; + position: absolute; + top: calc(100% + 5px); +} +.account .email-text-field, +.account .password-text-field { + margin: 0; +} + +.edit-profile-form__wrapper .MuiFormControl-root, +.edit-password-form__wrapper .MuiFormControl-root { + width: 100%; +} +.edit-profile-form__wrapper .MuiStack-root:has(.MuiFormControl-root), +.edit-password-form__wrapper .MuiStack-root:has(.MuiFormControl-root) { + margin: 0; +} \ No newline at end of file diff --git a/Client/src/Pages/Account/index.jsx b/Client/src/Pages/Account/index.jsx new file mode 100644 index 000000000..1a24bb36e --- /dev/null +++ b/Client/src/Pages/Account/index.jsx @@ -0,0 +1,84 @@ +import PropTypes from "prop-types"; +import { useState } from "react"; +import { Box, Tab, useTheme } from "@mui/material"; +import TabContext from "@mui/lab/TabContext"; +import TabList from "@mui/lab/TabList"; +import "./index.css"; +import ProfilePanel from "../../Components/TabPanels/Account/ProfilePanel"; +import PasswordPanel from "../../Components/TabPanels/Account/PasswordPanel"; +import TeamPanel from "../../Components/TabPanels/Account/TeamPanel"; +import { useNavigate } from "react-router"; + +/** + * Account component renders a settings page with tabs for Profile, Password, and Team settings. + * @param {string} [props.open] - Specifies the initially open tab: 'profile', 'password', or 'team'. + * @returns {JSX.Element} + */ + +const tabList = ["Profile", "Password", "Team"]; + +const Account = ({ open = "profile" }) => { + const theme = useTheme(); + const navigate = useNavigate(); + const tab = open; + const handleTabChange = (event, newTab) => { + navigate(`/account/${newTab}`); + }; + + return ( + + + + + {tabList.map((label, index) => ( + + ))} + + + + + + + + ); +}; + +Account.propTypes = { + open: PropTypes.oneOf(["profile", "password", "team"]), +}; + +export default Account; diff --git a/Client/src/Pages/Settings/index.css b/Client/src/Pages/Settings/index.css index b0c2afb8d..e69de29bb 100644 --- a/Client/src/Pages/Settings/index.css +++ b/Client/src/Pages/Settings/index.css @@ -1,203 +0,0 @@ -.settings h1.MuiTypography-root, -.settings p.MuiTypography-root, -.settings input, -.settings button, -.settings td, -.settings .MuiSelect-select { - font-size: var(--env-var-font-size-medium); -} -.settings h1.MuiTypography-root, -.settings p.MuiTypography-root, -.settings input, -.settings .MuiSelect-select { - color: var(--env-var-color-2); -} -.settings h1.MuiTypography-root { - font-weight: 600; -} -.settings .MuiStack-root:not(.table-stack) { - flex: 1; - -webkit-flex: 1; - -ms-flex: 1; -} -.settings .MuiTabPanel-root { - padding: 0; - margin-top: 50px; -} -.settings .MuiDivider-root:not(.short-divider) { - margin: 50px 0; -} -.settings .MuiDivider-root, -.settings .MuiOutlinedInput-root fieldset, -.announcement-tile { - border-color: var(--env-var-color-29); -} -.settings [class$="-form"] { - width: inherit; -} -.settings [class$="__wrapper"], -.settings [class$="__wrapper compact"] { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: flex-start; -} -.edit-profile-form__wrapper .MuiStack-root:not(.row-stack), -.edit-password-form__wrapper .MuiStack-root:not(.row-stack), -.edit-organization-form__wrapper .MuiStack-root:not(.row-stack) { - flex-direction: column; - gap: 8px; - margin-right: 10px; -} -.edit-profile-form__wrapper p.MuiTypography-root, -.delete-profile-form__wrapper p.MuiTypography-root { - opacity: 0.6; -} -#modal-delete-account, -#modal-edit-org-name, -#modal-invite-member { - font-size: var(--env-var-font-size-large); -} -.edit-profile-form__wrapper:not(:first-of-type), -.edit-password-form__wrapper:not(:first-of-type), -.edit-team-form__wrapper:not(:first-of-type) { - margin-top: var(--env-var-spacing-4); -} -.edit-team-form__wrapper.compact { - margin-top: var(--env-var-spacing-3); - justify-content: flex-start; - align-items: center; -} -.edit-profile-form__wrapper input, -.edit-password-form__wrapper input, -#edit-organization-name, -#input-team-member { - padding: 10px; -} -.edit-password-form__wrapper > .announcement-tile { - width: 100%; -} -.edit-password-form__wrapper - > .announcement-tile - .announcement-content-subject { - margin-bottom: 2px; -} -#edit-new-password + div > button > svg, -#edit-confirm-password + div > button > svg, -#edit-current-password + div > button > svg { - color: var(--env-var-color-29); -} -.edit-password-form__wrapper .announcement-tile { - margin: 0; -} -.edit-password-form__wrapper .announcement-tile .announcement-close { - margin-left: auto; -} -.MuiBox-root > .announcement-tile { - border-color: var(--env-var-color-27); - background-color: var(--lighter-env-var-color-27); -} -.MuiBox-root > .announcement-tile .announcement-content-body { - color: var(--env-var-color-28); -} -.MuiBox-root > .announcement-tile .announcement-close svg, -.edit-password-form__wrapper > .announcement-tile .announcement-close svg { - display: none; -} -#org-name-flex-container .Mui-disabled { - color: var(--env-var-color-2) !important; -} - -.edit-team-form__wrapper td { - padding: 10px 0; - padding-top: 20px; - vertical-align: text-top; -} -.edit-team-form__wrapper th { - padding: var(--env-var-spacing-1) 0; -} -.edit-team-form__wrapper th:first-of-type { - padding-left: 20px; -} -.edit-team-form__wrapper td { - color: var(--env-var-color-25); -} - -#select-actions, -#select-role { - padding: 5px 0; - padding-left: 15px; - padding-right: 50px; - line-height: 1.75; -} -.MuiInputBase-root:has(#select-actions) fieldset, -.MuiInputBase-root:has(#select-role) fieldset { - border-color: #a1a7b1; - margin: -1px; - border-width: 1px; -} -.MuiInputBase-root:has(#select-actions):hover fieldset, -.MuiInputBase-root:has(#select-role):hover fieldset { - border-color: var(--env-var-color-2); -} - -.MuiStack-root:has(.MuiBox-root > .members-query) .MuiBox-root { - cursor: pointer; -} -.members-query { - display: inline-flex; - justify-content: center; - align-items: center; - margin-left: 6px; - padding: 2px; - font-size: 12px; - font-weight: 500; - color: var(--env-var-color-3); - background-color: #e0e9fd; - min-width: 22px; - min-height: 22px; - border-radius: 50%; -} - -/* for testing, will remove later */ -@media only screen and (max-width: 1600px) { - .settings { - max-width: calc(90vw - 250px) !important; - } - .edit-profile-form__wrapper .MuiStack-root:not(.row-stack), - .edit-password-form__wrapper .MuiStack-root:not(.row-stack), - .edit-organization-form__wrapper .MuiStack-root:not(.row-stack) { - margin-right: 30px; - } -} - -.settings .MuiStack-root:has(p.MuiTypography-root.input-error) { - position: relative; -} -.settings .text-field-icon { - position: absolute; - top: 50%; - transform: translateY(-50%); - width: 18px !important; - height: 18px !important; - opacity: 0.8; -} -.settings p.MuiTypography-root.input-error { - color: var(--env-var-color-24); - opacity: 0.8; - position: absolute; - top: calc(100% + 5px); -} -.settings .email-text-field, -.settings .password-text-field { - margin: 0; -} - -.edit-profile-form__wrapper .MuiFormControl-root, -.edit-password-form__wrapper .MuiFormControl-root { - width: 100%; -} -.edit-profile-form__wrapper .MuiStack-root:has(.MuiFormControl-root), -.edit-password-form__wrapper .MuiStack-root:has(.MuiFormControl-root) { - margin: 0; -} \ No newline at end of file diff --git a/Client/src/Pages/Settings/index.jsx b/Client/src/Pages/Settings/index.jsx index 3dd47859b..3ca261a80 100644 --- a/Client/src/Pages/Settings/index.jsx +++ b/Client/src/Pages/Settings/index.jsx @@ -1,86 +1,7 @@ -import PropTypes from "prop-types"; -import { useState } from "react"; -import { Box, Tab, useTheme } from "@mui/material"; -import TabContext from "@mui/lab/TabContext"; -import TabList from "@mui/lab/TabList"; -import "./index.css"; -import ProfilePanel from "../../Components/TabPanels/ProfileSettings/ProfilePanel"; -import PasswordPanel from "../../Components/TabPanels/ProfileSettings/PasswordPanel"; -import TeamPanel from "../../Components/TabPanels/ProfileSettings/TeamPanel"; - -/** - * Settings component renders a settings page with tabs for Profile, Password, and Team settings. - * - * @returns {JSX.Element} - */ - -const tabList = ["Profile", "Password", "Team"]; +import React from "react"; const Settings = () => { - const theme = useTheme(); - //(tab) 0 - Profile - //(tab) 1 - Password - //(tab) 2 - Team - const [tab, setTab] = useState("0"); - const handleTabChange = (event, newTab) => { - setTab(newTab); - }; - - return ( - - - - - {tabList.map((label, index) => ( - - ))} - - - - - - - - ); -}; - -Settings.propTypes = { - // No props are being passed to this component, hence no specific PropTypes are defined. + return
Settings
; }; export default Settings;