uptime
This commit is contained in:
1 parent
c2dece862c
commit
a0a8ceb0db
27 files changed
+2326
-94
No files matched your search
@@ -0,0 +1,140 @@
|
||||
import Box from "@mui/material/Box";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import type { Check } from "@/Types/Check";
|
||||
import { getResponseColor } from "@/Utils/DataUtils";
|
||||
import { HeatmapResponseTimeTooltip } from "./HeatmapResponseTimeTooltip";
|
||||
import type { SxProps } from "@mui/material/styles";
|
||||
import type { ResponsiveStyleValue } from "@mui/system";
|
||||
|
||||
interface PlaceholderCheck {
|
||||
status: "placeholder";
|
||||
responseTime: 0;
|
||||
createdAt: "";
|
||||
}
|
||||
|
||||
interface HeatmapResponseTimeProps {
|
||||
checks: Check[];
|
||||
gap?: ResponsiveStyleValue<number | string>;
|
||||
availabilityCellSx?: SxProps;
|
||||
responseCellSx?: SxProps;
|
||||
}
|
||||
|
||||
export const HeatmapResponseTime = ({
|
||||
checks,
|
||||
gap,
|
||||
availabilityCellSx,
|
||||
responseCellSx,
|
||||
}: HeatmapResponseTimeProps) => {
|
||||
const theme = useTheme();
|
||||
if (!gap) {
|
||||
gap = theme.spacing(0.5);
|
||||
}
|
||||
|
||||
if (!checks || checks.length === 0) return null;
|
||||
|
||||
const latestChecks = checks.slice(-25).reverse();
|
||||
|
||||
let data: Array<Check | PlaceholderCheck>;
|
||||
if (latestChecks.length !== 25) {
|
||||
const placeholders = Array(25 - latestChecks.length).fill({
|
||||
status: "placeholder" as const,
|
||||
});
|
||||
data = [...latestChecks, ...placeholders];
|
||||
} else {
|
||||
data = latestChecks;
|
||||
}
|
||||
return (
|
||||
<Box sx={{ width: "100%" }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(25, 1fr)",
|
||||
gap: gap,
|
||||
alignItems: "stretch",
|
||||
}}
|
||||
>
|
||||
{data.map((check, index) => {
|
||||
const statusBg =
|
||||
check.status === "placeholder"
|
||||
? theme.palette.action.hover
|
||||
: check.status === true
|
||||
? theme.palette.success.light
|
||||
: theme.palette.error.light;
|
||||
const respBg =
|
||||
check.status === "placeholder"
|
||||
? theme.palette.action.hover
|
||||
: getResponseColor(check.responseTime || 0, {
|
||||
start: theme.palette.success.main,
|
||||
mid: theme.palette.warning.main,
|
||||
end: theme.palette.error.main,
|
||||
});
|
||||
const statusBorder =
|
||||
check.status === "placeholder"
|
||||
? theme.palette.divider
|
||||
: check.status === true
|
||||
? theme.palette.success.main
|
||||
: theme.palette.error.main;
|
||||
|
||||
return (
|
||||
<HeatmapResponseTimeTooltip
|
||||
key={`${check}-${index}`}
|
||||
check={check}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateRows: "auto auto",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: theme.spacing(2),
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
aspectRatio: "10",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
inset: 0,
|
||||
bgcolor: statusBg,
|
||||
borderRadius: theme.spacing(0.5),
|
||||
...availabilityCellSx,
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
inset: 0,
|
||||
pointerEvents: "none",
|
||||
borderRadius: theme.spacing(0.5),
|
||||
boxShadow: `inset 0 0 0 2px ${statusBorder}`,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
aspectRatio: "1 / 1",
|
||||
bgcolor: respBg,
|
||||
borderRadius: theme.spacing(0.5),
|
||||
...responseCellSx,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</HeatmapResponseTimeTooltip>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { formatDateWithTz } from "@/Utils/TimeUtils";
|
||||
import type { Check } from "@/Types/Check";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useSelector } from "react-redux";
|
||||
import type { RootState } from "@/Types/state";
|
||||
|
||||
type HeatmapCheck = Check | { status: "placeholder"; responseTime: 0; createdAt: "" };
|
||||
|
||||
export const HeatmapResponseTimeTooltip = ({
|
||||
children,
|
||||
check,
|
||||
}: {
|
||||
children: React.ReactElement;
|
||||
check: HeatmapCheck;
|
||||
}) => {
|
||||
const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
|
||||
const theme = useTheme();
|
||||
|
||||
const getColor = (status: boolean) => {
|
||||
if (status === true) return theme.palette.success.light;
|
||||
if (status === false) return theme.palette.error.light;
|
||||
};
|
||||
|
||||
if (check.status === "placeholder") {
|
||||
return children;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
slotProps={{
|
||||
tooltip: {
|
||||
sx: {
|
||||
backgroundColor: "transparent",
|
||||
color: "inherit",
|
||||
boxShadow: "none",
|
||||
},
|
||||
},
|
||||
}}
|
||||
title={
|
||||
<Stack
|
||||
sx={{
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
border: 1,
|
||||
borderColor: theme.palette.divider,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
p: theme.spacing(4),
|
||||
}}
|
||||
>
|
||||
<Typography>
|
||||
{formatDateWithTz(check?.createdAt, "ddd, MMMM D, YYYY, HH:mm A", uiTimezone)}
|
||||
</Typography>
|
||||
{check?.originalResponseTime && (
|
||||
<Typography>
|
||||
Response Time: {check.originalResponseTime.toFixed()} ms
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
<Typography textTransform={"capitalize"}>
|
||||
Status:{" "}
|
||||
<span style={{ color: getColor(check?.status) }}>{check?.status}</span>
|
||||
</Typography>
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
import Box from "@mui/material/Box";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import type { ResponsiveStyleValue } from "@mui/system";
|
||||
import type { Check } from "@/Types/Check";
|
||||
import { HeatmapResponseTimeTooltip } from "./HeatmapResponseTimeTooltip";
|
||||
|
||||
interface HistogramResponseTimeProps {
|
||||
checks: Check[];
|
||||
height?: ResponsiveStyleValue<number | string>;
|
||||
gap?: ResponsiveStyleValue<number | string>;
|
||||
}
|
||||
|
||||
const DEFAULT_HEIGHT = 50;
|
||||
|
||||
export const HistogramResponseTime = ({
|
||||
checks,
|
||||
height = DEFAULT_HEIGHT,
|
||||
gap,
|
||||
}: HistogramResponseTimeProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
if (!Array.isArray(checks) || checks.length === 0) return null;
|
||||
|
||||
let data = Array<any>();
|
||||
|
||||
if (!checks || checks.length === 0) {
|
||||
data = [];
|
||||
}
|
||||
if (checks.length !== 25) {
|
||||
const placeholders = Array(25 - checks.length).fill({
|
||||
status: "placeholder",
|
||||
});
|
||||
data = [...checks, ...placeholders];
|
||||
} else {
|
||||
data = checks;
|
||||
}
|
||||
const chartHeight = typeof height === "number" ? `${height}px` : height;
|
||||
const gridGap = gap ?? theme.spacing(0.5);
|
||||
return (
|
||||
<Box sx={{ width: "100%" }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(25, 1fr)",
|
||||
gap: gridGap,
|
||||
alignItems: "end",
|
||||
height: chartHeight,
|
||||
}}
|
||||
>
|
||||
{data.map((check, index) => {
|
||||
const isPlaceholder = (check as any).status === "placeholder";
|
||||
const heightPct = `${Math.max(0, Math.min(100, (check as any).responseTime ?? 0))}%`;
|
||||
const barColor =
|
||||
check.status === true
|
||||
? theme.palette.success.light
|
||||
: theme.palette.error.light;
|
||||
const bar = (
|
||||
<Box
|
||||
sx={{
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
borderRadius: theme.spacing(1),
|
||||
bgcolor: theme.palette.action.hover,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: isPlaceholder ? 0 : heightPct,
|
||||
bgcolor: barColor,
|
||||
transition: "height 500ms cubic-bezier(0.4, 0, 0.2, 1)",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
return isPlaceholder ? (
|
||||
<Box
|
||||
key={index}
|
||||
sx={{ height: "100%" }}
|
||||
>
|
||||
{bar}
|
||||
</Box>
|
||||
) : (
|
||||
<HeatmapResponseTimeTooltip
|
||||
key={index}
|
||||
check={check}
|
||||
>
|
||||
{bar}
|
||||
</HeatmapResponseTimeTooltip>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./HistogramResponseTime";
|
||||
export * from "./HeatmapResponseTime";
|
||||
export * from "./HeatmapResponseTimeTooltip";
|
||||
Reference in new issue
Block a user