Merge branch 'bluewave-labs:master' into monitors
This commit is contained in:
19 files changed
+672
-505
No files matched your search
@@ -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={<ProtectedRoute Component={Settings} />}
|
||||
/>
|
||||
<Route
|
||||
path="account/profile"
|
||||
element={<ProtectedRoute Component={Account} open="profile" />}
|
||||
/>
|
||||
<Route
|
||||
path="account/password"
|
||||
element={<ProtectedRoute Component={Account} open="password" />}
|
||||
/>
|
||||
<Route
|
||||
path="account/team"
|
||||
element={<ProtectedRoute Component={Account} open="team" />}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route exact path="/login" element={<Login />} />
|
||||
|
||||
@@ -33,5 +33,4 @@
|
||||
font-size: var(--env-var-font-size-medium);
|
||||
color: var(--env-var-color-5);
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -10,9 +10,9 @@ const CustomizableCheckbox = (props) => {
|
||||
onChange={props.handleChange}
|
||||
className="custom-checkbox"
|
||||
type="checkbox"
|
||||
id="checkbox"
|
||||
id={props.id}
|
||||
/>
|
||||
<label className="custom-checkbox-label">{props.title}</label>
|
||||
<label className="custom-checkbox-label" htmlFor={props.id}>{props.title}</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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) {
|
||||
<FormControlLabel
|
||||
checked={props.checked}
|
||||
value={props.value}
|
||||
control={<Radio size={props.size} />}
|
||||
control={<Radio id={props.id} size={props.size} />}
|
||||
onChange={props.onChange}
|
||||
label={
|
||||
<div>
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ const PasswordPanel = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<TabPanel value="1">
|
||||
<TabPanel value="password">
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="edit-password-form"
|
||||
+6
-7
@@ -148,12 +148,12 @@ const ProfilePanel = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<TabPanel value="0">
|
||||
<TabPanel value="profile">
|
||||
<form className="edit-profile-form" noValidate spellCheck="false">
|
||||
<div className="edit-profile-form__wrapper">
|
||||
<Stack>
|
||||
<Typography variant="h4" component="h1">
|
||||
First Name
|
||||
First name
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack>
|
||||
@@ -178,7 +178,7 @@ const ProfilePanel = () => {
|
||||
<div className="edit-profile-form__wrapper">
|
||||
<Stack>
|
||||
<Typography variant="h4" component="h1">
|
||||
Last Name
|
||||
Last name
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack>
|
||||
@@ -232,18 +232,17 @@ const ProfilePanel = () => {
|
||||
<div className="edit-profile-form__wrapper">
|
||||
<Stack>
|
||||
<Typography variant="h4" component="h1">
|
||||
Your Photo
|
||||
Your photo
|
||||
</Typography>
|
||||
<Typography variant="h5" component="p">
|
||||
This photo will be displayed in your profile page.
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack className="row-stack" direction="row" alignItems="center">
|
||||
{/* TODO - Update placeholder values with redux data */}
|
||||
<Avatar
|
||||
src={picture ? picture.src : "/static/images/avatar/2.jpg"}
|
||||
firstName={localData.firstname}
|
||||
lastName={localData.lastname}
|
||||
firstName={user?.firstname}
|
||||
lastName={user?.lastname}
|
||||
sx={{
|
||||
width: "64px",
|
||||
height: "64px",
|
||||
+1
-1
@@ -184,7 +184,7 @@ const TeamPanel = () => {
|
||||
return count;
|
||||
};
|
||||
return (
|
||||
<TabPanel value="2">
|
||||
<TabPanel value="team">
|
||||
<form className="edit-organization-form">
|
||||
<div
|
||||
className="edit-organization-form__wrapper"
|
||||
@@ -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 (
|
||||
<div className="flexibile-textfield">
|
||||
<div className="flexibile-textfield-title">{title}</div>
|
||||
<input
|
||||
id={id}
|
||||
type={type}
|
||||
className="flexibile-textfield-input"
|
||||
placeholder={placeholder}
|
||||
|
||||
@@ -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";
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
.account h1.MuiTypography-root,
|
||||
.account p.MuiTypography-root,
|
||||
.account input,
|
||||
.account button,
|
||||
.account td,
|
||||
.account .MuiSelect-select {
|
||||
font-size: var(--env-var-font-size-medium);
|
||||
}
|
||||
.account h1.MuiTypography-root,
|
||||
.account p.MuiTypography-root,
|
||||
.account input,
|
||||
.account .MuiSelect-select {
|
||||
color: var(--env-var-color-2);
|
||||
}
|
||||
.account h1.MuiTypography-root {
|
||||
font-weight: 600;
|
||||
}
|
||||
.account .MuiStack-root:not(.table-stack) {
|
||||
flex: 1;
|
||||
-webkit-flex: 1;
|
||||
-ms-flex: 1;
|
||||
}
|
||||
.account .MuiTabPanel-root {
|
||||
padding: 0;
|
||||
margin-top: 50px;
|
||||
}
|
||||
.account .MuiDivider-root:not(.short-divider) {
|
||||
margin: 50px 0;
|
||||
}
|
||||
.account .MuiDivider-root,
|
||||
.account .MuiOutlinedInput-root fieldset,
|
||||
.account .announcement-tile {
|
||||
border-color: var(--env-var-color-29);
|
||||
}
|
||||
.account [class$="-form"] {
|
||||
width: inherit;
|
||||
}
|
||||
.account [class$="__wrapper"],
|
||||
.account [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) {
|
||||
.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;
|
||||
}
|
||||
@@ -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 (
|
||||
<Box
|
||||
//TODO - breakpoints for responsive design
|
||||
minWidth={theme.spacing(55)}
|
||||
maxWidth="1200px"
|
||||
py={theme.spacing(5)}
|
||||
px={theme.spacing(10)}
|
||||
className="account"
|
||||
>
|
||||
<TabContext value={tab}>
|
||||
<Box
|
||||
sx={{
|
||||
borderBottom: 1,
|
||||
borderColor: theme.palette.section.borderColor,
|
||||
"& .MuiTabs-root": { height: "fit-content", minHeight: "0" },
|
||||
}}
|
||||
>
|
||||
<TabList onChange={handleTabChange} aria-label="account tabs">
|
||||
{tabList.map((label, index) => (
|
||||
<Tab
|
||||
label={label}
|
||||
key={index}
|
||||
value={label.toLowerCase()}
|
||||
sx={{
|
||||
fontSize: "13px",
|
||||
color: theme.palette.secondary.main,
|
||||
textTransform: "none",
|
||||
minWidth: "fit-content",
|
||||
minHeight: 0,
|
||||
paddingLeft: "0",
|
||||
paddingY: "10px",
|
||||
marginRight: "20px",
|
||||
"&:focus": {
|
||||
outline: "none",
|
||||
},
|
||||
"& .MuiTouchRipple-root": {
|
||||
pointerEvents: "none",
|
||||
display: "none",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</TabList>
|
||||
</Box>
|
||||
<ProfilePanel />
|
||||
<PasswordPanel />
|
||||
<TeamPanel />
|
||||
</TabContext>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Account.propTypes = {
|
||||
open: PropTypes.oneOf(["profile", "password", "team"]),
|
||||
};
|
||||
|
||||
export default Account;
|
||||
@@ -3,122 +3,124 @@ import ConfigBox from "../../Components/ConfigBox";
|
||||
import FlexibileTextField from "../../Components/TextFields/Flexibile/FlexibileTextField";
|
||||
import React, { useState } from "react";
|
||||
import RadioButton from "../../Components/RadioButton";
|
||||
import Dropdown from "../../Components/Dropdown";
|
||||
import CustomizableCheckBox from "../../Components/Checkbox/CustomizableCheckbox";
|
||||
import Button from "../../Components/Button";
|
||||
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 portOptions = [
|
||||
{ label: "Port 1", value: "Port1" },
|
||||
{ label: "Port 2", value: "Port2" },
|
||||
// Add more Ports as needed
|
||||
];
|
||||
const MS_PER_MINUTE = 60000;
|
||||
const { user, authToken } = useSelector((state) => state.auth);
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const frequencyOptions = [
|
||||
{ label: "1 minute", value: "1" },
|
||||
{ label: "5 minutes", value: "5" },
|
||||
// Add more Ports as needed
|
||||
];
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
const [incidentEmail, setIncidentEmail] = 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: "",
|
||||
// });
|
||||
//Advanced Settings Form
|
||||
const [advancedSettings, setAdvancedSettings] = useState({
|
||||
frequency: 1,
|
||||
// retries: "",
|
||||
// codes: "",
|
||||
// redirects: "",
|
||||
});
|
||||
//Proxy Settings Form
|
||||
// const [proxy, setProxy] = useState({
|
||||
// enabled: false,
|
||||
// protocol: "",
|
||||
// address: "",
|
||||
// proxy_port: "",
|
||||
// });
|
||||
|
||||
const [notifyViaSMS, setNotifyViaSMS] = useState(false);
|
||||
const [notifyViaEmail, setNotifyViaEmail] = useState(false);
|
||||
const [notifyViaCurrentEmail, setNotifyViaCurrentEmail] = useState(false);
|
||||
const handleChange = (event, id, setState, checkbox) => {
|
||||
const { value } = event.target;
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
[id]: checkbox ? true : value,
|
||||
}));
|
||||
|
||||
const [maxRetries, setMaxRetries] = useState(0);
|
||||
const [acceptCode, setAcceptCode] = useState(0);
|
||||
const [maxRedirects, setMaxRedirects] = useState(0);
|
||||
const validation = createMonitorValidation.validate(
|
||||
{ [id]: value },
|
||||
{ abortEarly: false }
|
||||
);
|
||||
|
||||
const [proxy, setProxy] = useState(false);
|
||||
setErrors((prev) => {
|
||||
const updatedErrors = { ...prev };
|
||||
|
||||
const [proxyAddress, setProxyAddress] = useState("");
|
||||
const [proxyPort, setProxyPort] = useState("");
|
||||
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 [selectedMonitorType, setSelectedMonitorType] = useState("");
|
||||
const handleCreateNewMonitor = async (event) => {
|
||||
event.preventDefault();
|
||||
//obj to submit
|
||||
let monitor = {
|
||||
...generalSettings,
|
||||
...checks,
|
||||
};
|
||||
|
||||
const [urlToMonitor, setUrlToMonitor] = useState("");
|
||||
const [friendlyName, setFriendlyName] = useState("");
|
||||
const { error } = createMonitorValidation.validate(monitor, {
|
||||
abortEarly: false,
|
||||
});
|
||||
|
||||
const [port, setPort] = useState("Select a port to check");
|
||||
const [frequncy, setFrequency] = useState("1 minute");
|
||||
|
||||
const handlePortOptionChange = (newValue) => {
|
||||
setPort(newValue);
|
||||
if (error) {
|
||||
const newErrors = {};
|
||||
error.details.forEach((err) => {
|
||||
newErrors[err.path[0]] = err.message;
|
||||
});
|
||||
setErrors(newErrors);
|
||||
} else {
|
||||
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,
|
||||
};
|
||||
try {
|
||||
const action = await dispatch(createMonitor({ authToken, monitor }));
|
||||
if (action.meta.requestStatus === "fulfilled") {
|
||||
navigate("/monitors");
|
||||
}
|
||||
} catch (error) {
|
||||
alert(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleFrequencyOptionChange = (newValue) => {
|
||||
setPort(newValue);
|
||||
};
|
||||
|
||||
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");
|
||||
};
|
||||
//select values
|
||||
// const ports = ["Port 1", "Port 2", "Port 3"];
|
||||
const frequencies = [1, 2, 3, 4, 5];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={handleCreateNewMonitor} noValidate spellCheck="false">
|
||||
<div className="new-monitor-header">Create new monitor</div>
|
||||
<div className="monitors-gaps-medium"></div>
|
||||
<ConfigBox
|
||||
@@ -134,18 +136,40 @@ const CreateNewMonitor = () => {
|
||||
rightLayout={
|
||||
<div className="config-box-inputs">
|
||||
<FlexibileTextField
|
||||
id="monitor-url"
|
||||
title="URL to monitor"
|
||||
type="url"
|
||||
placeholder="https://google.com"
|
||||
onChange={handleUrlToMonitor}
|
||||
value={generalSettings.url}
|
||||
onChange={(event) =>
|
||||
handleChange(event, "url", setGeneralSettings)
|
||||
}
|
||||
/>
|
||||
{errors["url"] ? (
|
||||
<Typography variant="h5" component="p" className="input-error">
|
||||
{errors["url"]}
|
||||
</Typography>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
<div className="monitors-gaps-medium"></div>
|
||||
<FlexibileTextField
|
||||
id="monitor-name"
|
||||
title="Friendly name (optional)"
|
||||
type="text"
|
||||
placeholder="Google"
|
||||
onChange={handleFriendlyName}
|
||||
value={generalSettings.name}
|
||||
onChange={(event) =>
|
||||
handleChange(event, "name", setGeneralSettings)
|
||||
}
|
||||
/>
|
||||
{errors["name"] ? (
|
||||
<Typography variant="h5" component="p" className="input-error">
|
||||
{errors["name"]}
|
||||
</Typography>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
@@ -163,42 +187,62 @@ const CreateNewMonitor = () => {
|
||||
rightLayout={
|
||||
<div className="service-check-list">
|
||||
<RadioButton
|
||||
id="monitor-checks-http"
|
||||
title="HTTP/website monitoring"
|
||||
desc="Use HTTP(s) to monitor your website or API endpoint."
|
||||
value="http"
|
||||
checked={selectedMonitorType === "http"}
|
||||
onChange={() => handleMonitorTypeChange("http")}
|
||||
checked={checks.type === "http"}
|
||||
onChange={(event) => handleChange(event, "type", setChecks)}
|
||||
/>
|
||||
<div className="monitors-gaps-medium"></div>
|
||||
<RadioButton
|
||||
id="monitor-checks-ping"
|
||||
title="Ping monitoring"
|
||||
desc="Check whether your server is available or not."
|
||||
value="ping"
|
||||
checked={selectedMonitorType === "ping"}
|
||||
onChange={() => handleMonitorTypeChange("ping")}
|
||||
checked={checks.type === "ping"}
|
||||
onChange={(event) => handleChange(event, "type", setChecks)}
|
||||
/>
|
||||
<div className="monitors-gaps-medium"></div>
|
||||
<RadioButton
|
||||
{/* TODO */}
|
||||
{/* <RadioButton
|
||||
id="monitor-checks-port"
|
||||
title="Port monitoring"
|
||||
desc="Monitor a specific service on your server."
|
||||
value="port"
|
||||
checked={selectedMonitorType === "port"}
|
||||
onChange={() => handleMonitorTypeChange("port")}
|
||||
checked={checks.type === "port"}
|
||||
onChange={(event) => handleChange(event, "type", setChecks)}
|
||||
/>
|
||||
<div className="monitors-gaps-small-plus"></div>
|
||||
<div className="monitors-dropdown-holder">
|
||||
<Dropdown
|
||||
id="ports-dropdown"
|
||||
label="Select a port to check"
|
||||
onChange={handlePortOptionChange}
|
||||
options={portOptions}
|
||||
value={port}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
id="monitor-ports"
|
||||
value={checks.port || "placeholder"}
|
||||
inputProps={{ id: "monitor-ports-select" }}
|
||||
onChange={(event) => handleChange(event, "port", setChecks)}
|
||||
>
|
||||
<MenuItem id="port-placeholder" value="placeholder">
|
||||
Select a port to check
|
||||
</MenuItem>
|
||||
{ports.map((port, index) => (
|
||||
<MenuItem key={`port-${index}`} value={port}>
|
||||
{port}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</div> */}
|
||||
{errors["type"] ? (
|
||||
<Typography variant="h5" component="p" className="input-error">
|
||||
{errors["type"]}
|
||||
</Typography>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<div className="monitors-gaps-medium"></div>
|
||||
{/* TODO */}
|
||||
{/* <div className="monitors-gaps-medium"></div>
|
||||
<div className="monitors-gaps-medium"></div>
|
||||
<ConfigBox
|
||||
leftLayout={
|
||||
@@ -217,35 +261,41 @@ const CreateNewMonitor = () => {
|
||||
<div className="monitors-gaps-medium"></div>
|
||||
<div className="incident-notif-config-checks">
|
||||
<CustomizableCheckBox
|
||||
isChecked={notifyViaSMS}
|
||||
handleChange={handleNotifyViaSMS}
|
||||
id="monitor-notify-sms"
|
||||
title="Notify via SMS (to be implemented)"
|
||||
isChecked={notifications.viaSms}
|
||||
handleChange={() => handleCheck("viaSms", setNotifications)}
|
||||
/>
|
||||
<div className="monitors-gaps-medium"></div>
|
||||
<CustomizableCheckBox
|
||||
isChecked={notifyViaEmail}
|
||||
handleChange={handleNotifyViaEmail}
|
||||
id="monitor-notify-email"
|
||||
title="Notify via email (to current email address)"
|
||||
isChecked={notifications.viaEmail}
|
||||
handleChange={() => handleCheck("viaEmail", setNotifications)}
|
||||
/>
|
||||
<div className="monitors-gaps-medium"></div>
|
||||
<CustomizableCheckBox
|
||||
isChecked={notifyViaCurrentEmail}
|
||||
handleChange={handleNotifyViaCurrentEmail}
|
||||
id="monitor-notify-other"
|
||||
title="Notify via email (to another email address below)"
|
||||
isChecked={notifications.viaOther}
|
||||
handleChange={() => handleCheck("viaOther", setNotifications)}
|
||||
/>
|
||||
<div className="monitors-gaps-small-plus"></div>
|
||||
<div className="monitors-dropdown-holder">
|
||||
<FlexibileTextField
|
||||
id="monitor-notify-other-email"
|
||||
placeholder="notifications@gmail.com"
|
||||
type="email"
|
||||
value={incidentEmail}
|
||||
onChange={handleIncidentEmail}
|
||||
value={notifications.email}
|
||||
onChange={(event) =>
|
||||
handleChange(event, "email", setNotifications)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
/> */}
|
||||
<div className="monitors-gaps-medium"></div>
|
||||
<div className="monitors-gaps-medium"></div>
|
||||
<ConfigBox
|
||||
@@ -258,40 +308,56 @@ const CreateNewMonitor = () => {
|
||||
<div className="advanced-setting-config">
|
||||
<div className="adv-setting-title">Check frequency</div>
|
||||
<div className="monitors-gaps-small"></div>
|
||||
<Dropdown
|
||||
id="freq-dropdown"
|
||||
label="1 minutes"
|
||||
onChange={handleFrequencyOptionChange}
|
||||
options={frequencyOptions}
|
||||
value={frequencyOptions}
|
||||
<Select
|
||||
id="monitor-frequencies"
|
||||
value={advancedSettings.frequency || 1}
|
||||
inputProps={{ id: "monitor-frequencies-select" }}
|
||||
onChange={(event) =>
|
||||
handleChange(event, "frequency", setAdvancedSettings)
|
||||
}
|
||||
>
|
||||
{frequencies.map((freq) => (
|
||||
<MenuItem key={`port-${freq}`} value={freq}>
|
||||
{freq} {freq === 1 ? "minute" : "minutes"}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<div className="monitors-gaps-medium"></div>
|
||||
{/* TODO */}
|
||||
{/* <FlexibileTextField
|
||||
id="monitor-settings-retries"
|
||||
title="Maximum retries before the service is marked as down"
|
||||
type="number"
|
||||
value={advancedSettings.retries}
|
||||
onChange={(event) =>
|
||||
handleChange(event, "retries", setAdvancedSettings)
|
||||
}
|
||||
/>
|
||||
<div className="monitors-gaps-medium"></div>
|
||||
<div className="adv-setting-title">
|
||||
Maximum retries before the service is marked as down
|
||||
</div>
|
||||
<FlexibileTextField
|
||||
onChange={handleSetMaxRetries}
|
||||
id="monitor-settings-codes"
|
||||
title="Accepted status codes"
|
||||
type="number"
|
||||
placeholder=""
|
||||
value={advancedSettings.codes}
|
||||
onChange={(event) =>
|
||||
handleChange(event, "codes", setAdvancedSettings)
|
||||
}
|
||||
/>
|
||||
<div className="monitors-gaps-medium"></div>
|
||||
<div className="adv-setting-title">Accepted status codes</div>
|
||||
<FlexibileTextField
|
||||
onChange={handleSetAcceptCode}
|
||||
id="monitor-settings-redirects"
|
||||
title="Maximum redirects"
|
||||
type="number"
|
||||
placeholder=""
|
||||
/>
|
||||
<div className="monitors-gaps-medium"></div>
|
||||
<div className="adv-setting-title">Maximum redirects</div>
|
||||
<FlexibileTextField
|
||||
onChange={handleSetMaxRedirects}
|
||||
type="number"
|
||||
placeholder=""
|
||||
/>
|
||||
value={advancedSettings.redirects}
|
||||
onChange={(event) =>
|
||||
handleChange(event, "redirects", setAdvancedSettings)
|
||||
}
|
||||
/> */}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<div className="monitors-gaps-medium"></div>
|
||||
{/* TODO */}
|
||||
{/* <div className="monitors-gaps-medium"></div>
|
||||
<div className="monitors-gaps-medium"></div>
|
||||
<ConfigBox
|
||||
leftLayout={
|
||||
@@ -302,22 +368,35 @@ const CreateNewMonitor = () => {
|
||||
rightLayout={
|
||||
<div className="proxy-setting-config">
|
||||
<CustomizableCheckBox
|
||||
isChecked={proxy}
|
||||
handleChange={handleEnableProxy}
|
||||
id="monitor-proxy-enable"
|
||||
title="Enable proxy"
|
||||
isChecked={proxy.enabled}
|
||||
handleChange={() => handleCheck("enabled", setProxy)}
|
||||
/>
|
||||
<div className="monitors-gaps-medium"></div>
|
||||
<div className="adv-setting-title">Proxy protocol</div>
|
||||
<FlexibileTextField />
|
||||
<FlexibileTextField
|
||||
id="monitor-proxy-protocol"
|
||||
title="Proxy protocol"
|
||||
value={proxy.protocol}
|
||||
onChange={(event) => handleChange(event, "protocol", setProxy)}
|
||||
/>
|
||||
<div className="monitors-gaps-medium"></div>
|
||||
<div className="adv-setting-title">Proxy address</div>
|
||||
<FlexibileTextField onChange={handleproxyAddress} />
|
||||
<FlexibileTextField
|
||||
id="monitor-proxy-address"
|
||||
title="Proxy address"
|
||||
value={proxy.address}
|
||||
onChange={(event) => handleChange(event, "address", setProxy)}
|
||||
/>
|
||||
<div className="monitors-gaps-medium"></div>
|
||||
<div className="adv-setting-title">Proxy port</div>
|
||||
<FlexibileTextField onChange={handleproxyPort} />
|
||||
<FlexibileTextField
|
||||
id="monitor-proxy-port"
|
||||
title="Proxy port"
|
||||
value={proxy.proxy_port}
|
||||
onChange={(event) => handleChange(event, "proxy_port", setProxy)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
/> */}
|
||||
<div className="monitors-gaps-medium"></div>
|
||||
<div className="monitors-gaps-small"></div>
|
||||
<div className="monitors-create-button-holder">
|
||||
@@ -326,10 +405,10 @@ const CreateNewMonitor = () => {
|
||||
level="primary"
|
||||
label="Create new monitor"
|
||||
sx={{ width: "210px", fontSize: "var(--env-var-font-size-medium)" }}
|
||||
onClick={() => handleCreateNewMonitor()}
|
||||
onClick={handleCreateNewMonitor}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -16,8 +16,7 @@ const Monitors = () => {
|
||||
|
||||
return (
|
||||
<div className="monitors">
|
||||
{/* <CurrentStats monitors={monitorState.monitors} /> */}
|
||||
<CreateNewMonitor />
|
||||
<CurrentStats monitors={monitorState.monitors} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,204 +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;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
@@ -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 (
|
||||
<Box
|
||||
//TODO - breakpoints for responsive design
|
||||
minWidth={theme.spacing(55)}
|
||||
width="100vw"
|
||||
maxWidth="calc(100vw - 800px)"
|
||||
py={theme.spacing(5)}
|
||||
px={theme.spacing(10)}
|
||||
className="settings"
|
||||
>
|
||||
<TabContext value={tab}>
|
||||
<Box
|
||||
sx={{
|
||||
borderBottom: 1,
|
||||
borderColor: theme.palette.section.borderColor,
|
||||
"& .MuiTabs-root": { height: "fit-content", minHeight: "0" },
|
||||
}}
|
||||
>
|
||||
<TabList onChange={handleTabChange} aria-label="settings tabs">
|
||||
{tabList.map((label, index) => (
|
||||
<Tab
|
||||
label={label}
|
||||
key={index}
|
||||
value={index.toString()}
|
||||
sx={{
|
||||
fontSize: "13px",
|
||||
color: theme.palette.secondary.main,
|
||||
textTransform: "none",
|
||||
minWidth: "fit-content",
|
||||
minHeight: 0,
|
||||
paddingLeft: "0",
|
||||
paddingY: "10px",
|
||||
marginRight: "20px",
|
||||
"&:focus": {
|
||||
outline: "none",
|
||||
},
|
||||
"& .MuiTouchRipple-root": {
|
||||
pointerEvents: "none",
|
||||
display: "none",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</TabList>
|
||||
</Box>
|
||||
<ProfilePanel />
|
||||
<PasswordPanel />
|
||||
<TeamPanel />
|
||||
</TabContext>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Settings.propTypes = {
|
||||
// No props are being passed to this component, hence no specific PropTypes are defined.
|
||||
return <div>Settings</div>;
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
@@ -82,43 +82,41 @@ const editProfileValidation = joi.object({
|
||||
}),
|
||||
});
|
||||
|
||||
const passwordSchema = 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;
|
||||
});
|
||||
|
||||
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,15 +124,22 @@ 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;
|
||||
}),
|
||||
});
|
||||
|
||||
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,
|
||||
|
||||
@@ -819,6 +819,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 |
|
||||
@@ -832,12 +844,15 @@ curl --request POST \
|
||||
--url http://localhost:5000/api/v1/monitors \
|
||||
--header <bearer_token> \
|
||||
--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
|
||||
|
||||
Reference in new issue
Block a user