Merge pull request #1663 from bluewave-labs/fix/fe/consistent-initial-state

Fix/fe/consistent initial state
This commit is contained in:
Alexander Holliday authored and GitHub committed 2025-01-29 12:36:55 -08:00
commit 9c04ecd725
29 files changed
+313 -239

No files matched your search

+16
View File
@@ -19,6 +19,22 @@
font-size: var(--env-var-font-size-medium);
}
[class*="fallback__"] .background-pattern-svg {
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%, -50%);
z-index: 0;
width: 100%;
max-width: 800px;
height: 100%;
max-height: 800px;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
}
.fallback__status > .MuiStack-root {
margin-left: var(--env-var-spacing-2);
}
@@ -26,20 +26,3 @@
min-height: calc(100vh - var(--env-var-spacing-2) * 2);
flex: 1;
}
.home-layout > div:has(> [class*="fallback__"]) .background-pattern-svg {
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%, -50%);
z-index: 0;
width: 100%;
max-width: 800px;
height: 100%;
max-height: 800px;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
}
@@ -8,7 +8,7 @@ import ConfigButton from "./ConfigButton";
import SkeletonLayout from "./skeleton";
import PropTypes from "prop-types";
const MonitorHeader = ({ shouldRender = true, isAdmin, monitor }) => {
const MonitorStatusHeader = ({ shouldRender = true, isAdmin, monitor }) => {
const theme = useTheme();
const { statusColor, statusMsg, determineState } = useUtils();
if (!shouldRender) {
@@ -21,7 +21,7 @@ const MonitorHeader = ({ shouldRender = true, isAdmin, monitor }) => {
justifyContent="space-between"
>
<Stack>
<Typography variant="h1">{monitor.name}</Typography>
<Typography variant="h1">{monitor?.name}</Typography>
<Stack
direction="row"
alignItems={"center"}
@@ -39,16 +39,16 @@ const MonitorHeader = ({ shouldRender = true, isAdmin, monitor }) => {
</Stack>
<ConfigButton
shouldRender={isAdmin}
monitorId={monitor._id}
monitorId={monitor?._id}
/>
</Stack>
);
};
MonitorHeader.propTypes = {
MonitorStatusHeader.propTypes = {
shouldRender: PropTypes.bool,
isAdmin: PropTypes.bool,
monitor: PropTypes.object,
};
export default MonitorHeader;
export default MonitorStatusHeader;
@@ -0,0 +1,85 @@
import { useTheme } from "@emotion/react";
import { Box, Stack, Typography } 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";
import { useSelector } from "react-redux";
/**
* Fallback component to display a fallback UI for network errors
*
* @returns {JSX.Element} The rendered fallback UI.
*/
const NetworkErrorFallback = () => {
const theme = useTheme();
const mode = useSelector((state) => state.ui.mode);
return (
<Box
className="page-speed"
position="relative"
border={1}
borderColor={theme.palette.primary.lowContrast}
borderRadius={theme.shape.borderRadius}
backgroundColor={theme.palette.primary.main}
overflow="hidden"
sx={{
borderStyle: "dashed",
}}
>
<Stack
alignItems="center"
gap={theme.spacing(20)}
sx={{
width: "fit-content",
margin: "auto",
marginTop: "100px",
}}
>
{mode === "light" ? (
<Skeleton style={{ zIndex: 1 }} />
) : (
<SkeletonDark style={{ zIndex: 1 }} />
)}
<Box
sx={{
"& svg g g:last-of-type path": {
stroke: theme.palette.primary.lowContrast,
},
position: "absolute",
top: "0",
left: "50%",
transform: "translate(-50%, -50%)",
width: "100%",
maxWidth: "800px",
height: "100%",
maxHeight: "800px",
backgroundPosition: "center",
backgroundSize: "cover",
backgroundRepeat: "no-repeat",
}}
>
<Background style={{ width: "100%" }} />
</Box>
<Stack
gap={theme.spacing(4)}
alignItems="center"
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>
</Stack>
</Stack>
</Box>
);
};
export default NetworkErrorFallback;
@@ -6,8 +6,8 @@ import SelectorVertical from "../../../assets/icons/selector-vertical.svg?react"
Pagination.propTypes = {
paginationLabel: PropTypes.string, // Label for the pagination.
itemCount: PropTypes.number.isRequired, // Total number of items for pagination.
page: PropTypes.number.isRequired, // Current page index.
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.
handleChangePage: PropTypes.func.isRequired, // Function to handle page changes.
handleChangeRowsPerPage: PropTypes.func, // Function to handle changes in rows per page.
@@ -29,9 +29,9 @@ const ROWS_PER_PAGE_OPTIONS = [5, 10, 15, 25];
*/
function Pagination({
paginationLabel,
itemCount,
page,
rowsPerPage,
itemCount = 0,
page = 0,
rowsPerPage = 5,
handleChangePage,
handleChangeRowsPerPage,
}) {
+10 -3
View File
@@ -32,7 +32,14 @@ import { useTheme } from "@emotion/react";
* @returns {JSX.Element} The rendered table component.
*/
const DataTable = ({ headers, data, config = { emptyView: "No data" } }) => {
const DataTable = ({
headers = [],
data = [],
config = {
emptyView: "No data",
onRowClick: () => {},
},
}) => {
const theme = useTheme();
if ((headers?.length ?? 0) === 0) {
return "No data";
@@ -117,9 +124,9 @@ DataTable.propTypes = {
render: PropTypes.func.isRequired,
})
).isRequired,
data: PropTypes.array.isRequired,
data: PropTypes.array,
config: PropTypes.shape({
onRowClick: PropTypes.func.isRequired,
onRowClick: PropTypes.func,
rowSX: PropTypes.object,
emptyView: PropTypes.node,
}),
@@ -118,8 +118,6 @@ const PieChart = ({ audits }) => {
const [highlightedItem, setHighLightedItem] = useState(null);
const [expand, setExpand] = useState(false);
if (typeof audits === "undefined") return null;
/**
* Retrieves color properties based on the performance value.
*
@@ -166,6 +164,8 @@ const PieChart = ({ audits }) => {
*/
let performance = 0;
const getPieData = (audits) => {
if (typeof audits === "undefined") return undefined;
let data = [];
let startAngle = 0;
const padding = 3; // padding between arcs
@@ -7,84 +7,84 @@ import PropTypes from "prop-types";
const PieChartLegend = ({ audits }) => {
const theme = useTheme();
if (typeof audits === "undefined") return null;
return (
<LegendBox
icon={<SpeedometerIcon />}
header="Performance metrics"
sx={{ flex: 1 }}
>
{Object.keys(audits).map((key) => {
if (key === "_id") return;
{typeof audits !== "undefined" &&
Object.keys(audits).map((key) => {
if (key === "_id") return;
let audit = audits[key];
let score = audit.score * 100;
let bg =
score >= 90
? theme.palette.success.main
: score >= 50
? theme.palette.warning.main
: score >= 0
? theme.palette.error.main
: theme.palette.tertiary.main;
let audit = audits[key];
let score = audit.score * 100;
let bg =
score >= 90
? theme.palette.success.main
: score >= 50
? theme.palette.warning.main
: score >= 0
? theme.palette.error.main
: theme.palette.tertiary.main;
// Find the position where the number ends and the unit begins
const match = audit.displayValue.match(/(\d+\.?\d*)\s*([a-zA-Z]+)/);
let value;
let unit;
if (match) {
value = match[1];
match[2] === "s" ? (unit = "seconds") : (unit = match[2]);
} else {
value = audit.displayValue;
}
// Find the position where the number ends and the unit begins
const match = audit.displayValue.match(/(\d+\.?\d*)\s*([a-zA-Z]+)/);
let value;
let unit;
if (match) {
value = match[1];
match[2] === "s" ? (unit = "seconds") : (unit = match[2]);
} else {
value = audit.displayValue;
}
return (
<Stack
flex={1}
key={`${key}-box`}
justifyContent="space-between"
direction="row"
gap={theme.spacing(4)}
p={theme.spacing(3)}
border={1}
borderColor={theme.palette.primary.lowContrast}
borderRadius={4}
>
<Box>
<Typography
fontSize={12}
fontWeight={500}
lineHeight={1}
mb={1}
textTransform="uppercase"
>
{audit.title}
</Typography>
<Typography
component="span"
fontSize={14}
fontWeight={500}
color={theme.palette.primary.contrastText}
>
{value}
return (
<Stack
flex={1}
key={`${key}-box`}
justifyContent="space-between"
direction="row"
gap={theme.spacing(4)}
p={theme.spacing(3)}
border={1}
borderColor={theme.palette.primary.lowContrast}
borderRadius={4}
>
<Box>
<Typography
fontSize={12}
fontWeight={500}
lineHeight={1}
mb={1}
textTransform="uppercase"
>
{audit.title}
</Typography>
<Typography
component="span"
variant="body2"
ml={2}
fontSize={14}
fontWeight={500}
color={theme.palette.primary.contrastText}
>
{unit}
{value}
<Typography
component="span"
variant="body2"
ml={2}
>
{unit}
</Typography>
</Typography>
</Typography>
</Box>
<Box
width={4}
backgroundColor={bg}
borderRadius={4}
/>
</Stack>
);
})}
</Box>
<Box
width={4}
backgroundColor={bg}
borderRadius={4}
/>
</Stack>
);
})}
</LegendBox>
);
};
@@ -15,10 +15,6 @@ const PageSpeedAreaChart = ({ shouldRender, monitor, metrics, handleMetrics }) =
return <SkeletonLayout />;
}
if (typeof monitor === "undefined") {
return null;
}
const data = monitor?.checks ? [...monitor.checks].reverse() : [];
return (
@@ -5,18 +5,14 @@ import { Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import PieChartLegend from "../Charts/PieChartLegend";
import SkeletonLayout from "./skeleton";
const PerformanceReport = ({ shouldRender, audits }) => {
console.log("shouldRender", shouldRender);
const theme = useTheme();
if (!shouldRender) {
return <SkeletonLayout />;
}
if (typeof audits === "undefined") {
return null;
}
return (
<ChartBox
icon={<PerformanceIcon />}
+1 -4
View File
@@ -1,11 +1,10 @@
// Components
import { Stack, Typography } from "@mui/material";
import { Stack, Typography, Skeleton } from "@mui/material";
import Breadcrumbs from "../../../Components/Breadcrumbs";
import MonitorStatusHeader from "../../../Components/MonitorStatusHeader";
import PageSpeedStatusBoxes from "./Components/PageSpeedStatusBoxes";
import PageSpeedAreaChart from "./Components/PageSpeedAreaChart";
import PerformanceReport from "./Components/PerformanceReport";
import Fallback from "../../../Components/Fallback";
// Utils
import { useTheme } from "@emotion/react";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
@@ -31,8 +30,6 @@ const PageSpeedDetails = () => {
monitorId,
});
console.log(monitor, audits, isLoading);
const [metrics, setMetrics] = useState({
accessibility: true,
bestPractices: true,
@@ -2,8 +2,6 @@ import { Grid, Grid2 } from "@mui/material";
import Card from "../Card";
const MonitorGrid = ({ shouldRender, monitors }) => {
if (!shouldRender) return null;
return (
<Grid
container
@@ -6,6 +6,7 @@ const useMonitorsFetch = ({ authToken, teamId }) => {
const [isLoading, setIsLoading] = useState(true);
const [monitors, setMonitors] = useState([]);
const [summary, setSummary] = useState({});
const [networkError, setNetworkError] = useState(false);
useEffect(() => {
const fetchMonitors = async () => {
@@ -27,6 +28,7 @@ const useMonitorsFetch = ({ authToken, teamId }) => {
setSummary(res.data.data.summary);
}
} catch (error) {
setNetworkError(true);
createToast({
body: error.message,
});
@@ -37,7 +39,7 @@ const useMonitorsFetch = ({ authToken, teamId }) => {
fetchMonitors();
}, [authToken, teamId]);
return { isLoading, monitors, summary };
return { isLoading, monitors, summary, networkError };
};
export default useMonitorsFetch;
@@ -11,6 +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";
// Constants
const BREADCRUMBS = [{ name: `pagespeed`, path: "/pagespeed" }];
@@ -20,11 +21,15 @@ const PageSpeed = () => {
const isAdmin = useIsAdmin();
const { user, authToken } = useSelector((state) => state.auth);
const { isLoading, monitors, summary } = useMonitorsFetch({
const { isLoading, monitors, summary, networkError } = useMonitorsFetch({
authToken: authToken,
teamId: user.teamId,
});
if (networkError === true) {
return <NetworkErrorFallback />;
}
if (!isLoading && monitors?.length === 0) {
return (
<Fallback
@@ -123,7 +123,7 @@ const ChartBoxes = ({
icon={<AverageResponseIcon />}
header="Average Response Time"
>
<ResponseGaugeChart avgResponseTime={monitor.avgResponseTime ?? 0} />
<ResponseGaugeChart avgResponseTime={monitor?.avgResponseTime ?? 0} />
</ChartBox>
</Stack>
);
@@ -133,7 +133,7 @@ export default ChartBoxes;
ChartBoxes.propTypes = {
shouldRender: PropTypes.bool,
monitor: PropTypes.object.isRequired,
monitor: PropTypes.object,
dateRange: PropTypes.string.isRequired,
uiTimezone: PropTypes.string.isRequired,
dateFormat: PropTypes.string.isRequired,
@@ -19,7 +19,7 @@ const DownBarChart = memo(({ monitor, type, onBarHover }) => {
<BarChart
width="100%"
height="100%"
data={monitor.groupedDownChecks}
data={monitor?.groupedDownChecks}
onMouseEnter={() => {
setChartHovered(true);
onBarHover({ time: null, totalChecks: 0 });
@@ -15,7 +15,7 @@ const ResponseTImeChart = ({ shouldRender = true, monitor, dateRange }) => {
header="Response Times"
>
<MonitorDetailsAreaChart
checks={monitor.groupedChecks ?? []}
checks={monitor?.groupedChecks ?? []}
dateRange={dateRange}
/>
</ChartBox>
@@ -8,7 +8,7 @@ import { formatDateWithTz } from "../../../../../Utils/timeUtils";
import SkeletonLayout from "./skeleton";
const ResponseTable = ({
shouldRender = true,
checks,
checks = [],
checksCount,
uiTimezone,
page,
@@ -77,8 +77,8 @@ const ResponseTable = ({
ResponseTable.propTypes = {
shouldRender: PropTypes.bool,
checks: PropTypes.array.isRequired,
checksCount: PropTypes.number.isRequired,
checks: PropTypes.array,
checksCount: PropTypes.number,
uiTimezone: PropTypes.string.isRequired,
page: PropTypes.number.isRequired,
setPage: PropTypes.func.isRequired,
@@ -8,6 +8,7 @@ import useUtils from "../../../Monitors/Hooks/useUtils";
const UptimeStatusBoxes = ({ shouldRender, monitor, certificateExpiry }) => {
const theme = useTheme();
const { determineState } = useUtils();
const { time: streakTime, units: streakUnits } = getHumanReadableDuration(
monitor?.uptimeStreak
);
@@ -10,7 +10,7 @@ const useCertificateFetch = ({
certificateDateFormat,
uiTimezone,
}) => {
const [certificateExpiry, setCertificateExpiry] = useState("N/A");
const [certificateExpiry, setCertificateExpiry] = useState(undefined);
const [certificateIsLoading, setCertificateIsLoading] = useState(false);
useEffect(() => {
@@ -10,8 +10,8 @@ export const useChecksFetch = ({
page,
rowsPerPage,
}) => {
const [checks, setChecks] = useState([]);
const [checksCount, setChecksCount] = useState(0);
const [checks, setChecks] = useState(undefined);
const [checksCount, setChecksCount] = useState(undefined);
const [checksAreLoading, setChecksAreLoading] = useState(false);
useEffect(() => {
@@ -5,7 +5,7 @@ import { useNavigate } from "react-router-dom";
export const useMonitorFetch = ({ authToken, monitorId, dateRange }) => {
const [monitorIsLoading, setMonitorsIsLoading] = useState(false);
const [monitor, setMonitor] = useState({});
const [monitor, setMonitor] = useState(undefined);
const navigate = useNavigate();
useEffect(() => {
@@ -2,13 +2,18 @@ import { useState } from "react";
import Search from "../../../../../Components/Inputs/Search";
import { Box } from "@mui/material";
import useDebounce from "../../Hooks/useDebounce";
import { useEffect } from "react";
import { useEffect, useRef } from "react";
import PropTypes from "prop-types";
const SearchComponent = ({ monitors, onSearchChange, setIsSearching }) => {
const SearchComponent = ({ monitors = [], onSearchChange, setIsSearching }) => {
const isFirstRender = useRef(true);
const [localSearch, setLocalSearch] = useState("");
const debouncedSearch = useDebounce(localSearch, 500);
useEffect(() => {
if (isFirstRender.current === true) {
isFirstRender.current = false;
return;
}
onSearchChange(debouncedSearch);
setIsSearching(false);
}, [debouncedSearch, onSearchChange, setIsSearching]);
@@ -7,12 +7,9 @@ import Host from "../Host";
import { StatusLabel } from "../../../../../Components/Label";
import BarChart from "../../../../../Components/Charts/BarChart";
import ActionsMenu from "../ActionsMenu";
import { useState } from "react";
import SearchComponent from "../SearchComponent";
import CircularCount from "../../../../../Components/CircularCount";
import LoadingSpinner from "../LoadingSpinner";
import UptimeDataTableSkeleton from "./skeleton";
import { Heading } from "../../../../../Components/Heading";
// Utils
import { useTheme } from "@emotion/react";
@@ -20,35 +17,6 @@ import useUtils from "../../Hooks/useUtils";
import { useNavigate } from "react-router-dom";
import PropTypes from "prop-types";
const MonitorDataTable = ({ shouldRender, isSearching, headers, filteredMonitors }) => {
const theme = useTheme();
const navigate = useNavigate();
if (!shouldRender) return null;
return (
<Box position="relative">
<LoadingSpinner shouldRender={isSearching} />
<DataTable
headers={headers}
data={filteredMonitors}
config={{
rowSX: {
cursor: "pointer",
"&:hover td": {
backgroundColor: theme.palette.tertiary.main,
transition: "background-color .3s ease",
},
},
onRowClick: (row) => {
navigate(`/uptime/${row.id}`);
},
emptyView: "No monitors found",
}}
/>
</Box>
);
};
/**
* UptimeDataTable displays a table of uptime monitors with sorting, searching, and action capabilities
* @param {Object} props - Component props
@@ -78,34 +46,29 @@ const MonitorDataTable = ({ shouldRender, isSearching, headers, filteredMonitors
* @param {Function} props.triggerUpdate - Callback to trigger a data refresh
* @returns {JSX.Element} Rendered component
*/
const UptimeDataTable = (props) => {
const UptimeDataTable = ({
isAdmin,
isSearching,
setIsLoading,
filteredMonitors,
sort,
setSort,
triggerUpdate,
monitorsAreLoading,
}) => {
// Utils
const {
isAdmin,
setIsLoading,
monitors,
filteredMonitors,
monitorCount,
sort,
setSort,
setSearch,
triggerUpdate,
monitorsAreLoading,
} = props;
const navigate = useNavigate();
const { determineState } = useUtils();
const theme = useTheme();
const navigate = useNavigate();
// Local state
const [isSearching, setIsSearching] = useState(false);
// Handlers
const handleSort = (field) => {
let order = "";
if (sort.field !== field) {
if (sort?.field !== field) {
order = "desc";
} else {
order = sort.order === "asc" ? "desc" : "asc";
order = sort?.order === "asc" ? "desc" : "asc";
}
setSort({ field, order });
};
@@ -124,10 +87,10 @@ const UptimeDataTable = (props) => {
<Stack
justifyContent="center"
style={{
visibility: sort.field === "name" ? "visible" : "hidden",
visibility: sort?.field === "name" ? "visible" : "hidden",
}}
>
{sort.order === "asc" ? (
{sort?.order === "asc" ? (
<ArrowUpwardRoundedIcon />
) : (
<ArrowDownwardRoundedIcon />
@@ -160,10 +123,10 @@ const UptimeDataTable = (props) => {
<Stack
justifyContent="center"
style={{
visibility: sort.field === "status" ? "visible" : "hidden",
visibility: sort?.field === "status" ? "visible" : "hidden",
}}
>
{sort.order === "asc" ? (
{sort?.order === "asc" ? (
<ArrowUpwardRoundedIcon fontSize="18px" />
) : (
<ArrowDownwardRoundedIcon fontSize="18px" />
@@ -210,41 +173,37 @@ const UptimeDataTable = (props) => {
),
},
];
return (
<Box
flex={1}
py={theme.spacing(8)}
>
<Stack
direction="row"
alignItems="center"
mb={theme.spacing(8)}
gap={theme.spacing(2)}
>
<Heading component="h2">Uptime monitors</Heading>
<CircularCount count={monitorCount} />
<SearchComponent
monitorsAreLoading={monitorsAreLoading}
monitors={monitors}
onSearchChange={setSearch}
setIsSearching={setIsSearching}
/>
</Stack>
<MonitorDataTable
shouldRender={!monitorsAreLoading}
isSearching={isSearching}
if (monitorsAreLoading) {
return <UptimeDataTableSkeleton />;
}
return (
<Box position="relative">
<LoadingSpinner shouldRender={isSearching} />
<DataTable
headers={headers}
filteredMonitors={filteredMonitors}
data={filteredMonitors}
config={{
rowSX: {
cursor: "pointer",
"&:hover td": {
backgroundColor: theme.palette.tertiary.main,
transition: "background-color .3s ease",
},
},
onRowClick: (row) => {
navigate(`/uptime/${row.id}`);
},
emptyView: "No monitors found",
}}
/>
<UptimeDataTableSkeleton shouldRender={monitorsAreLoading} />
</Box>
);
};
UptimeDataTable.propTypes = {
isSearching: PropTypes.bool,
setIsSearching: PropTypes.func,
setSort: PropTypes.func,
setSearch: PropTypes.func,
setIsLoading: PropTypes.func,
@@ -1,9 +1,6 @@
import { Skeleton } from "@mui/material";
import PropTypes from "prop-types";
const UptimeDataTableSkeleton = ({ shouldRender }) => {
if (!shouldRender) return null;
const UptimeDataTableSkeleton = () => {
return (
<Skeleton
variant="rounded"
@@ -14,8 +11,4 @@ const UptimeDataTableSkeleton = ({ shouldRender }) => {
);
};
UptimeDataTableSkeleton.propTypes = {
shouldRender: PropTypes.bool.isRequired,
};
export default UptimeDataTableSkeleton;
@@ -43,9 +43,10 @@ export const useMonitorFetch = ({
triggerUpdate,
}) => {
const [monitorsAreLoading, setMonitorsAreLoading] = useState(false);
const [monitors, setMonitors] = useState([]);
const [filteredMonitors, setFilteredMonitors] = useState([]);
const [monitorsSummary, setMonitorsSummary] = useState({});
const [monitors, setMonitors] = useState(undefined);
const [filteredMonitors, setFilteredMonitors] = useState(undefined);
const [monitorsSummary, setMonitorsSummary] = useState(undefined);
const [networkError, setNetworkError] = useState(false);
const theme = useTheme();
@@ -72,6 +73,7 @@ export const useMonitorFetch = ({
setFilteredMonitors(mappedMonitors);
setMonitorsSummary(summary);
} catch (error) {
setNetworkError(true);
createToast({
body: error.message,
});
@@ -92,7 +94,13 @@ export const useMonitorFetch = ({
theme,
triggerUpdate,
]);
return { monitors, filteredMonitors, monitorsSummary, monitorsAreLoading };
return {
monitors,
filteredMonitors,
monitorsSummary,
monitorsAreLoading,
networkError,
};
};
export default useMonitorFetch;
@@ -2,6 +2,7 @@ import { useTheme } from "@mui/material";
const useUtils = () => {
const determineState = (monitor) => {
if (typeof monitor === "undefined") return "pending";
if (monitor.isActive === false) return "paused";
if (monitor?.status === undefined) return "pending";
return monitor?.status == true ? "up" : "down";
+41 -18
View File
@@ -6,6 +6,11 @@ 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 SearchComponent from "./Components/SearchComponent";
import MonitorCountHeader from "../../../Components/MonitorCountHeader";
// MUI Components
import { Stack, Box, Button } from "@mui/material";
@@ -53,9 +58,9 @@ const UptimeMonitors = () => {
const rowsPerPage = useSelector((state) => state.ui.monitors.rowsPerPage);
// Local state
const [search, setSearch] = useState("");
const [page, setPage] = useState(0);
const [sort, setSort] = useState({});
const [search, setSearch] = useState(undefined);
const [page, setPage] = useState(undefined);
const [sort, setSort] = useState(undefined);
const [isSearching, setIsSearching] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [monitorUpdateTrigger, setMonitorUpdateTrigger] = useState(false);
@@ -85,22 +90,28 @@ const UptimeMonitors = () => {
}, []);
const teamId = user.teamId;
const { monitorsAreLoading, monitors, filteredMonitors, monitorsSummary } =
useMonitorsFetch({
authToken,
teamId,
limit: 25,
page,
rowsPerPage: rowsPerPage,
filter: search,
field: sort.field,
order: sort.order,
triggerUpdate: monitorUpdateTrigger,
});
const {
monitorsAreLoading,
monitors,
filteredMonitors,
monitorsSummary,
networkError,
} = useMonitorsFetch({
authToken,
teamId,
limit: 25,
page: page,
rowsPerPage: rowsPerPage,
filter: search,
field: sort?.field,
order: sort?.order,
triggerUpdate: monitorUpdateTrigger,
});
const totalMonitors = monitorsSummary?.totalMonitors ?? 0;
if (!isLoading && !monitorsAreLoading && monitors?.length === 0) {
if (networkError) return <NetworkErrorFallback />;
if (!isLoading && !monitorsAreLoading && totalMonitors === 0) {
return (
<Fallback
vowelStart={true}
@@ -128,6 +139,19 @@ const UptimeMonitors = () => {
monitorsSummary={monitorsSummary}
shouldRender={!monitorsAreLoading}
/>
<Stack direction={"row"}>
<MonitorCountHeader
shouldRender={monitors?.length > 0 && !monitorsAreLoading}
monitorCount={totalMonitors}
heading={"Uptime monitors"}
></MonitorCountHeader>
<SearchComponent
monitors={monitors}
onSearchChange={setSearch}
setIsSearching={setIsSearching}
/>
</Stack>
<UptimeDataTable
isAdmin={isAdmin}
isLoading={isLoading}
@@ -139,7 +163,6 @@ const UptimeMonitors = () => {
setSort={setSort}
setSearch={setSearch}
isSearching={isSearching}
setIsSearching={setIsSearching}
monitorsAreLoading={monitorsAreLoading}
triggerUpdate={triggerUpdate}
/>
-1
View File
@@ -3,7 +3,6 @@ const BASE_URL = import.meta.env.VITE_APP_API_BASE_URL;
const FALLBACK_BASE_URL = "http://localhost:5000/api/v1";
import { clearAuthState } from "../Features/Auth/authSlice";
import { clearUptimeMonitorState } from "../Features/UptimeMonitors/uptimeMonitorsSlice";
import { createToast } from "./toastUtils";
class NetworkService {
constructor(store, dispatch, navigate) {