diff --git a/client/src/components/checks/CheckDetailsCard.tsx b/client/src/components/checks/CheckDetailsCard.tsx
new file mode 100644
index 000000000..79e624cdf
--- /dev/null
+++ b/client/src/components/checks/CheckDetailsCard.tsx
@@ -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 (
+
+ Check details
+
+
+
+
+ Status
+
+
+
+
+
+
+
+
+
+
+
+ HTTP Code
+
+
+
+
+
+
+
+
+
+ Type
+
+
+
+ {check.metadata?.type || "-"}
+
+
+
+ Total request time
+
+
+
+ {Math.round(totalMs)} ms
+
+
+
+ Checked At
+
+
+
+
+ {formatDateWithTz(
+ check.createdAt,
+ "ddd, MMM D, YYYY, HH:mm A",
+ uiTimezone
+ )}
+
+
+
+
+ Message
+
+
+
+ {check.message || "-"}
+
+
+
+ );
+};
diff --git a/client/src/components/checks/TimingsChart.tsx b/client/src/components/checks/TimingsChart.tsx
new file mode 100644
index 000000000..935c2abf3
--- /dev/null
+++ b/client/src/components/checks/TimingsChart.tsx
@@ -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 (
+
+ Request timings
+
+ {" "}
+ {`Total request time: ${check.timings.phases.total} ms`}
+
+
+
+
+ {segments.map((entry, index) => (
+ |
+ ))}
+
+ `${Math.round(value as number)} ms`}
+ />
+
+
+
+
+ );
+};
diff --git a/client/src/components/checks/index.tsx b/client/src/components/checks/index.tsx
new file mode 100644
index 000000000..6f62eddbc
--- /dev/null
+++ b/client/src/components/checks/index.tsx
@@ -0,0 +1,2 @@
+export { TimingsChart } from "./TimingsChart";
+export { CheckDetailsCard } from "./CheckDetailsCard";
diff --git a/client/src/pages/checks/CheckDetails.tsx b/client/src/pages/checks/CheckDetails.tsx
index ea3630c9c..21b089830 100644
--- a/client/src/pages/checks/CheckDetails.tsx
+++ b/client/src/pages/checks/CheckDetails.tsx
@@ -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>(
`/checks/${id}`
);
- const check = response?.data;
+ const check = response?.data || null;
return (
- {JSON.stringify(check)}
+
+
+
+
+
+
+
+
);
};