mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-12 20:48:43 -05:00
add loading states, skeletons, fix map tooltip
This commit is contained in:
@@ -73,9 +73,12 @@ const CustomToolTip = ({ active, payload, label, dateRange }) => {
|
||||
component="span"
|
||||
sx={{ opacity: 0.8 }}
|
||||
>
|
||||
Response Time
|
||||
</Typography>{" "}
|
||||
<Typography component="span">
|
||||
Response time:
|
||||
</Typography>
|
||||
<Typography
|
||||
ml={theme.spacing(4)}
|
||||
component="span"
|
||||
>
|
||||
{Math.floor(responseTime)}
|
||||
<Typography
|
||||
component="span"
|
||||
|
||||
+122
-119
@@ -24,73 +24,70 @@ const CustomToolTip = ({ active, payload, label }) => {
|
||||
? payload[0]?.payload?.originalAvgResponseTime
|
||||
: (payload[0]?.payload?.avgResponseTime ?? 0);
|
||||
return (
|
||||
<ChartBox
|
||||
icon={<ResponseTimeIcon />}
|
||||
header="Response Times"
|
||||
sx={{ padding: 0 }}
|
||||
<Box
|
||||
className="area-tooltip"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
border: 1,
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
py: theme.spacing(2),
|
||||
px: theme.spacing(4),
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
className="area-tooltip"
|
||||
<Typography
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.main,
|
||||
border: 1,
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
py: theme.spacing(2),
|
||||
px: theme.spacing(4),
|
||||
color: theme.palette.text.tertiary,
|
||||
fontSize: 12,
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
{formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)}
|
||||
</Typography>
|
||||
<Box mt={theme.spacing(1)}>
|
||||
<Box
|
||||
display="inline-block"
|
||||
width={theme.spacing(4)}
|
||||
height={theme.spacing(4)}
|
||||
backgroundColor={theme.palette.primary.main}
|
||||
sx={{ borderRadius: "50%" }}
|
||||
/>
|
||||
<Stack
|
||||
display="inline-flex"
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
ml={theme.spacing(3)}
|
||||
sx={{
|
||||
color: theme.palette.text.tertiary,
|
||||
fontSize: 12,
|
||||
fontWeight: 500,
|
||||
"& span": {
|
||||
color: theme.palette.text.tertiary,
|
||||
fontSize: 11,
|
||||
fontWeight: 500,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)}
|
||||
</Typography>
|
||||
<Box mt={theme.spacing(1)}>
|
||||
<Box
|
||||
display="inline-block"
|
||||
width={theme.spacing(4)}
|
||||
height={theme.spacing(4)}
|
||||
backgroundColor={theme.palette.primary.main}
|
||||
sx={{ borderRadius: "50%" }}
|
||||
/>
|
||||
<Stack
|
||||
display="inline-flex"
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
ml={theme.spacing(3)}
|
||||
sx={{
|
||||
"& span": {
|
||||
color: theme.palette.text.tertiary,
|
||||
fontSize: 11,
|
||||
fontWeight: 500,
|
||||
},
|
||||
}}
|
||||
<Typography
|
||||
component="span"
|
||||
sx={{ opacity: 0.8 }}
|
||||
>
|
||||
Response time
|
||||
</Typography>
|
||||
<Typography
|
||||
ml={theme.spacing(4)}
|
||||
component="span"
|
||||
>
|
||||
{Math.floor(responseTime)}
|
||||
<Typography
|
||||
component="span"
|
||||
sx={{ opacity: 0.8 }}
|
||||
>
|
||||
Response Time
|
||||
</Typography>{" "}
|
||||
<Typography component="span">
|
||||
{Math.floor(responseTime)}
|
||||
<Typography
|
||||
component="span"
|
||||
sx={{ opacity: 0.8 }}
|
||||
>
|
||||
{" "}
|
||||
ms
|
||||
</Typography>
|
||||
{" "}
|
||||
ms
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
{/* Display original value */}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
</ChartBox>
|
||||
{/* Display original value */}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
@@ -143,75 +140,81 @@ const DistributedUptimeResponseChart = ({ checks }) => {
|
||||
|
||||
if (checks.length === 0) return null;
|
||||
return (
|
||||
<ResponsiveContainer
|
||||
width="100%"
|
||||
minWidth={25}
|
||||
height={220}
|
||||
<ChartBox
|
||||
icon={<ResponseTimeIcon />}
|
||||
header="Response Times"
|
||||
sx={{ padding: 0 }}
|
||||
>
|
||||
<AreaChart
|
||||
<ResponsiveContainer
|
||||
width="100%"
|
||||
height="100%"
|
||||
data={checks}
|
||||
margin={{
|
||||
top: 10,
|
||||
right: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
onMouseMove={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
minWidth={25}
|
||||
height={220}
|
||||
>
|
||||
<CartesianGrid
|
||||
stroke={theme.palette.primary.lowContrast}
|
||||
strokeWidth={1}
|
||||
strokeOpacity={1}
|
||||
fill="transparent"
|
||||
vertical={false}
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="colorUv"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="0"
|
||||
y2="1"
|
||||
>
|
||||
<stop
|
||||
offset="0%"
|
||||
stopColor={theme.palette.accent.darker}
|
||||
stopOpacity={0.8}
|
||||
/>
|
||||
<stop
|
||||
offset="100%"
|
||||
stopColor={theme.palette.accent.main}
|
||||
stopOpacity={0}
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<XAxis
|
||||
stroke={theme.palette.primary.lowContrast}
|
||||
dataKey="_id"
|
||||
tick={<CustomTick />}
|
||||
minTickGap={0}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
height={20}
|
||||
/>
|
||||
<Tooltip
|
||||
cursor={{ stroke: theme.palette.primary.lowContrast }}
|
||||
content={<CustomToolTip />}
|
||||
wrapperStyle={{ pointerEvents: "none" }}
|
||||
/>
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="avgResponseTime"
|
||||
stroke={theme.palette.primary.accent}
|
||||
fill="url(#colorUv)"
|
||||
strokeWidth={isHovered ? 2.5 : 1.5}
|
||||
activeDot={{ stroke: theme.palette.background.main, r: 5 }}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
<AreaChart
|
||||
width="100%"
|
||||
height="100%"
|
||||
data={checks}
|
||||
margin={{
|
||||
top: 10,
|
||||
right: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
onMouseMove={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<CartesianGrid
|
||||
stroke={theme.palette.primary.lowContrast}
|
||||
strokeWidth={1}
|
||||
strokeOpacity={1}
|
||||
fill="transparent"
|
||||
vertical={false}
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="colorUv"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="0"
|
||||
y2="1"
|
||||
>
|
||||
<stop
|
||||
offset="0%"
|
||||
stopColor={theme.palette.accent.darker}
|
||||
stopOpacity={0.8}
|
||||
/>
|
||||
<stop
|
||||
offset="100%"
|
||||
stopColor={theme.palette.accent.main}
|
||||
stopOpacity={0}
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<XAxis
|
||||
stroke={theme.palette.primary.lowContrast}
|
||||
dataKey="_id"
|
||||
tick={<CustomTick />}
|
||||
minTickGap={0}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
height={20}
|
||||
/>
|
||||
<Tooltip
|
||||
cursor={{ stroke: theme.palette.primary.lowContrast }}
|
||||
content={<CustomToolTip />}
|
||||
wrapperStyle={{ pointerEvents: "none" }}
|
||||
/>
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="avgResponseTime"
|
||||
stroke={theme.palette.primary.accent}
|
||||
fill="url(#colorUv)"
|
||||
strokeWidth={isHovered ? 2.5 : 1.5}
|
||||
activeDot={{ stroke: theme.palette.background.main, r: 5 }}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</ChartBox>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Stack, Skeleton } from "@mui/material";
|
||||
|
||||
export const SkeletonLayout = () => {
|
||||
return (
|
||||
<Stack>
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
height={"90vh"}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default SkeletonLayout;
|
||||
@@ -68,6 +68,9 @@ const useSubscribeToDetails = ({ monitorId, dateRange }) => {
|
||||
dateRange: dateRange,
|
||||
normalize: true,
|
||||
onUpdate: (data) => {
|
||||
if (isLoading === true) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
if (networkError === true) {
|
||||
setNetworkError(false);
|
||||
}
|
||||
@@ -87,6 +90,7 @@ const useSubscribeToDetails = ({ monitorId, dateRange }) => {
|
||||
setRetryCount(0); // Reset retry count on successful connection
|
||||
},
|
||||
onError: () => {
|
||||
setIsLoading(false);
|
||||
setNetworkError(true);
|
||||
setConnectionStatus("down");
|
||||
},
|
||||
@@ -94,8 +98,6 @@ const useSubscribeToDetails = ({ monitorId, dateRange }) => {
|
||||
return cleanup;
|
||||
} catch (error) {
|
||||
setNetworkError(true);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [
|
||||
authToken,
|
||||
@@ -105,6 +107,7 @@ const useSubscribeToDetails = ({ monitorId, dateRange }) => {
|
||||
setConnectionStatus,
|
||||
networkError,
|
||||
devices,
|
||||
isLoading,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -11,7 +11,7 @@ import MonitorHeader from "./Components/MonitorHeader";
|
||||
import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader";
|
||||
import GenericFallback from "../../../Components/GenericFallback";
|
||||
import MonitorCreateHeader from "../../../Components/MonitorCreateHeader";
|
||||
|
||||
import SkeletonLayout from "./Components/Skeleton";
|
||||
//Utils
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useState } from "react";
|
||||
@@ -35,6 +35,10 @@ const DistributedUptimeDetails = () => {
|
||||
{ name: "Details", path: `/distributed-uptime/${monitorId}` },
|
||||
];
|
||||
|
||||
if (isLoading) {
|
||||
return <SkeletonLayout />;
|
||||
}
|
||||
|
||||
if (networkError) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Stack, Skeleton } from "@mui/material";
|
||||
|
||||
export const SkeletonLayout = () => {
|
||||
return (
|
||||
<Stack>
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
height={"90vh"}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default SkeletonLayout;
|
||||
@@ -32,6 +32,10 @@ const useSubscribeToMonitors = () => {
|
||||
field: null,
|
||||
order: null,
|
||||
onUpdate: (data) => {
|
||||
if (isLoading === true) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
const res = data.monitors;
|
||||
const { monitors, filteredMonitors, summary } = res;
|
||||
const mappedMonitors = filteredMonitors.map((monitor) =>
|
||||
@@ -41,6 +45,9 @@ const useSubscribeToMonitors = () => {
|
||||
setMonitorsSummary(summary);
|
||||
setFilteredMonitors(mappedMonitors);
|
||||
},
|
||||
onError: () => {
|
||||
setIsLoading(false);
|
||||
},
|
||||
});
|
||||
|
||||
return cleanup;
|
||||
@@ -49,8 +56,6 @@ const useSubscribeToMonitors = () => {
|
||||
body: error.message,
|
||||
});
|
||||
setNetworkError(true);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [authToken, user, getMonitorWithPercentage, theme]);
|
||||
return [isLoading, networkError, monitors, monitorsSummary, filteredMonitors];
|
||||
|
||||
@@ -10,6 +10,7 @@ import GenericFallback from "../../../Components/GenericFallback";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
|
||||
import { useSubscribeToMonitors } from "./Hooks/useSubscribeToMonitors";
|
||||
import SkeletonLayout from "./Components/Skeleton";
|
||||
// Constants
|
||||
const BREADCRUMBS = [{ name: `Distributed Uptime`, path: "/distributed-uptime" }];
|
||||
|
||||
@@ -21,6 +22,10 @@ const DistributedUptimeMonitors = () => {
|
||||
const [isLoading, networkError, monitors, monitorsSummary, filteredMonitors] =
|
||||
useSubscribeToMonitors();
|
||||
|
||||
if (isLoading) {
|
||||
return <SkeletonLayout />;
|
||||
}
|
||||
|
||||
if (networkError) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Stack, Skeleton } from "@mui/material";
|
||||
|
||||
export const SkeletonLayout = () => {
|
||||
return (
|
||||
<Stack>
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
height={"90vh"}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default SkeletonLayout;
|
||||
@@ -11,6 +11,7 @@ import ControlsHeader from "../../StatusPage/Status/Components/ControlsHeader";
|
||||
import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader";
|
||||
import GenericFallback from "../../../Components/GenericFallback";
|
||||
import Dialog from "../../../Components/Dialog";
|
||||
import SkeletonLayout from "./Components/Skeleton";
|
||||
//Utils
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useState } from "react";
|
||||
@@ -46,6 +47,7 @@ const DistributedUptimeStatus = () => {
|
||||
// Constants
|
||||
const BREADCRUMBS = [
|
||||
{ name: "Distributed Uptime", path: "/distributed-uptime" },
|
||||
{ name: "details", path: `/distributed-uptime/${monitorId}` },
|
||||
{ name: "status", path: `` },
|
||||
];
|
||||
|
||||
@@ -59,7 +61,11 @@ const DistributedUptimeStatus = () => {
|
||||
};
|
||||
}
|
||||
|
||||
if (networkError) {
|
||||
if (isLoading || statusPageIsLoading) {
|
||||
return <SkeletonLayout />;
|
||||
}
|
||||
|
||||
if (networkError || statusPageNetworkError) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
<Typography
|
||||
@@ -74,7 +80,11 @@ const DistributedUptimeStatus = () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof monitor === "undefined" || monitor.totalChecks === 0) {
|
||||
if (
|
||||
typeof statusPage === "undefined" ||
|
||||
typeof monitor === "undefined" ||
|
||||
monitor.totalChecks === 0
|
||||
) {
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
@@ -93,6 +103,7 @@ const DistributedUptimeStatus = () => {
|
||||
>
|
||||
{!isPublic && <Breadcrumbs list={BREADCRUMBS} />}
|
||||
<ControlsHeader
|
||||
shouldShow={!isPublic}
|
||||
statusPage={statusPage}
|
||||
isDeleting={isDeleting}
|
||||
isDeleteOpen={isDeleteOpen}
|
||||
|
||||
Reference in New Issue
Block a user