diff --git a/Client/src/Pages/Monitors/Home/MonitorTable/Skeleton/index.jsx b/Client/src/Pages/Monitors/Home/MonitorTable/Skeleton/index.jsx
new file mode 100644
index 000000000..186b89725
--- /dev/null
+++ b/Client/src/Pages/Monitors/Home/MonitorTable/Skeleton/index.jsx
@@ -0,0 +1,31 @@
+import { Skeleton, TableBody, TableCell, TableRow } from "@mui/material";
+const ROWS_NUMBER = 7;
+const ROWS_ARRAY = Array.from({ length: ROWS_NUMBER }, (_, i) => i);
+
+const TableBodySkeleton = () => {
+ return (
+
+ {ROWS_ARRAY.map((row) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+ );
+};
+
+export { TableBodySkeleton };
diff --git a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx
index b86a739e0..4e8f5100b 100644
--- a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx
+++ b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx
@@ -1,17 +1,17 @@
import PropTypes from "prop-types";
import {
- TableContainer,
- Table,
- TableHead,
- TableRow,
- TableCell,
- TableBody,
- Paper,
- Box,
- TablePagination,
- Stack,
- Typography,
- Button,
+ TableContainer,
+ Table,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableBody,
+ Paper,
+ Box,
+ TablePagination,
+ Stack,
+ Typography,
+ Button,
} from "@mui/material";
import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded";
import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded";
@@ -34,6 +34,7 @@ import RightArrow from "../../../../assets/icons/right-arrow.svg?react";
import SelectorVertical from "../../../../assets/icons/selector-vertical.svg?react";
import ActionsMenu from "../actionsMenu";
import useUtils from "../../utils";
+import { TableBodySkeleton } from "./Skeleton";
/**
* Component for pagination actions (first, previous, next, last).
@@ -48,386 +49,385 @@ import useUtils from "../../utils";
* @returns {JSX.Element} Pagination actions component.
*/
const TablePaginationActions = (props) => {
- const { count, page, rowsPerPage, onPageChange } = props;
- const handleFirstPageButtonClick = (event) => {
- onPageChange(event, 0);
- };
- const handleBackButtonClick = (event) => {
- onPageChange(event, page - 1);
- };
- const handleNextButtonClick = (event) => {
- onPageChange(event, page + 1);
- };
- const handleLastPageButtonClick = (event) => {
- onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
- };
+ const { count, page, rowsPerPage, onPageChange } = props;
+ const handleFirstPageButtonClick = (event) => {
+ onPageChange(event, 0);
+ };
+ const handleBackButtonClick = (event) => {
+ onPageChange(event, page - 1);
+ };
+ const handleNextButtonClick = (event) => {
+ onPageChange(event, page + 1);
+ };
+ const handleLastPageButtonClick = (event) => {
+ onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
+ };
- return (
-
-
-
-
-
-
- );
+ return (
+
+
+
+
+
+
+ );
};
TablePaginationActions.propTypes = {
- count: PropTypes.number.isRequired,
- page: PropTypes.number.isRequired,
- rowsPerPage: PropTypes.number.isRequired,
- onPageChange: PropTypes.func.isRequired,
+ count: PropTypes.number.isRequired,
+ page: PropTypes.number.isRequired,
+ rowsPerPage: PropTypes.number.isRequired,
+ onPageChange: PropTypes.func.isRequired,
};
const MonitorTable = ({ isAdmin, filter, setLoading }) => {
- const theme = useTheme();
- const navigate = useNavigate();
- const dispatch = useDispatch();
- const { determineState } = useUtils();
+ const theme = useTheme();
+ const navigate = useNavigate();
+ const dispatch = useDispatch();
+ const { determineState } = useUtils();
- const { rowsPerPage } = useSelector((state) => state.ui.monitors);
- const [page, setPage] = useState(0);
- const [monitors, setMonitors] = useState([]);
- const [monitorCount, setMonitorCount] = useState(0);
- const authState = useSelector((state) => state.auth);
- const [updateTrigger, setUpdateTrigger] = useState(false);
- const [sort, setSort] = useState({});
- const prevFilter = useRef(filter);
+ const { rowsPerPage } = useSelector((state) => state.ui.monitors);
+ const authState = useSelector((state) => state.auth);
+ const [page, setPage] = useState(0);
+ const [monitors, setMonitors] = useState([]);
+ const [monitorCount, setMonitorCount] = useState(0);
+ const [updateTrigger, setUpdateTrigger] = useState(false);
+ const [sort, setSort] = useState({});
+ const prevFilter = useRef(filter);
- const handleActionMenuDelete = () => {
- setUpdateTrigger((prev) => !prev);
- };
+ const handleActionMenuDelete = () => {
+ setUpdateTrigger((prev) => !prev);
+ };
- const handleChangePage = (event, newPage) => {
- setPage(newPage);
- };
+ const handleChangePage = (event, newPage) => {
+ setPage(newPage);
+ };
- const handleChangeRowsPerPage = (event) => {
- dispatch(
- setRowsPerPage({
- value: parseInt(event.target.value, 10),
- table: "monitors",
- })
- );
- setPage(0);
- };
+ const handleChangeRowsPerPage = (event) => {
+ dispatch(
+ setRowsPerPage({
+ value: parseInt(event.target.value, 10),
+ table: "monitors",
+ })
+ );
+ setPage(0);
+ };
- const fetchPage = useCallback(async () => {
- try {
- const { authToken } = authState;
- const user = jwtDecode(authToken);
- const res = await networkService.getMonitorsByTeamId({
- authToken,
- teamId: user.teamId,
- limit: 25,
- types: ["http", "ping"],
- status: null,
- checkOrder: "desc",
- normalize: true,
- page: page,
- rowsPerPage: rowsPerPage,
- filter: filter,
- field: sort.field,
- order: sort.order,
- });
- setMonitors(res?.data?.data?.monitors ?? []);
- setMonitorCount(res?.data?.data?.monitorCount ?? 0);
- setLoading(false);
- } catch (error) {
- logger.error(error);
- }
- }, [authState, page, rowsPerPage, filter, sort, setLoading]);
+ const fetchPage = useCallback(async () => {
+ try {
+ const { authToken } = authState;
+ const user = jwtDecode(authToken);
+ const res = await networkService.getMonitorsByTeamId({
+ authToken,
+ teamId: user.teamId,
+ limit: 25,
+ types: ["http", "ping"],
+ status: null,
+ checkOrder: "desc",
+ normalize: true,
+ page: page,
+ rowsPerPage: rowsPerPage,
+ filter: filter,
+ field: sort.field,
+ order: sort.order,
+ });
+ setMonitors(res?.data?.data?.monitors ?? []);
+ setMonitorCount(res?.data?.data?.monitorCount ?? 0);
+ setLoading(false);
+ } catch (error) {
+ logger.error(error);
+ }
+ }, [authState, page, rowsPerPage, filter, sort, setLoading]);
- useEffect(() => {
- fetchPage();
- }, [
- updateTrigger,
- authState,
- page,
- rowsPerPage,
- filter,
- sort,
- setLoading,
- fetchPage,
- ]);
+ useEffect(() => {
+ fetchPage();
+ }, [updateTrigger, authState, page, rowsPerPage, filter, sort, setLoading, fetchPage]);
- // Listen for changes in filter, if new value reset the page
- useEffect(() => {
- if (prevFilter.current !== filter) {
- setPage(0);
- fetchPage();
- }
- prevFilter.current = filter;
- }, [filter, fetchPage]);
+ // Listen for changes in filter, if new value reset the page
+ useEffect(() => {
+ if (prevFilter.current !== filter) {
+ setPage(0);
+ fetchPage();
+ }
+ prevFilter.current = filter;
+ }, [filter, fetchPage]);
- /**
- * Helper function to calculate the range of displayed rows.
- * @returns {string}
- */
- const getRange = () => {
- let start = page * rowsPerPage + 1;
- let end = Math.min(page * rowsPerPage + rowsPerPage, monitorCount);
- return `${start} - ${end}`;
- };
+ /**
+ * Helper function to calculate the range of displayed rows.
+ * @returns {string}
+ */
+ const getRange = () => {
+ let start = page * rowsPerPage + 1;
+ let end = Math.min(page * rowsPerPage + rowsPerPage, monitorCount);
+ return `${start} - ${end}`;
+ };
- const handleSort = async (field) => {
- let order = "";
- if (sort.field !== field) {
- order = "desc";
- } else {
- order = sort.order === "asc" ? "desc" : "asc";
- }
- setSort({ field, order });
+ const handleSort = async (field) => {
+ let order = "";
+ if (sort.field !== field) {
+ order = "desc";
+ } else {
+ order = sort.order === "asc" ? "desc" : "asc";
+ }
+ setSort({ field, order });
- const { authToken } = authState;
- const user = jwtDecode(authToken);
+ const { authToken } = authState;
+ const user = jwtDecode(authToken);
- const res = await networkService.getMonitorsByTeamId({
- authToken,
- teamId: user.teamId,
- limit: 25,
- types: ["http", "ping"],
- status: null,
- checkOrder: "desc",
- normalize: true,
- page: page,
- rowsPerPage: rowsPerPage,
- filter: null,
- field: field,
- order: order,
- });
- setMonitors(res?.data?.data?.monitors ?? []);
- setMonitorCount(res?.data?.data?.monitorCount ?? 0);
- };
+ const res = await networkService.getMonitorsByTeamId({
+ authToken,
+ teamId: user.teamId,
+ limit: 25,
+ types: ["http", "ping"],
+ status: null,
+ checkOrder: "desc",
+ normalize: true,
+ page: page,
+ rowsPerPage: rowsPerPage,
+ filter: null,
+ field: field,
+ order: order,
+ });
+ setMonitors(res?.data?.data?.monitors ?? []);
+ setMonitorCount(res?.data?.data?.monitorCount ?? 0);
+ };
- return (
- <>
-
-
-
-
- handleSort("name")}
- >
-
- Host
-
- {sort.order === "asc" ? (
-
- ) : (
-
- )}
-
-
-
- handleSort("status")}
- >
- {" "}
-
- {" "}
- Status
-
- {sort.order === "asc" ? (
-
- ) : (
-
- )}
-
-
-
- Response Time
- Type
- Actions
-
-
-
- {monitors.map((monitor) => {
- let uptimePercentage = "";
- let percentageColor = theme.palette.percentage.uptimeExcellent;
+ return (
+ <>
+
+
+
+
+ handleSort("name")}
+ >
+
+ Host
+
+ {sort.order === "asc" ? (
+
+ ) : (
+
+ )}
+
+
+
+ handleSort("status")}
+ >
+ {" "}
+
+ {" "}
+ Status
+
+ {sort.order === "asc" ? (
+
+ ) : (
+
+ )}
+
+
+
+ Response Time
+ Type
+ Actions
+
+
+ {monitors.length > 0 ? (
+
+ {monitors.map((monitor) => {
+ let uptimePercentage = "";
+ let percentageColor = theme.palette.percentage.uptimeExcellent;
- // Determine uptime percentage and color based on the monitor's uptimePercentage value
- if (monitor.uptimePercentage !== undefined) {
- uptimePercentage =
- monitor.uptimePercentage === 0
- ? "0"
- : (monitor.uptimePercentage * 100).toFixed(2);
+ // Determine uptime percentage and color based on the monitor's uptimePercentage value
+ if (monitor.uptimePercentage !== undefined) {
+ uptimePercentage =
+ monitor.uptimePercentage === 0
+ ? "0"
+ : (monitor.uptimePercentage * 100).toFixed(2);
- percentageColor =
- monitor.uptimePercentage < 0.25
- ? theme.palette.percentage.uptimePoor
- : monitor.uptimePercentage < 0.5
- ? theme.palette.percentage.uptimeFair
- : monitor.uptimePercentage < 0.75
- ? theme.palette.percentage.uptimeGood
- : theme.palette.percentage.uptimeExcellent;
- }
+ percentageColor =
+ monitor.uptimePercentage < 0.25
+ ? theme.palette.percentage.uptimePoor
+ : monitor.uptimePercentage < 0.5
+ ? theme.palette.percentage.uptimeFair
+ : monitor.uptimePercentage < 0.75
+ ? theme.palette.percentage.uptimeGood
+ : theme.palette.percentage.uptimeExcellent;
+ }
- const params = {
- url: monitor.url,
- title: monitor.name,
- percentage: uptimePercentage,
- percentageColor,
- status: determineState(monitor),
- };
+ const params = {
+ url: monitor.url,
+ title: monitor.name,
+ percentage: uptimePercentage,
+ percentageColor,
+ status: determineState(monitor),
+ };
- return (
- {
- navigate(`/monitors/${monitor._id}`);
- }}
- >
-
-
-
-
-
-
-
-
-
-
-
- {monitor.type}
-
-
-
-
-
-
- );
- })}
-
-
-
-
-
- Showing {getRange()} of {monitorCount} monitor(s)
-
-
- `Page ${page + 1} of ${Math.max(0, Math.ceil(count / rowsPerPage))}`
- }
- slotProps={{
- select: {
- MenuProps: {
- keepMounted: true,
- disableScrollLock: true,
- PaperProps: {
- className: "pagination-dropdown",
- sx: {
- mt: 0,
- mb: theme.spacing(2),
- },
- },
- transformOrigin: { vertical: "bottom", horizontal: "left" },
- anchorOrigin: { vertical: "top", horizontal: "left" },
- sx: { mt: theme.spacing(-2) },
- },
- inputProps: { id: "pagination-dropdown" },
- IconComponent: SelectorVertical,
- sx: {
- ml: theme.spacing(4),
- mr: theme.spacing(12),
- minWidth: theme.spacing(20),
- textAlign: "left",
- "&.Mui-focused > div": {
- backgroundColor: theme.palette.background.main,
- },
- },
- },
- }}
- sx={{
- mt: theme.spacing(6),
- color: theme.palette.text.secondary,
- "& svg path": {
- stroke: theme.palette.text.tertiary,
- strokeWidth: 1.3,
- },
- "& .MuiSelect-select": {
- border: 1,
- borderColor: theme.palette.border.light,
- borderRadius: theme.shape.borderRadius,
- },
- }}
- />
-
- >
- );
+ return (
+ {
+ navigate(`/monitors/${monitor._id}`);
+ }}
+ >
+
+
+
+
+
+
+
+
+
+
+ {monitor.type}
+
+
+
+
+
+ );
+ })}
+
+ ) : (
+
+ )}
+
+
+
+
+ Showing {getRange()} of {monitorCount} monitor(s)
+
+
+ `Page ${page + 1} of ${Math.max(0, Math.ceil(count / rowsPerPage))}`
+ }
+ slotProps={{
+ select: {
+ MenuProps: {
+ keepMounted: true,
+ disableScrollLock: true,
+ PaperProps: {
+ className: "pagination-dropdown",
+ sx: {
+ mt: 0,
+ mb: theme.spacing(2),
+ },
+ },
+ transformOrigin: { vertical: "bottom", horizontal: "left" },
+ anchorOrigin: { vertical: "top", horizontal: "left" },
+ sx: { mt: theme.spacing(-2) },
+ },
+ inputProps: { id: "pagination-dropdown" },
+ IconComponent: SelectorVertical,
+ sx: {
+ ml: theme.spacing(4),
+ mr: theme.spacing(12),
+ minWidth: theme.spacing(20),
+ textAlign: "left",
+ "&.Mui-focused > div": {
+ backgroundColor: theme.palette.background.main,
+ },
+ },
+ },
+ }}
+ sx={{
+ mt: theme.spacing(6),
+ color: theme.palette.text.secondary,
+ "& svg path": {
+ stroke: theme.palette.text.tertiary,
+ strokeWidth: 1.3,
+ },
+ "& .MuiSelect-select": {
+ border: 1,
+ borderColor: theme.palette.border.light,
+ borderRadius: theme.shape.borderRadius,
+ },
+ }}
+ />
+
+ >
+ );
};
MonitorTable.propTypes = {
- isAdmin: PropTypes.bool,
- filter: PropTypes.string,
- setLoading: PropTypes.func,
+ isAdmin: PropTypes.bool,
+ filter: PropTypes.string,
+ setLoading: PropTypes.func,
};
const MemoizedMonitorTable = memo(MonitorTable);
diff --git a/Client/src/Pages/Monitors/Home/index.jsx b/Client/src/Pages/Monitors/Home/index.jsx
index d02d046ab..e52556bb9 100644
--- a/Client/src/Pages/Monitors/Home/index.jsx
+++ b/Client/src/Pages/Monitors/Home/index.jsx
@@ -20,9 +20,11 @@ const Monitors = ({ isAdmin }) => {
const navigate = useNavigate();
const monitorState = useSelector((state) => state.uptimeMonitors);
const authState = useSelector((state) => state.auth);
+ const dispatch = useDispatch({});
+
+ //TODO create components, and lower these states.
const [search, setSearch] = useState("");
const [isSearching, setIsSearching] = useState(false);
- const dispatch = useDispatch({});
const debouncedFilter = useDebounce(search, 500);
const handleSearch = (value) => {
@@ -34,13 +36,17 @@ const Monitors = ({ isAdmin }) => {
dispatch(getUptimeMonitorsByTeamId(authState.authToken));
}, [authState.authToken, dispatch]);
- console.log(monitorState);
- const loading = true;
- /* monitorState?.isLoading &&
- monitorState?.monitorsSummary?.monitors?.length === 0; */
- const canCreateMonitor =
- isAdmin && monitorState?.monitorsSummary?.monitors?.length !== 0;
+ //Why are we tying loading to monitors length?
+ const loading =
+ monitorState?.isLoading; /* && monitorState?.monitorsSummary?.monitors?.length === 0 */
+ const hasMonitors = monitorState?.monitorsSummary?.monitors?.length !== 0;
+ const noMonitors = monitorState?.monitorsSummary?.monitors?.length === 0;
+ const canAddMonitor = isAdmin && hasMonitors;
+ const needsAdmin = !isAdmin && noMonitors;
+
+ /* console.log({ loading });
+ console.log({ monitorState }); */
return (
{
gap={theme.spacing(6)}
>
- {canCreateMonitor && (
+ {canAddMonitor && (
+ {noMonitors && }
+ {needsAdmin &&
Wait for an admin to add some monitors!
}
{loading ? (
) : (
<>
- {isAdmin && monitorState?.monitorsSummary?.monitors?.length === 0 && (
-
- )}
-
- {monitorState?.monitorsSummary?.monitors?.length !== 0 && (
+ {hasMonitors && (
<>
{
borderColor={theme.palette.border.light}
backgroundColor={theme.palette.background.accent}
>
+ {/* TODO maybe we dont need the conditional here, since we already check for monitors before */}
{monitorState?.monitorsSummary?.monitorCounts?.total || 0}