Merge pull request #18 from uprockcom/feat/add-standard-monitors

feat: allow attaching standard monitors to depin status page
This commit is contained in:
Alexander Holliday authored and GitHub committed 2025-02-21 11:26:37 -08:00
commit c7284dc68a
6 files changed
+125 -10

No files matched your search

@@ -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"
@@ -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 "../../StatusPage/Status/Components/MonitorsList";
//Utils
import { useTheme } from "@mui/material/styles";
@@ -26,6 +27,7 @@ import { useStatusPageFetchByUrl } from "./Hooks/useStatusPageFetchByUrl";
import { useStatusPageDelete } from "../../StatusPage/Status/Hooks/useStatusPageDelete";
import { useNavigate } from "react-router-dom";
import { useLocation } from "react-router-dom";
const DistributedUptimeStatus = () => {
const { url } = useParams();
const location = useLocation();
@@ -38,6 +40,7 @@ const DistributedUptimeStatus = () => {
// Utils
const theme = useTheme();
const navigate = useNavigate();
const [
statusPageIsLoading,
statusPageNetworkError,
@@ -54,6 +57,7 @@ const DistributedUptimeStatus = () => {
const [deleteStatusPage, isDeleting] = useStatusPageDelete(() => {
navigate("/distributed-uptime");
}, url);
// Constants
const BREADCRUMBS = [
{ name: "Distributed Uptime", path: "/distributed-uptime" },
@@ -213,6 +217,7 @@ const DistributedUptimeStatus = () => {
monitor={monitor}
lastUpdateTrigger={lastUpdateTrigger}
/>
<MonitorsList monitors={statusPage?.subMonitors} />
<Footer />
<Dialog
// open={isOpen.deleteStats}
+2 -2
View File
@@ -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>
+10
View File
@@ -1041,6 +1041,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 +1060,7 @@ class NetworkService {
URL.revokeObjectURL(form.logo.src);
}
}
if (isCreate) {
return this.axiosInstance.post(`/status-page`, fd, {
headers: {
+1
View File
@@ -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(),