mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-01 06:00:24 -05:00
Refactor FE to make proper use of date
This commit is contained in:
@@ -1,22 +1,19 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Popover,
|
||||
Stack,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useTheme,
|
||||
Box,
|
||||
Button,
|
||||
Popover,
|
||||
Stack,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { networkService } from "../../../main";
|
||||
import { logger } from "../../../Utils/Logger";
|
||||
import {
|
||||
formatDurationRounded,
|
||||
formatDurationSplit,
|
||||
} from "../../../Utils/timeUtils";
|
||||
import { formatDurationRounded, formatDurationSplit } from "../../../Utils/timeUtils";
|
||||
import MonitorDetailsAreaChart from "../../../Components/Charts/MonitorDetailsAreaChart";
|
||||
import ButtonGroup from "@mui/material/ButtonGroup";
|
||||
import SettingsIcon from "../../../assets/icons/settings-bold.svg?react";
|
||||
@@ -41,447 +38,455 @@ import { formatDateWithTz } from "../../../Utils/timeUtils";
|
||||
* @component
|
||||
*/
|
||||
const DetailsPage = ({ isAdmin }) => {
|
||||
const theme = useTheme();
|
||||
const { statusColor, statusStyles, statusMsg, determineState } = useUtils();
|
||||
const [monitor, setMonitor] = useState({});
|
||||
const { monitorId } = useParams();
|
||||
const { authToken } = useSelector((state) => state.auth);
|
||||
const [dateRange, setDateRange] = useState("day");
|
||||
const [certificateExpiry, setCertificateExpiry] = useState("N/A");
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const { statusColor, statusStyles, statusMsg, determineState } = useUtils();
|
||||
const [monitor, setMonitor] = useState({});
|
||||
const { monitorId } = useParams();
|
||||
const { authToken } = useSelector((state) => state.auth);
|
||||
const [dateRange, setDateRange] = useState("day");
|
||||
const [certificateExpiry, setCertificateExpiry] = useState("N/A");
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const openCertificate = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const closeCertificate = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const openCertificate = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const closeCertificate = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const dateFormat = dateRange === "day" ? "MMM D, h A" : "MMM D";
|
||||
const uiTimezone = useSelector((state) => state.ui.timezone);
|
||||
const dateFormat = dateRange === "day" ? "MMM D, h A" : "MMM D";
|
||||
const uiTimezone = useSelector((state) => state.ui.timezone);
|
||||
|
||||
const fetchMonitor = useCallback(async () => {
|
||||
try {
|
||||
const res = await networkService.getStatsByMonitorId({
|
||||
authToken: authToken,
|
||||
monitorId: monitorId,
|
||||
sortOrder: null,
|
||||
limit: null,
|
||||
dateRange: dateRange,
|
||||
numToDisplay: 50,
|
||||
normalize: true,
|
||||
});
|
||||
setMonitor(res?.data?.data ?? {});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
navigate("/not-found", { replace: true });
|
||||
}
|
||||
}, [authToken, monitorId, navigate, dateRange]);
|
||||
const fetchMonitor = useCallback(async () => {
|
||||
try {
|
||||
const res = await networkService.getStatsByMonitorId({
|
||||
authToken: authToken,
|
||||
monitorId: monitorId,
|
||||
sortOrder: null,
|
||||
limit: null,
|
||||
dateRange: dateRange,
|
||||
numToDisplay: 50,
|
||||
normalize: true,
|
||||
});
|
||||
setMonitor(res?.data?.data ?? {});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
navigate("/not-found", { replace: true });
|
||||
}
|
||||
}, [authToken, monitorId, navigate, dateRange]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchMonitor();
|
||||
}, [fetchMonitor]);
|
||||
useEffect(() => {
|
||||
fetchMonitor();
|
||||
}, [fetchMonitor]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCertificate = async () => {
|
||||
if (monitor?.type !== "http") {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await networkService.getCertificateExpiry({
|
||||
authToken: authToken,
|
||||
monitorId: monitorId,
|
||||
});
|
||||
useEffect(() => {
|
||||
const fetchCertificate = async () => {
|
||||
if (monitor?.type !== "http") {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await networkService.getCertificateExpiry({
|
||||
authToken: authToken,
|
||||
monitorId: monitorId,
|
||||
});
|
||||
if (res?.data?.data?.certificateDate) {
|
||||
const date = res.data.data.certificateDate;
|
||||
setCertificateExpiry(formatDateWithTz(date, dateFormat, uiTimezone) ?? "N/A");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
fetchCertificate();
|
||||
}, [authToken, monitorId, monitor]);
|
||||
|
||||
if (res?.data?.data?.certificateDate) {
|
||||
let [month, day, year] = res.data.data.certificateDate.split("/");
|
||||
const date = new Date(year, month - 1, day);
|
||||
const splitDuration = (duration) => {
|
||||
const { time, format } = formatDurationSplit(duration);
|
||||
return (
|
||||
<>
|
||||
{time}
|
||||
<Typography component="span">{format}</Typography>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
setCertificateExpiry(
|
||||
formatDateWithTz(date, dateFormat, uiTimezone) ?? "N/A"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
fetchCertificate();
|
||||
}, [authToken, monitorId, monitor]);
|
||||
let loading = Object.keys(monitor).length === 0;
|
||||
|
||||
const splitDuration = (duration) => {
|
||||
const { time, format } = formatDurationSplit(duration);
|
||||
return (
|
||||
<>
|
||||
{time}
|
||||
<Typography component="span">{format}</Typography>
|
||||
</>
|
||||
);
|
||||
};
|
||||
const [hoveredUptimeData, setHoveredUptimeData] = useState(null);
|
||||
const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null);
|
||||
|
||||
let loading = Object.keys(monitor).length === 0;
|
||||
|
||||
const [hoveredUptimeData, setHoveredUptimeData] = useState(null);
|
||||
const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null);
|
||||
|
||||
return (
|
||||
<Box className="monitor-details">
|
||||
{loading ? (
|
||||
<SkeletonLayout />
|
||||
) : (
|
||||
<>
|
||||
<Breadcrumbs
|
||||
list={[
|
||||
{ name: "monitors", path: "/monitors" },
|
||||
{ name: "details", path: `/monitors/${monitorId}` },
|
||||
]}
|
||||
/>
|
||||
<Stack gap={theme.spacing(10)} mt={theme.spacing(10)}>
|
||||
<Stack direction="row" gap={theme.spacing(2)}>
|
||||
<Box>
|
||||
<Typography component="h1" variant="h1">
|
||||
{monitor.name}
|
||||
</Typography>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
height="fit-content"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Tooltip
|
||||
title={statusMsg[determineState(monitor)]}
|
||||
disableInteractive
|
||||
slotProps={{
|
||||
popper: {
|
||||
modifiers: [
|
||||
{
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: [0, -8],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<PulseDot color={statusColor[determineState(monitor)]} />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
<Typography component="h2" variant="h2">
|
||||
{monitor.url?.replace(/^https?:\/\//, "") || "..."}
|
||||
</Typography>
|
||||
<Typography
|
||||
position="relative"
|
||||
variant="body2"
|
||||
mt={theme.spacing(1)}
|
||||
ml={theme.spacing(6)}
|
||||
sx={{
|
||||
"&:before": {
|
||||
position: "absolute",
|
||||
content: `""`,
|
||||
width: 4,
|
||||
height: 4,
|
||||
borderRadius: "50%",
|
||||
backgroundColor: theme.palette.text.tertiary,
|
||||
opacity: 0.8,
|
||||
left: -9,
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
Checking every {formatDurationRounded(monitor?.interval)}.
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Stack
|
||||
direction="row"
|
||||
height={34}
|
||||
sx={{
|
||||
ml: "auto",
|
||||
alignSelf: "flex-end",
|
||||
}}
|
||||
>
|
||||
<IconBox
|
||||
mr={theme.spacing(4)}
|
||||
onClick={openCertificate}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
"& svg": {
|
||||
width: 23,
|
||||
height: 23,
|
||||
top: "52%",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CertificateIcon />
|
||||
</IconBox>
|
||||
<Popover
|
||||
id="certificate-dropdown"
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={closeCertificate}
|
||||
disableScrollLock
|
||||
marginThreshold={null}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "center",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
}}
|
||||
slotProps={{
|
||||
paper: {
|
||||
sx: {
|
||||
mt: theme.spacing(4),
|
||||
py: theme.spacing(2),
|
||||
px: theme.spacing(4),
|
||||
width: 140,
|
||||
backgroundColor: theme.palette.background.accent,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2">Certificate Expiry</Typography>
|
||||
<Typography
|
||||
component="span"
|
||||
fontSize={13}
|
||||
color={theme.palette.text.primary}
|
||||
>
|
||||
{certificateExpiry}
|
||||
</Typography>
|
||||
</Popover>
|
||||
{isAdmin && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={() => navigate(`/monitors/configure/${monitorId}`)}
|
||||
sx={{
|
||||
px: theme.spacing(5),
|
||||
"& svg": {
|
||||
mr: theme.spacing(3),
|
||||
"& path": {
|
||||
stroke: theme.palette.text.tertiary,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SettingsIcon /> Configure
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack direction="row" gap={theme.spacing(8)}>
|
||||
<StatBox sx={statusStyles[determineState(monitor)]}>
|
||||
<Typography component="h2">active for</Typography>
|
||||
<Typography>
|
||||
{splitDuration(monitor?.uptimeDuration)}
|
||||
</Typography>
|
||||
</StatBox>
|
||||
<StatBox>
|
||||
<Typography component="h2">last check</Typography>
|
||||
<Typography>
|
||||
{splitDuration(monitor?.lastChecked)}
|
||||
<Typography component="span">ago</Typography>
|
||||
</Typography>
|
||||
</StatBox>
|
||||
<StatBox>
|
||||
<Typography component="h2">last response time</Typography>
|
||||
<Typography>
|
||||
{monitor?.latestResponseTime}
|
||||
<Typography component="span">ms</Typography>
|
||||
</Typography>
|
||||
</StatBox>
|
||||
</Stack>
|
||||
<Box>
|
||||
<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>
|
||||
<Stack direction="row" flexWrap="wrap" gap={theme.spacing(8)}>
|
||||
<ChartBox>
|
||||
<Stack>
|
||||
<IconBox>
|
||||
<UptimeIcon />
|
||||
</IconBox>
|
||||
<Typography component="h2">Uptime</Typography>
|
||||
</Stack>
|
||||
<Stack justifyContent="space-between">
|
||||
<Box position="relative">
|
||||
<Typography>Total Checks</Typography>
|
||||
<Typography component="span">
|
||||
{hoveredUptimeData !== null
|
||||
? hoveredUptimeData.totalChecks
|
||||
: monitor?.periodTotalChecks}
|
||||
</Typography>
|
||||
{hoveredUptimeData !== null &&
|
||||
hoveredUptimeData.time !== null && (
|
||||
<Typography
|
||||
component="h5"
|
||||
position="absolute"
|
||||
top="100%"
|
||||
fontSize={11}
|
||||
color={theme.palette.text.tertiary}
|
||||
>
|
||||
{formatDateWithTz(
|
||||
hoveredUptimeData.time,
|
||||
dateFormat,
|
||||
uiTimezone
|
||||
)}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography>Uptime Percentage</Typography>
|
||||
<Typography component="span">
|
||||
{hoveredUptimeData !== null
|
||||
? Math.floor(
|
||||
hoveredUptimeData.uptimePercentage * 10
|
||||
) / 10
|
||||
: Math.floor(monitor?.periodUptime * 10) / 10}
|
||||
<Typography component="span">%</Typography>
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
<UpBarChart
|
||||
data={monitor?.aggregateData}
|
||||
type={dateRange}
|
||||
onBarHover={setHoveredUptimeData}
|
||||
/>
|
||||
</ChartBox>
|
||||
<ChartBox>
|
||||
<Stack>
|
||||
<IconBox>
|
||||
<IncidentsIcon />
|
||||
</IconBox>
|
||||
<Typography component="h2">Incidents</Typography>
|
||||
</Stack>
|
||||
<Box position="relative">
|
||||
<Typography>Total Incidents</Typography>
|
||||
<Typography component="span">
|
||||
{hoveredIncidentsData !== null
|
||||
? hoveredIncidentsData.totalIncidents
|
||||
: monitor?.periodIncidents}
|
||||
</Typography>
|
||||
{hoveredIncidentsData !== null &&
|
||||
hoveredIncidentsData.time !== null && (
|
||||
<Typography
|
||||
component="h5"
|
||||
position="absolute"
|
||||
top="100%"
|
||||
fontSize={11}
|
||||
color={theme.palette.text.tertiary}
|
||||
>
|
||||
{formatDateWithTz(
|
||||
hoveredIncidentsData.time,
|
||||
dateFormat,
|
||||
uiTimezone
|
||||
)}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<DownBarChart
|
||||
data={monitor?.aggregateData}
|
||||
type={dateRange}
|
||||
onBarHover={setHoveredIncidentsData}
|
||||
/>
|
||||
</ChartBox>
|
||||
<ChartBox justifyContent="space-between">
|
||||
<Stack>
|
||||
<IconBox>
|
||||
<AverageResponseIcon />
|
||||
</IconBox>
|
||||
<Typography component="h2">
|
||||
Average Response Time
|
||||
</Typography>
|
||||
</Stack>
|
||||
<ResponseGaugeChart
|
||||
data={[{ response: monitor?.periodAvgResponseTime }]}
|
||||
/>
|
||||
</ChartBox>
|
||||
<ChartBox sx={{ padding: 0 }}>
|
||||
<Stack pt={theme.spacing(8)} pl={theme.spacing(8)}>
|
||||
<IconBox>
|
||||
<ResponseTimeIcon />
|
||||
</IconBox>
|
||||
<Typography component="h2">Response Times</Typography>
|
||||
</Stack>
|
||||
<MonitorDetailsAreaChart
|
||||
checks={[...monitor.checks].reverse()}
|
||||
/>
|
||||
</ChartBox>
|
||||
<ChartBox
|
||||
gap={theme.spacing(8)}
|
||||
sx={{
|
||||
flex: "100%",
|
||||
height: "fit-content",
|
||||
"& nav": { mt: theme.spacing(12) },
|
||||
}}
|
||||
>
|
||||
<Stack mb={theme.spacing(8)}>
|
||||
<IconBox>
|
||||
<HistoryIcon />
|
||||
</IconBox>
|
||||
<Typography
|
||||
component="h2"
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
History
|
||||
</Typography>
|
||||
</Stack>
|
||||
<PaginationTable
|
||||
monitorId={monitorId}
|
||||
dateRange={dateRange}
|
||||
/>
|
||||
</ChartBox>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
return (
|
||||
<Box className="monitor-details">
|
||||
{loading ? (
|
||||
<SkeletonLayout />
|
||||
) : (
|
||||
<>
|
||||
<Breadcrumbs
|
||||
list={[
|
||||
{ name: "monitors", path: "/monitors" },
|
||||
{ name: "details", path: `/monitors/${monitorId}` },
|
||||
]}
|
||||
/>
|
||||
<Stack
|
||||
gap={theme.spacing(10)}
|
||||
mt={theme.spacing(10)}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Box>
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="h1"
|
||||
>
|
||||
{monitor.name}
|
||||
</Typography>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
height="fit-content"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Tooltip
|
||||
title={statusMsg[determineState(monitor)]}
|
||||
disableInteractive
|
||||
slotProps={{
|
||||
popper: {
|
||||
modifiers: [
|
||||
{
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: [0, -8],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<PulseDot color={statusColor[determineState(monitor)]} />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="h2"
|
||||
>
|
||||
{monitor.url?.replace(/^https?:\/\//, "") || "..."}
|
||||
</Typography>
|
||||
<Typography
|
||||
position="relative"
|
||||
variant="body2"
|
||||
mt={theme.spacing(1)}
|
||||
ml={theme.spacing(6)}
|
||||
sx={{
|
||||
"&:before": {
|
||||
position: "absolute",
|
||||
content: `""`,
|
||||
width: 4,
|
||||
height: 4,
|
||||
borderRadius: "50%",
|
||||
backgroundColor: theme.palette.text.tertiary,
|
||||
opacity: 0.8,
|
||||
left: -9,
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
Checking every {formatDurationRounded(monitor?.interval)}.
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Stack
|
||||
direction="row"
|
||||
height={34}
|
||||
sx={{
|
||||
ml: "auto",
|
||||
alignSelf: "flex-end",
|
||||
}}
|
||||
>
|
||||
<IconBox
|
||||
mr={theme.spacing(4)}
|
||||
onClick={openCertificate}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
"& svg": {
|
||||
width: 23,
|
||||
height: 23,
|
||||
top: "52%",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CertificateIcon />
|
||||
</IconBox>
|
||||
<Popover
|
||||
id="certificate-dropdown"
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={closeCertificate}
|
||||
disableScrollLock
|
||||
marginThreshold={null}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "center",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
}}
|
||||
slotProps={{
|
||||
paper: {
|
||||
sx: {
|
||||
mt: theme.spacing(4),
|
||||
py: theme.spacing(2),
|
||||
px: theme.spacing(4),
|
||||
width: 140,
|
||||
backgroundColor: theme.palette.background.accent,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2">Certificate Expiry</Typography>
|
||||
<Typography
|
||||
component="span"
|
||||
fontSize={13}
|
||||
color={theme.palette.text.primary}
|
||||
>
|
||||
{certificateExpiry}
|
||||
</Typography>
|
||||
</Popover>
|
||||
{isAdmin && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={() => navigate(`/monitors/configure/${monitorId}`)}
|
||||
sx={{
|
||||
px: theme.spacing(5),
|
||||
"& svg": {
|
||||
mr: theme.spacing(3),
|
||||
"& path": {
|
||||
stroke: theme.palette.text.tertiary,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SettingsIcon /> Configure
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<StatBox sx={statusStyles[determineState(monitor)]}>
|
||||
<Typography component="h2">active for</Typography>
|
||||
<Typography>{splitDuration(monitor?.uptimeDuration)}</Typography>
|
||||
</StatBox>
|
||||
<StatBox>
|
||||
<Typography component="h2">last check</Typography>
|
||||
<Typography>
|
||||
{splitDuration(monitor?.lastChecked)}
|
||||
<Typography component="span">ago</Typography>
|
||||
</Typography>
|
||||
</StatBox>
|
||||
<StatBox>
|
||||
<Typography component="h2">last response time</Typography>
|
||||
<Typography>
|
||||
{monitor?.latestResponseTime}
|
||||
<Typography component="span">ms</Typography>
|
||||
</Typography>
|
||||
</StatBox>
|
||||
</Stack>
|
||||
<Box>
|
||||
<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>
|
||||
<Stack
|
||||
direction="row"
|
||||
flexWrap="wrap"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<ChartBox>
|
||||
<Stack>
|
||||
<IconBox>
|
||||
<UptimeIcon />
|
||||
</IconBox>
|
||||
<Typography component="h2">Uptime</Typography>
|
||||
</Stack>
|
||||
<Stack justifyContent="space-between">
|
||||
<Box position="relative">
|
||||
<Typography>Total Checks</Typography>
|
||||
<Typography component="span">
|
||||
{hoveredUptimeData !== null
|
||||
? hoveredUptimeData.totalChecks
|
||||
: monitor?.periodTotalChecks}
|
||||
</Typography>
|
||||
{hoveredUptimeData !== null && hoveredUptimeData.time !== null && (
|
||||
<Typography
|
||||
component="h5"
|
||||
position="absolute"
|
||||
top="100%"
|
||||
fontSize={11}
|
||||
color={theme.palette.text.tertiary}
|
||||
>
|
||||
{formatDateWithTz(
|
||||
hoveredUptimeData.time,
|
||||
dateFormat,
|
||||
uiTimezone
|
||||
)}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography>Uptime Percentage</Typography>
|
||||
<Typography component="span">
|
||||
{hoveredUptimeData !== null
|
||||
? Math.floor(hoveredUptimeData.uptimePercentage * 10) / 10
|
||||
: Math.floor(monitor?.periodUptime * 10) / 10}
|
||||
<Typography component="span">%</Typography>
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
<UpBarChart
|
||||
data={monitor?.aggregateData}
|
||||
type={dateRange}
|
||||
onBarHover={setHoveredUptimeData}
|
||||
/>
|
||||
</ChartBox>
|
||||
<ChartBox>
|
||||
<Stack>
|
||||
<IconBox>
|
||||
<IncidentsIcon />
|
||||
</IconBox>
|
||||
<Typography component="h2">Incidents</Typography>
|
||||
</Stack>
|
||||
<Box position="relative">
|
||||
<Typography>Total Incidents</Typography>
|
||||
<Typography component="span">
|
||||
{hoveredIncidentsData !== null
|
||||
? hoveredIncidentsData.totalIncidents
|
||||
: monitor?.periodIncidents}
|
||||
</Typography>
|
||||
{hoveredIncidentsData !== null &&
|
||||
hoveredIncidentsData.time !== null && (
|
||||
<Typography
|
||||
component="h5"
|
||||
position="absolute"
|
||||
top="100%"
|
||||
fontSize={11}
|
||||
color={theme.palette.text.tertiary}
|
||||
>
|
||||
{formatDateWithTz(
|
||||
hoveredIncidentsData.time,
|
||||
dateFormat,
|
||||
uiTimezone
|
||||
)}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<DownBarChart
|
||||
data={monitor?.aggregateData}
|
||||
type={dateRange}
|
||||
onBarHover={setHoveredIncidentsData}
|
||||
/>
|
||||
</ChartBox>
|
||||
<ChartBox justifyContent="space-between">
|
||||
<Stack>
|
||||
<IconBox>
|
||||
<AverageResponseIcon />
|
||||
</IconBox>
|
||||
<Typography component="h2">Average Response Time</Typography>
|
||||
</Stack>
|
||||
<ResponseGaugeChart
|
||||
data={[{ response: monitor?.periodAvgResponseTime }]}
|
||||
/>
|
||||
</ChartBox>
|
||||
<ChartBox sx={{ padding: 0 }}>
|
||||
<Stack
|
||||
pt={theme.spacing(8)}
|
||||
pl={theme.spacing(8)}
|
||||
>
|
||||
<IconBox>
|
||||
<ResponseTimeIcon />
|
||||
</IconBox>
|
||||
<Typography component="h2">Response Times</Typography>
|
||||
</Stack>
|
||||
<MonitorDetailsAreaChart checks={[...monitor.checks].reverse()} />
|
||||
</ChartBox>
|
||||
<ChartBox
|
||||
gap={theme.spacing(8)}
|
||||
sx={{
|
||||
flex: "100%",
|
||||
height: "fit-content",
|
||||
"& nav": { mt: theme.spacing(12) },
|
||||
}}
|
||||
>
|
||||
<Stack mb={theme.spacing(8)}>
|
||||
<IconBox>
|
||||
<HistoryIcon />
|
||||
</IconBox>
|
||||
<Typography
|
||||
component="h2"
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
History
|
||||
</Typography>
|
||||
</Stack>
|
||||
<PaginationTable
|
||||
monitorId={monitorId}
|
||||
dateRange={dateRange}
|
||||
/>
|
||||
</ChartBox>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
DetailsPage.propTypes = {
|
||||
isAdmin: PropTypes.bool,
|
||||
isAdmin: PropTypes.bool,
|
||||
};
|
||||
export default DetailsPage;
|
||||
|
||||
Reference in New Issue
Block a user