Merge pull request #798 from bluewave-labs/feat/monitor-pagination

Feat/monitor pagination
This commit is contained in:
Alexander Holliday
2024-09-04 19:32:18 -07:00
committed by GitHub
10 changed files with 1549 additions and 46 deletions
@@ -15,14 +15,12 @@ import {
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
import ArrowForwardRoundedIcon from "@mui/icons-material/ArrowForwardRounded";
import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import { networkService } from "../../../main";
import { StatusLabel } from "../../../Components/Label";
import { logger } from "../../../Utils/Logger";
import { useTheme } from "@emotion/react";
const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
const theme = useTheme();
const { authToken, user } = useSelector((state) => state.auth);
+2 -33
View File
@@ -1,43 +1,12 @@
import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import {
ButtonGroup,
Stack,
Skeleton,
Typography,
Button,
} from "@mui/material";
import { ButtonGroup, Stack, Typography, Button } from "@mui/material";
import { networkService } from "../../main";
import { useTheme } from "@emotion/react";
import Select from "../../Components/Inputs/Select";
import IncidentTable from "./IncidentTable";
import "./index.css";
/**
* Renders a skeleton layout.
*
* @returns {JSX.Element}
*/
const SkeletonLayout = () => {
const theme = useTheme();
return (
<>
<Stack direction="row" alignItems="center" gap={theme.spacing(6)}>
<Skeleton variant="rounded" width={150} height={34} />
<Skeleton variant="rounded" width="15%" height={34} />
<Skeleton
variant="rounded"
width="20%"
height={34}
sx={{ ml: "auto" }}
/>
</Stack>
<Skeleton variant="rounded" width="100%" height={300} />
<Skeleton variant="rounded" width="100%" height={100} />
</>
);
};
import SkeletonLayout from "./skeleton";
const Incidents = () => {
const theme = useTheme();
+30
View File
@@ -0,0 +1,30 @@
import { Stack, Skeleton } from "@mui/material";
import { useTheme } from "@emotion/react";
/**
* Renders a skeleton layout.
*
* @returns {JSX.Element}
*/
const SkeletonLayout = () => {
const theme = useTheme();
return (
<>
<Stack direction="row" alignItems="center" gap={theme.spacing(6)}>
<Skeleton variant="rounded" width={150} height={34} />
<Skeleton variant="rounded" width="15%" height={34} />
<Skeleton
variant="rounded"
width="20%"
height={34}
sx={{ ml: "auto" }}
/>
</Stack>
<Skeleton variant="rounded" width="100%" height={300} />
<Skeleton variant="rounded" width="100%" height={100} />
</>
);
};
export default SkeletonLayout;
@@ -0,0 +1,201 @@
import PropTypes from "prop-types";
import {
TableContainer,
Table,
TableHead,
TableRow,
TableCell,
TableBody,
Pagination,
PaginationItem,
Paper,
Box,
} from "@mui/material";
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
import ArrowForwardRoundedIcon from "@mui/icons-material/ArrowForwardRounded";
import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded";
import { useState, useEffect } from "react";
import { logger } from "../../../../Utils/Logger";
import Host from "../host";
import { StatusLabel } from "../../../../Components/Label";
import { jwtDecode } from "jwt-decode";
import { useSelector } from "react-redux";
import { networkService } from "../../../../main";
import { useTheme } from "@emotion/react";
import BarChart from "../../../../Components/Charts/BarChart";
import ActionsMenu from "../actionsMenu";
const MonitorTable = ({ isAdmin }) => {
const theme = useTheme();
const [paginationController, setPaginationController] = useState({
page: 0,
rowsPerPage: 14,
});
const [monitors, setMonitors] = useState([]);
const [monitorCount, setMonitorCount] = useState(0);
const authState = useSelector((state) => state.auth);
const [updateTrigger, setUpdateTrigger] = useState(false);
const handleActionMenuDelete = () => {
setUpdateTrigger((prev) => !prev);
};
const handlePageChange = (_, newPage) => {
setPaginationController({
...paginationController,
page: newPage - 1, // 0-indexed
});
};
useEffect(() => {
const fetchPage = async () => {
try {
const { authToken } = authState;
const user = jwtDecode(authToken);
const res = await networkService.getMonitorsByTeamId(
authToken,
user.teamId,
25,
["http", "ping"],
null,
"desc",
true,
paginationController.page,
paginationController.rowsPerPage
);
setMonitors(res?.data?.data?.monitors ?? []);
setMonitorCount(res?.data?.data?.monitorCount ?? 0);
} catch (error) {
logger.error(error);
}
};
fetchPage();
}, [
updateTrigger,
authState,
paginationController.page,
paginationController.rowsPerPage,
]);
let paginationComponent = <></>;
if (monitorCount > paginationController.rowsPerPage) {
paginationComponent = (
<Pagination
count={Math.ceil(monitorCount / paginationController.rowsPerPage)}
page={paginationController.page + 1} //0-indexed
onChange={handlePageChange}
shape="rounded"
renderItem={(item) => (
<PaginationItem
slots={{
previous: ArrowBackRoundedIcon,
next: ArrowForwardRoundedIcon,
}}
{...item}
/>
)}
/>
);
}
return (
<>
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell>Host</TableCell>
<TableCell>
{" "}
<Box width="max-content">
Status
<span>
<ArrowDownwardRoundedIcon />
</span>
</Box>
</TableCell>
<TableCell>Response Time</TableCell>
<TableCell>Type</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{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);
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:
monitor.status === undefined
? "pending"
: monitor.status === true
? "up"
: "down",
};
return (
<TableRow key={monitor._id}>
<TableCell>
<Host key={monitor._id} params={params} />
</TableCell>
<TableCell>
<StatusLabel
status={params.status}
text={params.status}
customStyles={{ textTransform: "capitalize" }}
/>
</TableCell>
<TableCell>
<BarChart checks={monitor.checks.slice().reverse()} />
</TableCell>
<TableCell>
<span style={{ textTransform: "uppercase" }}>
{monitor.type}
</span>
</TableCell>
<TableCell>
<ActionsMenu
monitor={monitor}
isAdmin={isAdmin}
updateCallback={handleActionMenuDelete}
/>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
{paginationComponent}
</>
);
};
MonitorTable.propTypes = {
isAdmin: PropTypes.bool,
};
export default MonitorTable;
@@ -19,7 +19,7 @@ import {
import Settings from "../../../assets/icons/settings-bold.svg?react";
import PropTypes from "prop-types";
const ActionsMenu = ({ monitor, isAdmin }) => {
const ActionsMenu = ({ monitor, isAdmin, updateCallback }) => {
const [anchorEl, setAnchorEl] = useState(null);
const [actions, setActions] = useState({});
const [isOpen, setIsOpen] = useState(false);
@@ -37,6 +37,7 @@ const ActionsMenu = ({ monitor, isAdmin }) => {
if (action.meta.requestStatus === "fulfilled") {
setIsOpen(false); // close modal
dispatch(getUptimeMonitorsByTeamId(authState.authToken));
updateCallback();
createToast({ body: "Monitor deleted successfully." });
} else {
createToast({ body: "Failed to delete monitor." });
@@ -222,6 +223,7 @@ ActionsMenu.propTypes = {
type: PropTypes.string,
}).isRequired,
isAdmin: PropTypes.bool,
updateCallback: PropTypes.func,
};
export default ActionsMenu;
+3 -6
View File
@@ -4,15 +4,14 @@ import { useSelector, useDispatch } from "react-redux";
import { getUptimeMonitorsByTeamId } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
import { useNavigate } from "react-router-dom";
import { useTheme } from "@emotion/react";
import BasicTable from "../../../Components/BasicTable";
import { Box, Button, Stack, Typography } from "@mui/material";
import PropTypes from "prop-types";
import SkeletonLayout from "./skeleton";
import Fallback from "./fallback";
import StatusBox from "./StatusBox";
import { buildData } from "./monitorData";
import Breadcrumbs from "../../../Components/Breadcrumbs";
import Greeting from "../../../Utils/greeting";
import MonitorTable from "./MonitorTable";
const Monitors = ({ isAdmin }) => {
const theme = useTheme();
@@ -25,7 +24,7 @@ const Monitors = ({ isAdmin }) => {
dispatch(getUptimeMonitorsByTeamId(authState.authToken));
}, [authState.authToken, dispatch]);
const monitorStats = monitorState.monitors.reduce(
const monitorStats = monitorState.monitors.monitors.reduce(
(acc, monitor) => {
if (monitor.isActive === false) {
acc["paused"] += 1;
@@ -39,8 +38,6 @@ const Monitors = ({ isAdmin }) => {
{ paused: 0, up: 0, down: 0 }
);
const data = buildData(monitorState.monitors, isAdmin, navigate);
let loading = monitorState.isLoading && monitorState.monitors.length === 0;
return (
@@ -118,7 +115,7 @@ const Monitors = ({ isAdmin }) => {
</Box>
{/* TODO - add search bar */}
</Stack>
<BasicTable data={data} paginated={true} table={"monitors"} />
<MonitorTable isAdmin={isAdmin} />
</Box>
</>
)}
+5 -1
View File
@@ -80,7 +80,9 @@ class NetworkService {
types,
status,
sortOrder,
normalize
normalize,
page,
rowsPerPage
) {
const params = new URLSearchParams();
@@ -93,6 +95,8 @@ class NetworkService {
if (status) params.append("status", status);
if (sortOrder) params.append("sortOrder", sortOrder);
if (normalize) params.append("normalize", normalize);
if (page) params.append("page", page);
if (rowsPerPage) params.append("rowsPerPage", rowsPerPage);
return this.axiosInstance.get(
`/monitors/team/${teamId}?${params.toString()}`,
File diff suppressed because it is too large Load Diff
+13 -3
View File
@@ -302,8 +302,16 @@ const getMonitorById = async (monitorId) => {
*/
const getMonitorsByTeamId = async (req, res) => {
try {
let { limit, type, status, sortOrder, normalize } = req.query || {};
let { limit, type, status, sortOrder, normalize, page, rowsPerPage } =
req.query || {};
const monitorQuery = { teamId: req.params.teamId };
const monitorsCount = await Monitor.countDocuments(monitorQuery);
// Pagination
let skip = 0;
if (page && rowsPerPage) {
skip = page * rowsPerPage;
}
if (type !== undefined) {
const types = Array.isArray(type) ? type : [type];
@@ -320,7 +328,9 @@ const getMonitorsByTeamId = async (req, res) => {
// This effectively removes limit, returning all checks
if (limit === undefined) limit = 0;
const monitors = await Monitor.find(monitorQuery);
const monitors = await Monitor.find(monitorQuery)
.skip(skip)
.limit(rowsPerPage);
// Map each monitor to include its associated checks
const monitorsWithChecks = await Promise.all(
monitors.map(async (monitor) => {
@@ -349,7 +359,7 @@ const getMonitorsByTeamId = async (req, res) => {
return { ...monitor.toObject(), checks };
})
);
return monitorsWithChecks;
return { monitors: monitorsWithChecks, monitorCount: monitorsCount };
} catch (error) {
throw error;
}
+2
View File
@@ -176,6 +176,8 @@ const getMonitorsByTeamIdQueryValidation = joi.object({
joi.string().valid("http", "ping", "pagespeed"),
joi.array().items(joi.string().valid("http", "ping", "pagespeed"))
),
page: joi.number(),
rowsPerPage: joi.number(),
});
const getMonitorStatsByIdParamValidation = joi.object({