diff --git a/Client/src/Pages/Incidents/index.jsx b/Client/src/Pages/Incidents/index.jsx
index e245e0b6c..4c328a364 100644
--- a/Client/src/Pages/Incidents/index.jsx
+++ b/Client/src/Pages/Incidents/index.jsx
@@ -1,6 +1,6 @@
import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
-import { Typography, ButtonGroup, Stack, Box } from "@mui/material";
+import { Typography, ButtonGroup, Stack, Box, Skeleton } from "@mui/material";
import WestRoundedIcon from "@mui/icons-material/WestRounded";
import Button from "../../Components/Button";
import axiosInstance from "../../Utils/axiosConfig";
@@ -12,12 +12,39 @@ import Select from "../../Components/Inputs/Select";
import "./index.css";
+/**
+ * Renders a skeleton layout.
+ *
+ * @returns {JSX.Element}
+ */
+const SkeletonLayout = () => {
+ const theme = useTheme();
+
+ return (
+ <>
+
+
+
+
+
+
+
+ >
+ );
+};
+
const Incidents = () => {
const theme = useTheme();
const navigate = useNavigate();
const authState = useSelector((state) => state.auth);
const [monitors, setMonitors] = useState({});
const [selectedMonitor, setSelectedMonitor] = useState("0");
+ const [loading, setLoading] = useState(false);
// TODO do something with these filters
const [filter, setFilter] = useState("all");
@@ -34,6 +61,7 @@ const Incidents = () => {
useEffect(() => {
const fetchIncidents = async () => {
+ setLoading(true);
const res = await axiosInstance.get(
`/monitors/user/${authState.user._id}?status=false`,
{
@@ -51,6 +79,7 @@ const Incidents = () => {
}, {});
setMonitors(monitorLookup);
}
+ setLoading(false);
};
fetchIncidents();
@@ -92,8 +121,8 @@ const Incidents = () => {
data: ,
},
{ id: idx + 1, data: new Date(incident.createdAt).toLocaleString() },
- { id: idx + 2, data: incident.statusCode },
- { id: idx + 3, data: monitors[incident.monitorId].name },
+ { id: idx + 2, data: monitors[incident.monitorId].name },
+ { id: idx + 3, data: incident.statusCode },
],
};
});
@@ -102,7 +131,9 @@ const Incidents = () => {
return (
<>
- {hasAnyIncidents ? (
+ {loading ? (
+
+ ) : hasAnyIncidents ? (
<>
Incident history for:
@@ -155,8 +186,7 @@ const Incidents = () => {
)}
>
) : (
-
- No incidents, yet.
+
+
+ No incidents, yet.
+
+
)}
>
diff --git a/Client/src/Pages/Monitors/Configure/index.jsx b/Client/src/Pages/Monitors/Configure/index.jsx
index 12f97ab1e..0df88078f 100644
--- a/Client/src/Pages/Monitors/Configure/index.jsx
+++ b/Client/src/Pages/Monitors/Configure/index.jsx
@@ -4,7 +4,7 @@ import { useDispatch, useSelector } from "react-redux";
import { useEffect, useState } from "react";
import Button from "../../../Components/Button";
import Field from "../../../Components/Inputs/Field";
-import { Box, Modal, Stack, Typography } from "@mui/material";
+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";
@@ -36,6 +36,54 @@ const parseUrl = (url) => {
}
};
+/**
+ * Renders a skeleton layout.
+ *
+ * @returns {JSX.Element}
+ */
+const SkeletonLayout = () => {
+ const theme = useTheme();
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
/**
* Configure page displays monitor configurations and allows for editing actions.
* @component
@@ -47,7 +95,7 @@ const Configure = () => {
const dispatch = useDispatch();
const { user, authToken } = useSelector((state) => state.auth);
const { monitors } = useSelector((state) => state.uptimeMonitors);
- const [monitor, setMonitor] = useState();
+ const [monitor, setMonitor] = useState({});
const [duration, setDuration] = useState(0);
const [errors, setErrors] = useState({});
const { monitorId } = useParams();
@@ -164,214 +212,228 @@ const Configure = () => {
var lastChecked = formatDurationRounded(getLastChecked(monitor?.checks));
+ let loading = Object.keys(monitor).length === 0;
+
return (
- }
- onClick={() => navigate(-1)}
- sx={{
- backgroundColor: "#f4f4f4",
- mb: theme.gap.medium,
- 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.
-
-
-
-
-
-
-
-
-
- Incident notifications
-
- When there is an incident, notify users.
-
-
-
-
- When there is a new incident,
-
- console.log("disabled")}
- isDisabled={true}
- />
- notification.type === "email"
- ) || false
- }
- value={user?.email}
- onChange={(event) => handleChange(event)}
- />
- console.log("disabled")}
- isDisabled={true}
- />
- {monitor?.notifications?.some(
- (notification) => notification.type === "emails"
- ) ? (
-
- console.log("disabled")}
- />
-
- You can separate multiple emails with a comma
+
+ {monitor?.status ? : }
+
+ {parsedUrl?.host ? (
+
+ {parsedUrl.host || "..."}
+
+ ) : (
+ ""
+ )}
+
+
+ Your site is {monitor?.status ? "up" : "down"}.
+ {" "}
+ Checking every {duration}.{" "}
+ {lastChecked ? `Last time checked ${lastChecked} ago.` : ""}
- ) : (
- ""
- )}
-
-
-
-
- Advanced settings
-
-
-
-
-
-
-
-
+
+ }
+ sx={{
+ backgroundColor: "#f4f4f4",
+ pl: theme.gap.small,
+ pr: theme.gap.medium,
+ "& svg": {
+ mr: theme.gap.xs,
+ },
+ }}
+ />
+
+
+
+
+ General settings
+
+ Here you can select the URL of the host, together with the
+ type of monitor.
+
+
+
+
+
+
+
+
+
+ Incident notifications
+
+ When there is an incident, notify users.
+
+
+
+
+ When there is a new incident,
+
+ console.log("disabled")}
+ isDisabled={true}
+ />
+ notification.type === "email"
+ ) || false
+ }
+ value={user?.email}
+ onChange={(event) => handleChange(event)}
+ />
+ console.log("disabled")}
+ isDisabled={true}
+ />
+ {monitor?.notifications?.some(
+ (notification) => notification.type === "emails"
+ ) ? (
+
+ console.log("disabled")}
+ />
+
+ You can separate multiple emails with a comma
+
+
+ ) : (
+ ""
+ )}
+
+
+
+
+ Advanced settings
+
+
+
+
+
+
+
+
+ >
+ )}
{
);
};
+StatBox.propTypes = {
+ title: PropTypes.string,
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+};
+
+/**
+ * Renders a skeleton layout.
+ *
+ * @returns {JSX.Element}
+ */
+const SkeletonLayout = () => {
+ const theme = useTheme();
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
/**
* Details page component displaying monitor details and related information.
* @component
@@ -158,135 +233,150 @@ const DetailsPage = () => {
}, 0);
};
+ let loading = Object.keys(monitor).length === 0;
+
return (
- }
- onClick={() => navigate("/monitors")}
- sx={{
- backgroundColor: "#f4f4f4",
- mb: theme.gap.medium,
- px: theme.gap.ml,
- "& svg.MuiSvgIcon-root": {
- mr: theme.gap.small,
- fill: theme.palette.otherColors.slateGray,
- },
- }}
- />
-
-
- {monitor?.status ? : }
-
-
- {monitor.url?.replace(/^https?:\/\//, "") || "..."}
-
-
-
- Your site is {monitor?.status ? "up" : "down"}.
- {" "}
- Checking every {formatDurationRounded(monitor?.interval)}. Last
- time checked{" "}
- {formatDurationRounded(getLastChecked(monitor?.checks))} ago.
-
-
+ {loading ? (
+
+ ) : (
+ <>
- }
- onClick={() => navigate(`/monitors/configure/${monitorId}`)}
+ label="Back to Monitors"
+ animate="slideLeft"
+ img={}
+ onClick={() => navigate("/monitors")}
sx={{
- ml: "auto",
- alignSelf: "flex-end",
backgroundColor: "#f4f4f4",
- px: theme.gap.medium,
- "& svg": {
- mr: "6px",
+ px: theme.gap.ml,
+ "& svg.MuiSvgIcon-root": {
+ mr: theme.gap.small,
+ fill: theme.palette.otherColors.slateGray,
},
}}
/>
-
-
-
-
-
-
-
-
-
- Response Times
-
-
+
+
+ {monitor?.status ? : }
+
+
+ {monitor.url?.replace(/^https?:\/\//, "") || "..."}
+
+
+
+ Your site is {monitor?.status ? "up" : "down"}.
+ {" "}
+ Checking every {formatDurationRounded(monitor?.interval)}.
+ Last time checked{" "}
+ {formatDurationRounded(getLastChecked(monitor?.checks))} ago.
+
+
+
+
- setFilter("month")}
- sx={{
- backgroundColor:
- filter === "month" && theme.palette.otherColors.fillGray,
- }}
+
-
+
+
+
+
+
+ Response Times
+
+
+ setFilter("day")}
+ sx={{
+ backgroundColor:
+ filter === "day" && theme.palette.otherColors.fillGray,
+ }}
+ />
+ setFilter("week")}
+ sx={{
+ backgroundColor:
+ filter === "week" && theme.palette.otherColors.fillGray,
+ }}
+ />
+ setFilter("month")}
+ sx={{
+ backgroundColor:
+ filter === "month" &&
+ theme.palette.otherColors.fillGray,
+ }}
+ />
+
+
+
+
+
+
+
+ History
+
+
-
-
-
-
-
-
- History
-
-
-
-
+ >
+ )}
);
};
-StatBox.propTypes = {
- title: PropTypes.string,
- value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
-};
export default DetailsPage;
diff --git a/Client/src/Pages/Monitors/index.jsx b/Client/src/Pages/Monitors/index.jsx
index 71a5eccd6..99fd141e9 100644
--- a/Client/src/Pages/Monitors/index.jsx
+++ b/Client/src/Pages/Monitors/index.jsx
@@ -22,6 +22,7 @@ import {
Menu,
MenuItem,
Modal,
+ Skeleton,
Stack,
Typography,
} from "@mui/material";
@@ -71,6 +72,38 @@ const Host = ({ params }) => {
);
};
+/**
+ * Renders a skeleton layout.
+ *
+ * @returns {JSX.Element}
+ */
+const SkeletonLayout = () => {
+ const theme = useTheme();
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
const Monitors = () => {
const theme = useTheme();
const navigate = useNavigate();
@@ -204,47 +237,60 @@ const Monitors = () => {
};
});
+ let loading = monitorState.isLoading && monitorState.monitors.length === 0;
+
return (
-
-
- Hello, {authState.user.firstName}
-
- {
- navigate("/monitors/create");
- }}
- />
-
-
-
-
-
-
-
-
- Current monitors
-
- {monitorState.monitors.length}
-
- {/* TODO - add search bar */}
-
-
-
+ {loading ? (
+
+ ) : (
+ <>
+
+
+ Hello, {authState.user.firstName}
+
+ {
+ navigate("/monitors/create");
+ }}
+ />
+
+
+
+
+
+
+
+
+ Current monitors
+
+ {monitorState.monitors.length}
+
+ {/* TODO - add search bar */}
+
+
+
+ >
+ )}