check details
This commit is contained in:
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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export { TimingsChart } from "./TimingsChart";
|
||||
export { CheckDetailsCard } from "./CheckDetailsCard";
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user