mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-24 11:59:39 -05:00
refactor public status page
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Box, Stack, Tooltip, Typography } from "@mui/material";
|
||||
import { formatDateWithTz } from "../../../Utils/timeUtils";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
/* TODO add prop validation and jsdocs */
|
||||
const StatusPageBarChart = ({ checks = [] }) => {
|
||||
const theme = useTheme();
|
||||
const [animate, setAnimate] = useState(false);
|
||||
const uiTimezone = useSelector((state) => state.ui.timezone);
|
||||
|
||||
useEffect(() => {
|
||||
setAnimate(true);
|
||||
}, []);
|
||||
|
||||
// set responseTime to average if there's only one check
|
||||
if (checks.length === 1) {
|
||||
checks[0] = { ...checks[0], responseTime: 50 };
|
||||
}
|
||||
|
||||
if (checks.length !== 25) {
|
||||
const placeholders = Array(25 - checks.length).fill("placeholder");
|
||||
checks = [...checks, ...placeholders];
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
width="100%"
|
||||
flexWrap="nowrap"
|
||||
height="50px"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
sx={{
|
||||
cursor: "default",
|
||||
}}
|
||||
>
|
||||
{checks.map((check, index) =>
|
||||
check === "placeholder" ? (
|
||||
/* TODO what is the purpose of this box? */
|
||||
// CAIO_REVIEW the purpose of this box is to make sure there are always at least 25 bars
|
||||
// even if there are less than 25 checks
|
||||
<Box
|
||||
key={`${check}-${index}`}
|
||||
position="relative"
|
||||
width="calc(30vw / 25)"
|
||||
height="100%"
|
||||
backgroundColor={theme.palette.primary.lowContrast}
|
||||
sx={{
|
||||
borderRadius: theme.spacing(1.5),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Tooltip
|
||||
title={
|
||||
<>
|
||||
<Typography>
|
||||
{formatDateWithTz(
|
||||
check.createdAt,
|
||||
"ddd, MMMM D, YYYY, HH:mm A",
|
||||
uiTimezone
|
||||
)}
|
||||
</Typography>
|
||||
<Box mt={theme.spacing(2)}>
|
||||
<Box
|
||||
display="inline-block"
|
||||
width={theme.spacing(4)}
|
||||
height={theme.spacing(4)}
|
||||
backgroundColor={
|
||||
check.status
|
||||
? theme.palette.success.lowContrast
|
||||
: theme.palette.error.lowContrast
|
||||
}
|
||||
sx={{ borderRadius: "50%" }}
|
||||
/>
|
||||
<Stack
|
||||
display="inline-flex"
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
ml={theme.spacing(2)}
|
||||
gap={theme.spacing(12)}
|
||||
>
|
||||
<Typography
|
||||
component="span"
|
||||
sx={{ opacity: 0.8 }}
|
||||
>
|
||||
Response Time
|
||||
</Typography>
|
||||
<Typography component="span">
|
||||
{check.originalResponseTime}
|
||||
<Typography
|
||||
component="span"
|
||||
sx={{ opacity: 0.8 }}
|
||||
>
|
||||
{" "}
|
||||
ms
|
||||
</Typography>
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
</>
|
||||
}
|
||||
placement="top"
|
||||
key={`check-${check?._id}`}
|
||||
slotProps={{
|
||||
popper: {
|
||||
className: "bar-tooltip",
|
||||
modifiers: [
|
||||
{
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: [0, -10],
|
||||
},
|
||||
},
|
||||
],
|
||||
sx: {
|
||||
"& .MuiTooltip-tooltip": {
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
border: 1,
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
boxShadow: theme.shape.boxShadow,
|
||||
px: theme.spacing(4),
|
||||
py: theme.spacing(3),
|
||||
},
|
||||
"& .MuiTooltip-tooltip p": {
|
||||
/* TODO Font size should point to theme */
|
||||
fontSize: 12,
|
||||
color: theme.palette.secondary.contrastText,
|
||||
fontWeight: 500,
|
||||
},
|
||||
"& .MuiTooltip-tooltip span": {
|
||||
/* TODO Font size should point to theme */
|
||||
fontSize: 11,
|
||||
color: theme.palette.secondary.contrastText,
|
||||
fontWeight: 600,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
position="relative"
|
||||
width="calc(30vw / 25)"
|
||||
height="100%"
|
||||
backgroundColor={theme.palette.primary.lowContrast} // CAIO_REVIEW
|
||||
sx={{
|
||||
borderRadius: theme.spacing(1.5),
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
position="absolute"
|
||||
bottom={0}
|
||||
width="100%"
|
||||
height={`${animate ? check.responseTime : 0}%`}
|
||||
backgroundColor={
|
||||
check.status
|
||||
? theme.palette.success.lowContrast
|
||||
: theme.palette.error.lowContrast
|
||||
}
|
||||
sx={{
|
||||
borderRadius: theme.spacing(1.5),
|
||||
transition: "height 600ms cubic-bezier(0.4, 0, 0.2, 1)",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusPageBarChart;
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Box } from "@mui/material";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const Image = ({
|
||||
src,
|
||||
alt,
|
||||
width = "auto",
|
||||
height = "auto",
|
||||
maxWidth = "auto",
|
||||
maxHeight = "auto",
|
||||
}) => {
|
||||
return (
|
||||
<Box
|
||||
component="img"
|
||||
src={src}
|
||||
alt={alt}
|
||||
maxWidth={maxWidth}
|
||||
maxHeight={maxHeight}
|
||||
width={width}
|
||||
height={height}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Image.propTypes = {
|
||||
src: PropTypes.string,
|
||||
alt: PropTypes.string.isRequired,
|
||||
width: PropTypes.string,
|
||||
height: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Image;
|
||||
@@ -47,19 +47,6 @@ const Content = ({
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
{/* <Typography
|
||||
component="p"
|
||||
alignSelf={"center"}
|
||||
>
|
||||
{" "}
|
||||
Servers list{" "}
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
>
|
||||
Add new
|
||||
</Button> */}
|
||||
<Search
|
||||
options={monitors}
|
||||
multiple={true}
|
||||
|
||||
@@ -11,6 +11,7 @@ import Progress from "../Progress";
|
||||
// Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
import timezones from "../../../../../Utils/timezones.json";
|
||||
import { memo } from "react";
|
||||
|
||||
const TabSettings = ({
|
||||
tabValue,
|
||||
@@ -101,7 +102,7 @@ const TabSettings = ({
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
<ImageField
|
||||
id="logo"
|
||||
src={form.logo?.src}
|
||||
src={form?.logo?.src}
|
||||
isRound={false}
|
||||
onChange={handleImageChange}
|
||||
/>
|
||||
|
||||
@@ -6,6 +6,7 @@ import Content from "./Content";
|
||||
|
||||
// Utils
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const Tabs = ({
|
||||
form,
|
||||
errors,
|
||||
@@ -70,7 +71,7 @@ Tabs.propTypes = {
|
||||
setSelectedMonitors: PropTypes.func,
|
||||
handleFormChange: PropTypes.func,
|
||||
handleImageChange: PropTypes.func,
|
||||
progress: PropTypes.number,
|
||||
progress: PropTypes.object,
|
||||
removeLogo: PropTypes.func,
|
||||
tab: PropTypes.number,
|
||||
setTab: PropTypes.func,
|
||||
|
||||
@@ -5,14 +5,13 @@ import GenericFallback from "../../../Components/GenericFallback";
|
||||
import SkeletonLayout from "./Components/Skeleton";
|
||||
//Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useState, useRef } from "react";
|
||||
import { useState, useRef, useCallback } from "react";
|
||||
import { statusPageValidation } from "../../../Validation/validation";
|
||||
import { buildErrors } from "../../../Validation/error";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useMonitorsFetch } from "./Hooks/useMonitorsFetch";
|
||||
import { useCreateStatusPage } from "./Hooks/useCreateStatusPage";
|
||||
import { createToast } from "../../../Utils/toastUtils";
|
||||
|
||||
import { useNavigate } from "react-router-dom";
|
||||
//Constants
|
||||
const TAB_LIST = ["General settings", "Contents"];
|
||||
|
||||
@@ -22,8 +21,6 @@ const ERROR_TAB_MAPPING = [
|
||||
];
|
||||
|
||||
const CreateStatusPage = () => {
|
||||
//Redux state
|
||||
const { authToken, user } = useSelector((state) => state.auth);
|
||||
//Local state
|
||||
const [tab, setTab] = useState(0);
|
||||
const [progress, setProgress] = useState({ value: 0, isLoading: false });
|
||||
@@ -31,7 +28,7 @@ const CreateStatusPage = () => {
|
||||
isPublished: false,
|
||||
companyName: "",
|
||||
url: "/status/public",
|
||||
logo: null,
|
||||
logo: undefined,
|
||||
timezone: "America/Toronto",
|
||||
color: "#4169E1",
|
||||
monitors: [],
|
||||
@@ -40,7 +37,6 @@ const CreateStatusPage = () => {
|
||||
});
|
||||
const [errors, setErrors] = useState({});
|
||||
const [selectedMonitors, setSelectedMonitors] = useState([]);
|
||||
|
||||
// Refs
|
||||
const intervalRef = useRef(null);
|
||||
|
||||
@@ -49,6 +45,7 @@ const CreateStatusPage = () => {
|
||||
const [monitors, isLoading, networkError] = useMonitorsFetch();
|
||||
const [createStatusPage, createSatusIsLoading, createStatusPageNetworkError] =
|
||||
useCreateStatusPage();
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Handlers
|
||||
const handleFormChange = (e) => {
|
||||
@@ -78,7 +75,7 @@ const CreateStatusPage = () => {
|
||||
}));
|
||||
};
|
||||
|
||||
const handleImageChange = (event) => {
|
||||
const handleImageChange = useCallback((event) => {
|
||||
const img = event.target?.files?.[0];
|
||||
const newLogo = {
|
||||
src: URL.createObjectURL(img),
|
||||
@@ -100,7 +97,7 @@ const CreateStatusPage = () => {
|
||||
return { ...prev, value: prev.value + buffer };
|
||||
});
|
||||
}, 120);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const removeLogo = () => {
|
||||
setForm((prev) => ({
|
||||
@@ -125,6 +122,7 @@ const CreateStatusPage = () => {
|
||||
const success = await createStatusPage({ form });
|
||||
if (success) {
|
||||
createToast({ body: "Status page created successfully" });
|
||||
navigate("/status");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import { useState } from "react";
|
||||
import { networkService } from "../../../../main";
|
||||
import { createToast } from "../../../../Utils/toastUtils";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const useStatusPageDelete = (fetchStatusPage) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const { authToken } = useSelector((state) => state.auth);
|
||||
|
||||
const deleteStatusPage = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await networkService.deleteStatusPage({ authToken });
|
||||
setIsLoading(false);
|
||||
fetchStatusPage?.();
|
||||
return true;
|
||||
} catch (error) {
|
||||
createToast({
|
||||
body: error.message,
|
||||
});
|
||||
return false;
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return [deleteStatusPage, isLoading];
|
||||
};
|
||||
|
||||
export { useStatusPageDelete };
|
||||
@@ -1,35 +1,69 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
import { networkService } from "../../../../main";
|
||||
import { useSelector } from "react-redux";
|
||||
import { createToast } from "../../../../Utils/toastUtils";
|
||||
import { useTheme } from "@emotion/react";
|
||||
const getMonitorWithPercentage = (monitor, theme) => {
|
||||
let uptimePercentage = "";
|
||||
let percentageColor = "";
|
||||
|
||||
if (monitor.uptimePercentage !== undefined) {
|
||||
uptimePercentage =
|
||||
monitor.uptimePercentage === 0 ? "0" : (monitor.uptimePercentage * 100).toFixed(2);
|
||||
|
||||
percentageColor =
|
||||
monitor.uptimePercentage < 0.25
|
||||
? theme.palette.error.main
|
||||
: monitor.uptimePercentage < 0.5
|
||||
? theme.palette.warning.main
|
||||
: monitor.uptimePercentage < 0.75
|
||||
? theme.palette.success.main
|
||||
: theme.palette.success.main;
|
||||
}
|
||||
|
||||
return {
|
||||
...monitor,
|
||||
percentage: uptimePercentage,
|
||||
percentageColor,
|
||||
};
|
||||
};
|
||||
|
||||
const useStatusPageFetch = () => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
const [statusPage, setStatusPage] = useState(undefined);
|
||||
const [monitors, setMonitors] = useState(undefined);
|
||||
const { authToken } = useSelector((state) => state.auth);
|
||||
const theme = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchStatusPage = async () => {
|
||||
try {
|
||||
const res = await networkService.getStatusPage({ authToken });
|
||||
setStatusPage(res.data.data);
|
||||
} catch (error) {
|
||||
// If there is a 404, status page is not found
|
||||
if (error?.response?.status === 404) {
|
||||
setStatusPage(undefined);
|
||||
return;
|
||||
}
|
||||
createToast({ body: error.message });
|
||||
setNetworkError(true);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
const fetchStatusPage = useCallback(async () => {
|
||||
try {
|
||||
const response = await networkService.getStatusPage({ authToken });
|
||||
if (!response?.data?.data) return;
|
||||
const { statusPage, monitors } = response.data.data;
|
||||
setStatusPage(statusPage);
|
||||
|
||||
const monitorsWithPercentage = monitors.map((monitor) =>
|
||||
getMonitorWithPercentage(monitor, theme)
|
||||
);
|
||||
setMonitors(monitorsWithPercentage);
|
||||
} catch (error) {
|
||||
// If there is a 404, status page is not found
|
||||
if (error?.response?.status === 404) {
|
||||
setStatusPage(undefined);
|
||||
return;
|
||||
}
|
||||
};
|
||||
createToast({ body: error.message });
|
||||
setNetworkError(true);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [authToken, theme]);
|
||||
useEffect(() => {
|
||||
fetchStatusPage();
|
||||
}, [authToken]);
|
||||
}, [fetchStatusPage]);
|
||||
|
||||
return [statusPage, isLoading, networkError];
|
||||
return [statusPage, monitors, isLoading, networkError, fetchStatusPage];
|
||||
};
|
||||
|
||||
export { useStatusPageFetch };
|
||||
|
||||
@@ -1,17 +1,100 @@
|
||||
// Components
|
||||
import { Typography, Stack } from "@mui/material";
|
||||
import { Typography, Stack, Box, Button } from "@mui/material";
|
||||
import GenericFallback from "../../../Components/GenericFallback";
|
||||
import Fallback from "../../../Components/Fallback";
|
||||
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
|
||||
import StatusPageBarChart from "../../../Components/Charts/StatusPageBarChart";
|
||||
import Host from "../../Uptime/Monitors/Components/Host";
|
||||
import { StatusLabel } from "../../../Components/Label";
|
||||
import SettingsIcon from "../../../assets/icons/settings-bold.svg?react";
|
||||
import Image from "../../../Components/Image";
|
||||
// Utils
|
||||
import { useState, useEffect } from "react";
|
||||
import { useStatusPageFetch } from "./Hooks/useStatusPageFetch";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
|
||||
|
||||
import useUtils from "../../Uptime/Monitors/Hooks/useUtils";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useStatusPageDelete } from "./Hooks/useStatusPageDelete";
|
||||
const PublicStatus = () => {
|
||||
// Local state
|
||||
const [status, setStatus] = useState(undefined);
|
||||
|
||||
// Utils
|
||||
const theme = useTheme();
|
||||
const isAdmin = useIsAdmin();
|
||||
const [statusPage, isLoading, networkError] = useStatusPageFetch();
|
||||
const [statusPage, monitors, isLoading, networkError, fetchStatusPage] =
|
||||
useStatusPageFetch();
|
||||
const [deleteStatusPage, isDeleting] = useStatusPageDelete(fetchStatusPage);
|
||||
|
||||
const { determineState } = useUtils();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
// Setup
|
||||
const currentPath = location.pathname;
|
||||
let sx = { paddingLeft: theme.spacing(20), paddingRight: theme.spacing(20) };
|
||||
let AdminLink = undefined;
|
||||
|
||||
// Public status page
|
||||
if (currentPath === "/status/public") {
|
||||
sx = {
|
||||
paddingTop: theme.spacing(20),
|
||||
paddingLeft: "20vw",
|
||||
paddingRight: "20vw",
|
||||
};
|
||||
AdminLink = (
|
||||
<Box>
|
||||
<Typography
|
||||
className="forgot-p"
|
||||
display="inline-block"
|
||||
color={theme.palette.primary.contrastText}
|
||||
>
|
||||
Administrator?
|
||||
</Typography>
|
||||
<Typography
|
||||
component="span"
|
||||
color={theme.palette.accent.main}
|
||||
ml={theme.spacing(2)}
|
||||
sx={{ cursor: "pointer" }}
|
||||
onClick={() => navigate("/login")}
|
||||
>
|
||||
Login here
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Effects
|
||||
useEffect(() => {
|
||||
if (typeof monitors === "undefined") return;
|
||||
const monitorsStatus = {};
|
||||
if (monitors.every((monitor) => monitor.status === true)) {
|
||||
monitorsStatus.msg = "All systems operational";
|
||||
monitorsStatus.color = theme.palette.success.lowContrast;
|
||||
monitorsStatus.icon = <CheckCircleIcon />;
|
||||
}
|
||||
|
||||
if (monitors.every((monitor) => monitor.status === false)) {
|
||||
monitorsStatus.msg = "All systems down";
|
||||
monitorsStatus.color = theme.palette.error.lowContrast;
|
||||
}
|
||||
|
||||
if (monitors.some((monitor) => monitor.status === false)) {
|
||||
monitorsStatus.msg = "Degraded performance";
|
||||
monitorsStatus.color = theme.palette.warning.lowContrast;
|
||||
}
|
||||
|
||||
// Paused or unknown
|
||||
if (monitors.some((monitor) => typeof monitor.status === "undefined")) {
|
||||
monitorsStatus.msg = "Unknown status";
|
||||
monitorsStatus.color = theme.palette.warning.lowContrast;
|
||||
}
|
||||
setStatus(monitorsStatus);
|
||||
}, [monitors, theme]);
|
||||
|
||||
//Handlers
|
||||
|
||||
if (networkError === true) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
@@ -41,7 +124,118 @@ const PublicStatus = () => {
|
||||
);
|
||||
}
|
||||
|
||||
return <Stack gap={theme.spacing(10)}>Content Here</Stack>;
|
||||
return (
|
||||
<Stack
|
||||
gap={theme.spacing(10)}
|
||||
alignItems="center"
|
||||
sx={sx}
|
||||
>
|
||||
<Stack
|
||||
alignSelf="flex-start"
|
||||
direction="row"
|
||||
width="100%"
|
||||
gap={theme.spacing(2)}
|
||||
justifyContent="space-between"
|
||||
alignItems="flex-end"
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(8)}
|
||||
alignItems="flex-end"
|
||||
>
|
||||
<Image
|
||||
src={
|
||||
statusPage?.logo?.data
|
||||
? `data:image/png;base64,${statusPage?.logo?.data}`
|
||||
: undefined
|
||||
}
|
||||
alt={"Company logo"}
|
||||
maxWidth={"100px"}
|
||||
/>
|
||||
<Typography variant="h2">{statusPage?.companyName}</Typography>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Box>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={deleteStatusPage}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</Box>
|
||||
<Box>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={() => navigate(`/status/create`)}
|
||||
sx={{
|
||||
px: theme.spacing(5),
|
||||
"& svg": {
|
||||
mr: theme.spacing(3),
|
||||
"& path": {
|
||||
stroke: theme.palette.secondary.contrastText,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SettingsIcon /> Configure
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Typography variant="h2">Service status</Typography>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
gap={theme.spacing(2)}
|
||||
height={theme.spacing(30)}
|
||||
width={"100%"}
|
||||
backgroundColor={status?.color}
|
||||
borderRadius={theme.spacing(2)}
|
||||
>
|
||||
{status?.icon}
|
||||
<Typography>{status?.msg}</Typography>
|
||||
</Stack>
|
||||
{monitors?.map((monitor) => {
|
||||
const status = determineState(monitor);
|
||||
return (
|
||||
<Stack
|
||||
key={monitor._id}
|
||||
width="100%"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Host
|
||||
key={monitor._id}
|
||||
url={monitor.url}
|
||||
title={monitor.title}
|
||||
percentageColor={monitor.percentageColor}
|
||||
percentage={monitor.percentage}
|
||||
/>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(20)}
|
||||
>
|
||||
<StatusPageBarChart checks={monitor.checks.slice().reverse()} />
|
||||
<Box>
|
||||
<StatusLabel
|
||||
status={status}
|
||||
text={status}
|
||||
customStyles={{ textTransform: "capitalize" }}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
})}
|
||||
{AdminLink}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default PublicStatus;
|
||||
|
||||
@@ -183,6 +183,10 @@ const Routes = () => {
|
||||
path="/new-password-confirmed"
|
||||
element={<AuthNewPasswordConfirmed />}
|
||||
/>
|
||||
<Route
|
||||
path="/status/public"
|
||||
element={<PublicStatus />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="*"
|
||||
|
||||
@@ -902,6 +902,15 @@ class NetworkService {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async deleteStatusPage(config) {
|
||||
const { authToken } = config;
|
||||
return this.axiosInstance.delete(`/status-page`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default NetworkService;
|
||||
|
||||
Reference in New Issue
Block a user