mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-03 06:59:32 -05:00
Merge branch 'develop' into status-page-subheader
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
// Components
|
||||
import { Stack, Box, Tooltip, Typography } from "@mui/material";
|
||||
|
||||
// Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useState, useEffect } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { formatDateWithTz } from "../../../Utils/timeUtils";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const PlaceholderCheck = ({ daysToShow }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
width={`calc(30vw / ${daysToShow})`}
|
||||
height="100%"
|
||||
backgroundColor={theme.palette.primary.lowContrast}
|
||||
sx={{
|
||||
borderRadius: theme.spacing(1.5),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
PlaceholderCheck.propTypes = {
|
||||
daysToShow: PropTypes.number,
|
||||
};
|
||||
|
||||
const Check = ({ check, daysToShow }) => {
|
||||
const [animate, setAnimate] = useState(false);
|
||||
|
||||
const theme = useTheme();
|
||||
useEffect(() => {
|
||||
setAnimate(true);
|
||||
}, []);
|
||||
const uiTimezone = useSelector((state) => state.ui.timezone);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
<>
|
||||
<Typography>
|
||||
{formatDateWithTz(check._id, "ddd, MMMM D, YYYY", uiTimezone)}
|
||||
</Typography>
|
||||
<Box mt={theme.spacing(2)}>
|
||||
<Stack
|
||||
display="inline-flex"
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
gap={theme.spacing(4)}
|
||||
>
|
||||
<Typography
|
||||
component="span"
|
||||
sx={{ opacity: 0.8 }}
|
||||
>
|
||||
Uptime percentage
|
||||
</Typography>
|
||||
<Typography component="span">
|
||||
{check.upPercentage.toFixed(2)}
|
||||
<Typography
|
||||
component="span"
|
||||
sx={{ opacity: 0.8 }}
|
||||
>
|
||||
{" "}
|
||||
%
|
||||
</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 / ${daysToShow})`}
|
||||
height="100%"
|
||||
backgroundColor={theme.palette.error.lowContrast}
|
||||
sx={{
|
||||
borderRadius: theme.spacing(1.5),
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
position="absolute"
|
||||
bottom={0}
|
||||
width="100%"
|
||||
height={`${animate ? check.upPercentage : 0}%`}
|
||||
backgroundColor={theme.palette.success.lowContrast}
|
||||
sx={{
|
||||
borderRadius: theme.spacing(1.5),
|
||||
transition: "height 600ms cubic-bezier(0.4, 0, 0.2, 1)",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
Check.propTypes = {
|
||||
check: PropTypes.object,
|
||||
daysToShow: PropTypes.number,
|
||||
};
|
||||
|
||||
const DePINStatusPageBarChart = ({ checks = [], daysToShow = 30 }) => {
|
||||
if (checks.length !== daysToShow) {
|
||||
const placeholders = Array(daysToShow - checks.length).fill("placeholder");
|
||||
checks = [...checks, ...placeholders];
|
||||
}
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
width="100%"
|
||||
flexWrap="nowrap"
|
||||
height="50px"
|
||||
>
|
||||
{checks.map((check) => {
|
||||
if (check === "placeholder") {
|
||||
return (
|
||||
<PlaceholderCheck
|
||||
key={Math.random()}
|
||||
daysToShow={daysToShow}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Check
|
||||
key={Math.random()}
|
||||
check={check}
|
||||
daysToShow={daysToShow}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
DePINStatusPageBarChart.propTypes = {
|
||||
checks: PropTypes.array,
|
||||
daysToShow: PropTypes.number,
|
||||
};
|
||||
|
||||
export default DePINStatusPageBarChart;
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
import { networkService } from "../../../../main";
|
||||
import { useSelector } from "react-redux";
|
||||
import { createToast } from "../../../../Utils/toastUtils";
|
||||
|
||||
const useDUStatusPageFetch = (isCreate = false, url) => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
const [statusPage, setStatusPage] = useState(undefined);
|
||||
const { authToken } = useSelector((state) => state.auth);
|
||||
const fetchStatusPage = useCallback(async () => {
|
||||
try {
|
||||
const response = await networkService.getStatusPageByUrl({
|
||||
authToken,
|
||||
url,
|
||||
type: "distributed",
|
||||
});
|
||||
|
||||
if (!response?.data?.data) return;
|
||||
|
||||
const statusPage = response.data.data;
|
||||
setStatusPage(statusPage);
|
||||
} 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, url]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isCreate === true) {
|
||||
return;
|
||||
}
|
||||
fetchStatusPage();
|
||||
}, [isCreate, fetchStatusPage]);
|
||||
|
||||
return [statusPage, isLoading, networkError, fetchStatusPage];
|
||||
};
|
||||
|
||||
export { useDUStatusPageFetch };
|
||||
@@ -7,26 +7,34 @@ import VisuallyHiddenInput from "./Components/VisuallyHiddenInput";
|
||||
import Image from "../../../Components/Image";
|
||||
import LogoPlaceholder from "../../../assets/Images/logo_placeholder.svg";
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
import Search from "../../../Components/Inputs/Search";
|
||||
import MonitorList from "../../StatusPage/Create/Components/MonitorList";
|
||||
// Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useStatusPageFetch } from "../../StatusPage/Status/Hooks/useStatusPageFetch";
|
||||
import { useDUStatusPageFetch } from "./Hooks/useDUStatusPageFetch";
|
||||
import { useCreateStatusPage } from "../../StatusPage/Create/Hooks/useCreateStatusPage";
|
||||
import { statusPageValidation } from "../../../Validation/validation";
|
||||
import { buildErrors } from "../../../Validation/error";
|
||||
import { createToast } from "../../../Utils/toastUtils";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useMonitorsFetch } from "../../StatusPage/Create/Hooks/useMonitorsFetch";
|
||||
|
||||
const CreateStatus = () => {
|
||||
const theme = useTheme();
|
||||
const { monitorId, url } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const isCreate = typeof url === "undefined";
|
||||
|
||||
const [createStatusPage, isLoading, networkError] = useCreateStatusPage(isCreate);
|
||||
|
||||
const [statusPage, statusPageMonitors, statusPageIsLoading, statusPageNetworkError] =
|
||||
useStatusPageFetch(isCreate, url);
|
||||
const [statusPage, statusPageIsLoading, statusPageNetworkError] = useDUStatusPageFetch(
|
||||
isCreate,
|
||||
url
|
||||
);
|
||||
|
||||
const [monitors, monitorsIsLoading, monitorsNetworkError] = useMonitorsFetch();
|
||||
|
||||
const BREADCRUMBS = [
|
||||
{ name: "distributed uptime", path: "/distributed-uptime" },
|
||||
@@ -43,6 +51,8 @@ const CreateStatus = () => {
|
||||
monitors: [monitorId],
|
||||
});
|
||||
const [errors, setErrors] = useState({});
|
||||
const [search, setSearch] = useState("");
|
||||
const [selectedMonitors, setSelectedMonitors] = useState([]);
|
||||
|
||||
const handleFormChange = (e) => {
|
||||
const { name, value, checked, type } = e.target;
|
||||
@@ -62,6 +72,16 @@ const CreateStatus = () => {
|
||||
setForm({ ...form, [name]: value });
|
||||
};
|
||||
|
||||
const handleMonitorsChange = (selectedMonitors) => {
|
||||
handleFormChange({
|
||||
target: {
|
||||
name: "subMonitors",
|
||||
value: selectedMonitors.map((monitor) => monitor._id),
|
||||
},
|
||||
});
|
||||
setSelectedMonitors(selectedMonitors);
|
||||
};
|
||||
|
||||
const handleImageUpload = (e) => {
|
||||
const img = e.target?.files?.[0];
|
||||
setForm((prev) => ({
|
||||
@@ -73,19 +93,20 @@ const CreateStatus = () => {
|
||||
let logoToSubmit = undefined;
|
||||
|
||||
// Handle image
|
||||
if (typeof form.logo !== "undefined") {
|
||||
if (typeof form.logo !== "undefined" && typeof form.logo.src === "undefined") {
|
||||
logoToSubmit = {
|
||||
src: URL.createObjectURL(form.logo),
|
||||
name: form.logo.name,
|
||||
type: form.logo.type,
|
||||
size: form.logo.size,
|
||||
};
|
||||
} else if (typeof form.logo !== "undefined") {
|
||||
logoToSubmit = form.logo;
|
||||
}
|
||||
const formToSubmit = { ...form };
|
||||
if (typeof logoToSubmit !== "undefined") {
|
||||
formToSubmit.logo = logoToSubmit;
|
||||
}
|
||||
|
||||
// Validate
|
||||
const { error } = statusPageValidation.validate(formToSubmit, { abortEarly: false });
|
||||
if (typeof error === "undefined") {
|
||||
@@ -127,12 +148,20 @@ const CreateStatus = () => {
|
||||
companyName: statusPage?.companyName,
|
||||
isPublished: statusPage?.isPublished,
|
||||
timezone: statusPage?.timezone,
|
||||
monitors: statusPageMonitors.map((monitor) => monitor._id),
|
||||
monitors: statusPage?.monitors,
|
||||
subMonitors: statusPage?.subMonitors.map((monitor) => monitor._id),
|
||||
color: statusPage?.color,
|
||||
logo: newLogo,
|
||||
};
|
||||
});
|
||||
}, [isCreate, statusPage, statusPageMonitors]);
|
||||
setSelectedMonitors(statusPage?.subMonitors);
|
||||
}, [isCreate, statusPage]);
|
||||
|
||||
const imgSrc = form?.logo?.src
|
||||
? form.logo.src
|
||||
: form.logo
|
||||
? URL.createObjectURL(form.logo)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
@@ -212,7 +241,7 @@ const CreateStatus = () => {
|
||||
alignItems="center"
|
||||
>
|
||||
<Image
|
||||
src={form.logo ? URL.createObjectURL(form.logo) : undefined}
|
||||
src={imgSrc}
|
||||
alt="Logo"
|
||||
minWidth={"300px"}
|
||||
minHeight={"100px"}
|
||||
@@ -234,6 +263,30 @@ const CreateStatus = () => {
|
||||
</Box>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Stack>
|
||||
<Typography component="h2">Standard Monitors</Typography>
|
||||
<Typography component="p">
|
||||
Attach standard monitors to your status page.
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack gap={theme.spacing(18)}>
|
||||
<Search
|
||||
options={monitors ?? []}
|
||||
multiple={true}
|
||||
filteredBy="name"
|
||||
value={selectedMonitors}
|
||||
inputValue={search}
|
||||
handleInputChange={setSearch}
|
||||
handleChange={handleMonitorsChange}
|
||||
/>
|
||||
<MonitorList
|
||||
monitors={monitors}
|
||||
selectedMonitors={selectedMonitors}
|
||||
setSelectedMonitors={handleMonitorsChange}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="flex-end"
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Components
|
||||
import { Stack, Box } from "@mui/material";
|
||||
import Host from "../../../../../Components/Host";
|
||||
import DePINStatusPageBarChart from "../../../../../Components/Charts/DePINStatusPageBarChart";
|
||||
import { StatusLabel } from "../../../../../Components/Label";
|
||||
|
||||
//Utils
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import useUtils from "../../../../Uptime/Monitors/Hooks/useUtils";
|
||||
import PropTypes from "prop-types";
|
||||
const MonitorsList = ({
|
||||
isLoading = false,
|
||||
shouldRender = true,
|
||||
monitors = [],
|
||||
timeFrame,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const { determineState } = useUtils();
|
||||
return (
|
||||
<>
|
||||
{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.name}
|
||||
percentageColor={monitor.percentageColor}
|
||||
percentage={monitor.percentage}
|
||||
/>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(20)}
|
||||
>
|
||||
<Box flex={9}>
|
||||
<DePINStatusPageBarChart
|
||||
checks={monitor?.checks?.slice().reverse()}
|
||||
daysToShow={timeFrame}
|
||||
/>
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<StatusLabel
|
||||
status={status}
|
||||
text={status}
|
||||
customStyles={{ textTransform: "capitalize" }}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
MonitorsList.propTypes = {
|
||||
monitors: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
export default MonitorsList;
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Stack, Button, ButtonGroup } from "@mui/material";
|
||||
import { RowContainer } from "../../../../../Components/StandardContainer";
|
||||
import { useTheme } from "@emotion/react";
|
||||
|
||||
const TimeFrameHeader = ({ timeFrame, setTimeFrame }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(timeFrame === 30).toString()}
|
||||
onClick={() => setTimeFrame(30)}
|
||||
>
|
||||
30 days
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(timeFrame === 60).toString()}
|
||||
onClick={() => setTimeFrame(60)}
|
||||
>
|
||||
60 days
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(timeFrame === 90).toString()}
|
||||
onClick={() => setTimeFrame(90)}
|
||||
>
|
||||
90 days
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default TimeFrameHeader;
|
||||
@@ -2,25 +2,41 @@ import { useState, useEffect } from "react";
|
||||
import { networkService } from "../../../../main";
|
||||
import { createToast } from "../../../../Utils/toastUtils";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils";
|
||||
|
||||
const useStatusPageFetchByUrl = ({ url }) => {
|
||||
const useStatusPageFetchByUrl = ({ url, timeFrame }) => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
const [statusPage, setStatusPage] = useState(undefined);
|
||||
const [monitorId, setMonitorId] = useState(undefined);
|
||||
const [isPublished, setIsPublished] = useState(false);
|
||||
const { authToken } = useSelector((state) => state.auth);
|
||||
const theme = useTheme();
|
||||
const { getMonitorWithPercentage } = useMonitorUtils();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchStatusPageByUrl = async () => {
|
||||
try {
|
||||
const response = await networkService.getStatusPageByUrl({
|
||||
const response = await networkService.getDistributedStatusPageByUrl({
|
||||
authToken,
|
||||
url,
|
||||
type: "distributed",
|
||||
timeFrame,
|
||||
});
|
||||
if (!response?.data?.data) return;
|
||||
const statusPage = response.data.data;
|
||||
setStatusPage(statusPage);
|
||||
|
||||
const monitorsWithPercentage = statusPage?.subMonitors.map((monitor) =>
|
||||
getMonitorWithPercentage(monitor, theme)
|
||||
);
|
||||
|
||||
const statusPageWithSubmonitorPercentages = {
|
||||
...statusPage,
|
||||
subMonitors: monitorsWithPercentage,
|
||||
};
|
||||
setStatusPage(statusPageWithSubmonitorPercentages);
|
||||
|
||||
setMonitorId(statusPage?.monitors[0]);
|
||||
setIsPublished(statusPage?.isPublished);
|
||||
} catch (error) {
|
||||
@@ -33,7 +49,7 @@ const useStatusPageFetchByUrl = ({ url }) => {
|
||||
}
|
||||
};
|
||||
fetchStatusPageByUrl();
|
||||
}, [authToken, url]);
|
||||
}, [authToken, url, getMonitorWithPercentage, theme, timeFrame]);
|
||||
|
||||
return [isLoading, networkError, statusPage, monitorId, isPublished];
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ import UptLogo from "../../../assets/icons/upt_logo.png";
|
||||
import PeopleAltOutlinedIcon from "@mui/icons-material/PeopleAltOutlined";
|
||||
import InfoBox from "../../../Components/InfoBox";
|
||||
import StatusHeader from "../../DistributedUptime/Details/Components/StatusHeader";
|
||||
import MonitorsList from "./Components/MonitorsList";
|
||||
|
||||
//Utils
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
@@ -27,6 +28,8 @@ import { useStatusPageDelete } from "../../StatusPage/Status/Hooks/useStatusPage
|
||||
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import TimeFrameHeader from "./Components/TimeframeHeader";
|
||||
|
||||
import SubHeader from "../../DistributedUptime/Details/Components/Subheader";
|
||||
const DistributedUptimeStatus = () => {
|
||||
const { url } = useParams();
|
||||
@@ -37,9 +40,11 @@ const DistributedUptimeStatus = () => {
|
||||
// Local State
|
||||
const [dateRange, setDateRange] = useState("day");
|
||||
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
||||
const [timeFrame, setTimeFrame] = useState(30);
|
||||
// Utils
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [
|
||||
statusPageIsLoading,
|
||||
statusPageNetworkError,
|
||||
@@ -48,6 +53,7 @@ const DistributedUptimeStatus = () => {
|
||||
isPublished,
|
||||
] = useStatusPageFetchByUrl({
|
||||
url,
|
||||
timeFrame,
|
||||
});
|
||||
|
||||
const [isLoading, networkError, connectionStatus, monitor, lastUpdateTrigger] =
|
||||
@@ -56,6 +62,7 @@ const DistributedUptimeStatus = () => {
|
||||
const [deleteStatusPage, isDeleting] = useStatusPageDelete(() => {
|
||||
navigate("/distributed-uptime");
|
||||
}, url);
|
||||
|
||||
// Constants
|
||||
const BREADCRUMBS = [
|
||||
{ name: "Distributed Uptime", path: "/distributed-uptime" },
|
||||
@@ -223,6 +230,14 @@ const DistributedUptimeStatus = () => {
|
||||
monitor={monitor}
|
||||
lastUpdateTrigger={lastUpdateTrigger}
|
||||
/>
|
||||
<TimeFrameHeader
|
||||
timeFrame={timeFrame}
|
||||
setTimeFrame={setTimeFrame}
|
||||
/>
|
||||
<MonitorsList
|
||||
monitors={statusPage?.subMonitors}
|
||||
timeFrame={timeFrame}
|
||||
/>
|
||||
<Footer />
|
||||
<Dialog
|
||||
// open={isOpen.deleteStats}
|
||||
|
||||
@@ -139,7 +139,6 @@ const Settings = () => {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
createToast({ body: "Failed to save settings" });
|
||||
} finally {
|
||||
setChecksIsLoading(false);
|
||||
@@ -368,7 +367,8 @@ const Settings = () => {
|
||||
<Box>
|
||||
<Typography component="h1">Wallet</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2) }}>
|
||||
Connect your wallet here. This is required for the Distributed Uptime monitor to connect to multiple nodes globally.
|
||||
Connect your wallet here. This is required for the Distributed Uptime
|
||||
monitor to connect to multiple nodes globally.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { StatusLabel } from "../../../../../Components/Label";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import useUtils from "../../../../Uptime/Monitors/Hooks/useUtils";
|
||||
import PropTypes from "prop-types";
|
||||
const MonitorsList = ({ monitors = [] }) => {
|
||||
const MonitorsList = ({ isLoading = false, shouldRender = true, monitors = [] }) => {
|
||||
const theme = useTheme();
|
||||
const { determineState } = useUtils();
|
||||
return (
|
||||
@@ -24,7 +24,7 @@ const MonitorsList = ({ monitors = [] }) => {
|
||||
<Host
|
||||
key={monitor._id}
|
||||
url={monitor.url}
|
||||
title={monitor.title}
|
||||
title={monitor.name}
|
||||
percentageColor={monitor.percentageColor}
|
||||
percentage={monitor.percentage}
|
||||
/>
|
||||
|
||||
@@ -1011,6 +1011,21 @@ class NetworkService {
|
||||
},
|
||||
});
|
||||
}
|
||||
async getDistributedStatusPageByUrl(config) {
|
||||
const { authToken, url, type, timeFrame } = config;
|
||||
const params = new URLSearchParams();
|
||||
params.append("type", type);
|
||||
params.append("timeFrame", timeFrame);
|
||||
return this.axiosInstance.get(
|
||||
`/status-page/distributed/${url}?${params.toString()}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async getStatusPagesByTeamId(config) {
|
||||
const { authToken, teamId } = config;
|
||||
@@ -1041,6 +1056,15 @@ class NetworkService {
|
||||
form.monitors.forEach((monitorId) => {
|
||||
fd.append("monitors[]", monitorId);
|
||||
});
|
||||
// Handle subMonitors, even if it's an empty array
|
||||
if (form.subMonitors && form.subMonitors.length > 0) {
|
||||
form.subMonitors.forEach((monitorId) => {
|
||||
fd.append("subMonitors[]", monitorId);
|
||||
});
|
||||
} else {
|
||||
fd.append("deleteSubmonitors", true);
|
||||
}
|
||||
|
||||
if (form?.logo?.src && form?.logo?.src !== "") {
|
||||
const imageResult = await axios.get(form.logo.src, {
|
||||
responseType: "blob",
|
||||
@@ -1051,6 +1075,7 @@ class NetworkService {
|
||||
URL.revokeObjectURL(form.logo.src);
|
||||
}
|
||||
}
|
||||
|
||||
if (isCreate) {
|
||||
return this.axiosInstance.post(`/status-page`, fd, {
|
||||
headers: {
|
||||
|
||||
@@ -226,6 +226,7 @@ const statusPageValidation = joi.object({
|
||||
"array.empty": "At least one monitor is required",
|
||||
"any.required": "At least one monitor is required",
|
||||
}),
|
||||
subMonitors: joi.array().optional(),
|
||||
logo: logoImageValidation,
|
||||
showUptimePercentage: joi.boolean(),
|
||||
showCharts: joi.boolean(),
|
||||
|
||||
Reference in New Issue
Block a user