mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-19 07:58:46 -05:00
Merge pull request #1664 from bluewave-labs/fix/incidents-refactor
fix: incidents refactor
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
import { Typography } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
|
||||
const NetworkError = () => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<>
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
Network error
|
||||
</Typography>
|
||||
<Typography>Please check your connection</Typography>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NetworkError;
|
||||
+5
-12
@@ -1,5 +1,5 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Box, Stack, Typography } from "@mui/material";
|
||||
import { Box, Stack } from "@mui/material";
|
||||
import Skeleton from "../../assets/Images/create-placeholder.svg?react";
|
||||
import SkeletonDark from "../../assets/Images/create-placeholder-dark.svg?react";
|
||||
import Background from "../../assets/Images/background-grid.svg?react";
|
||||
@@ -11,13 +11,13 @@ import { useSelector } from "react-redux";
|
||||
* @returns {JSX.Element} The rendered fallback UI.
|
||||
*/
|
||||
|
||||
const NetworkErrorFallback = () => {
|
||||
const GenericFallback = ({ children }) => {
|
||||
const theme = useTheme();
|
||||
const mode = useSelector((state) => state.ui.mode);
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="page-speed"
|
||||
padding={theme.spacing(16)}
|
||||
position="relative"
|
||||
border={1}
|
||||
borderColor={theme.palette.primary.lowContrast}
|
||||
@@ -68,18 +68,11 @@ const NetworkErrorFallback = () => {
|
||||
maxWidth={"300px"}
|
||||
zIndex={1}
|
||||
>
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
Network error
|
||||
</Typography>
|
||||
<Typography>Please check your connection</Typography>
|
||||
{children}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default NetworkErrorFallback;
|
||||
export default GenericFallback;
|
||||
@@ -8,7 +8,7 @@ Pagination.propTypes = {
|
||||
paginationLabel: PropTypes.string, // Label for the pagination.
|
||||
itemCount: PropTypes.number, // Total number of items for pagination.
|
||||
page: PropTypes.number, // Current page index.
|
||||
rowsPerPage: PropTypes.number.isRequired, // Number of rows displayed per page.
|
||||
rowsPerPage: PropTypes.number, // Number of rows displayed per page.
|
||||
handleChangePage: PropTypes.func.isRequired, // Function to handle page changes.
|
||||
handleChangeRowsPerPage: PropTypes.func, // Function to handle changes in rows per page.
|
||||
};
|
||||
|
||||
+3
-3
@@ -1,14 +1,14 @@
|
||||
import { Skeleton } from "@mui/material";
|
||||
|
||||
const UptimeDataTableSkeleton = () => {
|
||||
const TableSkeleton = () => {
|
||||
return (
|
||||
<Skeleton
|
||||
variant="rounded"
|
||||
width="100%"
|
||||
height="100%"
|
||||
height="80%"
|
||||
flex={1}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default UptimeDataTableSkeleton;
|
||||
export default TableSkeleton;
|
||||
@@ -0,0 +1,126 @@
|
||||
//Components
|
||||
import Table from "../../../../Components/Table";
|
||||
import TableSkeleton from "../../../../Components/Table/skeleton";
|
||||
import Pagination from "../../../../Components/Table/TablePagination";
|
||||
import { StatusLabel } from "../../../../Components/Label";
|
||||
import { HttpStatusLabel } from "../../../../Components/HttpStatusLabel";
|
||||
import GenericFallback from "../../../../Components/GenericFallback";
|
||||
import NetworkError from "../../../../Components/GenericFallback/NetworkError";
|
||||
|
||||
//Utils
|
||||
import { formatDateWithTz } from "../../../../Utils/timeUtils";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useState } from "react";
|
||||
import useChecksFetch from "../../Hooks/useChecksFetch";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const IncidentTable = ({
|
||||
shouldRender,
|
||||
monitors,
|
||||
selectedMonitor,
|
||||
filter,
|
||||
dateRange,
|
||||
}) => {
|
||||
//Redux state
|
||||
const uiTimezone = useSelector((state) => state.ui.timezone);
|
||||
|
||||
//Local state
|
||||
const [page, setPage] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(10);
|
||||
const { isLoading, networkError, checks, checksCount } = useChecksFetch({
|
||||
selectedMonitor,
|
||||
filter,
|
||||
dateRange,
|
||||
page,
|
||||
rowsPerPage,
|
||||
});
|
||||
|
||||
//Handlers
|
||||
const handleChangePage = (_, newPage) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (event) => {
|
||||
setRowsPerPage(event.target.value);
|
||||
};
|
||||
|
||||
const headers = [
|
||||
{
|
||||
id: "monitorName",
|
||||
content: "Monitor Name",
|
||||
render: (row) => monitors[row.monitorId]?.name ?? "N/A",
|
||||
},
|
||||
{
|
||||
id: "status",
|
||||
content: "Status",
|
||||
render: (row) => {
|
||||
const status = row.status === true ? "up" : "down";
|
||||
return (
|
||||
<StatusLabel
|
||||
status={status}
|
||||
text={status}
|
||||
customStyles={{ textTransform: "capitalize" }}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "dateTime",
|
||||
content: "Date & Time",
|
||||
render: (row) => {
|
||||
const formattedDate = formatDateWithTz(
|
||||
row.createdAt,
|
||||
"YYYY-MM-DD HH:mm:ss A",
|
||||
uiTimezone
|
||||
);
|
||||
return formattedDate;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "statusCode",
|
||||
content: "Status Code",
|
||||
render: (row) => <HttpStatusLabel status={row.statusCode} />,
|
||||
},
|
||||
{ id: "message", content: "Message", render: (row) => row.message },
|
||||
];
|
||||
|
||||
if (!shouldRender || isLoading) return <TableSkeleton />;
|
||||
|
||||
if (networkError) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
<NetworkError />
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isLoading && typeof checksCount === "undefined") {
|
||||
return <GenericFallback>No incidents recorded</GenericFallback>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table
|
||||
headers={headers}
|
||||
data={checks}
|
||||
/>
|
||||
<Pagination
|
||||
paginationLabel="incidents"
|
||||
itemCount={checksCount}
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
handleChangePage={handleChangePage}
|
||||
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
IncidentTable.propTypes = {
|
||||
shouldRender: PropTypes.bool,
|
||||
monitors: PropTypes.object,
|
||||
selectedMonitor: PropTypes.string,
|
||||
filter: PropTypes.string,
|
||||
dateRange: PropTypes.string,
|
||||
};
|
||||
export default IncidentTable;
|
||||
@@ -0,0 +1,162 @@
|
||||
// Components
|
||||
import { Stack, Typography, Button, ButtonGroup } from "@mui/material";
|
||||
import Select from "../../../../Components/Inputs/Select";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
//Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
import SkeletonLayout from "./skeleton";
|
||||
|
||||
const OptionsHeader = ({
|
||||
shouldRender,
|
||||
selectedMonitor = 0,
|
||||
setSelectedMonitor,
|
||||
monitors,
|
||||
filter = "all",
|
||||
setFilter,
|
||||
dateRange = "hour",
|
||||
setDateRange,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const monitorNames = typeof monitors !== "undefined" ? Object.values(monitors) : [];
|
||||
|
||||
if (!shouldRender) return <SkeletonLayout />;
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(6)}
|
||||
>
|
||||
<Typography
|
||||
display="inline-block"
|
||||
component="h1"
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
>
|
||||
Incidents for
|
||||
</Typography>
|
||||
<Select
|
||||
id="incidents-select-monitor"
|
||||
placeholder="All servers"
|
||||
value={selectedMonitor}
|
||||
onChange={(e) => setSelectedMonitor(e.target.value)}
|
||||
items={monitorNames}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.contrastTextSecondary,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(6)}
|
||||
>
|
||||
<Typography
|
||||
display="inline-block"
|
||||
component="h1"
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
>
|
||||
Filter by:
|
||||
</Typography>
|
||||
<ButtonGroup
|
||||
sx={{
|
||||
ml: "auto",
|
||||
"& .MuiButtonBase-root, & .MuiButtonBase-root:hover": {
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(filter === "all").toString()}
|
||||
onClick={() => setFilter("all")}
|
||||
>
|
||||
All
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(filter === "down").toString()}
|
||||
onClick={() => setFilter("down")}
|
||||
>
|
||||
Down
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(filter === "resolve").toString()}
|
||||
onClick={() => setFilter("resolve")}
|
||||
>
|
||||
Cannot resolve
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(6)}
|
||||
>
|
||||
<Typography
|
||||
display="inline-block"
|
||||
component="h1"
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
>
|
||||
Show:
|
||||
</Typography>
|
||||
<ButtonGroup
|
||||
sx={{
|
||||
ml: "auto",
|
||||
"& .MuiButtonBase-root, & .MuiButtonBase-root:hover": {
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "hour").toString()}
|
||||
onClick={() => setDateRange("hour")}
|
||||
>
|
||||
Last hour
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "day").toString()}
|
||||
onClick={() => setDateRange("day")}
|
||||
>
|
||||
Last day
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "week").toString()}
|
||||
onClick={() => setDateRange("week")}
|
||||
>
|
||||
Last week
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "all").toString()}
|
||||
onClick={() => setDateRange("all")}
|
||||
>
|
||||
All
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
OptionsHeader.propTypes = {
|
||||
shouldRender: PropTypes.bool,
|
||||
selectedMonitor: PropTypes.string,
|
||||
setSelectedMonitor: PropTypes.func,
|
||||
monitors: PropTypes.object,
|
||||
filter: PropTypes.string,
|
||||
setFilter: PropTypes.func,
|
||||
dateRange: PropTypes.string,
|
||||
setDateRange: PropTypes.func,
|
||||
};
|
||||
|
||||
export default OptionsHeader;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Stack, Skeleton } from "@mui/material";
|
||||
|
||||
const SkeletonLayout = () => {
|
||||
return (
|
||||
<Stack>
|
||||
<Skeleton height={40} />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default SkeletonLayout;
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { networkService } from "../../../main";
|
||||
import { createToast } from "../../../Utils/toastUtils";
|
||||
import { useSelector } from "react-redux";
|
||||
const useChecksFetch = ({ selectedMonitor, filter, dateRange, page, rowsPerPage }) => {
|
||||
//Redux
|
||||
const { authToken, user } = useSelector((state) => state.auth);
|
||||
|
||||
//Local
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
const [checks, setChecks] = useState(undefined);
|
||||
const [checksCount, setChecksCount] = useState(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchChecks = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
let res;
|
||||
|
||||
if (selectedMonitor === "0") {
|
||||
res = await networkService.getChecksByTeam({
|
||||
authToken: authToken,
|
||||
status: false,
|
||||
teamId: user.teamId,
|
||||
sortOrder: "desc",
|
||||
limit: null,
|
||||
dateRange,
|
||||
filter: filter,
|
||||
page: page,
|
||||
rowsPerPage: rowsPerPage,
|
||||
});
|
||||
} else {
|
||||
res = await networkService.getChecksByMonitor({
|
||||
authToken: authToken,
|
||||
status: false,
|
||||
monitorId: selectedMonitor,
|
||||
sortOrder: "desc",
|
||||
limit: null,
|
||||
dateRange,
|
||||
filter: filter,
|
||||
page,
|
||||
rowsPerPage,
|
||||
});
|
||||
}
|
||||
setChecks(res.data.data.checks);
|
||||
setChecksCount(res.data.data.checksCount);
|
||||
} catch (error) {
|
||||
setNetworkError(true);
|
||||
createToast({ body: error.message });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
fetchChecks();
|
||||
}, [authToken, user, dateRange, page, rowsPerPage, filter, selectedMonitor]);
|
||||
return { isLoading, networkError, checks, checksCount };
|
||||
};
|
||||
|
||||
export default useChecksFetch;
|
||||
@@ -0,0 +1,51 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { networkService } from "../../../main";
|
||||
import { createToast } from "../../../Utils/toastUtils";
|
||||
const useMonitorsFetch = ({ authToken, teamId }) => {
|
||||
//Local state
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
|
||||
const [monitors, setMonitors] = useState(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMonitors = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const res = await networkService.getMonitorsByTeamId({
|
||||
authToken,
|
||||
teamId,
|
||||
limit: null,
|
||||
types: null,
|
||||
status: null,
|
||||
checkOrder: null,
|
||||
normalize: null,
|
||||
page: null,
|
||||
rowsPerPage: null,
|
||||
filter: null,
|
||||
field: null,
|
||||
order: null,
|
||||
});
|
||||
if (res?.data?.data?.monitors?.length > 0) {
|
||||
const monitorLookup = res.data.data.monitors.reduce((acc, monitor) => {
|
||||
acc[monitor._id] = monitor;
|
||||
return acc;
|
||||
}, {});
|
||||
setMonitors(monitorLookup);
|
||||
}
|
||||
} catch (error) {
|
||||
setNetworkError(true);
|
||||
createToast({
|
||||
body: error.message,
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchMonitors();
|
||||
}, [authToken, teamId]);
|
||||
return { isLoading, monitors, networkError };
|
||||
};
|
||||
|
||||
export { useMonitorsFetch };
|
||||
@@ -1,32 +0,0 @@
|
||||
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 (
|
||||
<Box sx={{ ...styles }}>
|
||||
<Box
|
||||
textAlign="center"
|
||||
pb={theme.spacing(20)}
|
||||
>
|
||||
{mode === "light" ? <PlaceholderLight /> : <PlaceholderDark />}
|
||||
</Box>
|
||||
<Typography
|
||||
textAlign="center"
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
>
|
||||
No incidents recorded yet.
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Empty.propTypes = {
|
||||
styles: PropTypes.object,
|
||||
mode: PropTypes.string,
|
||||
};
|
||||
|
||||
export { Empty };
|
||||
@@ -1,21 +0,0 @@
|
||||
import { Skeleton /* , Stack */ } from "@mui/material";
|
||||
const IncidentSkeleton = () => {
|
||||
return (
|
||||
<>
|
||||
<Skeleton
|
||||
animation={"wave"}
|
||||
variant="rounded"
|
||||
width="100%"
|
||||
height={300}
|
||||
/>
|
||||
<Skeleton
|
||||
animation={"wave"}
|
||||
variant="rounded"
|
||||
width="100%"
|
||||
height={100}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export { IncidentSkeleton };
|
||||
@@ -1,187 +0,0 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { Typography, Box } from "@mui/material";
|
||||
|
||||
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";
|
||||
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 { HttpStatusLabel } from "../../../Components/HttpStatusLabel";
|
||||
import { Empty } from "./Empty/Empty";
|
||||
import { IncidentSkeleton } from "./Skeleton/Skeleton";
|
||||
import DataTable from "../../../Components/Table";
|
||||
import Pagination from "../../../Components/Table/TablePagination";
|
||||
|
||||
const IncidentTable = ({ monitors, selectedMonitor, filter, dateRange }) => {
|
||||
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 [page, setPage] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(10);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
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,
|
||||
status: false,
|
||||
teamId: user.teamId,
|
||||
sortOrder: "desc",
|
||||
limit: null,
|
||||
dateRange,
|
||||
filter: filter,
|
||||
page: page,
|
||||
rowsPerPage: rowsPerPage,
|
||||
});
|
||||
} else {
|
||||
res = await networkService.getChecksByMonitor({
|
||||
authToken: authToken,
|
||||
status: false,
|
||||
monitorId: selectedMonitor,
|
||||
sortOrder: "desc",
|
||||
limit: null,
|
||||
dateRange,
|
||||
filter: filter,
|
||||
page,
|
||||
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, page, rowsPerPage, dateRange]);
|
||||
|
||||
const handlePageChange = (_, newPage) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (event) => {
|
||||
setRowsPerPage(event.target.value);
|
||||
};
|
||||
|
||||
const headers = [
|
||||
{
|
||||
id: "monitorName",
|
||||
content: "Monitor Name",
|
||||
render: (row) => monitors[row.monitorId]?.name ?? "N/A",
|
||||
},
|
||||
{
|
||||
id: "status",
|
||||
content: "Status",
|
||||
render: (row) => {
|
||||
const status = row.status === true ? "up" : "down";
|
||||
return (
|
||||
<StatusLabel
|
||||
status={status}
|
||||
text={status}
|
||||
customStyles={{ textTransform: "capitalize" }}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "dateTime",
|
||||
content: "Date & Time",
|
||||
render: (row) => {
|
||||
const formattedDate = formatDateWithTz(
|
||||
row.createdAt,
|
||||
"YYYY-MM-DD HH:mm:ss A",
|
||||
uiTimezone
|
||||
);
|
||||
return formattedDate;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "statusCode",
|
||||
content: "Status Code",
|
||||
render: (row) => <HttpStatusLabel status={row.statusCode} />,
|
||||
},
|
||||
{ id: "message", content: "Message", render: (row) => row.message },
|
||||
];
|
||||
|
||||
let sharedStyles = {
|
||||
border: 1,
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
p: theme.spacing(30),
|
||||
};
|
||||
|
||||
const hasChecks = checks?.length === 0;
|
||||
const noIncidentsRecordedYet = hasChecks && selectedMonitor === "0";
|
||||
const noIncidentsForThatMonitor = hasChecks && selectedMonitor !== "0";
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoading ? (
|
||||
<IncidentSkeleton />
|
||||
) : noIncidentsRecordedYet ? (
|
||||
<Empty
|
||||
mode={mode}
|
||||
styles={sharedStyles}
|
||||
/>
|
||||
) : noIncidentsForThatMonitor ? (
|
||||
<Box sx={{ ...sharedStyles }}>
|
||||
<Box
|
||||
textAlign="center"
|
||||
pb={theme.spacing(20)}
|
||||
>
|
||||
{mode === "light" ? <PlaceholderLight /> : <PlaceholderDark />}
|
||||
</Box>
|
||||
<Typography
|
||||
textAlign="center"
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
>
|
||||
The monitor you have selected has no recorded incidents yet.
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
<>
|
||||
<DataTable
|
||||
headers={headers}
|
||||
data={checks}
|
||||
/>
|
||||
<Pagination
|
||||
paginationLabel="incidents"
|
||||
itemCount={checksCount}
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
handleChangePage={handlePageChange}
|
||||
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
IncidentTable.propTypes = {
|
||||
monitors: PropTypes.object.isRequired,
|
||||
selectedMonitor: PropTypes.string.isRequired,
|
||||
filter: PropTypes.string.isRequired,
|
||||
dateRange: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default IncidentTable;
|
||||
@@ -1,211 +1,63 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { ButtonGroup, Stack, Typography, Button } from "@mui/material";
|
||||
import { useParams } from "react-router-dom";
|
||||
// Components
|
||||
import { Stack } from "@mui/material";
|
||||
import Breadcrumbs from "../../Components/Breadcrumbs";
|
||||
|
||||
import { networkService } from "../../main";
|
||||
//Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
import Select from "../../Components/Inputs/Select";
|
||||
import IncidentTable from "./IncidentTable";
|
||||
import SkeletonLayout from "./skeleton";
|
||||
import { useMonitorsFetch } from "./Hooks/useMonitorsFetch";
|
||||
import { useSelector } from "react-redux";
|
||||
import OptionsHeader from "./Components/OptionsHeader";
|
||||
import { useState } from "react";
|
||||
import IncidentTable from "./Components/IncidentTable";
|
||||
import GenericFallback from "../../Components/GenericFallback";
|
||||
import NetworkError from "../../Components/GenericFallback/NetworkError";
|
||||
//Constants
|
||||
const BREADCRUMBS = [{ name: `Incidents`, path: "/incidents" }];
|
||||
|
||||
const Incidents = () => {
|
||||
const theme = useTheme();
|
||||
const authState = useSelector((state) => state.auth);
|
||||
const { monitorId } = useParams();
|
||||
// Redux state
|
||||
const { authToken, user } = useSelector((state) => state.auth);
|
||||
|
||||
const [monitors, setMonitors] = useState({});
|
||||
// Local state
|
||||
const [selectedMonitor, setSelectedMonitor] = useState("0");
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [filter, setFilter] = useState(undefined);
|
||||
const [dateRange, setDateRange] = useState(undefined);
|
||||
//Utils
|
||||
const theme = useTheme();
|
||||
|
||||
// TODO do something with these filters
|
||||
const [filter, setFilter] = useState("all");
|
||||
const [dateRange, setDateRange] = useState("hour");
|
||||
const { monitors, isLoading, networkError } = useMonitorsFetch({
|
||||
authToken,
|
||||
teamId: user.teamId,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMonitors = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const res = await networkService.getMonitorsByTeamId({
|
||||
authToken: authState.authToken,
|
||||
teamId: authState.user.teamId,
|
||||
limit: null,
|
||||
types: null,
|
||||
status: null,
|
||||
checkOrder: null,
|
||||
normalize: null,
|
||||
page: null,
|
||||
rowsPerPage: null,
|
||||
filter: null,
|
||||
field: null,
|
||||
order: null,
|
||||
});
|
||||
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, monitorId]);
|
||||
|
||||
useEffect(() => {}, [monitors]);
|
||||
|
||||
const handleSelect = (event) => {
|
||||
setSelectedMonitor(event.target.value);
|
||||
};
|
||||
|
||||
const isActuallyLoading = isLoading && Object.keys(monitors)?.length === 0;
|
||||
if (networkError) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
<NetworkError />
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack
|
||||
className="incidents"
|
||||
pt={theme.spacing(6)}
|
||||
gap={theme.spacing(12)}
|
||||
>
|
||||
{isActuallyLoading ? (
|
||||
<SkeletonLayout />
|
||||
) : (
|
||||
<>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
gap={theme.spacing(6)}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(6)}
|
||||
>
|
||||
<Typography
|
||||
display="inline-block"
|
||||
component="h1"
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
>
|
||||
Incidents for
|
||||
</Typography>
|
||||
<Select
|
||||
id="incidents-select-monitor"
|
||||
placeholder="All servers"
|
||||
value={selectedMonitor}
|
||||
onChange={handleSelect}
|
||||
items={Object.values(monitors)}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.contrastTextSecondary,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(6)}
|
||||
>
|
||||
<Typography
|
||||
display="inline-block"
|
||||
component="h1"
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
>
|
||||
Filter by:
|
||||
</Typography>
|
||||
<ButtonGroup
|
||||
sx={{
|
||||
ml: "auto",
|
||||
"& .MuiButtonBase-root, & .MuiButtonBase-root:hover": {
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(filter === "all").toString()}
|
||||
onClick={() => setFilter("all")}
|
||||
>
|
||||
All
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(filter === "down").toString()}
|
||||
onClick={() => setFilter("down")}
|
||||
>
|
||||
Down
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(filter === "resolve").toString()}
|
||||
onClick={() => setFilter("resolve")}
|
||||
>
|
||||
Cannot resolve
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(6)}
|
||||
>
|
||||
<Typography
|
||||
display="inline-block"
|
||||
component="h1"
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
>
|
||||
Show:
|
||||
</Typography>
|
||||
<ButtonGroup
|
||||
sx={{
|
||||
ml: "auto",
|
||||
"& .MuiButtonBase-root, & .MuiButtonBase-root:hover": {
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "hour").toString()}
|
||||
onClick={() => setDateRange("hour")}
|
||||
>
|
||||
Last hour
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "day").toString()}
|
||||
onClick={() => setDateRange("day")}
|
||||
>
|
||||
Last day
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "week").toString()}
|
||||
onClick={() => setDateRange("week")}
|
||||
>
|
||||
Last week
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "all").toString()}
|
||||
onClick={() => setDateRange("all")}
|
||||
>
|
||||
All
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<IncidentTable
|
||||
monitors={monitors}
|
||||
selectedMonitor={selectedMonitor}
|
||||
filter={filter}
|
||||
dateRange={dateRange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<OptionsHeader
|
||||
shouldRender={!isLoading}
|
||||
monitors={monitors}
|
||||
selectedMonitor={selectedMonitor}
|
||||
setSelectedMonitor={setSelectedMonitor}
|
||||
filter={filter}
|
||||
setFilter={setFilter}
|
||||
dateRange={dateRange}
|
||||
setDateRange={setDateRange}
|
||||
/>
|
||||
<IncidentTable
|
||||
shouldRender={!isLoading}
|
||||
monitors={monitors}
|
||||
selectedMonitor={selectedMonitor}
|
||||
filter={filter}
|
||||
dateRange={dateRange}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
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;
|
||||
@@ -1,6 +1,6 @@
|
||||
// Components
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
import { Stack } from "@mui/material";
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import CreateMonitorHeader from "../../../Components/CreateMonitorHeader";
|
||||
import MonitorCountHeader from "../../../Components/MonitorCountHeader";
|
||||
import MonitorGrid from "./Components/MonitorGrid";
|
||||
@@ -11,7 +11,7 @@ import { useTheme } from "@emotion/react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
|
||||
import useMonitorsFetch from "./Hooks/useMonitorsFetch";
|
||||
import NetworkErrorFallback from "../../../Components/NetworkErrorFallback";
|
||||
import GenericFallback from "../../../Components/GenericFallback";
|
||||
|
||||
// Constants
|
||||
const BREADCRUMBS = [{ name: `pagespeed`, path: "/pagespeed" }];
|
||||
@@ -27,7 +27,18 @@ const PageSpeed = () => {
|
||||
});
|
||||
|
||||
if (networkError === true) {
|
||||
return <NetworkErrorFallback />;
|
||||
return (
|
||||
<GenericFallback>
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
Network error
|
||||
</Typography>
|
||||
<Typography>Please check your connection</Typography>
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isLoading && monitors?.length === 0) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import BarChart from "../../../../../Components/Charts/BarChart";
|
||||
import ActionsMenu from "../ActionsMenu";
|
||||
|
||||
import LoadingSpinner from "../LoadingSpinner";
|
||||
import UptimeDataTableSkeleton from "./skeleton";
|
||||
import TableSkeleton from "../../../../../Components/Table/skeleton";
|
||||
|
||||
// Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
@@ -175,7 +175,7 @@ const UptimeDataTable = ({
|
||||
];
|
||||
|
||||
if (monitorsAreLoading) {
|
||||
return <UptimeDataTableSkeleton />;
|
||||
return <TableSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,13 +6,13 @@ import UptimeDataTable from "./Components/UptimeDataTable";
|
||||
import Pagination from "../../../Components/Table/TablePagination";
|
||||
import CreateMonitorHeader from "../../../Components/CreateMonitorHeader";
|
||||
import Fallback from "../../../Components/Fallback";
|
||||
import NetworkErrorFallback from "../../../Components/NetworkErrorFallback";
|
||||
import GenericFallback from "../../../Components/GenericFallback";
|
||||
import SearchComponent from "./Components/SearchComponent";
|
||||
|
||||
import MonitorCountHeader from "../../../Components/MonitorCountHeader";
|
||||
|
||||
// MUI Components
|
||||
import { Stack, Box, Button } from "@mui/material";
|
||||
import { Stack, Box, Button, Typography } from "@mui/material";
|
||||
|
||||
// Utils
|
||||
import { useState, useCallback } from "react";
|
||||
@@ -109,8 +109,21 @@ const UptimeMonitors = () => {
|
||||
});
|
||||
const totalMonitors = monitorsSummary?.totalMonitors ?? 0;
|
||||
|
||||
if (networkError) return <NetworkErrorFallback />;
|
||||
|
||||
if (networkError) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
{" "}
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
Network error
|
||||
</Typography>
|
||||
<Typography>Please check your connection</Typography>
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
if (!isLoading && !monitorsAreLoading && totalMonitors === 0) {
|
||||
return (
|
||||
<Fallback
|
||||
|
||||
@@ -18,7 +18,6 @@ import UptimeCreate from "../Pages/Uptime/Create";
|
||||
import UptimeConfigure from "../Pages/Uptime/Configure";
|
||||
|
||||
// PageSpeed
|
||||
// import PageSpeed from "../Pages/PageSpeed";
|
||||
import PageSpeed from "../Pages/PageSpeed/Monitors";
|
||||
import PageSpeedCreate from "../Pages/PageSpeed/Create";
|
||||
import PageSpeedDetails from "../Pages/PageSpeed/Details";
|
||||
@@ -29,6 +28,7 @@ import InfrastructureCreate from "../Pages/Infrastructure/Create";
|
||||
import InfrastructureDetails from "../Pages/Infrastructure/Details";
|
||||
|
||||
import Incidents from "../Pages/Incidents";
|
||||
|
||||
import Status from "../Pages/Status";
|
||||
import Integrations from "../Pages/Integrations";
|
||||
|
||||
|
||||
@@ -171,7 +171,6 @@ class NetworkService {
|
||||
async getMonitorsByTeamId(config) {
|
||||
const { authToken, teamId, limit, types, page, rowsPerPage, filter, field, order } =
|
||||
config;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (limit) params.append("limit", limit);
|
||||
|
||||
Reference in New Issue
Block a user