mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-18 23:48:43 -05:00
refactor details
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
// Components
|
||||
import { Stack, Typography, Box } from "@mui/material";
|
||||
import ChartBox from "../Charts/ChartBox";
|
||||
import UptimeIcon from "../../../../../assets/icons/uptime-icon.svg?react";
|
||||
import IncidentsIcon from "../../../../../assets/icons/incidents.svg?react";
|
||||
import AverageResponseIcon from "../../../../../assets/icons/average-response-icon.svg?react";
|
||||
import UpBarChart from "../Charts/UpBarChart";
|
||||
import DownBarChart from "../Charts/DownBarChart";
|
||||
import ResponseGaugeChart from "../Charts/ResponseGaugeChart";
|
||||
|
||||
// Utils
|
||||
import { formatDateWithTz } from "../../../../../Utils/timeUtils";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@emotion/react";
|
||||
|
||||
const ChartBoxes = ({
|
||||
monitor,
|
||||
dateRange,
|
||||
uiTimezone,
|
||||
dateFormat,
|
||||
hoveredUptimeData,
|
||||
setHoveredUptimeData,
|
||||
hoveredIncidentsData,
|
||||
setHoveredIncidentsData,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
flexWrap="wrap"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<ChartBox
|
||||
icon={<UptimeIcon />}
|
||||
header="Uptime"
|
||||
>
|
||||
<Stack justifyContent="space-between">
|
||||
<Box position="relative">
|
||||
<Typography>Total Checks</Typography>
|
||||
<Typography component="span">
|
||||
{hoveredUptimeData !== null
|
||||
? hoveredUptimeData.totalChecks
|
||||
: (monitor?.groupedUpChecks?.reduce((count, checkGroup) => {
|
||||
return count + checkGroup.totalChecks;
|
||||
}, 0) ?? 0)}
|
||||
</Typography>
|
||||
{hoveredUptimeData !== null && hoveredUptimeData.time !== null && (
|
||||
<Typography
|
||||
component="h5"
|
||||
position="absolute"
|
||||
top="100%"
|
||||
fontSize={11}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{formatDateWithTz(hoveredUptimeData._id, dateFormat, uiTimezone)}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography>
|
||||
{hoveredUptimeData !== null ? "Avg Response Time" : "Uptime Percentage"}
|
||||
</Typography>
|
||||
<Typography component="span">
|
||||
{hoveredUptimeData !== null
|
||||
? Math.floor(hoveredUptimeData?.avgResponseTime ?? 0)
|
||||
: Math.floor(
|
||||
((monitor?.upChecks?.totalChecks ?? 0) /
|
||||
(monitor?.totalChecks ?? 1)) *
|
||||
100
|
||||
)}
|
||||
<Typography component="span">
|
||||
{hoveredUptimeData !== null ? " ms" : " %"}
|
||||
</Typography>
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
<UpBarChart
|
||||
monitor={monitor}
|
||||
type={dateRange}
|
||||
onBarHover={setHoveredUptimeData}
|
||||
/>
|
||||
</ChartBox>
|
||||
<ChartBox
|
||||
icon={<IncidentsIcon />}
|
||||
header="Incidents"
|
||||
>
|
||||
<Box position="relative">
|
||||
<Typography component="span">
|
||||
{hoveredIncidentsData !== null
|
||||
? hoveredIncidentsData.totalChecks
|
||||
: (monitor?.groupedDownChecks?.reduce((count, checkGroup) => {
|
||||
return count + checkGroup.totalChecks;
|
||||
}, 0) ?? 0)}
|
||||
</Typography>
|
||||
{hoveredIncidentsData !== null && hoveredIncidentsData.time !== null && (
|
||||
<Typography
|
||||
component="h5"
|
||||
position="absolute"
|
||||
top="100%"
|
||||
fontSize={11}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{formatDateWithTz(hoveredIncidentsData._id, dateFormat, uiTimezone)}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<DownBarChart
|
||||
monitor={monitor}
|
||||
type={dateRange}
|
||||
onBarHover={setHoveredIncidentsData}
|
||||
/>
|
||||
</ChartBox>
|
||||
<ChartBox
|
||||
icon={<AverageResponseIcon />}
|
||||
header="Average Response Time"
|
||||
>
|
||||
<ResponseGaugeChart avgResponseTime={monitor.avgResponseTime ?? 0} />
|
||||
</ChartBox>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChartBoxes;
|
||||
|
||||
ChartBoxes.propTypes = {
|
||||
monitor: PropTypes.object.isRequired,
|
||||
dateRange: PropTypes.string.isRequired,
|
||||
uiTimezone: PropTypes.string.isRequired,
|
||||
dateFormat: PropTypes.string.isRequired,
|
||||
hoveredUptimeData: PropTypes.object,
|
||||
setHoveredUptimeData: PropTypes.func,
|
||||
hoveredIncidentsData: PropTypes.object,
|
||||
setHoveredIncidentsData: PropTypes.func,
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import IconBox from "../../../../../Components/IconBox";
|
||||
import PropTypes from "prop-types";
|
||||
const ChartBox = ({ children, icon, header }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Stack
|
||||
sx={{
|
||||
justifyContent: "space-between",
|
||||
flex: "1 30%",
|
||||
gap: theme.spacing(8),
|
||||
height: 300,
|
||||
minWidth: 250,
|
||||
padding: theme.spacing(8),
|
||||
border: 1,
|
||||
borderStyle: "solid",
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
borderRadius: 4,
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
"& h2": {
|
||||
color: theme.palette.primary.contrastTextSecondary,
|
||||
fontSize: 15,
|
||||
fontWeight: 500,
|
||||
},
|
||||
"& .MuiBox-root:not(.area-tooltip) p": {
|
||||
color: theme.palette.primary.contrastTextTertiary,
|
||||
fontSize: 13,
|
||||
},
|
||||
"& .MuiBox-root > span": {
|
||||
color: theme.palette.primary.contrastText,
|
||||
fontSize: 20,
|
||||
"& span": {
|
||||
opacity: 0.8,
|
||||
marginLeft: 2,
|
||||
fontSize: 15,
|
||||
},
|
||||
},
|
||||
"& .MuiStack-root": {
|
||||
flexDirection: "row",
|
||||
gap: theme.spacing(6),
|
||||
},
|
||||
"& .MuiStack-root:first-of-type": {
|
||||
alignItems: "center",
|
||||
},
|
||||
"& tspan, & text": {
|
||||
fill: theme.palette.primary.contrastTextTertiary,
|
||||
},
|
||||
"& path": {
|
||||
transition: "fill 300ms ease, stroke-width 400ms ease",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<IconBox>{icon}</IconBox>
|
||||
<Typography component="h2">{header}</Typography>
|
||||
</Stack>
|
||||
|
||||
{children}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChartBox;
|
||||
|
||||
ChartBox.propTypes = {
|
||||
children: PropTypes.node,
|
||||
icon: PropTypes.node.isRequired,
|
||||
header: PropTypes.string.isRequired,
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { useSelector } from "react-redux";
|
||||
import { formatDateWithTz } from "../../../../../Utils/timeUtils";
|
||||
|
||||
const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint, type }) => {
|
||||
const uiTimezone = useSelector((state) => state.ui.timezone);
|
||||
const dateFormat = type === "day" ? "MMM D, h:mm A" : "MMM D";
|
||||
|
||||
return (
|
||||
<>
|
||||
<text
|
||||
x={x}
|
||||
y={height}
|
||||
dy={-3}
|
||||
textAnchor="start"
|
||||
fontSize={11}
|
||||
>
|
||||
{formatDateWithTz(firstDataPoint._id, dateFormat, uiTimezone)}
|
||||
</text>
|
||||
<text
|
||||
x={width}
|
||||
y={height}
|
||||
dy={-3}
|
||||
textAnchor="end"
|
||||
fontSize={11}
|
||||
>
|
||||
{formatDateWithTz(lastDataPoint._id, dateFormat, uiTimezone)}
|
||||
</text>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
CustomLabels.propTypes = {
|
||||
x: PropTypes.number.isRequired,
|
||||
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
firstDataPoint: PropTypes.object,
|
||||
lastDataPoint: PropTypes.object,
|
||||
type: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default CustomLabels;
|
||||
@@ -0,0 +1,92 @@
|
||||
import { memo, useState } from "react";
|
||||
import { useTheme } from "@mui/material";
|
||||
import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts";
|
||||
import PropTypes from "prop-types";
|
||||
import CustomLabels from "./CustomLabels";
|
||||
|
||||
const DownBarChart = memo(({ monitor, type, onBarHover }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [chartHovered, setChartHovered] = useState(false);
|
||||
const [hoveredBarIndex, setHoveredBarIndex] = useState(null);
|
||||
|
||||
return (
|
||||
<ResponsiveContainer
|
||||
width="100%"
|
||||
minWidth={250}
|
||||
height={155}
|
||||
>
|
||||
<BarChart
|
||||
width="100%"
|
||||
height="100%"
|
||||
data={monitor.groupedDownChecks}
|
||||
onMouseEnter={() => {
|
||||
setChartHovered(true);
|
||||
onBarHover({ time: null, totalChecks: 0 });
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setChartHovered(false);
|
||||
setHoveredBarIndex(null);
|
||||
onBarHover(null);
|
||||
}}
|
||||
>
|
||||
<XAxis
|
||||
stroke={theme.palette.primary.lowContrast}
|
||||
height={15}
|
||||
tick={false}
|
||||
label={
|
||||
<CustomLabels
|
||||
x={0}
|
||||
y={0}
|
||||
width="100%"
|
||||
height="100%"
|
||||
firstDataPoint={monitor?.groupedDownChecks?.[0] ?? {}}
|
||||
lastDataPoint={
|
||||
monitor?.groupedDownChecks?.[monitor?.groupedDownChecks?.length - 1] ?? {}
|
||||
}
|
||||
type={type}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="avgResponseTime"
|
||||
maxBarSize={7}
|
||||
background={{ fill: "transparent" }}
|
||||
>
|
||||
{monitor?.groupedDownChecks?.map((entry, index) => {
|
||||
return (
|
||||
<Cell
|
||||
key={`cell-${entry.time}`}
|
||||
fill={
|
||||
hoveredBarIndex === index
|
||||
? theme.palette.error.main
|
||||
: chartHovered
|
||||
? theme.palette.error.light // CAIO_REVIEW
|
||||
: theme.palette.error.main
|
||||
}
|
||||
onMouseEnter={() => {
|
||||
setHoveredBarIndex(index);
|
||||
onBarHover(entry);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setHoveredBarIndex(null);
|
||||
onBarHover({ time: null, totalChecks: 0 });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
});
|
||||
|
||||
DownBarChart.displayName = "DownBarChart";
|
||||
DownBarChart.propTypes = {
|
||||
monitor: PropTypes.shape({
|
||||
groupedDownChecks: PropTypes.arrayOf(PropTypes.object),
|
||||
}),
|
||||
type: PropTypes.string,
|
||||
onBarHover: PropTypes.func,
|
||||
};
|
||||
export default DownBarChart;
|
||||
@@ -0,0 +1,117 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@mui/material";
|
||||
import { ResponsiveContainer, RadialBarChart, RadialBar, Cell } from "recharts";
|
||||
|
||||
const ResponseGaugeChart = ({ avgResponseTime }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
let max = 1000; // max ms
|
||||
|
||||
const data = [
|
||||
{ response: max, fill: "transparent", background: false },
|
||||
{ response: avgResponseTime, background: true },
|
||||
];
|
||||
let responseTime = Math.floor(avgResponseTime);
|
||||
let responseProps =
|
||||
responseTime <= 200
|
||||
? {
|
||||
category: "Excellent",
|
||||
main: theme.palette.success.main,
|
||||
bg: theme.palette.success.contrastText,
|
||||
}
|
||||
: responseTime <= 500
|
||||
? {
|
||||
category: "Fair",
|
||||
main: theme.palette.success.main,
|
||||
bg: theme.palette.success.contrastText,
|
||||
}
|
||||
: responseTime <= 600
|
||||
? {
|
||||
category: "Acceptable",
|
||||
main: theme.palette.warning.main,
|
||||
bg: theme.palette.warning.lowContrast,
|
||||
}
|
||||
: {
|
||||
category: "Poor",
|
||||
main: theme.palette.error.main,
|
||||
bg: theme.palette.error.contrastText,
|
||||
};
|
||||
|
||||
return (
|
||||
<ResponsiveContainer
|
||||
width="100%"
|
||||
minWidth={210}
|
||||
height={155}
|
||||
>
|
||||
<RadialBarChart
|
||||
width="100%"
|
||||
height="100%"
|
||||
cy="89%"
|
||||
data={data}
|
||||
startAngle={180}
|
||||
endAngle={0}
|
||||
innerRadius={100}
|
||||
outerRadius={150}
|
||||
>
|
||||
<text
|
||||
x={0}
|
||||
y="100%"
|
||||
dx="5%"
|
||||
dy={-2}
|
||||
textAnchor="start"
|
||||
fontSize={11}
|
||||
>
|
||||
low
|
||||
</text>
|
||||
<text
|
||||
x="100%"
|
||||
y="100%"
|
||||
dx="-3%"
|
||||
dy={-2}
|
||||
textAnchor="end"
|
||||
fontSize={11}
|
||||
>
|
||||
high
|
||||
</text>
|
||||
<text
|
||||
x="50%"
|
||||
y="45%"
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
fontSize={18}
|
||||
fontWeight={400}
|
||||
>
|
||||
{responseProps.category}
|
||||
</text>
|
||||
<text
|
||||
x="50%"
|
||||
y="55%"
|
||||
textAnchor="middle"
|
||||
dominantBaseline="hanging"
|
||||
fontSize={25}
|
||||
>
|
||||
<tspan fontWeight={600}>{responseTime}</tspan> <tspan opacity={0.8}>ms</tspan>
|
||||
</text>
|
||||
<RadialBar
|
||||
background={{ fill: responseProps.bg }}
|
||||
clockWise
|
||||
dataKey="response"
|
||||
stroke="none"
|
||||
>
|
||||
<Cell
|
||||
fill="transparent"
|
||||
background={false}
|
||||
barSize={0}
|
||||
/>
|
||||
<Cell fill={responseProps.main} />
|
||||
</RadialBar>
|
||||
</RadialBarChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
};
|
||||
|
||||
ResponseGaugeChart.propTypes = {
|
||||
avgResponseTime: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
export default ResponseGaugeChart;
|
||||
@@ -0,0 +1,18 @@
|
||||
import ChartBox from "./ChartBox";
|
||||
import MonitorDetailsAreaChart from "../../../../../Components/Charts/MonitorDetailsAreaChart";
|
||||
import ResponseTimeIcon from "../../../../../assets/icons/response-time-icon.svg?react";
|
||||
const ResponseTImeChart = ({ monitor, dateRange }) => {
|
||||
return (
|
||||
<ChartBox
|
||||
icon={<ResponseTimeIcon />}
|
||||
header="Response Times"
|
||||
>
|
||||
<MonitorDetailsAreaChart
|
||||
checks={monitor.groupedChecks ?? []}
|
||||
dateRange={dateRange}
|
||||
/>
|
||||
</ChartBox>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResponseTImeChart;
|
||||
@@ -0,0 +1,109 @@
|
||||
import { memo, useState } from "react";
|
||||
import { useTheme } from "@mui/material";
|
||||
import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts";
|
||||
import PropTypes from "prop-types";
|
||||
import CustomLabels from "./CustomLabels";
|
||||
|
||||
const getThemeColor = (responseTime) => {
|
||||
if (responseTime < 200) {
|
||||
return "success";
|
||||
} else if (responseTime < 300) {
|
||||
return "warning";
|
||||
} else {
|
||||
return "error";
|
||||
}
|
||||
};
|
||||
|
||||
const UpBarChart = memo(({ monitor, type, onBarHover }) => {
|
||||
const theme = useTheme();
|
||||
const [chartHovered, setChartHovered] = useState(false);
|
||||
const [hoveredBarIndex, setHoveredBarIndex] = useState(null);
|
||||
|
||||
return (
|
||||
<ResponsiveContainer
|
||||
width="100%"
|
||||
minWidth={210}
|
||||
height={155}
|
||||
>
|
||||
<BarChart
|
||||
width="100%"
|
||||
height="100%"
|
||||
data={monitor?.groupedUpChecks}
|
||||
onMouseEnter={() => {
|
||||
setChartHovered(true);
|
||||
onBarHover({ time: null, totalChecks: 0, avgResponseTime: 0 });
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setChartHovered(false);
|
||||
setHoveredBarIndex(null);
|
||||
onBarHover(null);
|
||||
}}
|
||||
>
|
||||
<XAxis
|
||||
stroke={theme.palette.primary.lowContrast}
|
||||
height={15}
|
||||
tick={false}
|
||||
label={
|
||||
<CustomLabels
|
||||
x={0}
|
||||
y={0}
|
||||
width="100%"
|
||||
height="100%"
|
||||
firstDataPoint={monitor?.groupedUpChecks?.[0]}
|
||||
lastDataPoint={
|
||||
monitor?.groupedUpChecks?.[monitor?.groupedUpChecks?.length - 1]
|
||||
}
|
||||
type={type}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="avgResponseTime"
|
||||
maxBarSize={7}
|
||||
background={{ fill: "transparent" }}
|
||||
>
|
||||
{monitor?.groupedUpChecks?.map((entry, index) => {
|
||||
const themeColor = getThemeColor(entry.avgResponseTime);
|
||||
return (
|
||||
<Cell
|
||||
key={`cell-${entry.time}`}
|
||||
fill={
|
||||
hoveredBarIndex === index
|
||||
? theme.palette[themeColor].main
|
||||
: chartHovered
|
||||
? theme.palette[themeColor].light // CAIO_REVIEW
|
||||
: theme.palette[themeColor].main
|
||||
}
|
||||
onMouseEnter={() => {
|
||||
setHoveredBarIndex(index);
|
||||
onBarHover(entry);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setHoveredBarIndex(null);
|
||||
onBarHover({
|
||||
time: null,
|
||||
totalChecks: 0,
|
||||
groupUptimePercentage: 0,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
});
|
||||
|
||||
// Add display name for the component
|
||||
UpBarChart.displayName = "UpBarChart";
|
||||
|
||||
// Validate props using PropTypes
|
||||
UpBarChart.propTypes = {
|
||||
monitor: PropTypes.shape({
|
||||
groupedUpChecks: PropTypes.array,
|
||||
}),
|
||||
type: PropTypes.string,
|
||||
onBarHover: PropTypes.func,
|
||||
};
|
||||
export default UpBarChart;
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Button, Box } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import SettingsIcon from "../../../../../assets/icons/settings-bold.svg?react";
|
||||
|
||||
const ConfigButton = ({ shouldRender, monitorId }) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
|
||||
if (!shouldRender) return null;
|
||||
|
||||
return (
|
||||
<Box alignSelf="flex-end">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={() => navigate(`/uptime/configure/${monitorId}`)}
|
||||
sx={{
|
||||
px: theme.spacing(5),
|
||||
"& svg": {
|
||||
mr: theme.spacing(3),
|
||||
"& path": {
|
||||
/* Should always be contrastText for the button color */
|
||||
stroke: theme.palette.secondary.contrastText,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SettingsIcon /> Configure
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfigButton;
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import PulseDot from "../../../../../Components/Animated/PulseDot";
|
||||
import Dot from "../../../../../Components/Dot";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import useUtils from "../../../Home/Hooks/useUtils";
|
||||
import { formatDurationRounded } from "../../../../../Utils/timeUtils";
|
||||
import ConfigButton from "../ConfigButton";
|
||||
|
||||
const MonitorHeader = ({ monitor }) => {
|
||||
const theme = useTheme();
|
||||
const { statusColor, statusMsg, determineState } = useUtils();
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Stack>
|
||||
<Typography variant="h1">{monitor.name}</Typography>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems={"center"}
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<PulseDot color={statusColor[determineState(monitor)]} />
|
||||
<Typography variant="h2">
|
||||
{monitor?.url?.replace(/^https?:\/\//, "") || "..."}
|
||||
</Typography>
|
||||
<Dot />
|
||||
<Typography>
|
||||
Checking every {formatDurationRounded(monitor?.interval)}.
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<ConfigButton
|
||||
shouldRender={true}
|
||||
monitorId={monitor._id}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default MonitorHeader;
|
||||
@@ -0,0 +1,72 @@
|
||||
// Components
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import StatBox from "../../../../../Components/StatBox";
|
||||
|
||||
// Utils
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import useUtils from "../../../Home/Hooks/useUtils";
|
||||
import { getHumanReadableDuration } from "../../../../../Utils/timeUtils";
|
||||
|
||||
const StatusBoxes = ({ monitor, certificateExpiry }) => {
|
||||
const theme = useTheme();
|
||||
const { time: streakTime, units: streakUnits } = getHumanReadableDuration(
|
||||
monitor?.uptimeStreak
|
||||
);
|
||||
|
||||
const { time: lastCheckTime, units: lastCheckUnits } = getHumanReadableDuration(
|
||||
monitor?.timeSinceLastCheck
|
||||
);
|
||||
|
||||
const { determineState } = useUtils();
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<StatBox
|
||||
gradient={true}
|
||||
status={determineState(monitor)}
|
||||
heading={"active for"}
|
||||
subHeading={
|
||||
<>
|
||||
{streakTime}
|
||||
<Typography component="span">{streakUnits}</Typography>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<StatBox
|
||||
heading="last check"
|
||||
subHeading={
|
||||
<>
|
||||
{lastCheckTime}
|
||||
<Typography component="span">{lastCheckUnits}</Typography>
|
||||
<Typography component="span">{"ago"}</Typography>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<StatBox
|
||||
heading="last response time"
|
||||
subHeading={
|
||||
<>
|
||||
{monitor?.latestResponseTime}
|
||||
<Typography component="span">{"ms"}</Typography>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<StatBox
|
||||
heading="certificate expiry"
|
||||
subHeading={
|
||||
<Typography
|
||||
component="span"
|
||||
fontSize={13}
|
||||
color={theme.palette.primary.contrastText}
|
||||
>
|
||||
{certificateExpiry}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusBoxes;
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Stack, Typography, Button, ButtonGroup } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
const TimeFramePicker = ({ dateRange, setDateRange }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="flex-end"
|
||||
gap={theme.spacing(4)}
|
||||
mb={theme.spacing(8)}
|
||||
>
|
||||
<Typography variant="body2">
|
||||
Showing statistics for past{" "}
|
||||
{dateRange === "day" ? "24 hours" : dateRange === "week" ? "7 days" : "30 days"}.
|
||||
</Typography>
|
||||
<ButtonGroup sx={{ height: 32 }}>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "day").toString()}
|
||||
onClick={() => setDateRange("day")}
|
||||
>
|
||||
Day
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "week").toString()}
|
||||
onClick={() => setDateRange("week")}
|
||||
>
|
||||
Week
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "month").toString()}
|
||||
onClick={() => setDateRange("month")}
|
||||
>
|
||||
Month
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default TimeFramePicker;
|
||||
@@ -5,7 +5,7 @@ import { useNavigate } from "react-router-dom";
|
||||
|
||||
export const useMonitorFetch = ({ authToken, monitorId, dateRange }) => {
|
||||
const [monitorIsLoading, setMonitorsIsLoading] = useState(false);
|
||||
const [monitor, setMonitor] = useState([]);
|
||||
const [monitor, setMonitor] = useState({});
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
// Components
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
|
||||
import MonitorHeader from "./Components/MonitorHeader";
|
||||
import StatusBoxes from "./Components/StatusBoxes";
|
||||
import TimeFramePicker from "./Components/TimeFramePicker";
|
||||
import ChartBoxes from "./Components/ChartBoxes";
|
||||
import ResponseTimeChart from "./Components/Charts/ResponseTimeChart";
|
||||
// MUI Components
|
||||
import { Stack } from "@mui/material";
|
||||
|
||||
@@ -8,6 +12,7 @@ import { Stack } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import useMonitorFetch from "./Hooks/useMonitorFetch";
|
||||
import useCertificateFetch from "./Hooks/useCertificateFetch";
|
||||
// Constants
|
||||
@@ -26,10 +31,13 @@ const UptimeDetails = () => {
|
||||
|
||||
// Local state
|
||||
const [dateRange, setDateRange] = useState("day");
|
||||
const [hoveredUptimeData, setHoveredUptimeData] = useState(null);
|
||||
const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null);
|
||||
|
||||
// Utils
|
||||
const dateFormat = dateRange === "day" ? "MMM D, h A" : "MMM D";
|
||||
const { monitorId } = useParams();
|
||||
const theme = useTheme();
|
||||
|
||||
const { monitor, monitorIsLoading } = useMonitorFetch({
|
||||
authToken,
|
||||
@@ -45,11 +53,32 @@ const UptimeDetails = () => {
|
||||
uiTimezone,
|
||||
});
|
||||
|
||||
console.log(monitor);
|
||||
console.log(certificateExpiry);
|
||||
return (
|
||||
<Stack>
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<MonitorHeader monitor={monitor} />
|
||||
<StatusBoxes
|
||||
monitor={monitor}
|
||||
certificateExpiry={certificateExpiry}
|
||||
/>
|
||||
<TimeFramePicker
|
||||
dateRange={dateRange}
|
||||
setDateRange={setDateRange}
|
||||
/>
|
||||
<ChartBoxes
|
||||
monitor={monitor}
|
||||
uiTimezone={uiTimezone}
|
||||
dateRange={dateRange}
|
||||
dateFormat={dateFormat}
|
||||
hoveredUptimeData={hoveredUptimeData}
|
||||
setHoveredUptimeData={setHoveredUptimeData}
|
||||
hoveredIncidentsData={hoveredIncidentsData}
|
||||
setHoveredIncidentsData={setHoveredIncidentsData}
|
||||
/>
|
||||
<ResponseTimeChart
|
||||
monitor={monitor}
|
||||
dateRange={dateRange}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user