check details

This commit is contained in:
Alex Holliday committed 2025-12-22 06:39:00 +00:00
1 parent 7681edbf49
commit 6323f70bd2
4 files changed
+227 -3

No files matched your search

@@ -0,0 +1,111 @@
import Stack from "@mui/material/Stack";
import Grid from "@mui/material/Grid";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { StatusLabel, ValueLabel } from "@/components/design-elements";
import type { ICheck } from "@/types/check";
import type { MonitorStatus } from "@/types/monitor";
import { useAppSelector } from "@/hooks/AppHooks";
import { formatDateWithTz } from "@/utils/TimeUtils";
type CheckDetailsCardProps = {
check: ICheck;
};
export const CheckDetailsCard = ({ check }: CheckDetailsCardProps) => {
const uiTimezone = useAppSelector((state: any) => state.ui.timezone);
const phases = check?.timings?.phases;
const phaseKeys = [
"wait",
"dns",
"tcp",
"tls",
"request",
"firstByte",
"download",
] as const;
const totalMs = phases
? phaseKeys.reduce(
(sum, key) => sum + Math.max(0, Number((phases as any)[key] ?? 0)),
0
)
: Number(check.responseTime ?? 0);
return (
<Stack spacing={2} sx={{ width: "100%" }}>
<Typography>Check details</Typography>
<Grid container spacing={2}>
<Grid size={6} sx={{ display: "flex", alignItems: "center" }}>
<Typography variant="body1" color="text.secondary">
Status
</Typography>
</Grid>
<Grid size={6} sx={{ display: "flex", alignItems: "center" }}>
<Box>
<StatusLabel status={check.status as MonitorStatus} />
</Box>
</Grid>
<Grid size={6} sx={{ display: "flex", alignItems: "center" }}>
<Typography variant="body1" color="text.secondary">
HTTP Code
</Typography>
</Grid>
<Grid size={6} sx={{ display: "flex", alignItems: "center" }}>
<Box>
<ValueLabel
value={
check.httpStatusCode < 300
? "positive"
: check.httpStatusCode < 400
? "neutral"
: "negative"
}
text={String(check.httpStatusCode ?? "-")}
/>
</Box>
</Grid>
<Grid size={6}>
<Typography variant="body1" color="text.secondary">
Type
</Typography>
</Grid>
<Grid size={6}>
<Typography variant="body2">{check.metadata?.type || "-"}</Typography>
</Grid>
<Grid size={6}>
<Typography variant="body1" color="text.secondary">
Total request time
</Typography>
</Grid>
<Grid size={6}>
<Typography variant="body2">{Math.round(totalMs)} ms</Typography>
</Grid>
<Grid size={6}>
<Typography variant="body1" color="text.secondary">
Checked At
</Typography>
</Grid>
<Grid size={6}>
<Typography variant="body2">
{formatDateWithTz(
check.createdAt,
"ddd, MMM D, YYYY, HH:mm A",
uiTimezone
)}
</Typography>
</Grid>
<Grid size={6}>
<Typography variant="body1" color="text.secondary">
Message
</Typography>
</Grid>
<Grid size={6}>
<Typography variant="body2">{check.message || "-"}</Typography>
</Grid>
</Grid>
</Stack>
);
};
@@ -0,0 +1,97 @@
import type { ICheck, CheckTimingPhases } from "@/types/check";
import {
PieChart,
Pie,
Cell,
ResponsiveContainer,
Tooltip,
Legend,
} from "recharts";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { alpha } from "@mui/material/styles";
type TimingsChartProps = { check: ICheck | null };
export type TimingSegment = {
key: keyof CheckTimingPhases;
label: string;
value: number; // milliseconds
percent: number; // 0..100
};
const PHASE_ORDER: (keyof CheckTimingPhases)[] = [
"wait",
"dns",
"tcp",
"tls",
"request",
"firstByte",
"download",
];
const buildTimingSegments = (phases?: CheckTimingPhases): TimingSegment[] => {
if (!phases) return [];
const raw = PHASE_ORDER.map((key) => ({
key,
value: Math.max(0, Number((phases as any)[key] ?? 0)),
}));
const total = raw.reduce((sum, p) => sum + p.value, 0);
if (total <= 0) {
return raw.map(({ key, value }) => ({
key,
label: key,
value,
percent: 0,
}));
}
return raw.map(({ key, value }) => ({
key,
label: key,
value,
percent: (value / total) * 100,
}));
};
export const TimingsChart = ({ check }: TimingsChartProps) => {
console.log(check);
const segments = buildTimingSegments(check?.timings?.phases);
if (!segments.length) return null;
if (!check) return null;
const colors = [
alpha("#1DE9B6", 0.8),
alpha("#7C4DFF", 0.8),
alpha("#FFB200", 0.8),
alpha("#00AFFE", 0.8),
alpha("#FF4181", 0.8),
alpha("#59A14F", 0.8),
alpha("#E15759", 0.8),
];
return (
<Stack spacing={2} sx={{ width: "100%", height: 260 }}>
<Typography>Request timings</Typography>
<Typography variant="body2">
{" "}
{`Total request time: ${check.timings.phases.total} ms`}
</Typography>
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie data={segments} dataKey="value" nameKey="label" stroke="none">
{segments.map((entry, index) => (
<Cell
key={`cell-${entry.key}`}
fill={colors[index % colors.length]}
/>
))}
</Pie>
<Tooltip
formatter={(value: any) => `${Math.round(value as number)} ms`}
/>
<Legend />
</PieChart>
</ResponsiveContainer>
</Stack>
);
};
+2
View File
@@ -0,0 +1,2 @@
export { TimingsChart } from "./TimingsChart";
export { CheckDetailsCard } from "./CheckDetailsCard";
+17 -3
View File
@@ -1,19 +1,33 @@
import { BasePage } from "@/components/design-elements";
import { BasePage, BaseBox } from "@/components/design-elements";
import { TimingsChart, CheckDetailsCard } from "@/components/checks/";
import Stack from "@mui/material/Stack";
import { useParams } from "react-router";
import { useGet } from "@/hooks/UseApi";
import type { ICheck } from "@/types/check";
import useMediaQuery from "@mui/material/useMediaQuery";
import type { ApiResponse } from "@/types/api";
import { useTheme } from "@mui/material/styles";
const CheckDetails = () => {
const { id } = useParams();
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
const { response, loading, error } = useGet<ApiResponse<ICheck>>(
`/checks/${id}`
);
const check = response?.data;
const check = response?.data || null;
return (
<BasePage loading={loading} error={error}>
{JSON.stringify(check)}
<Stack direction={isSmall ? "column" : "row"} spacing={4}>
<BaseBox p={4} flex={1}>
<CheckDetailsCard check={check!} />
</BaseBox>
<BaseBox p={4} flex={1}>
<TimingsChart check={check} />
</BaseBox>
</Stack>
</BasePage>
);
};