diff --git a/Client/src/App.jsx b/Client/src/App.jsx
index f8a2f72f5..22313a50b 100644
--- a/Client/src/App.jsx
+++ b/Client/src/App.jsx
@@ -7,7 +7,7 @@ import Login from "./Pages/Auth/Login";
import Register from "./Pages/Auth/Register/Register";
import HomeLayout from "./Layouts/HomeLayout";
import Account from "./Pages/Account";
-import Monitors from "./Pages/Monitors";
+import Monitors from "./Pages/Monitors/Home";
import CreateMonitor from "./Pages/Monitors/CreateMonitor";
import Incidents from "./Pages/Incidents";
import Status from "./Pages/Status";
diff --git a/Client/src/Pages/Monitors/Home/actionsMenu.jsx b/Client/src/Pages/Monitors/Home/actionsMenu.jsx
new file mode 100644
index 000000000..b2b26e92a
--- /dev/null
+++ b/Client/src/Pages/Monitors/Home/actionsMenu.jsx
@@ -0,0 +1,199 @@
+import { useState } from "react";
+import { useSelector, useDispatch } from "react-redux";
+import { createToast } from "../../../Utils/toastUtils";
+import {
+ IconButton,
+ Menu,
+ MenuItem,
+ Modal,
+ Stack,
+ Typography,
+} from "@mui/material";
+import {
+ deleteUptimeMonitor,
+ getUptimeMonitorsByTeamId,
+} from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
+import Settings from "../../../assets/icons/settings-bold.svg?react";
+import { useTheme } from "@emotion/react";
+import Button from "../../../Components/Button";
+import PropTypes from "prop-types";
+import { useNavigate } from "react-router-dom";
+
+const ActionsMenu = ({ monitor, isAdmin }) => {
+ 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(getUptimeMonitorsByTeamId(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,
+ isAdmin: PropTypes.bool,
+};
+
+export default ActionsMenu;
diff --git a/Client/src/Pages/Monitors/Home/fallback.jsx b/Client/src/Pages/Monitors/Home/fallback.jsx
new file mode 100644
index 000000000..ccb4ef5fb
--- /dev/null
+++ b/Client/src/Pages/Monitors/Home/fallback.jsx
@@ -0,0 +1,43 @@
+import { Stack, Typography } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import Button from "../../../Components/Button";
+import { useNavigate } from "react-router-dom";
+import PropTypes from "prop-types";
+
+const Fallback = ({ isAdmin }) => {
+ const theme = useTheme();
+ const navigate = useNavigate();
+
+ return (
+
+ No monitors found
+
+ It looks like you don’t have any monitors set up yet.
+
+ {isAdmin && (
+ {
+ navigate("/monitors/create");
+ }}
+ sx={{ mt: theme.gap.large }}
+ />
+ )}
+
+ );
+};
+
+Fallback.propTypes = {
+ isAdmin: PropTypes.bool,
+};
+
+export default Fallback;
diff --git a/Client/src/Pages/Monitors/Home/host.jsx b/Client/src/Pages/Monitors/Home/host.jsx
new file mode 100644
index 000000000..36798cca6
--- /dev/null
+++ b/Client/src/Pages/Monitors/Home/host.jsx
@@ -0,0 +1,33 @@
+import { Stack, Typography } from "@mui/material";
+import PropTypes from "prop-types";
+/**
+ * Host component.
+ * This subcomponent receives a params object and displays the host details.
+ *
+ * @component
+ * @param {Object} params - An object containing the following properties:
+ * @param {string} params.title - The name of the host.
+ * @param {string} params.percentageColor - The color of the percentage text.
+ * @param {number} params.percentage - The percentage to display.
+ * @returns {React.ElementType} Returns a div element with the host details.
+ */
+const Host = ({ params }) => {
+ return (
+
+ {params.title}
+
+ {params.percentage}%
+
+
+ );
+};
+
+Host.propTypes = {
+ params: PropTypes.shape({
+ title: PropTypes.string,
+ percentageColor: PropTypes.string,
+ percentage: PropTypes.number,
+ }).isRequired,
+};
+
+export default Host;
diff --git a/Client/src/Pages/Monitors/index.css b/Client/src/Pages/Monitors/Home/index.css
similarity index 100%
rename from Client/src/Pages/Monitors/index.css
rename to Client/src/Pages/Monitors/Home/index.css
diff --git a/Client/src/Pages/Monitors/Home/index.jsx b/Client/src/Pages/Monitors/Home/index.jsx
new file mode 100644
index 000000000..13aa6067d
--- /dev/null
+++ b/Client/src/Pages/Monitors/Home/index.jsx
@@ -0,0 +1,110 @@
+import "./index.css";
+import { useEffect } from "react";
+import { useSelector, useDispatch } from "react-redux";
+import { getUptimeMonitorsByTeamId } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
+import { useNavigate } from "react-router-dom";
+import Button from "../../../Components/Button";
+import ServerStatus from "../../../Components/Charts/Servers/ServerStatus";
+import { useTheme } from "@emotion/react";
+import BasicTable from "../../../Components/BasicTable";
+import { Box, Stack, Typography } from "@mui/material";
+import PropTypes from "prop-types";
+import SkeletonLayout from "./skeleton";
+import Fallback from "./fallback";
+import { buildData } from "./monitorData";
+
+const Monitors = ({ isAdmin }) => {
+ const theme = useTheme();
+ const navigate = useNavigate();
+ const monitorState = useSelector((state) => state.uptimeMonitors);
+ const authState = useSelector((state) => state.auth);
+ const dispatch = useDispatch({});
+
+ useEffect(() => {
+ dispatch(getUptimeMonitorsByTeamId(authState.authToken));
+ }, [authState.authToken, dispatch]);
+
+ const up = monitorState.monitors.reduce((acc, cur) => {
+ return cur.status === true ? acc + 1 : acc;
+ }, 0);
+
+ const down = monitorState.monitors.length - up;
+ const data = buildData(monitorState.monitors, isAdmin, navigate);
+
+ let loading = monitorState.isLoading && monitorState.monitors.length === 0;
+
+ return (
+
+ {loading ? (
+
+ ) : (
+ <>
+
+
+ Hello, {authState.user.firstName}
+
+ {monitorState.monitors?.length !== 0 && (
+ {
+ navigate("/monitors/create");
+ }}
+ sx={{ fontWeight: 500 }}
+ />
+ )}
+
+
+ {monitorState.monitors?.length === 0 && (
+
+ )}
+
+ {monitorState.monitors?.length !== 0 && (
+ <>
+
+
+
+
+
+
+
+ Current monitors
+
+ {monitorState.monitors.length}
+
+ {/* TODO - add search bar */}
+
+
+
+ >
+ )}
+ >
+ )}
+
+ );
+};
+
+Monitors.propTypes = {
+ isAdmin: PropTypes.bool,
+};
+export default Monitors;
diff --git a/Client/src/Pages/Monitors/Home/monitorData.jsx b/Client/src/Pages/Monitors/Home/monitorData.jsx
new file mode 100644
index 000000000..303a2d289
--- /dev/null
+++ b/Client/src/Pages/Monitors/Home/monitorData.jsx
@@ -0,0 +1,77 @@
+import Host from "./host";
+import ActionsMenu from "./actionsMenu";
+import BarChart from "../../../Components/Charts/BarChart";
+import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded";
+import { StatusLabel } from "../../../Components/Label";
+import { Box } from "@mui/material";
+
+const data = {
+ cols: [
+ { id: 1, name: "Host" },
+ {
+ id: 2,
+ name: (
+
+ Status
+
+
+
+
+ ),
+ },
+ { id: 3, name: "Response Time" },
+ { id: 4, name: "Type" },
+ { id: 5, name: "Actions" },
+ ],
+ rows: [],
+};
+
+export const buildData = (monitors, isAdmin, navigate) => {
+ data.rows = monitors.map((monitor, idx) => {
+ const params = {
+ title: monitor.name,
+ percentage: 100,
+ percentageColor:
+ monitor.status === true
+ ? "var(--env-var-color-17)"
+ : "var(--env-var-color-19)",
+ status: monitor.status === true ? "up" : "down",
+ };
+
+ // Reverse checks so latest check is on the right
+ const reversedChecks = monitor.checks.slice().reverse();
+
+ return {
+ id: monitor._id,
+ // disabled for now
+ handleClick: () => {
+ navigate(`/monitors/${monitor._id}`);
+ },
+ data: [
+ { id: idx, data: },
+ {
+ id: idx + 1,
+ data: (
+
+ ),
+ },
+ { id: idx + 2, data: },
+ {
+ id: idx + 3,
+ data: (
+ {monitor.type}
+ ),
+ },
+ {
+ id: idx + 4,
+ data: ,
+ },
+ ],
+ };
+ });
+ return data;
+};
diff --git a/Client/src/Pages/Monitors/Home/skeleton.jsx b/Client/src/Pages/Monitors/Home/skeleton.jsx
new file mode 100644
index 000000000..d4ea54206
--- /dev/null
+++ b/Client/src/Pages/Monitors/Home/skeleton.jsx
@@ -0,0 +1,36 @@
+import { Skeleton, Stack } from "@mui/material";
+import { useTheme } from "@emotion/react";
+
+/**
+ * Renders a skeleton layout.
+ *
+ * @returns {JSX.Element}
+ */
+const SkeletonLayout = () => {
+ const theme = useTheme();
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default SkeletonLayout;
diff --git a/Client/src/Pages/Monitors/index.jsx b/Client/src/Pages/Monitors/index.jsx
deleted file mode 100644
index bfcced973..000000000
--- a/Client/src/Pages/Monitors/index.jsx
+++ /dev/null
@@ -1,451 +0,0 @@
-import "./index.css";
-import { useEffect, useState } from "react";
-import { useSelector, useDispatch } from "react-redux";
-import {
- deleteUptimeMonitor,
- getUptimeMonitorsByTeamId,
-} from "../../Features/UptimeMonitors/uptimeMonitorsSlice";
-import { useNavigate } from "react-router-dom";
-import Button from "../../Components/Button";
-import ServerStatus from "../../Components/Charts/Servers/ServerStatus";
-import { useTheme } from "@emotion/react";
-import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded";
-import BasicTable from "../../Components/BasicTable";
-import { StatusLabel } from "../../Components/Label";
-import { createToast } from "../../Utils/toastUtils";
-import {
- Box,
- IconButton,
- Menu,
- MenuItem,
- Modal,
- Skeleton,
- Stack,
- Typography,
-} from "@mui/material";
-
-import Settings from "../../assets/icons/settings-bold.svg?react";
-import PropTypes from "prop-types";
-import BarChart from "../../Components/Charts/BarChart";
-
-const ActionsMenu = ({ monitor, isAdmin }) => {
- 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(getUptimeMonitorsByTeamId(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.
-
-
- setIsOpen(false)}
- />
- handleRemove(e)}
- />
-
-
-
- >
- );
-};
-
-ActionsMenu.propTypes = {
- monitor: PropTypes.shape({
- _id: PropTypes.string,
- url: PropTypes.string,
- type: PropTypes.string,
- }).isRequired,
- isAdmin: PropTypes.bool,
-};
-
-/**
- * Host component.
- * This subcomponent receives a params object and displays the host details.
- *
- * @component
- * @param {Object} params - An object containing the following properties:
- * @param {string} params.title - The name of the host.
- * @param {string} params.percentageColor - The color of the percentage text.
- * @param {number} params.percentage - The percentage to display.
- * @returns {React.ElementType} Returns a div element with the host details.
- */
-const Host = ({ params }) => {
- return (
-
- {params.title}
-
- {params.percentage}%
-
-
- );
-};
-
-Host.propTypes = {
- params: PropTypes.shape({
- title: PropTypes.string,
- percentageColor: PropTypes.string,
- percentage: PropTypes.number,
- }).isRequired,
-};
-
-/**
- * Renders a skeleton layout.
- *
- * @returns {JSX.Element}
- */
-const SkeletonLayout = () => {
- const theme = useTheme();
-
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-};
-
-const Monitors = ({ isAdmin }) => {
- const theme = useTheme();
- const navigate = useNavigate();
- const monitorState = useSelector((state) => state.uptimeMonitors);
- const authState = useSelector((state) => state.auth);
- const dispatch = useDispatch({});
-
- useEffect(() => {
- dispatch(getUptimeMonitorsByTeamId(authState.authToken));
- }, [authState.authToken, dispatch]);
-
- const up = monitorState.monitors.reduce((acc, cur) => {
- return cur.status === true ? acc + 1 : acc;
- }, 0);
-
- const down = monitorState.monitors.length - up;
-
- const data = {
- cols: [
- { id: 1, name: "Host" },
- {
- id: 2,
- name: (
-
- Status
-
-
-
-
- ),
- },
- { id: 3, name: "Response Time" },
- { id: 4, name: "Type" },
- { id: 5, name: "Actions" },
- ],
- rows: [],
- };
-
- data.rows = monitorState.monitors.map((monitor, idx) => {
- const params = {
- title: monitor.name,
- percentage: 100,
- percentageColor:
- monitor.status === true
- ? "var(--env-var-color-17)"
- : "var(--env-var-color-19)",
- status: monitor.status === true ? "up" : "down",
- };
-
- // Reverse checks so latest check is on the right
- const reversedChecks = monitor.checks.slice().reverse();
-
- return {
- id: monitor._id,
- // disabled for now
- handleClick: () => {
- navigate(`/monitors/${monitor._id}`);
- },
- data: [
- { id: idx, data: },
- {
- id: idx + 1,
- data: (
-
- ),
- },
- { id: idx + 2, data: },
- {
- id: idx + 3,
- data: (
- {monitor.type}
- ),
- },
- {
- id: idx + 4,
- data: ,
- },
- ],
- };
- });
-
- let loading = monitorState.isLoading && monitorState.monitors.length === 0;
-
- return (
-
- {loading ? (
-
- ) : (
- <>
-
-
- Hello, {authState.user.firstName}
-
- {monitorState.monitors?.length !== 0 && (
- {
- navigate("/monitors/create");
- }}
- sx={{ fontWeight: 500 }}
- />
- )}
-
- {monitorState.monitors?.length === 0 ? (
-
- No monitors found
-
- It looks like you don’t have any monitors set up yet.
-
- {isAdmin && (
- {
- navigate("/monitors/create");
- }}
- sx={{ mt: theme.gap.large }}
- />
- )}
-
- ) : (
- <>
-
-
-
-
-
-
-
- Current monitors
-
- {monitorState.monitors.length}
-
- {/* TODO - add search bar */}
-
-
-
- >
- )}
- >
- )}
-
- );
-};
-
-Monitors.propTypes = {
- isAdmin: PropTypes.bool,
-};
-export default Monitors;