Renamed filter to dateRange, add incidents table
This commit is contained in:
1 parent
60996da401
commit
d95fcb119d
7 files changed
+285
-113
No files matched your search
@@ -79,7 +79,6 @@ const MonitorDetailsAreaChart = ({ checks }) => {
|
||||
|
||||
MonitorDetailsAreaChart.propTypes = {
|
||||
checks: PropTypes.array,
|
||||
filter: PropTypes.string,
|
||||
};
|
||||
|
||||
CustomToolTip.propTypes = {
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import PropTypes from "prop-types";
|
||||
import {
|
||||
TableContainer,
|
||||
Table,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableBody,
|
||||
TableFooter,
|
||||
TablePagination,
|
||||
} from "@mui/material";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import axiosInstance from "../../../Utils/axiosConfig";
|
||||
import { StatusLabel } from "../../../Components/Label";
|
||||
|
||||
const IncidentTable = ({ monitorId, filter }) => {
|
||||
const { authToken } = useSelector((state) => state.auth);
|
||||
const [checks, setChecks] = useState([]);
|
||||
const [checksCount, setChecksCount] = useState(0);
|
||||
const [paginationController, setPaginationController] = useState({
|
||||
page: 0,
|
||||
rowsPerPage: 5,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPage = async () => {
|
||||
try {
|
||||
if (monitorId === "0") {
|
||||
return;
|
||||
}
|
||||
const res = await axiosInstance.get(
|
||||
`/checks/${monitorId}?sortOrder=desc&filter=${filter}&page=${paginationController.page}&rowsPerPage=${paginationController.rowsPerPage}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
setChecks(res.data.data.checks);
|
||||
setChecksCount(res.data.data.checksCount);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
fetchPage();
|
||||
}, [
|
||||
authToken,
|
||||
monitorId,
|
||||
filter,
|
||||
paginationController.page,
|
||||
paginationController.rowsPerPage,
|
||||
]);
|
||||
|
||||
const handlePageChange = (_, newPage) => {
|
||||
setPaginationController({
|
||||
...paginationController,
|
||||
page: newPage,
|
||||
});
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (event) => {
|
||||
setPaginationController({
|
||||
...paginationController,
|
||||
rowsPerPage: parseInt(event.target.value, 10),
|
||||
page: 0,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Monitor Name</TableCell>
|
||||
<TableCell>Status</TableCell>
|
||||
<TableCell>Date & Time</TableCell>
|
||||
<TableCell>Message</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{checks.map((check) => {
|
||||
const status = check.status === true ? "up" : "down";
|
||||
|
||||
return (
|
||||
<TableRow key={check._id}>
|
||||
<TableCell>
|
||||
<StatusLabel
|
||||
status={status}
|
||||
text={status}
|
||||
customStyles={{ textTransform: "capitalize" }}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{new Date(check.createdAt).toLocaleString()}
|
||||
</TableCell>
|
||||
<TableCell>{check.statusCode}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[5, 10, 25]}
|
||||
onPageChange={handlePageChange}
|
||||
page={paginationController.page}
|
||||
count={checksCount}
|
||||
rowsPerPage={paginationController.rowsPerPage}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
|
||||
IncidentTable.propTypes = {
|
||||
monitorId: PropTypes.string.isRequired,
|
||||
filter: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default IncidentTable;
|
||||
@@ -9,7 +9,7 @@ import { StatusLabel } from "../../Components/Label";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useNavigate } from "react-router";
|
||||
import Select from "../../Components/Inputs/Select";
|
||||
|
||||
import IncidentTable from "./IncidentTable";
|
||||
import "./index.css";
|
||||
|
||||
/**
|
||||
@@ -76,99 +76,142 @@ const Incidents = () => {
|
||||
fetchMonitors();
|
||||
}, [authState]);
|
||||
|
||||
useEffect(() => {}, []);
|
||||
|
||||
const handleSelect = (event) => {
|
||||
setSelectedMonitor(event.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack className="incidents" gap={theme.gap.large}>
|
||||
{loading ? (
|
||||
<SkeletonLayout />
|
||||
) : false ? (
|
||||
<>
|
||||
<Stack direction="row" alignItems="center" gap={theme.gap.medium}>
|
||||
<Typography component="h1">Incident history for: </Typography>
|
||||
<Select
|
||||
id="incidents-select-monitor"
|
||||
placeholder="All servers"
|
||||
value={selectedMonitor}
|
||||
onChange={handleSelect}
|
||||
items={Object.values(monitors)}
|
||||
/>
|
||||
{hasAnyIncidents && hasSpecificIncidents && (
|
||||
<ButtonGroup sx={{ ml: "auto" }}>
|
||||
<Button
|
||||
level="secondary"
|
||||
label="All"
|
||||
onClick={() => setFilter("all")}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
filter === "all" && theme.palette.otherColors.fillGray,
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
level="secondary"
|
||||
label="Down"
|
||||
onClick={() => setFilter("down")}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
filter === "down" && theme.palette.otherColors.fillGray,
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
level="secondary"
|
||||
label="Cannot Resolve"
|
||||
onClick={() => setFilter("resolve")}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
filter === "resolve" &&
|
||||
theme.palette.otherColors.fillGray,
|
||||
}}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
)}
|
||||
</Stack>
|
||||
{hasAnyIncidents && hasSpecificIncidents ? (
|
||||
<div>thing</div>
|
||||
) : (
|
||||
<Typography alignSelf="center" mt={theme.gap.xxl}>
|
||||
The monitor you have selected, has no recorded incidents yet.
|
||||
</Typography>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Box>
|
||||
<Button
|
||||
level="tertiary"
|
||||
label="Back"
|
||||
animate="slideLeft"
|
||||
img={<WestRoundedIcon />}
|
||||
onClick={() => navigate(-1)}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.otherColors.fillGray,
|
||||
px: theme.gap.ml,
|
||||
mb: theme.gap.ml,
|
||||
"& svg.MuiSvgIcon-root": {
|
||||
mr: theme.gap.small,
|
||||
fill: theme.palette.otherColors.slateGray,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
padding={theme.gap.xxl}
|
||||
gap={theme.gap.small}
|
||||
border={1}
|
||||
borderRadius={`${theme.shape.borderRadius}px`}
|
||||
borderColor={theme.palette.otherColors.graishWhite}
|
||||
backgroundColor={theme.palette.otherColors.white}
|
||||
>
|
||||
<Typography textAlign="center">No incidents, yet.</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
<Stack className="incidents" gap={theme.gap.large}>
|
||||
<Stack direction="row" alignItems="center" gap={theme.gap.medium}>
|
||||
<Select
|
||||
id="incidents-select-monitor"
|
||||
placeholder="All servers"
|
||||
value={selectedMonitor}
|
||||
onChange={handleSelect}
|
||||
items={Object.values(monitors)}
|
||||
/>
|
||||
<ButtonGroup sx={{ ml: "auto" }}>
|
||||
<Button
|
||||
level="secondary"
|
||||
label="All"
|
||||
onClick={() => setFilter("all")}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
filter === "all" && theme.palette.otherColors.fillGray,
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
level="secondary"
|
||||
label="Down"
|
||||
onClick={() => setFilter("down")}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
filter === "down" && theme.palette.otherColors.fillGray,
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
level="secondary"
|
||||
label="Cannot Resolve"
|
||||
onClick={() => setFilter("resolve")}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
filter === "resolve" && theme.palette.otherColors.fillGray,
|
||||
}}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
</>
|
||||
<IncidentTable monitorId={selectedMonitor} filter={filter} />
|
||||
</Stack>
|
||||
// <>
|
||||
// <Stack className="incidents" gap={theme.gap.large}>
|
||||
// {loading ? (
|
||||
// <SkeletonLayout />
|
||||
// ) : false ? (
|
||||
// <>
|
||||
// <Stack direction="row" alignItems="center" gap={theme.gap.medium}>
|
||||
// <Typography component="h1">Incident history for: </Typography>
|
||||
// <Select
|
||||
// id="incidents-select-monitor"
|
||||
// placeholder="All servers"
|
||||
// value={selectedMonitor}
|
||||
// onChange={handleSelect}
|
||||
// items={Object.values(monitors)}
|
||||
// />
|
||||
// {hasAnyIncidents && hasSpecificIncidents && (
|
||||
// <ButtonGroup sx={{ ml: "auto" }}>
|
||||
// <Button
|
||||
// level="secondary"
|
||||
// label="All"
|
||||
// onClick={() => setFilter("all")}
|
||||
// sx={{
|
||||
// backgroundColor:
|
||||
// filter === "all" && theme.palette.otherColors.fillGray,
|
||||
// }}
|
||||
// />
|
||||
// <Button
|
||||
// level="secondary"
|
||||
// label="Down"
|
||||
// onClick={() => setFilter("down")}
|
||||
// sx={{
|
||||
// backgroundColor:
|
||||
// filter === "down" && theme.palette.otherColors.fillGray,
|
||||
// }}
|
||||
// />
|
||||
// <Button
|
||||
// level="secondary"
|
||||
// label="Cannot Resolve"
|
||||
// onClick={() => setFilter("resolve")}
|
||||
// sx={{
|
||||
// backgroundColor:
|
||||
// filter === "resolve" &&
|
||||
// theme.palette.otherColors.fillGray,
|
||||
// }}
|
||||
// />
|
||||
// </ButtonGroup>
|
||||
// )}
|
||||
// </Stack>
|
||||
// {hasAnyIncidents && hasSpecificIncidents ? (
|
||||
// <div>thing</div>
|
||||
// ) : (
|
||||
// <Typography alignSelf="center" mt={theme.gap.xxl}>
|
||||
// The monitor you have selected, has no recorded incidents yet.
|
||||
// </Typography>
|
||||
// )}
|
||||
// </>
|
||||
// ) : (
|
||||
// <Box>
|
||||
// <Button
|
||||
// level="tertiary"
|
||||
// label="Back"
|
||||
// animate="slideLeft"
|
||||
// img={<WestRoundedIcon />}
|
||||
// onClick={() => navigate(-1)}
|
||||
// sx={{
|
||||
// backgroundColor: theme.palette.otherColors.fillGray,
|
||||
// px: theme.gap.ml,
|
||||
// mb: theme.gap.ml,
|
||||
// "& svg.MuiSvgIcon-root": {
|
||||
// mr: theme.gap.small,
|
||||
// fill: theme.palette.otherColors.slateGray,
|
||||
// },
|
||||
// }}
|
||||
// />
|
||||
// <Box
|
||||
// padding={theme.gap.xxl}
|
||||
// gap={theme.gap.small}
|
||||
// border={1}
|
||||
// borderRadius={`${theme.shape.borderRadius}px`}
|
||||
// borderColor={theme.palette.otherColors.graishWhite}
|
||||
// backgroundColor={theme.palette.otherColors.white}
|
||||
// >
|
||||
// <Typography textAlign="center">No incidents, yet.</Typography>
|
||||
// </Box>
|
||||
// </Box>
|
||||
// )}
|
||||
// </Stack>
|
||||
// </>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import { useSelector } from "react-redux";
|
||||
import axiosInstance from "../../../../Utils/axiosConfig";
|
||||
import { StatusLabel } from "../../../../Components/Label";
|
||||
|
||||
const PaginationTable = ({ monitorId, filter }) => {
|
||||
const PaginationTable = ({ monitorId, dateRange }) => {
|
||||
const { authToken } = useSelector((state) => state.auth);
|
||||
const [checks, setChecks] = useState([]);
|
||||
const [checksCount, setChecksCount] = useState(0);
|
||||
@@ -28,7 +28,7 @@ const PaginationTable = ({ monitorId, filter }) => {
|
||||
const fetchPage = async () => {
|
||||
try {
|
||||
const res = await axiosInstance.get(
|
||||
`/checks/${monitorId}?sortOrder=desc&filter=${filter}&page=${paginationController.page}&rowsPerPage=${paginationController.rowsPerPage}`,
|
||||
`/checks/${monitorId}?sortOrder=desc&dateRange=${dateRange}&page=${paginationController.page}&rowsPerPage=${paginationController.rowsPerPage}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
@@ -45,7 +45,7 @@ const PaginationTable = ({ monitorId, filter }) => {
|
||||
}, [
|
||||
authToken,
|
||||
monitorId,
|
||||
filter,
|
||||
dateRange,
|
||||
paginationController.page,
|
||||
paginationController.rowsPerPage,
|
||||
]);
|
||||
@@ -115,7 +115,7 @@ const PaginationTable = ({ monitorId, filter }) => {
|
||||
|
||||
PaginationTable.propTypes = {
|
||||
monitorId: PropTypes.string.isRequired,
|
||||
filter: PropTypes.string.isRequired,
|
||||
dateRange: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default PaginationTable;
|
||||
@@ -113,13 +113,13 @@ const DetailsPage = () => {
|
||||
const [monitor, setMonitor] = useState({});
|
||||
const { monitorId } = useParams();
|
||||
const { authToken } = useSelector((state) => state.auth);
|
||||
const [filter, setFilter] = useState("day");
|
||||
const [dateRange, setDateRange] = useState("day");
|
||||
const navigate = useNavigate();
|
||||
|
||||
const fetchMonitor = useCallback(async () => {
|
||||
try {
|
||||
const res = await axiosInstance.get(
|
||||
`/monitors/${monitorId}?sortOrder=asc&filter=${filter}&numToDisplay=50&normalize=true`,
|
||||
`/monitors/${monitorId}?sortOrder=asc&filter=${dateRange}&numToDisplay=50&normalize=true`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
@@ -131,7 +131,7 @@ const DetailsPage = () => {
|
||||
console.error("Error fetching monitor of id: " + monitorId);
|
||||
navigate("/not-found");
|
||||
}
|
||||
}, [authToken, monitorId, navigate, filter]);
|
||||
}, [authToken, monitorId, navigate, dateRange]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchMonitor();
|
||||
@@ -301,44 +301,43 @@ const DetailsPage = () => {
|
||||
<Button
|
||||
level="secondary"
|
||||
label="Day"
|
||||
onClick={() => setFilter("day")}
|
||||
onClick={() => setDateRange("day")}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
filter === "day" && theme.palette.otherColors.fillGray,
|
||||
dateRange === "day" &&
|
||||
theme.palette.otherColors.fillGray,
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
level="secondary"
|
||||
label="Week"
|
||||
onClick={() => setFilter("week")}
|
||||
onClick={() => setDateRange("week")}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
filter === "week" && theme.palette.otherColors.fillGray,
|
||||
dateRange === "week" &&
|
||||
theme.palette.otherColors.fillGray,
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
level="secondary"
|
||||
label="Month"
|
||||
onClick={() => setFilter("month")}
|
||||
onClick={() => setDateRange("month")}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
filter === "month" &&
|
||||
dateRange === "month" &&
|
||||
theme.palette.otherColors.fillGray,
|
||||
}}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
<Box sx={{ height: "200px" }}>
|
||||
<MonitorDetailsAreaChart
|
||||
checks={monitor.checks}
|
||||
filter={filter}
|
||||
/>
|
||||
<MonitorDetailsAreaChart checks={monitor.checks} />
|
||||
</Box>
|
||||
</Box>
|
||||
<Stack gap={theme.gap.ml}>
|
||||
<Typography component="h2">History</Typography>
|
||||
{/* TODO New Table */}
|
||||
<PaginationTable monitorId={monitorId} filter={filter} />
|
||||
<PaginationTable monitorId={monitorId} dateRange={dateRange} />
|
||||
{/* <BasicTable data={data} paginated={true} /> */}
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
const Check = require("../../../models/Check");
|
||||
const Monitor = require("../../../models/Monitor");
|
||||
const mongoose = require("mongoose");
|
||||
const filterLookup = {
|
||||
const dateRangeLookup = {
|
||||
day: new Date(new Date().setDate(new Date().getDate() - 1)),
|
||||
week: new Date(new Date().setDate(new Date().getDate() - 7)),
|
||||
month: new Date(new Date().setMonth(new Date().getMonth() - 1)),
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a check for a monitor
|
||||
* @async
|
||||
@@ -35,7 +36,7 @@ const getChecksCount = async (req) => {
|
||||
const checksQuery = { monitorId: monitorId };
|
||||
// Filter checks by "day", "week", or "month"
|
||||
if (filter !== undefined) {
|
||||
checksQuery.createdAt = { $gte: filterLookup[filter] };
|
||||
checksQuery.createdAt = { $gte: dateRangeLookup[filter] };
|
||||
}
|
||||
const count = await Check.countDocuments(checksQuery);
|
||||
return count;
|
||||
@@ -52,8 +53,8 @@ const getChecksCount = async (req) => {
|
||||
const getChecks = async (req) => {
|
||||
try {
|
||||
const { monitorId } = req.params;
|
||||
let { sortOrder, limit, filter, page, rowsPerPage } = req.query;
|
||||
|
||||
let { sortOrder, limit, dateRange, filter, page, rowsPerPage } = req.query;
|
||||
console.log(filter);
|
||||
// Default limit to 0 if not provided
|
||||
if (limit === undefined) limit = 0;
|
||||
|
||||
@@ -67,8 +68,12 @@ const getChecks = async (req) => {
|
||||
// Build query
|
||||
const checksQuery = { monitorId: monitorId };
|
||||
// Filter checks by "day", "week", or "month"
|
||||
if (dateRange !== undefined) {
|
||||
checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] };
|
||||
}
|
||||
// Fitler checks by status
|
||||
if (filter !== undefined) {
|
||||
checksQuery.createdAt = { $gte: filterLookup[filter] };
|
||||
checksQuery.status = false;
|
||||
}
|
||||
|
||||
// Need to skip and limit here
|
||||
|
||||
@@ -235,7 +235,8 @@ const getChecksParamValidation = joi.object({
|
||||
const getChecksQueryValidation = joi.object({
|
||||
sortOrder: joi.string().valid("asc", "desc"),
|
||||
limit: joi.number(),
|
||||
filter: joi.string().valid("day", "week", "month"),
|
||||
dateRange: joi.string().valid("day", "week", "month"),
|
||||
filter: joi.string().valid("all", "down", "resolve"),
|
||||
page: joi.number(),
|
||||
rowsPerPage: joi.number(),
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user