Merge pull request #1664 from bluewave-labs/fix/incidents-refactor

fix: incidents refactor
This commit is contained in:
Alexander Holliday
2025-01-30 08:25:16 -08:00
committed by GitHub
19 changed files with 523 additions and 515 deletions
@@ -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;
@@ -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.
};
@@ -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;
+50 -198
View File
@@ -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>
);
};
-50
View File
@@ -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;
+14 -3
View File
@@ -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 (
+17 -4
View File
@@ -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
+1 -1
View File
@@ -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";
-1
View File
@@ -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);