diff --git a/client/src/components/checks/CardChecktDetails.tsx b/client/src/components/checks/CardChecktDetails.tsx new file mode 100644 index 000000000..245cb9b20 --- /dev/null +++ b/client/src/components/checks/CardChecktDetails.tsx @@ -0,0 +1,116 @@ +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 { BaseBox } from "@/components/design-elements"; + +import { useAppSelector } from "@/hooks/AppHooks"; +import type { ICheck } from "@/types/check"; +import type { MonitorStatus } from "@/types/monitor"; +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 + + {`Total request time: ${check.timings.phases.total} ms`} + + + + + + Status + + + + + + + + + + + + HTTP Code + + + + + + + + + + Type + + + + + {check.metadata?.type || "-"} + + + + + {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/ChartTiming.tsx b/client/src/components/checks/ChartTiming.tsx new file mode 100644 index 000000000..c272d0074 --- /dev/null +++ b/client/src/components/checks/ChartTiming.tsx @@ -0,0 +1,88 @@ +import type { ICheck, CheckTimingPhases } from "@/types/check"; +import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from "recharts"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import { alpha, useTheme } from "@mui/material/styles"; +import { BaseBox } from "@/components/design-elements"; + +type TimingsChartProps = { check: ICheck | null }; + +export type TimingSegment = { + key: keyof CheckTimingPhases; + label: string; + value: number; + percent: number; +}; + +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) => { + const segments = buildTimingSegments(check?.timings?.phases); + if (!segments.length) return null; + if (!check) return null; + + const theme = useTheme(); + const strokeColor = alpha(theme.palette.success.main, 0.8); + + return ( + + + Request timings + + {`Total request time: ${check.timings.phases.total} ms`} + + + + `${name}: ${Math.round(Number(value) || 0)} ms`} + > + {segments.map((entry) => ( + + ))} + + `${Math.round(value as number)} ms`} + /> + + + + + ); +}; diff --git a/client/src/components/checks/CheckDetailsCard.tsx b/client/src/components/checks/CheckDetailsCard.tsx deleted file mode 100644 index 79e624cdf..000000000 --- a/client/src/components/checks/CheckDetailsCard.tsx +++ /dev/null @@ -1,111 +0,0 @@ -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 deleted file mode 100644 index 935c2abf3..000000000 --- a/client/src/components/checks/TimingsChart.tsx +++ /dev/null @@ -1,97 +0,0 @@ -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 index 6f62eddbc..e594e2429 100644 --- a/client/src/components/checks/index.tsx +++ b/client/src/components/checks/index.tsx @@ -1,2 +1,2 @@ -export { TimingsChart } from "./TimingsChart"; -export { CheckDetailsCard } from "./CheckDetailsCard"; +export { TimingsChart } from "./ChartTiming"; +export { CheckDetailsCard } from "./CardChecktDetails"; diff --git a/client/src/pages/checks/CheckDetails.tsx b/client/src/pages/checks/CheckDetails.tsx index 21b089830..86e9f51bc 100644 --- a/client/src/pages/checks/CheckDetails.tsx +++ b/client/src/pages/checks/CheckDetails.tsx @@ -1,17 +1,13 @@ -import { BasePage, BaseBox } from "@/components/design-elements"; +import { BasePage } from "@/components/design-elements"; import { TimingsChart, CheckDetailsCard } from "@/components/checks/"; -import Stack from "@mui/material/Stack"; +import Grid from "@mui/material/Grid"; 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}` @@ -20,14 +16,14 @@ const CheckDetails = () => { const check = response?.data || null; return ( - - + + - - + + - - + + ); };