diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
new file mode 100644
index 000000000..c884b2e12
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
@@ -0,0 +1,9 @@
+## Describe your changes
+
+## Issue Number if applicable
+
+## Checklist before requesting a review
+
+- [ ] I have performed a self-review of my code
+- [ ] I have inlcuded the issue # in the PR if applicable
+- [ ] I have labelled the PR correctly
diff --git a/Client/src/Components/Breadcrumbs/index.css b/Client/src/Components/Breadcrumbs/index.css
new file mode 100644
index 000000000..b7d190de1
--- /dev/null
+++ b/Client/src/Components/Breadcrumbs/index.css
@@ -0,0 +1,31 @@
+.MuiBreadcrumbs-root {
+ height: 36px;
+}
+.MuiBreadcrumbs-root svg {
+ width: 16px;
+ height: 16px;
+}
+.MuiBreadcrumbs-root .MuiBreadcrumbs-li a {
+ font-size: var(--env-var-font-size-medium);
+ color: var(--env-var-color-2);
+ opacity: 0.8;
+ font-weight: 400;
+}
+.MuiBreadcrumbs-root .MuiBreadcrumbs-li:hover a {
+ color: var(--env-var-color-2);
+}
+.MuiBreadcrumbs-root .MuiBreadcrumbs-li:not(:last-child):hover a {
+ opacity: 1;
+ background-color: #e3e3e3;
+}
+.MuiBreadcrumbs-root .MuiBreadcrumbs-li:not(:last-child) {
+ cursor: pointer;
+}
+.MuiBreadcrumbs-root .MuiBreadcrumbs-li:last-child a {
+ font-weight: 500;
+ opacity: 1;
+ cursor: default;
+}
+.MuiBreadcrumbs-root .MuiBreadcrumbs-separator {
+ margin: 0;
+}
diff --git a/Client/src/Components/Breadcrumbs/index.jsx b/Client/src/Components/Breadcrumbs/index.jsx
new file mode 100644
index 000000000..506263fce
--- /dev/null
+++ b/Client/src/Components/Breadcrumbs/index.jsx
@@ -0,0 +1,63 @@
+import PropTypes from "prop-types";
+import { Box, Breadcrumbs as MUIBreadcrumbs } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import { useNavigate } from "react-router";
+import ArrowRight from "../../assets/icons/right-arrow.svg?react";
+
+import "./index.css";
+
+/**
+ * Breadcrumbs component that displays a list of breadcrumb items.
+ *
+ * @param {Object} props
+ * @param {Array} props.list - Array of breadcrumb items. Each item should have `name` and `path` properties.
+ * @param {string} props.list.name - The name to display for the breadcrumb.
+ * @param {string} props.list.path - The path to navigate to when the breadcrumb is clicked.
+ *
+ * @returns {JSX.Element} The rendered Breadcrumbs component.
+ */
+
+const Breadcrumbs = ({ list }) => {
+ const theme = useTheme();
+ const navigate = useNavigate();
+
+ return (
+ }
+ aria-label="breadcrumb"
+ p={theme.gap.small}
+ width="fit-content"
+ backgroundColor={theme.palette.otherColors.fillGray}
+ borderRadius={`${theme.shape.borderRadius}px`}
+ lineHeight="18px"
+ >
+ {list.map((item, index) => {
+ return (
+ navigate(item.path)}
+ >
+ {item.name}
+
+ );
+ })}
+
+ );
+};
+
+Breadcrumbs.propTypes = {
+ list: PropTypes.arrayOf(
+ PropTypes.shape({
+ name: PropTypes.string.isRequired,
+ path: PropTypes.string.isRequired,
+ }).isRequired
+ ).isRequired,
+};
+
+export default Breadcrumbs;
diff --git a/Client/src/Components/Sidebar/index.css b/Client/src/Components/Sidebar/index.css
index 36415438c..59924b1a1 100644
--- a/Client/src/Components/Sidebar/index.css
+++ b/Client/src/Components/Sidebar/index.css
@@ -1,4 +1,5 @@
aside .selected-path,
+.sidebar-popup .selected-path,
aside .MuiListItemButton-root:hover {
background-color: var(--env-var-color-13);
}
@@ -52,7 +53,7 @@ aside
> .MuiListItemButton-root::before {
content: "";
position: absolute;
- left: -8px;
+ left: -7px;
top: 0;
height: 100%;
width: 100%;
@@ -65,7 +66,8 @@ aside
> .MuiListItemButton-root:last-child::before {
height: 50%;
}
-aside .MuiCollapse-wrapperInner .MuiList-root svg {
+aside .MuiCollapse-wrapperInner .MuiList-root svg,
+aside .MuiList-root .MuiListItemText-root + svg {
width: 18px;
height: 18px;
}
@@ -73,11 +75,50 @@ aside .MuiCollapse-wrapperInner .MuiList-root svg {
aside .MuiCollapse-wrapperInner .MuiList-root > .MuiListItemButton-root::after {
content: "";
position: absolute;
- left: -8px;
+ left: -7px;
top: 50%;
height: 1px;
- width: 7px;
+ width: 6px;
border-radius: 50%;
background-color: var(--env-var-color-5);
opacity: 0.3;
}
+
+aside {
+ transition: flex 300ms ease-out;
+}
+
+.sidebar-popup .MuiPaper-root {
+ box-shadow: var(--env-var-shadow-1);
+ border: solid 1px var(--env-var-color-6);
+ border-radius: var(--env-var-radius-1);
+ gap: 1px;
+}
+.sidebar-popup .MuiList-root {
+ padding: 6px 6px;
+ min-width: 100px;
+}
+.sidebar-popup li.MuiButtonBase-root {
+ font-size: var(--env-var-font-size-medium);
+ color: var(--env-var-color-5);
+ padding: 5px 8px;
+ border-radius: var(--env-var-radius-1);
+}
+.sidebar-popup li.MuiButtonBase-root:hover{
+ background-color: var(--env-var-color-13);
+}
+.sidebar-popup li.MuiButtonBase-root:has(.MuiBox-root):hover{
+ background-color: white;
+}
+.sidebar-popup li.MuiButtonBase-root:has(.MuiBox-root){
+ padding-bottom: 0;
+}
+.sidebar-popup .MuiModal-root p.MuiTypography-root {
+ font-size: var(--env-var-font-size-medium);
+ color: var(--env-var-color-2);
+}
+.sidebar-popup svg{
+ width: 16px;
+ height: 16px;
+ opacity: 0.9;
+}
diff --git a/Client/src/Components/Sidebar/index.jsx b/Client/src/Components/Sidebar/index.jsx
index 159b01503..38baa33ec 100644
--- a/Client/src/Components/Sidebar/index.jsx
+++ b/Client/src/Components/Sidebar/index.jsx
@@ -9,6 +9,8 @@ import {
ListItemIcon,
ListItemText,
ListSubheader,
+ Menu,
+ MenuItem,
Stack,
Tooltip,
Typography,
@@ -24,6 +26,7 @@ import UserSvg from "../../assets/icons/user.svg?react";
import TeamSvg from "../../assets/icons/user-two.svg?react";
import LogoutSvg from "../../assets/icons/logout.svg?react";
import BWULogo from "../../assets/Images/bwl-logo.svg?react";
+import BWULogoAbbreviated from "../../assets/icons/bwu-abbreviated.svg?react";
import Support from "../../assets/icons/support.svg?react";
import Dashboard from "../../assets/icons/dashboard.svg?react";
import Account from "../../assets/icons/user-edit.svg?react";
@@ -36,6 +39,8 @@ import PageSpeed from "../../assets/icons/page-speed.svg?react";
import Settings from "../../assets/icons/settings.svg?react";
import ArrowDown from "../../assets/icons/down-arrow.svg?react";
import ArrowUp from "../../assets/icons/up-arrow.svg?react";
+import ArrowRight from "../../assets/icons/right-arrow.svg?react";
+import ArrowLeft from "../../assets/icons/left-arrow.svg?react";
import "./index.css";
@@ -82,6 +87,17 @@ function Sidebar() {
const dispatch = useDispatch();
const authState = useSelector((state) => state.auth);
const [open, setOpen] = useState({ Dashboard: false, Account: false });
+ const [collapsed, setCollapsed] = useState(false);
+ const [anchorEl, setAnchorEl] = useState(null);
+ const [popup, setPopup] = useState();
+
+ const openPopup = (event, id) => {
+ setAnchorEl(event.currentTarget);
+ setPopup(id);
+ };
+ const closePopup = () => {
+ setAnchorEl(null);
+ };
/**
* Handles logging out the user
@@ -105,10 +121,47 @@ function Sidebar() {
}, []);
return (
-
-
-
-
+
+
+ {collapsed ? (
+
+ ) : (
+
+ )}
+ setCollapsed(!collapsed)}
+ >
+ {collapsed ? : }
+
+
{/* menu */}
+
-
-
-
- {authState.user?.firstName} {authState.user?.lastName}
-
-
- {authState.user?.role}
-
-
-
-
-
-
-
+ {collapsed ? (
+ <>
+
+ openPopup(event, "logout")}
+ sx={{ p: 0, "&:focus": { outline: "none" } }}
+ >
+
+
+
+
+ >
+ ) : (
+ <>
+
+
+
+ {authState.user?.firstName} {authState.user?.lastName}
+
+
+ {authState.user?.role}
+
+
+
+
+
+
+
+ >
+ )}
);
diff --git a/Client/src/Layouts/HomeLayout/index.css b/Client/src/Layouts/HomeLayout/index.css
index f143b3fd9..dcdcf0535 100644
--- a/Client/src/Layouts/HomeLayout/index.css
+++ b/Client/src/Layouts/HomeLayout/index.css
@@ -19,7 +19,6 @@
height: calc(100vh - var(--env-var-spacing-2) * 2);
max-width: var(--env-var-side-bar-width);
- flex: 1;
border: 1px solid var(--color-border-0);
border-radius: var(--env-var-radius-1);
@@ -27,10 +26,6 @@
padding: var(--env-var-spacing-1) 0;
}
-.home-layout aside > *:not(:first-child) {
- padding-left: var(--env-var-spacing-1-plus);
- padding-right: var(--env-var-spacing-1-plus);
-}
.home-layout > div {
min-height: calc(100vh - var(--env-var-spacing-2) * 2);
diff --git a/Client/src/Pages/Auth/Login.jsx b/Client/src/Pages/Auth/Login.jsx
index 12dc52bef..2776866dc 100644
--- a/Client/src/Pages/Auth/Login.jsx
+++ b/Client/src/Pages/Auth/Login.jsx
@@ -4,7 +4,7 @@ import { Box, Stack, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import { credentials } from "../../Validation/validation";
import { login } from "../../Features/Auth/authSlice";
-import { useDispatch } from "react-redux";
+import { useDispatch, useSelector } from "react-redux";
import { createToast } from "../../Utils/toastUtils";
import Button from "../../Components/Button";
import { networkService } from "../../main";
@@ -13,6 +13,7 @@ import background from "../../assets/Images/background_pattern_decorative.png";
import Logo from "../../assets/icons/bwu-icon.svg?react";
import Mail from "../../assets/icons/mail.svg?react";
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
+import PropTypes from "prop-types";
import "./index.css";
@@ -63,6 +64,10 @@ const LandingPage = ({ onContinue }) => {
);
};
+LandingPage.propTypes = {
+ onContinue: PropTypes.func.isRequired,
+};
+
/**
* Renders the first step of the login process.
*
@@ -136,6 +141,14 @@ const StepOne = ({ form, errors, onSubmit, onChange, onBack }) => {
);
};
+StepOne.propTypes = {
+ form: PropTypes.object.isRequired,
+ errors: PropTypes.object.isRequired,
+ onSubmit: PropTypes.func.isRequired,
+ onChange: PropTypes.func.isRequired,
+ onBack: PropTypes.func.isRequired,
+};
+
/**
* Renders the second step of the login process, including a password input field.
*
@@ -230,11 +243,22 @@ const StepTwo = ({ form, errors, onSubmit, onChange, onBack }) => {
);
};
+StepTwo.propTypes = {
+ form: PropTypes.object.isRequired,
+ errors: PropTypes.object.isRequired,
+ onSubmit: PropTypes.func.isRequired,
+ onChange: PropTypes.func.isRequired,
+ onBack: PropTypes.func.isRequired,
+};
+
const Login = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const theme = useTheme();
+ const authState = useSelector((state) => state.auth);
+ const { authToken } = authState;
+
const idMap = {
"login-email-input": "email",
"login-password-input": "password",
@@ -248,6 +272,10 @@ const Login = () => {
const [step, setStep] = useState(0);
useEffect(() => {
+ if (authToken) {
+ navigate("/monitors");
+ return;
+ }
networkService
.doesAdminExist()
.then((response) => {
@@ -258,7 +286,7 @@ const Login = () => {
.catch((error) => {
console.log(error);
});
- }, [navigate]);
+ }, [authToken, navigate]);
const handleChange = (event) => {
const { value, id } = event.target;
diff --git a/Client/src/Pages/Maintenance/CreateMaintenanceWindow/index.jsx b/Client/src/Pages/Maintenance/CreateMaintenanceWindow/index.jsx
index 187d9c63d..8fff6e4a1 100644
--- a/Client/src/Pages/Maintenance/CreateMaintenanceWindow/index.jsx
+++ b/Client/src/Pages/Maintenance/CreateMaintenanceWindow/index.jsx
@@ -2,7 +2,7 @@ import { Box, Stack, Typography } from "@mui/material";
import "./index.css";
import React, { useState } from "react";
import Button from "../../../Components/Button";
-import Back from "../../../assets/icons/left-arrow.svg?react";
+import Back from "../../../assets/icons/left-arrow-long.svg?react";
import Select from "../../../Components/Inputs/Select";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
diff --git a/Client/src/Pages/Monitors/Configure/index.css b/Client/src/Pages/Monitors/Configure/index.css
index ac9cd8a0d..ec140887c 100644
--- a/Client/src/Pages/Monitors/Configure/index.css
+++ b/Client/src/Pages/Monitors/Configure/index.css
@@ -23,8 +23,7 @@
opacity: 0.6;
}
.configure-monitor h6.MuiTypography-root,
-.configure-monitor p.MuiTypography-root:has(span),
-.configure-monitor p.MuiTypography-root span.MuiTypography-root {
+.configure-monitor h1.MuiTypography-root + span.MuiTypography-root {
font-size: var(--env-var-font-size-small-plus);
}
.configure-monitor button.MuiButtonBase-root {
@@ -33,19 +32,20 @@
.configure-monitor button.MuiButtonBase-root {
height: var(--env-var-height-2);
}
-.configure-monitor button.MuiButtonBase-root:has(svg) {
- line-height: 1;
-}
.configure-monitor .config-box {
- padding: var(--env-var-spacing-4) 50px;
- padding-bottom: 60px;
border: 1px solid var(--env-var-color-16);
border-radius: var(--env-var-radius-1);
background-color: var(--env-var-color-8);
}
-.configure-monitor .config-box .MuiBox-root,
-.configure-monitor .config-box .MuiStack-root {
+.configure-monitor .config-box > .MuiBox-root,
+.configure-monitor .config-box > .MuiStack-root {
flex: 1;
+ padding: 24px 30px;
+ padding-bottom: 40px;
+}
+.configure-monitor .config-box > *:first-child {
+ flex: 0.7;
+ border-right: solid 1px var(--env-var-color-16);
}
.configure-monitor .MuiStack-root:has(span.MuiTypography-root.input-error) {
position: relative;
@@ -72,12 +72,6 @@
border-color: var(--env-var-color-29);
}
-.configure-monitor-form {
- display: flex;
- flex-direction: column;
- gap: var(--env-var-spacing-2);
-}
-
body:has(.configure-monitor) .select-dropdown .MuiMenuItem-root,
.configure-monitor .select-wrapper .select-component > .MuiSelect-select {
text-transform: lowercase;
diff --git a/Client/src/Pages/Monitors/Configure/index.jsx b/Client/src/Pages/Monitors/Configure/index.jsx
index 08c8cbe39..c873482a9 100644
--- a/Client/src/Pages/Monitors/Configure/index.jsx
+++ b/Client/src/Pages/Monitors/Configure/index.jsx
@@ -5,15 +5,11 @@ import { useEffect, useState } from "react";
import Button from "../../../Components/Button";
import Field from "../../../Components/Inputs/Field";
import { Box, Modal, Skeleton, Stack, Typography } from "@mui/material";
-import WestRoundedIcon from "@mui/icons-material/WestRounded";
import GreenCheck from "../../../assets/icons/checkbox-green.svg?react";
import RedCheck from "../../../assets/icons/checkbox-red.svg?react";
import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline";
-import { getLastChecked } from "../../../Utils/monitorUtils";
-import "./index.css";
import { monitorValidation } from "../../../Validation/validation";
import Select from "../../../Components/Inputs/Select";
-import { formatDurationRounded } from "../../../Utils/timeUtils";
import { createToast } from "../../../Utils/toastUtils";
import {
updateUptimeMonitor,
@@ -21,6 +17,8 @@ import {
deleteUptimeMonitor,
} from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
import Checkbox from "../../../Components/Inputs/Checkbox";
+import Breadcrumbs from "../../../Components/Breadcrumbs";
+import "./index.css";
/**
* Parses a URL string and returns a URL object.
@@ -96,7 +94,6 @@ const Configure = () => {
const { user, authToken } = useSelector((state) => state.auth);
const { monitors } = useSelector((state) => state.uptimeMonitors);
const [monitor, setMonitor] = useState({});
- const [duration, setDuration] = useState(0);
const [errors, setErrors] = useState({});
const { monitorId } = useParams();
@@ -117,7 +114,6 @@ const Configure = () => {
setMonitor({
...data,
});
- setDuration(formatDurationRounded(data?.interval));
}, [monitorId, authToken, monitors]);
const handleChange = (event, name) => {
@@ -210,74 +206,54 @@ const Configure = () => {
const parsedUrl = parseUrl(monitor?.url);
const protocol = parsedUrl?.protocol?.replace(":", "") || "";
- var lastChecked = formatDurationRounded(getLastChecked(monitor?.checks));
-
let loading = Object.keys(monitor).length === 0;
return (
-
+
{loading ? (
) : (
<>
- }
- onClick={() => navigate(-1)}
- sx={{
- backgroundColor: "#f4f4f4",
- px: theme.gap.ml,
- "& svg.MuiSvgIcon-root": {
- mr: theme.gap.small,
- fill: theme.palette.otherColors.slateGray,
- },
- }}
+
-
General settings
-
+
Here you can select the URL of the host, together with the
type of monitor.
@@ -345,11 +321,10 @@ const Configure = () => {
className="config-box"
direction="row"
justifyContent="space-between"
- gap={theme.gap.xxl}
>
Incident notifications
-
+
When there is an incident, notify users.
@@ -408,7 +383,6 @@ const Configure = () => {
className="config-box"
direction="row"
justifyContent="space-between"
- gap={theme.gap.xxl}
>
Advanced settings
@@ -423,7 +397,7 @@ const Configure = () => {
/>
-
+
-
+
>
)}
{
}}
>
- Do you really want to delete this monitor?
+ Do you really want to delete this monitor?
Once deleted, this monitor cannot be retrieved.
@@ -480,7 +454,7 @@ const Configure = () => {
-
+
);
};
diff --git a/Client/src/Pages/Monitors/CreateMonitor/index.css b/Client/src/Pages/Monitors/CreateMonitor/index.css
index 3fa38f14a..ecb27969c 100644
--- a/Client/src/Pages/Monitors/CreateMonitor/index.css
+++ b/Client/src/Pages/Monitors/CreateMonitor/index.css
@@ -13,7 +13,9 @@
.create-monitor button.MuiButtonBase-root {
font-size: var(--env-var-font-size-medium);
}
-.create-monitor h2.MuiTypography-root,
+.create-monitor h2.MuiTypography-root {
+ color: var(--env-var-color-5);
+}
.create-monitor h6.MuiTypography-root,
.create-monitor p.MuiTypography-root {
color: var(--env-var-color-2);
@@ -43,23 +45,20 @@
top: 100%;
}
.create-monitor .config-box {
- padding: var(--env-var-spacing-4) 50px;
- padding-bottom: 60px;
+ flex-direction: row;
border: 1px solid var(--env-var-color-16);
border-radius: var(--env-var-radius-1);
- flex-direction: row;
- gap: var(--env-var-spacing-4);
background-color: var(--env-var-color-8);
}
-.create-monitor .config-box .MuiBox-root,
-.create-monitor .config-box .MuiStack-root {
+.create-monitor .config-box > .MuiBox-root,
+.create-monitor .config-box > .MuiStack-root {
flex: 1;
+ padding: 24px 30px;
+ padding-bottom: 40px;
}
-
-.create-monitor-form {
- display: flex;
- flex-direction: column;
- gap: var(--env-var-spacing-2);
+.create-monitor .config-box > *:first-child {
+ flex: 0.7;
+ border-right: solid 1px var(--env-var-color-16);
}
.create-monitor .MuiStack-root .MuiButtonGroup-root button {
diff --git a/Client/src/Pages/Monitors/CreateMonitor/index.jsx b/Client/src/Pages/Monitors/CreateMonitor/index.jsx
index e4e3f4d1c..d0eafb476 100644
--- a/Client/src/Pages/Monitors/CreateMonitor/index.jsx
+++ b/Client/src/Pages/Monitors/CreateMonitor/index.jsx
@@ -8,11 +8,11 @@ import { monitorValidation } from "../../../Validation/validation";
import { createUptimeMonitor } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
import { useNavigate } from "react-router-dom";
import { useTheme } from "@emotion/react";
-import WestRoundedIcon from "@mui/icons-material/WestRounded";
import Field from "../../../Components/Inputs/Field";
import Select from "../../../Components/Inputs/Select";
import Checkbox from "../../../Components/Inputs/Checkbox";
import { createToast } from "../../../Utils/toastUtils";
+import Breadcrumbs from "../../../Components/Breadcrumbs";
const CreateMonitor = () => {
const MS_PER_MINUTE = 60000;
@@ -143,33 +143,26 @@ const CreateMonitor = () => {
return (
- }
- onClick={() => navigate("/monitors")}
- sx={{
- backgroundColor: theme.palette.otherColors.fillGray,
- mb: theme.gap.medium,
- px: theme.gap.ml,
- "& svg.MuiSvgIcon-root": {
- mr: theme.gap.small,
- fill: theme.palette.otherColors.slateGray,
- },
- }}
+
-
+
);
};
diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx
index 46f181f97..dad80f4be 100644
--- a/Client/src/Pages/Monitors/Details/index.jsx
+++ b/Client/src/Pages/Monitors/Details/index.jsx
@@ -7,7 +7,6 @@ import { networkService } from "../../../main";
import MonitorDetailsAreaChart from "../../../Components/Charts/MonitorDetailsAreaChart";
import ButtonGroup from "@mui/material/ButtonGroup";
import Button from "../../../Components/Button";
-import WestRoundedIcon from "@mui/icons-material/WestRounded";
import GreenCheck from "../../../assets/icons/checkbox-green.svg?react";
import RedCheck from "../../../assets/icons/checkbox-red.svg?react";
import SettingsIcon from "../../../assets/icons/settings-bold.svg?react";
@@ -18,6 +17,7 @@ import {
} from "../../../Utils/timeUtils";
import "./index.css";
import MonitorDetails60MinChart from "../../../Components/Charts/MonitorDetails60MinChart";
+import Breadcrumbs from "../../../Components/Breadcrumbs";
const StatBox = ({ title, value }) => {
return (
@@ -161,39 +161,19 @@ const DetailsPage = () => {
) : (
<>
- }
- onClick={() => navigate("/monitors")}
- sx={{
- backgroundColor: theme.palette.otherColors.fillGray,
- px: theme.gap.ml,
- "& svg.MuiSvgIcon-root": {
- mr: theme.gap.small,
- fill: theme.palette.otherColors.slateGray,
- },
- }}
+
-
-
+
+
{monitor?.status ? : }
-
-
- {monitor.url?.replace(/^https?:\/\//, "") || "..."}
-
-
-
+
+ {monitor.url?.replace(/^https?:\/\//, "") || "..."}
+
{
+ const [anchorEl, setAnchorEl] = useState(null);
+ const [actions, setActions] = useState({});
+ const [isOpen, setIsOpen] = useState(false);
+ const dispatch = useDispatch();
+ const theme = useTheme();
+ const authState = useSelector((state) => state.auth);
+
+ const handleRemove = async (event) => {
+ event.preventDefault();
+ event.stopPropagation();
+ let monitor = { _id: actions.id };
+ const action = await dispatch(
+ deleteUptimeMonitor({ authToken: authState.authToken, monitor })
+ );
+ if (action.meta.requestStatus === "fulfilled") {
+ setIsOpen(false); // close modal
+ dispatch(getUptimeMonitorsByUserId(authState.authToken));
+ createToast({ body: "Monitor deleted successfully." });
+ } else {
+ createToast({ body: "Failed to delete monitor." });
+ }
+ };
+
+ const openMenu = (event, id, url) => {
+ event.preventDefault();
+ event.stopPropagation();
+ setAnchorEl(event.currentTarget);
+ setActions({ id: id, url: url });
+ };
+
+ const openRemove = (e) => {
+ closeMenu(e);
+ setIsOpen(true);
+ };
+
+ const closeMenu = (e) => {
+ e.stopPropagation();
+ setAnchorEl(null);
+ };
+
+ const navigate = useNavigate();
+ return (
+ <>
+ {
+ event.stopPropagation();
+ openMenu(
+ event,
+ monitor._id,
+ monitor.type === "ping" ? null : monitor.url
+ );
+ }}
+ sx={{
+ "&:focus": {
+ outline: "none",
+ },
+ }}
+ >
+
+
+
+
+ setIsOpen(false)}
+ disablePortal
+ >
+
+
+ Do you really want to delete this monitor?
+
+
+ Once deleted, this monitor cannot be retrieved.
+
+
+
+
+
+ >
+ );
+};
+
+ActionsMenu.propTypes = {
+ monitor: PropTypes.shape({
+ _id: PropTypes.string,
+ url: PropTypes.string,
+ type: PropTypes.string,
+ }).isRequired,
+};
+
/**
* Host component.
* This subcomponent receives a params object and displays the host details.
@@ -72,6 +246,15 @@ const Host = ({ params }) => {
);
};
+Host.propTypes = {
+ params: PropTypes.shape({
+ url: PropTypes.string,
+ title: PropTypes.string,
+ percentageColor: PropTypes.string,
+ percentage: PropTypes.number,
+ }).isRequired,
+};
+
/**
* Renders a skeleton layout.
*
@@ -111,37 +294,6 @@ const Monitors = () => {
const authState = useSelector((state) => state.auth);
const dispatch = useDispatch({});
- const [anchorEl, setAnchorEl] = useState(null);
- const [actions, setActions] = useState({});
- const openMenu = (event, id, url) => {
- event.preventDefault();
- setAnchorEl(event.currentTarget);
- setActions({ id: id, url: url });
- };
- const closeMenu = () => {
- setAnchorEl(null);
- };
-
- const [isOpen, setIsOpen] = useState(false);
- const openRemove = () => {
- closeMenu();
- setIsOpen(true);
- };
- const handleRemove = async (event) => {
- event.preventDefault();
- let monitor = { _id: actions.id };
- const action = await dispatch(
- deleteUptimeMonitor({ authToken: authState.authToken, monitor })
- );
- if (action.meta.requestStatus === "fulfilled") {
- setIsOpen(false); // close modal
- dispatch(getUptimeMonitorsByUserId(authState.authToken));
- createToast({ body: "Monitor deleted successfully." });
- } else {
- createToast({ body: "Failed to delete monitor." });
- }
- };
-
useEffect(() => {
dispatch(getUptimeMonitorsByUserId(authState.authToken));
}, [authState.authToken, dispatch]);
@@ -191,7 +343,9 @@ const Monitors = () => {
return {
id: monitor._id,
// disabled for now
- handleClick: () => navigate(`/monitors/${monitor._id}`),
+ handleClick: () => {
+ navigate(`/monitors/${monitor._id}`);
+ },
data: [
{ id: idx, data: },
{
@@ -213,28 +367,7 @@ const Monitors = () => {
},
{
id: idx + 4,
- data: (
- <>
- {
- event.stopPropagation();
- openMenu(
- event,
- monitor._id,
- monitor.type === "ping" ? null : monitor.url
- );
- }}
- sx={{
- "&:focus": {
- outline: "none",
- },
- }}
- >
-
-
- >
- ),
+ data: ,
},
],
};
@@ -327,79 +460,6 @@ const Monitors = () => {
)}
>
)}
-
- setIsOpen(false)}
- disablePortal
- >
-
-
- Do you really want to delete this monitor?
-
-
- Once deleted, this monitor cannot be retrieved.
-
-
- setIsOpen(false)}
- />
-
-
-
-
);
};
diff --git a/Client/src/Pages/PageSpeed/Configure/index.css b/Client/src/Pages/PageSpeed/Configure/index.css
index 577d18389..408e2d639 100644
--- a/Client/src/Pages/PageSpeed/Configure/index.css
+++ b/Client/src/Pages/PageSpeed/Configure/index.css
@@ -2,7 +2,7 @@
background-color: var(--env-var-color-8);
border: solid 1px var(--env-var-color-6);
border-radius: var(--env-var-radius-1);
- padding: var(--env-var-default-2);
+ padding: var(--env-var-font-size-large-plus) var(--env-var-font-size-xlarge);
}
.configure-pagespeed h1.MuiTypography-root {
font-size: var(--env-var-font-size-large-plus);
@@ -60,9 +60,12 @@ body:has(.configure-pagespeed) .select-dropdown .MuiMenuItem-root,
text-transform: lowercase;
}
+.configure-pagespeed h3.MuiTypography-root {
+ flex: 0.7;
+}
+
.configure-pagespeed .field,
.configure-pagespeed .section-disabled,
-.configure-pagespeed .select-wrapper,
-.configure-pagespeed h3.MuiTypography-root {
+.configure-pagespeed .select-wrapper {
flex: 1;
}
diff --git a/Client/src/Pages/PageSpeed/Configure/index.jsx b/Client/src/Pages/PageSpeed/Configure/index.jsx
index 91d250dd9..0dff8d7eb 100644
--- a/Client/src/Pages/PageSpeed/Configure/index.jsx
+++ b/Client/src/Pages/PageSpeed/Configure/index.jsx
@@ -15,9 +15,9 @@ import Field from "../../../Components/Inputs/Field";
import Select from "../../../Components/Inputs/Select";
import Checkbox from "../../../Components/Inputs/Checkbox";
import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline";
-import WestRoundedIcon from "@mui/icons-material/WestRounded";
import GreenCheck from "../../../assets/icons/checkbox-green.svg?react";
import RedCheck from "../../../assets/icons/checkbox-red.svg?react";
+import Breadcrumbs from "../../../Components/Breadcrumbs";
import "./index.css";
@@ -147,37 +147,25 @@ const PageSpeedConfigure = () => {
let loading = Object.keys(monitor).length === 0;
return (
-
+
{loading ? (
) : (
<>
- }
- onClick={() => navigate(-1)}
- sx={{
- backgroundColor: theme.palette.otherColors.fillGray,
- mb: theme.gap.large,
- px: theme.gap.ml,
- "& svg.MuiSvgIcon-root": {
- mr: theme.gap.small,
- fill: theme.palette.otherColors.slateGray,
- },
- }}
+
-
-
+
{
sx={{ px: theme.gap.large, mt: theme.gap.large }}
/>
-
+
>
)}
{
-
+
);
};
diff --git a/Client/src/Pages/PageSpeed/CreatePageSpeed/index.css b/Client/src/Pages/PageSpeed/CreatePageSpeed/index.css
index f1f10621f..42ca0ffa8 100644
--- a/Client/src/Pages/PageSpeed/CreatePageSpeed/index.css
+++ b/Client/src/Pages/PageSpeed/CreatePageSpeed/index.css
@@ -2,10 +2,10 @@
background-color: var(--env-var-color-8);
border: solid 1px var(--env-var-color-6);
border-radius: var(--env-var-radius-1);
- padding: var(--env-var-default-2);
+ padding: var(--env-var-font-size-large-plus) var(--env-var-font-size-xlarge);
}
.create-page-speed h1.MuiTypography-root {
- font-size: var(--env-var-font-size-large);
+ font-size: 22px;
color: var(--env-var-color-1);
}
.create-page-speed p.MuiTypography-root,
@@ -50,9 +50,12 @@ body:has(.create-page-speed) .select-dropdown .MuiMenuItem-root,
text-transform: lowercase;
}
+.create-page-speed h3.MuiTypography-root {
+ flex: 0.7;
+}
+
.create-page-speed .field,
.create-page-speed .section-disabled,
-.create-page-speed .select-wrapper,
-.create-page-speed h3.MuiTypography-root {
+.create-page-speed .select-wrapper {
flex: 1;
}
diff --git a/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx b/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx
index e9b271326..6b9e6dbde 100644
--- a/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx
+++ b/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx
@@ -10,7 +10,7 @@ import Checkbox from "../../../Components/Inputs/Checkbox";
import { monitorValidation } from "../../../Validation/validation";
import { createToast } from "../../../Utils/toastUtils";
import { createPageSpeed } from "../../../Features/PageSpeedMonitor/pageSpeedMonitorSlice";
-import WestRoundedIcon from "@mui/icons-material/WestRounded";
+import Breadcrumbs from "../../../Components/Breadcrumbs";
import "./index.css";
const CreatePageSpeed = () => {
@@ -97,34 +97,22 @@ const CreatePageSpeed = () => {
};
return (
-
- }
- onClick={() => navigate("/pagespeed")}
- sx={{
- backgroundColor: theme.palette.otherColors.fillGray,
- mb: theme.gap.medium,
- px: theme.gap.ml,
- "& svg.MuiSvgIcon-root": {
- mr: theme.gap.small,
- fill: theme.palette.otherColors.slateGray,
- },
- }}
+
+
-
-
+
{
sx={{ px: theme.gap.large, mt: theme.gap.large }}
/>
-
-
+
+
);
};
diff --git a/Client/src/Pages/PageSpeed/Details/index.css b/Client/src/Pages/PageSpeed/Details/index.css
index 5d4d037b7..357177c78 100644
--- a/Client/src/Pages/PageSpeed/Details/index.css
+++ b/Client/src/Pages/PageSpeed/Details/index.css
@@ -37,7 +37,7 @@
.page-speed-details > .MuiStack-root:last-child,
.page-speed-details > .MuiBox-root:nth-last-child(3) {
border: solid 1px var(--env-var-color-6);
- border-radius: var(--env-var-radius-2);
+ border-radius: var(--env-var-radius-1);
background-color: var(--env-var-color-8);
min-width: 200px;
}
diff --git a/Client/src/Pages/PageSpeed/Details/index.jsx b/Client/src/Pages/PageSpeed/Details/index.jsx
index 4c2185e13..4155170b6 100644
--- a/Client/src/Pages/PageSpeed/Details/index.jsx
+++ b/Client/src/Pages/PageSpeed/Details/index.jsx
@@ -11,26 +11,17 @@ import {
} from "../../../Utils/timeUtils";
import { networkService } from "../../../main";
import Button from "../../../Components/Button";
-import WestRoundedIcon from "@mui/icons-material/WestRounded";
import SettingsIcon from "../../../assets/icons/settings-bold.svg?react";
import LastCheckedIcon from "../../../assets/icons/calendar-check.svg?react";
import ClockIcon from "../../../assets/icons/maintenance.svg?react";
import IntervalCheckIcon from "../../../assets/icons/interval-check.svg?react";
import GreenCheck from "../../../assets/icons/checkbox-green.svg?react";
import RedCheck from "../../../assets/icons/checkbox-red.svg?react";
-
-import "./index.css";
import PageSpeedLineChart from "../../../Components/Charts/PagespeedLineChart";
+import Breadcrumbs from "../../../Components/Breadcrumbs";
+import "./index.css";
+import PropTypes from "prop-types";
-/**
- * Displays a box with an icon, title, and value.
- *
- * @param {Object} props
- * @param {ReactNode} props.icon - The icon to display in the box.
- * @param {string} props.title - The title text to display above the value.
- * @param {string | number} props.value - The value text to display in the box.
- * @returns {JSX.Element}
- */
const StatBox = ({ icon, title, value }) => {
const theme = useTheme();
@@ -39,7 +30,8 @@ const StatBox = ({ icon, title, value }) => {
className="stat-box"
direction="row"
gap={theme.gap.small}
- p={theme.gap.ml}
+ pt={theme.gap.ml}
+ px={theme.gap.ml}
pb={theme.gap.mlplus}
>
{icon}
@@ -53,6 +45,12 @@ const StatBox = ({ icon, title, value }) => {
);
};
+StatBox.propTypes = {
+ icon: PropTypes.element,
+ title: PropTypes.string,
+ value: PropTypes.node,
+};
+
/**
* Renders a centered label within a pie chart.
*
@@ -85,6 +83,12 @@ const PieCenterLabel = ({ value, color, setExpand }) => {
);
};
+PieCenterLabel.propTypes = {
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ color: PropTypes.string,
+ setExpand: PropTypes.func,
+};
+
/**
* A component that renders a label on a pie chart slice.
* The label is positioned relative to the center of the pie chart and is optionally highlighted.
@@ -128,6 +132,14 @@ const PieValueLabel = ({ value, startAngle, endAngle, color, highlighted }) => {
);
};
+PieValueLabel.propTypes = {
+ value: PropTypes.number,
+ startAngle: PropTypes.number,
+ endAngle: PropTypes.number,
+ color: PropTypes.string,
+ highlighted: PropTypes.bool,
+};
+
/**
* Renders a skeleton layout.
*
@@ -195,16 +207,16 @@ const PageSpeedDetails = () => {
null,
null
);
- setMonitor(res.data.data);
- setAudits(res.data.data.checks[0].audits);
+ setMonitor(res?.data?.data ?? {});
+ setAudits(res?.data?.data?.checks?.[0]?.audits ?? []);
} catch (error) {
- console.error("Error fetching pagespeed monitor of id: " + monitorId);
+ console.log(error);
navigate("/not-found");
}
};
fetchMonitor();
- }, [monitorId]);
+ }, [monitorId, authToken, navigate]);
/**
* Weight constants for different performance metrics.
@@ -306,21 +318,11 @@ const PageSpeedDetails = () => {
) : (
<>
- }
- onClick={() => navigate("/pagespeed")}
- sx={{
- width: "fit-content",
- backgroundColor: theme.palette.otherColors.fillGray,
- px: theme.gap.ml,
- "& svg.MuiSvgIcon-root": {
- mr: theme.gap.small,
- fill: theme.palette.otherColors.slateGray,
- },
- }}
+
{monitor?.status ? : }
@@ -662,4 +664,8 @@ const PageSpeedDetails = () => {
);
};
+PageSpeedDetails.propTypes = {
+ push: PropTypes.func,
+};
+
export default PageSpeedDetails;
diff --git a/Client/src/assets/icons/bwu-abbreviated.svg b/Client/src/assets/icons/bwu-abbreviated.svg
new file mode 100644
index 000000000..a5e00f484
--- /dev/null
+++ b/Client/src/assets/icons/bwu-abbreviated.svg
@@ -0,0 +1,4 @@
+
diff --git a/Client/src/assets/icons/left-arrow-long.svg b/Client/src/assets/icons/left-arrow-long.svg
new file mode 100644
index 000000000..db9b827ed
--- /dev/null
+++ b/Client/src/assets/icons/left-arrow-long.svg
@@ -0,0 +1,3 @@
+
diff --git a/Client/src/assets/icons/left-arrow.svg b/Client/src/assets/icons/left-arrow.svg
index db9b827ed..8b38c408a 100644
--- a/Client/src/assets/icons/left-arrow.svg
+++ b/Client/src/assets/icons/left-arrow.svg
@@ -1,3 +1,3 @@
-