response time chart

This commit is contained in:
Alex Holliday committed 2026-01-26 02:06:37 +00:00
1 parent 3208cc9e2c
commit e354fa7ebf
33 files changed
+338 -37

No files matched your search

@@ -1,6 +1,6 @@
import { useTheme } from "@emotion/react";
import { Box, Stack, Tooltip, Typography } from "@mui/material";
import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
import { useEffect, useState } from "react";
import "./index.css";
import { useSelector } from "react-redux";
@@ -5,7 +5,7 @@ import { Stack, Box, Tooltip, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useState, useEffect } from "react";
import PropTypes from "prop-types";
import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
import { useSelector } from "react-redux";
const PlaceholderCheck = ({ daysToShow }) => {
@@ -12,7 +12,7 @@ import { Box, Stack, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useMemo, useState } from "react";
import { useSelector } from "react-redux";
import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
import {
tooltipDateFormatLookup,
tickDateFormatLookup,
@@ -1,6 +1,6 @@
import { useTheme } from "@emotion/react";
import { Box, Stack, Tooltip, Typography } from "@mui/material";
import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
import { useEffect, useState, forwardRef } from "react";
import { useSelector } from "react-redux";
import PropTypes from "prop-types";
@@ -2,7 +2,7 @@ import PropTypes from "prop-types";
import { useSelector } from "react-redux";
import { useTheme } from "@mui/material";
import { Text } from "recharts";
import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
import { Box, Stack, Typography } from "@mui/material";
import { tickDateFormatLookup, tooltipDateFormatLookup } from "./chartUtilFunctions.js";
/**
@@ -4,7 +4,7 @@ import PulseDot from "../Animated/PulseDot.jsx";
import Typography from "@mui/material/Typography";
import Dot from "../Dot/index.jsx";
// Utils
import { formatDurationRounded } from "../../../Utils/timeUtils.js";
import { formatDurationRounded } from "../../../Utils/timeUtilsLegacy.js";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js";
@@ -3,7 +3,7 @@ import { StatBox } from "@/Components/v2/design-elements";
import { useTheme } from "@mui/material/styles";
import type { MonitorStats, Monitor } from "@/Types/Monitor";
import { getHumanReadableDuration } from "@/Utils/timeUtils.js";
import { getHumanReadableDuration } from "@/Utils/timeUtilsLegacy.js";
import { getStatusPalette } from "@/Utils/MonitorUtils";
import { useTranslation } from "react-i18next";
@@ -0,0 +1,164 @@
import { BaseChart, BaseBox } from "@/Components/v2/design-elements";
import { Clock } from "lucide-react";
import {
AreaChart,
Area,
XAxis,
Tooltip,
CartesianGrid,
ResponsiveContainer,
Text,
} from "recharts";
import Typography from "@mui/material/Typography";
import { useSelector } from "react-redux";
import {
formatDateWithTz,
tickDateFormatLookup,
tooltipDateFormatLookup,
} from "@/Utils/TimeUtils";
import { useTheme } from "@mui/material/styles";
import type { GroupedCheck } from "@/Types/Check";
import type { RootState } from "@/Types/state";
type XTickProps = {
x: number;
y: number;
payload: { value: any };
range: string;
};
export const XTick = ({ x, y, payload, range }: XTickProps) => {
const format = tickDateFormatLookup(range);
const theme = useTheme();
const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
return (
<Text
x={x}
y={y + 10}
textAnchor="middle"
fill={theme.palette.text.secondary}
fontSize={11}
fontWeight={400}
>
{formatDateWithTz(payload?.value, format, uiTimezone)}
</Text>
);
};
type ResponseTimeToolTipProps = {
active?: boolean | undefined;
payload?: any[];
label?: string | number;
range: string;
theme: any;
uiTimezone: string;
};
const ResponseTimeToolTip = ({
active,
payload,
label,
range,
theme,
uiTimezone,
}: ResponseTimeToolTipProps) => {
if (!label) return null;
if (!payload) return null;
if (!active) return null;
const format = tooltipDateFormatLookup(range);
const responseTime = Math.floor(payload?.[0]?.payload?.avgResponseTime || 0);
return (
<BaseBox sx={{ py: theme.spacing(2), px: theme.spacing(4) }}>
<Typography>{formatDateWithTz(String(label), format, uiTimezone)}</Typography>
<Typography>Response time: {responseTime} ms</Typography>
</BaseBox>
);
};
export const HistogramDetails = ({
checks,
range,
}: {
checks: GroupedCheck[];
range: string;
}) => {
console.log(range);
const theme = useTheme();
const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
return (
<BaseChart
icon={
<Clock
size={20}
strokeWidth={1.5}
/>
}
title="Response times"
>
<ResponsiveContainer
width="100%"
height={300}
>
<AreaChart data={checks?.slice()}>
<CartesianGrid
stroke={theme.palette.divider}
strokeWidth={1}
strokeOpacity={1}
fill="transparent"
vertical={false}
/>
<defs>
<linearGradient
id="colorUv"
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="0%"
stopColor={theme.palette.primary.main}
stopOpacity={0.8}
/>
<stop
offset="100%"
stopColor={theme.palette.primary.light}
stopOpacity={0}
/>
</linearGradient>
</defs>
<XAxis
axisLine={false}
tickLine={false}
dataKey="_id"
tick={(props) => (
<XTick
{...props}
range={range}
/>
)}
/>
<Tooltip
content={(props) => (
<ResponseTimeToolTip
{...props}
range={range}
theme={theme}
uiTimezone={uiTimezone}
/>
)}
/>
<Area
type="monotone"
dataKey="avgResponseTime"
stroke={theme.palette.primary.main}
fill="url(#colorUv)"
/>
</AreaChart>
</ResponsiveContainer>
</BaseChart>
);
};
@@ -3,7 +3,7 @@ import Typography from "@mui/material/Typography";
import { BaseChart } from "@/Components/v2/design-elements";
import { useTheme } from "@mui/material/styles";
import { useSelector } from "react-redux";
import { formatDateWithTz } from "@/Utils/timeUtils";
import { formatDateWithTz } from "@/Utils/timeUtilsLegacy";
import { useTranslation } from "react-i18next";
import { ResponsiveContainer, BarChart, XAxis, Bar, Cell, Tooltip } from "recharts";
import { getResponseTimeColor } from "@/Utils/MonitorUtils";
@@ -0,0 +1,91 @@
import { BaseChart } from "@/Components/v2/design-elements";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { Gauge } from "lucide-react";
import { Cell, RadialBarChart, RadialBar, ResponsiveContainer } from "recharts";
import { useTranslation } from "react-i18next";
import { getResponseTimeColor } from "@/Utils/MonitorUtils";
import { useTheme } from "@mui/material/styles";
export const RadialAvgResponse = ({ avg, max }: { avg: number; max: number }) => {
const { t } = useTranslation();
const theme = useTheme();
const chartData = [
{ name: "max", value: max - avg, color: "transparent" },
{ name: "avg", value: avg, color: "red" },
];
const palette = getResponseTimeColor(avg);
const msg: Record<string, string> = {
success: "Excellent",
warning: "Average",
danger: "Poor",
};
return (
<BaseChart
icon={
<Gauge
size={20}
strokeWidth={1.5}
/>
}
title={t("common.charts.labels.averageResponseTime")}
>
<Stack
height="100%"
position={"relative"}
justifyContent={"flex-end"}
>
<ResponsiveContainer
width="100%"
height={155}
>
<RadialBarChart
cy="100%"
data={chartData}
startAngle={180}
endAngle={0}
innerRadius={"120%"}
outerRadius={"200%"}
>
<RadialBar
dataKey="value"
background={{ fill: theme.palette[palette].light }}
>
<Cell visibility={"hidden"} />
<Cell fill={theme.palette[palette].main} />
</RadialBar>
</RadialBarChart>
</ResponsiveContainer>
<Stack
direction={"row"}
justifyContent={"space-between"}
>
<Typography variant="body2">{t("common.charts.labels.low")}</Typography>
<Typography variant="body2">{t("common.charts.labels.high")}</Typography>
</Stack>
<Stack
position="absolute"
top={"50%"}
right={"50%"}
sx={{
transform: "translate(50%, 0%)",
}}
>
<Typography
variant="h6"
textAlign={"center"}
>
{msg[palette]}
</Typography>
<Typography
variant="h6"
textAlign={"center"}
>{`${avg?.toFixed()}ms`}</Typography>
</Stack>
</Stack>
</BaseChart>
);
};
@@ -1,3 +1,5 @@
export * from "./ControlsFilter";
export * from "./MonitorStatBoxes";
export * from "./charts/HistogramStatus";
export * from "./charts/RadialAvgResponse";
export * from "./charts/HistogramDetails";
@@ -13,7 +13,7 @@ import GenericFallback from "@/Components/v1/GenericFallback/index.jsx";
import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx";
//Utils
import { formatDateWithTz } from "@/Utils/timeUtils.js";
import { formatDateWithTz } from "@/Utils/timeUtilsLegacy.js";
import { useSelector } from "react-redux";
import { useState } from "react";
import PropTypes from "prop-types";
@@ -9,7 +9,7 @@ import ResolveIncidentDialog from "../ResolveIncidentDialog/index.jsx";
import { useEffect, useState } from "react";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
import { formatDateWithTz } from "@/Utils/timeUtils";
import { formatDateWithTz } from "@/Utils/timeUtilsLegacy.js";
import { useSelector } from "react-redux";
import PropTypes from "prop-types";
@@ -9,7 +9,7 @@ import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx";
import IncidentActionsMenu from "./IncidentActionsMenu.jsx";
//Utils
import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
import { useSelector } from "react-redux";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
@@ -2,7 +2,7 @@ import PropTypes from "prop-types";
import { Box, Stack, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import { StatusLabel } from "@/Components/v1/Label/index.jsx";
import { getHumanReadableDuration } from "@/Utils/timeUtils.js";
import { getHumanReadableDuration } from "@/Utils/timeUtilsLegacy.js";
import Icon from "@/Components/v1/Icon";
import { useTranslation } from "react-i18next";
import useGetIncidentsDuration from "../../hooks/useGetIncidentsDuration";
@@ -1,4 +1,4 @@
import { getHumanReadableDuration } from "@/Utils/timeUtils.js";
import { getHumanReadableDuration } from "@/Utils/timeUtilsLegacy.js";
const useGetIncidentsDuration = (incident, isActive) => {
if (!incident?.startTime) {
return "-";
@@ -7,7 +7,7 @@ import CircularProgress from "@mui/material/CircularProgress";
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
import { getHumanReadableDuration } from "../../../../../Utils/timeUtils.js";
import { getHumanReadableDuration } from "../../../../../Utils/timeUtilsLegacy.js";
import { formatBytes } from "../../utils/utils.js";
import { useTranslation } from "react-i18next";
+1 -1
View File
@@ -7,7 +7,7 @@ import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
import { useFetchDiagnostics } from "../../../Hooks/logHooks.js";
import { getHumanReadableDuration } from "../../../Utils/timeUtils.js";
import { getHumanReadableDuration } from "../../../Utils/timeUtilsLegacy.js";
import { formatBytes, getPercentage } from "./utils/utils.js";
const Diagnostics = () => {
@@ -6,7 +6,7 @@ import {
MS_PER_HOUR,
MS_PER_DAY,
MS_PER_WEEK,
} from "../../../../Utils/timeUtils.js";
} from "../../../../Utils/timeUtilsLegacy.js";
const useMaintenanceActions = () => {
const MS_LOOKUP = {
@@ -6,7 +6,7 @@ import {
MS_PER_HOUR,
MS_PER_DAY,
MS_PER_WEEK,
} from "../../../../Utils/timeUtils.js";
} from "../../../../Utils/timeUtilsLegacy.js";
const useMaintenanceData = () => {
const REVERSE_REPEAT_LOOKUP = {
0: "none",
@@ -6,7 +6,7 @@ import Icon from "@/Components/v1/Icon";
import ActionsMenu from "./ActionsMenu/index.jsx";
import { memo } from "react";
import { useDispatch, useSelector } from "react-redux";
import { formatDurationRounded } from "../../../Utils/timeUtils.js";
import { formatDurationRounded } from "../../../Utils/timeUtilsLegacy.js";
import { StatusLabel } from "@/Components/v1/Label/index.jsx";
import { setRowsPerPage } from "../../../Features/UI/uiSlice.js";
import { useTranslation } from "react-i18next";
@@ -11,7 +11,7 @@ import {
import { useTheme } from "@emotion/react";
import { useMemo, useState } from "react";
import { Box, Stack, Typography } from "@mui/material";
import { formatDateWithTz } from "../../../../../Utils/timeUtils.js";
import { formatDateWithTz } from "../../../../../Utils/timeUtilsLegacy.js";
import { useSelector } from "react-redux";
const config = {
@@ -1,7 +1,7 @@
import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx";
import StatBox from "@/Components/v1/StatBox/index.jsx";
import { Typography } from "@mui/material";
import { getHumanReadableDuration } from "../../../../../Utils/timeUtils.js";
import { getHumanReadableDuration } from "../../../../../Utils/timeUtilsLegacy.js";
import { useTranslation } from "react-i18next";
const PageSpeedStatusBoxes = ({ shouldRender, monitorStats }) => {
@@ -6,7 +6,7 @@ import { useNavigate } from "react-router-dom";
import { useTheme } from "@emotion/react";
import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip } from "recharts";
import { useSelector } from "react-redux";
import { formatDateWithTz, formatDurationSplit } from "@/Utils/timeUtils.js";
import { formatDateWithTz, formatDurationSplit } from "@/Utils/timeUtilsLegacy.js";
import { useMonitorUtils } from "@/Hooks/useMonitorUtils.js";
import { useState } from "react";
import { useTranslation } from "react-i18next";
@@ -7,7 +7,7 @@ import DownBarChart from "../Charts/DownBarChart.jsx";
import ResponseGaugeChart from "../Charts/ResponseGaugeChart.jsx";
import SkeletonLayout from "./skeleton.jsx";
// Utils
import { formatDateWithTz } from "../../../../../Utils/timeUtils.js";
import { formatDateWithTz } from "../../../../../Utils/timeUtilsLegacy.js";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { useState } from "react";
@@ -1,6 +1,6 @@
import PropTypes from "prop-types";
import { useSelector } from "react-redux";
import { formatDateWithTz } from "../../../../../Utils/timeUtils.js";
import { formatDateWithTz } from "../../../../../Utils/timeUtilsLegacy.js";
const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint, type }) => {
const uiTimezone = useSelector((state) => state.ui.timezone);
@@ -5,7 +5,7 @@ import Table from "@/Components/v1/Table/index.jsx";
import TablePagination from "@/Components/v1/Table/TablePagination/index.jsx";
import { StatusLabel } from "@/Components/v1/Label/index.jsx";
import { useTranslation } from "react-i18next";
import { formatDateWithTz } from "../../../../../Utils/timeUtils.js";
import { formatDateWithTz } from "../../../../../Utils/timeUtilsLegacy.js";
import SkeletonLayout from "./skeleton.jsx";
import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
@@ -2,7 +2,7 @@ import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx";
import StatBox from "@/Components/v1/StatBox/index.jsx";
import PropTypes from "prop-types";
import { getHumanReadableDuration } from "../../../../../Utils/timeUtils.js";
import { getHumanReadableDuration } from "../../../../../Utils/timeUtilsLegacy.js";
import { useTheme } from "@mui/material/styles";
import { Typography } from "@mui/material";
import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils.js";
@@ -1,7 +1,7 @@
import { logger } from "../../../../Utils/Logger.js";
import { useEffect, useState } from "react";
import { networkService } from "../../../../main.jsx";
import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
const useCertificateFetch = ({
monitor,
+14 -2
View File
@@ -1,7 +1,11 @@
import { BasePage } from "@/Components/v2/design-elements";
import { HeaderMonitorControls, HeaderTimeRange } from "@/Components/v2/common";
import Stack from "@mui/material/Stack";
import { HistogramStatus } from "@/Components/v2/monitors";
import {
HistogramStatus,
RadialAvgResponse,
HistogramDetails,
} from "@/Components/v2/monitors";
import { TrendingUp, AlertTriangle } from "lucide-react";
import { useTheme } from "@mui/material/styles";
@@ -14,7 +18,7 @@ import type { MonitorDetailsResponse } from "@/Types/Monitor";
import type { ChecksResponse } from "@/Types/Check";
import type { RootState } from "@/Types/state";
import { MonitorStatBoxes } from "@/Components/v2/monitors";
import { formatDateWithTz } from "@/Utils/timeUtils";
import { formatDateWithTz } from "@/Utils/timeUtilsLegacy";
import { t } from "i18next";
const certificateDateFormat = "MMM D, YYYY h A";
@@ -135,7 +139,15 @@ const UptimeDetailsPage = () => {
checks={monitorData?.groupedDownChecks}
range={dateRange}
/>
<RadialAvgResponse
avg={monitorStats?.avgResponseTime || 0}
max={500}
/>
</Stack>
<HistogramDetails
checks={monitorData?.groupedChecks || []}
range={dateRange}
/>
</BasePage>
);
};
+8 -8
View File
@@ -25,10 +25,10 @@ export const formatDateWithTz = (timestamp: string, format: string, timezone: st
export const tickDateFormatLookup = (range: string) => {
const tickFormatLookup: Record<string, string> = {
"1h": "h:mm A",
"24h": "h:mm A",
"7d": "MM/D, h:mm A",
"30d": "ddd. M/D",
recent: "h:mm A",
day: "h:mm A",
week: "MM/D, h:mm A",
month: "ddd. M/D",
};
const format = tickFormatLookup[range];
if (format === undefined) {
@@ -39,10 +39,10 @@ export const tickDateFormatLookup = (range: string) => {
export const tooltipDateFormatLookup = (range: string) => {
const dateFormatLookup: Record<string, string> = {
"1h": "ddd. MMMM D, YYYY, hh:mm A",
"24h": "ddd. MMMM D, YYYY, hh:mm A",
"7d": "ddd. MMMM D, YYYY, hh:mm A",
"30d": "ddd. MMMM D, YYYY",
recent: "ddd. MMMM D, YYYY, hh:mm A",
day: "ddd. MMMM D, YYYY, hh:mm A",
week: "ddd. MMMM D, YYYY, hh:mm A",
month: "ddd. MMMM D, YYYY",
};
const format = dateFormatLookup[range];
if (format === undefined) {
@@ -135,3 +135,33 @@ export const formatDateWithTz = (timestamp, format, timezone) => {
const formattedDate = dayjs(timestamp).tz(timezone).format(format);
return formattedDate;
};
export const tickDateFormatLookup = (range) => {
switch (range) {
case "recent":
return "h:mm A";
case "day":
return "h A";
case "week":
return "MMM D";
case "month":
return "MMM D";
default:
return "MMM D, h A";
}
};
export const tooltipDateFormatLookup = (range) => {
switch (range) {
case "recent":
return "MMM D, h:mm A";
case "day":
return "MMM D, h:mm A";
case "week":
return "ddd, MMM D";
case "month":
return "ddd, MMM D";
default:
return "MMM D, h:mm A";
}
};
+3 -1
View File
@@ -29,7 +29,9 @@
"labels": {
"uptime": "Uptime",
"downtime": "Downtime",
"averageRepsonseTime": "Average response time"
"averageResponseTime": "Average response time",
"low": "Low",
"high": "High"
}
}
},