diff --git a/Client/src/Pages/Incidents/IncidentTable/Empty/Empty.jsx b/Client/src/Pages/Incidents/IncidentTable/Empty/Empty.jsx
new file mode 100644
index 000000000..b8f6a0986
--- /dev/null
+++ b/Client/src/Pages/Incidents/IncidentTable/Empty/Empty.jsx
@@ -0,0 +1,32 @@
+import { useTheme } from "@emotion/react";
+import PlaceholderLight from "../../../../assets/Images/data_placeholder.svg?react";
+import PlaceholderDark from "../../../../assets/Images/data_placeholder_dark.svg?react";
+import { Box, Typography } from "@mui/material";
+import PropTypes from "prop-types";
+
+const Empty = ({ styles, mode }) => {
+ const theme = useTheme();
+ return (
+
+
+ {mode === "light" ? : }
+
+
+ No incidents recorded yet.
+
+
+ );
+};
+
+Empty.propTypes = {
+ styles: PropTypes.object,
+ mode: PropTypes.string,
+};
+
+export { Empty };
diff --git a/Client/src/Pages/Incidents/IncidentTable/Skeleton/Skeleton.jsx b/Client/src/Pages/Incidents/IncidentTable/Skeleton/Skeleton.jsx
new file mode 100644
index 000000000..a3a23c14c
--- /dev/null
+++ b/Client/src/Pages/Incidents/IncidentTable/Skeleton/Skeleton.jsx
@@ -0,0 +1,21 @@
+import { Skeleton /* , Stack */ } from "@mui/material";
+const IncidentSkeleton = () => {
+ return (
+ <>
+
+
+ >
+ );
+};
+
+export { IncidentSkeleton };
diff --git a/Client/src/Pages/Incidents/IncidentTable/index.jsx b/Client/src/Pages/Incidents/IncidentTable/index.jsx
index ef8356031..da533c7ba 100644
--- a/Client/src/Pages/Incidents/IncidentTable/index.jsx
+++ b/Client/src/Pages/Incidents/IncidentTable/index.jsx
@@ -1,16 +1,16 @@
import PropTypes from "prop-types";
import {
- TableContainer,
- Table,
- TableHead,
- TableRow,
- TableCell,
- TableBody,
- Pagination,
- PaginationItem,
- Paper,
- Typography,
- Box,
+ TableContainer,
+ Table,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableBody,
+ Pagination,
+ PaginationItem,
+ Paper,
+ Typography,
+ Box,
} from "@mui/material";
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
@@ -24,185 +24,197 @@ import { useTheme } from "@emotion/react";
import { formatDateWithTz } from "../../../Utils/timeUtils";
import PlaceholderLight from "../../../assets/Images/data_placeholder.svg?react";
import PlaceholderDark from "../../../assets/Images/data_placeholder_dark.svg?react";
+import { Empty } from "./Empty/Empty";
+import { IncidentSkeleton } from "./Skeleton/Skeleton";
const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
- const uiTimezone = useSelector((state) => state.ui.timezone);
+ const uiTimezone = useSelector((state) => state.ui.timezone);
- const theme = useTheme();
- const { authToken, user } = useSelector((state) => state.auth);
- const mode = useSelector((state) => state.ui.mode);
- const [checks, setChecks] = useState([]);
- const [checksCount, setChecksCount] = useState(0);
- const [paginationController, setPaginationController] = useState({
- page: 0,
- rowsPerPage: 14,
- });
+ const theme = useTheme();
+ const { authToken, user } = useSelector((state) => state.auth);
+ const mode = useSelector((state) => state.ui.mode);
+ const [checks, setChecks] = useState([]);
+ const [checksCount, setChecksCount] = useState(0);
+ const [paginationController, setPaginationController] = useState({
+ page: 0,
+ rowsPerPage: 14,
+ });
+ const [isLoading, setIsLoading] = useState(true);
- useEffect(() => {
- setPaginationController((prevPaginationController) => ({
- ...prevPaginationController,
- page: 0,
- }));
- }, [filter, selectedMonitor]);
+ useEffect(() => {
+ setPaginationController((prevPaginationController) => ({
+ ...prevPaginationController,
+ page: 0,
+ }));
+ }, [filter, selectedMonitor]);
- useEffect(() => {
- const fetchPage = async () => {
- if (!monitors || Object.keys(monitors).length === 0) {
- return;
- }
- try {
- let res;
- if (selectedMonitor === "0") {
- res = await networkService.getChecksByTeam({
- authToken: authToken,
- teamId: user.teamId,
- sortOrder: "desc",
- limit: null,
- dateRange: null,
- filter: filter,
- page: paginationController.page,
- rowsPerPage: paginationController.rowsPerPage,
- });
- } else {
- res = await networkService.getChecksByMonitor({
- authToken: authToken,
- monitorId: selectedMonitor,
- sortOrder: "desc",
- limit: null,
- dateRange: null,
- sitler: filter,
- page: paginationController.page,
- rowsPerPage: paginationController.rowsPerPage,
- });
- }
- setChecks(res.data.data.checks);
- setChecksCount(res.data.data.checksCount);
- } catch (error) {
- logger.error(error);
- }
- };
- fetchPage();
- }, [
- authToken,
- user,
- monitors,
- selectedMonitor,
- filter,
- paginationController.page,
- paginationController.rowsPerPage,
- ]);
+ useEffect(() => {
+ const fetchPage = async () => {
+ if (!monitors || Object.keys(monitors).length === 0) {
+ return;
+ }
+ try {
+ setIsLoading(true);
+ let res;
+ if (selectedMonitor === "0") {
+ res = await networkService.getChecksByTeam({
+ authToken: authToken,
+ teamId: user.teamId,
+ sortOrder: "desc",
+ limit: null,
+ dateRange: null,
+ filter: filter,
+ page: paginationController.page,
+ rowsPerPage: paginationController.rowsPerPage,
+ });
+ } else {
+ res = await networkService.getChecksByMonitor({
+ authToken: authToken,
+ monitorId: selectedMonitor,
+ sortOrder: "desc",
+ limit: null,
+ dateRange: null,
+ sitler: filter,
+ page: paginationController.page,
+ rowsPerPage: paginationController.rowsPerPage,
+ });
+ }
+ setChecks(res.data.data.checks);
+ setChecksCount(res.data.data.checksCount);
+ } catch (error) {
+ logger.error(error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ fetchPage();
+ }, [
+ authToken,
+ user,
+ monitors,
+ selectedMonitor,
+ filter,
+ paginationController.page,
+ paginationController.rowsPerPage,
+ ]);
- const handlePageChange = (_, newPage) => {
- setPaginationController({
- ...paginationController,
- page: newPage - 1, // 0-indexed
- });
- };
+ const handlePageChange = (_, newPage) => {
+ setPaginationController({
+ ...paginationController,
+ page: newPage - 1, // 0-indexed
+ });
+ };
- let paginationComponent = <>>;
- if (checksCount > paginationController.rowsPerPage) {
- paginationComponent = (
- (
-
- )}
- sx={{ mt: "auto" }}
- />
- );
- }
+ let paginationComponent = <>>;
+ if (checksCount > paginationController.rowsPerPage) {
+ paginationComponent = (
+ (
+
+ )}
+ sx={{ mt: "auto" }}
+ />
+ );
+ }
- let sharedStyles = {
- border: 1,
- borderColor: theme.palette.border.light,
- borderRadius: theme.shape.borderRadius,
- backgroundColor: theme.palette.background.main,
- p: theme.spacing(30),
- };
+ let sharedStyles = {
+ border: 1,
+ borderColor: theme.palette.border.light,
+ borderRadius: theme.shape.borderRadius,
+ backgroundColor: theme.palette.background.main,
+ p: theme.spacing(30),
+ };
- return (
- <>
- {checks?.length === 0 && selectedMonitor === "0" ? (
-
-
- {mode === "light" ? : }
-
-
- No incidents recorded yet.
-
-
- ) : checks?.length === 0 ? (
-
-
- {mode === "light" ? : }
-
-
- The monitor you have selected has no recorded incidents yet.
-
-
- ) : (
- <>
-
-
-
-
- Monitor Name
- Status
- Date & Time
- Status Code
- Message
-
-
-
- {checks.map((check) => {
- const status = check.status === true ? "up" : "down";
- const formattedDate = formatDateWithTz(
- check.createdAt,
- "YYYY-MM-DD HH:mm:ss A",
- uiTimezone
- );
+ const hasChecks = checks?.length === 0;
+ const noIncidentsRecordedYet = hasChecks && selectedMonitor === "0";
+ const noIncidentsForThatMonitor = hasChecks && selectedMonitor !== "0";
- return (
-
- {monitors[check.monitorId]?.name}
-
-
-
- {formattedDate}
-
- {check.statusCode ? check.statusCode : "N/A"}
-
- {check.message}
-
- );
- })}
-
-
-
- {paginationComponent}
- >
- )}
- >
- );
+ return (
+ <>
+ {isLoading ? (
+
+ ) : noIncidentsRecordedYet ? (
+
+ ) : noIncidentsForThatMonitor ? (
+
+
+ {mode === "light" ? : }
+
+
+ The monitor you have selected has no recorded incidents yet.
+
+
+ ) : (
+ <>
+
+
+
+
+ Monitor Name
+ Status
+ Date & Time
+ Status Code
+ Message
+
+
+
+ {checks.map((check) => {
+ const status = check.status === true ? "up" : "down";
+ const formattedDate = formatDateWithTz(
+ check.createdAt,
+ "YYYY-MM-DD HH:mm:ss A",
+ uiTimezone
+ );
+
+ return (
+
+ {monitors[check.monitorId]?.name}
+
+
+
+ {formattedDate}
+ {check.statusCode ? check.statusCode : "N/A"}
+ {check.message}
+
+ );
+ })}
+
+
+
+ {paginationComponent}
+ >
+ )}
+ >
+ );
};
IncidentTable.propTypes = {
- monitors: PropTypes.object.isRequired,
- selectedMonitor: PropTypes.string.isRequired,
- filter: PropTypes.string.isRequired,
+ monitors: PropTypes.object.isRequired,
+ selectedMonitor: PropTypes.string.isRequired,
+ filter: PropTypes.string.isRequired,
};
export default IncidentTable;
diff --git a/Client/src/Pages/Incidents/index.jsx b/Client/src/Pages/Incidents/index.jsx
index 10ab30ad5..31f853e34 100644
--- a/Client/src/Pages/Incidents/index.jsx
+++ b/Client/src/Pages/Incidents/index.jsx
@@ -11,119 +11,133 @@ import SkeletonLayout from "./skeleton";
import "./index.css";
const Incidents = () => {
- const theme = useTheme();
- const authState = useSelector((state) => state.auth);
- const { monitorId } = useParams();
+ const theme = useTheme();
+ const authState = useSelector((state) => state.auth);
+ const { monitorId } = useParams();
- const [monitors, setMonitors] = useState({});
- const [selectedMonitor, setSelectedMonitor] = useState("0");
- const [loading, setLoading] = useState(false);
+ const [monitors, setMonitors] = useState({});
+ const [selectedMonitor, setSelectedMonitor] = useState("0");
+ const [isLoading, setIsLoading] = useState(true);
- // TODO do something with these filters
- const [filter, setFilter] = useState("all");
+ // TODO do something with these filters
+ const [filter, setFilter] = useState("all");
- useEffect(() => {
- const fetchMonitors = async () => {
- setLoading(true);
- const res = await networkService.getMonitorsByTeamId({
- authToken: authState.authToken,
- teamId: authState.user.teamId,
- limit: -1,
- types: null,
- status: null,
- checkOrder: null,
- normalize: null,
- page: null,
- rowsPerPage: null,
- filter: null,
- field: null,
- order: null,
- });
- // Reduce to a lookup object for 0(1) lookup
- if (res?.data?.data?.monitors?.length > 0) {
- const monitorLookup = res.data.data.monitors.reduce((acc, monitor) => {
- acc[monitor._id] = monitor;
- return acc;
- }, {});
- setMonitors(monitorLookup);
- monitorId !== undefined && setSelectedMonitor(monitorId);
- }
- setLoading(false);
- };
+ useEffect(() => {
+ const fetchMonitors = async () => {
+ try {
+ setIsLoading(true);
+ const res = await networkService.getMonitorsByTeamId({
+ authToken: authState.authToken,
+ teamId: authState.user.teamId,
+ limit: -1,
+ types: null,
+ status: null,
+ checkOrder: null,
+ normalize: null,
+ page: null,
+ rowsPerPage: null,
+ filter: null,
+ field: null,
+ order: null,
+ });
+ // Reduce to a lookup object for 0(1) lookup
+ if (res?.data?.data?.monitors?.length > 0) {
+ const monitorLookup = res.data.data.monitors.reduce((acc, monitor) => {
+ acc[monitor._id] = monitor;
+ return acc;
+ }, {});
+ setMonitors(monitorLookup);
+ monitorId !== undefined && setSelectedMonitor(monitorId);
+ }
+ } catch (error) {
+ console.info(error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ fetchMonitors();
+ }, [authState]);
- fetchMonitors();
- }, [authState]);
+ useEffect(() => {}, [monitors]);
- useEffect(() => {}, []);
+ const handleSelect = (event) => {
+ setSelectedMonitor(event.target.value);
+ };
- const handleSelect = (event) => {
- setSelectedMonitor(event.target.value);
- };
+ const isActuallyLoading = isLoading && Object.keys(monitors)?.length === 0;
- return (
-
- {loading ? (
-
- ) : (
- <>
-
-
- Incidents for
-
-
-
-
-
-
-
-
-
- >
- )}
-
- );
+ return (
+
+ {isActuallyLoading ? (
+
+ ) : (
+ <>
+
+
+ Incidents for
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+ );
};
export default Incidents;
diff --git a/Client/src/Pages/Maintenance/index.jsx b/Client/src/Pages/Maintenance/index.jsx
index e69711eec..2fc688bb2 100644
--- a/Client/src/Pages/Maintenance/index.jsx
+++ b/Client/src/Pages/Maintenance/index.jsx
@@ -10,103 +10,105 @@ import Breadcrumbs from "../../Components/Breadcrumbs";
import { useNavigate } from "react-router-dom";
const Maintenance = ({ isAdmin }) => {
- const theme = useTheme();
- const navigate = useNavigate();
- const { authToken } = useSelector((state) => state.auth);
- const { rowsPerPage } = useSelector((state) => state.ui.maintenance);
+ const theme = useTheme();
+ const navigate = useNavigate();
+ const { authToken } = useSelector((state) => state.auth);
+ const { rowsPerPage } = useSelector((state) => state.ui.maintenance);
- const [maintenanceWindows, setMaintenanceWindows] = useState([]);
- const [maintenanceWindowCount, setMaintenanceWindowCount] = useState(0);
- const [page, setPage] = useState(0);
- const [sort, setSort] = useState({});
- const [updateTrigger, setUpdateTrigger] = useState(false);
+ const [maintenanceWindows, setMaintenanceWindows] = useState([]);
+ const [maintenanceWindowCount, setMaintenanceWindowCount] = useState(0);
+ const [page, setPage] = useState(0);
+ const [sort, setSort] = useState({});
+ const [updateTrigger, setUpdateTrigger] = useState(false);
+ const [isLoading, setIsLoading] = useState(true);
- const handleActionMenuDelete = () => {
- setUpdateTrigger((prev) => !prev);
- };
+ const handleActionMenuDelete = () => {
+ setUpdateTrigger((prev) => !prev);
+ };
- useEffect(() => {
- const fetchMaintenanceWindows = async () => {
- try {
- const response = await networkService.getMaintenanceWindowsByTeamId({
- authToken: authToken,
- page: page,
- rowsPerPage: rowsPerPage,
- });
- const { maintenanceWindows, maintenanceWindowCount } =
- response.data.data;
- setMaintenanceWindows(maintenanceWindows);
- setMaintenanceWindowCount(maintenanceWindowCount);
- } catch (error) {
- console.log(error);
- }
- };
- fetchMaintenanceWindows();
- }, [authToken, page, rowsPerPage, updateTrigger]);
+ useEffect(() => {
+ const fetchMaintenanceWindows = async () => {
+ try {
+ setIsLoading(true);
+ const response = await networkService.getMaintenanceWindowsByTeamId({
+ authToken: authToken,
+ page: page,
+ rowsPerPage: rowsPerPage,
+ });
+ const { maintenanceWindows, maintenanceWindowCount } = response.data.data;
+ setMaintenanceWindows(maintenanceWindows);
+ setMaintenanceWindowCount(maintenanceWindowCount);
+ } catch (error) {
+ console.log(error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ fetchMaintenanceWindows();
+ }, [authToken, page, rowsPerPage, updateTrigger]);
- return (
- [class*="fallback__"])': {
- position: "relative",
- border: 1,
- borderColor: theme.palette.border.light,
- borderRadius: theme.shape.borderRadius,
- borderStyle: "dashed",
- backgroundColor: theme.palette.background.main,
- overflow: "hidden",
- },
- }}
- >
- {maintenanceWindows.length > 0 && (
-
-
-
-
-
-
-
- )}
- {maintenanceWindows.length === 0 && (
-
- )}
-
- );
+ const isActuallyLoading = isLoading && maintenanceWindows?.length === 0;
+ return (
+ [class*="fallback__"])': {
+ position: "relative",
+ border: 1,
+ borderColor: theme.palette.border.light,
+ borderRadius: theme.shape.borderRadius,
+ borderStyle: "dashed",
+ backgroundColor: theme.palette.background.main,
+ overflow: "hidden",
+ },
+ }}
+ >
+ {maintenanceWindows.length > 0 && (
+
+
+
+
+
+
+
+ )}
+ {maintenanceWindows.length === 0 && (
+
+ )}
+
+ );
};
export default Maintenance;