mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-01-08 18:59:43 -06:00
purge distributed uptime
This commit is contained in:
@@ -23,7 +23,6 @@ import UserSvg from "../../assets/icons/user.svg?react";
|
||||
import TeamSvg from "../../assets/icons/user-two.svg?react";
|
||||
import LogoutSvg from "../../assets/icons/logout.svg?react";
|
||||
import Support from "../../assets/icons/support.svg?react";
|
||||
import Account from "../../assets/icons/user-edit.svg?react";
|
||||
import Maintenance from "../../assets/icons/maintenance.svg?react";
|
||||
import Monitors from "../../assets/icons/monitors.svg?react";
|
||||
import Incidents from "../../assets/icons/incidents.svg?react";
|
||||
@@ -37,10 +36,8 @@ import ArrowLeft from "../../assets/icons/left-arrow.svg?react";
|
||||
import DotsVertical from "../../assets/icons/dots-vertical.svg?react";
|
||||
import ChangeLog from "../../assets/icons/changeLog.svg?react";
|
||||
import Docs from "../../assets/icons/docs.svg?react";
|
||||
import Folder from "../../assets/icons/folder.svg?react";
|
||||
import StatusPages from "../../assets/icons/status-pages.svg?react";
|
||||
import Discussions from "../../assets/icons/discussions.svg?react";
|
||||
import DistributedUptimeIcon from "../../assets/icons/distributed-uptime.svg?react";
|
||||
import "./index.css";
|
||||
|
||||
// Utils
|
||||
@@ -56,11 +53,7 @@ const getMenu = (t) => [
|
||||
{ name: t("menu.uptime"), path: "uptime", icon: <Monitors /> },
|
||||
{ name: t("menu.pagespeed"), path: "pagespeed", icon: <PageSpeed /> },
|
||||
{ name: t("menu.infrastructure"), path: "infrastructure", icon: <Integrations /> },
|
||||
{
|
||||
name: t("menu.distributedUptime"),
|
||||
path: "distributed-uptime",
|
||||
icon: <DistributedUptimeIcon />,
|
||||
},
|
||||
|
||||
{ name: t("menu.incidents"), path: "incidents", icon: <Incidents /> },
|
||||
|
||||
{ name: t("menu.statusPages"), path: "status", icon: <StatusPages /> },
|
||||
@@ -95,14 +88,13 @@ const URL_MAP = {
|
||||
support: "https://discord.com/invite/NAb6H3UTjK",
|
||||
discussions: "https://github.com/bluewave-labs/checkmate/discussions",
|
||||
docs: "https://bluewavelabs.gitbook.io/checkmate",
|
||||
changelog: "https://github.com/bluewave-labs/bluewave-uptime/releases",
|
||||
changelog: "https://github.com/bluewave-labs/checkmate/releases",
|
||||
};
|
||||
|
||||
const PATH_MAP = {
|
||||
monitors: "Dashboard",
|
||||
pagespeed: "Dashboard",
|
||||
infrastructure: "Dashboard",
|
||||
["distributed-uptime"]: "Dashboard",
|
||||
account: "Account",
|
||||
settings: "Settings",
|
||||
};
|
||||
@@ -375,12 +367,6 @@ function Sidebar() {
|
||||
}}
|
||||
>
|
||||
{menu.map((item) => {
|
||||
if (
|
||||
item.path === "distributed-uptime" &&
|
||||
distributedUptimeEnabled === false
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return item.path ? (
|
||||
/* If item has a path */
|
||||
<Tooltip
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
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 "./useMonitorUtils";
|
||||
|
||||
const useFetchDepinStatusPage = ({ url, timeFrame, isCreate = false }) => {
|
||||
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(() => {
|
||||
if (isCreate) return;
|
||||
const fetchStatusPageByUrl = async () => {
|
||||
try {
|
||||
const response = await networkService.getDistributedStatusPageByUrl({
|
||||
authToken,
|
||||
url,
|
||||
type: "distributed",
|
||||
timeFrame,
|
||||
});
|
||||
if (!response?.data?.data) return;
|
||||
const statusPage = response.data.data;
|
||||
|
||||
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) {
|
||||
setNetworkError(true);
|
||||
createToast({
|
||||
body: error.message,
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
fetchStatusPageByUrl();
|
||||
}, [authToken, url, getMonitorWithPercentage, theme, timeFrame, isCreate]);
|
||||
|
||||
return [isLoading, networkError, statusPage, monitorId, isPublished];
|
||||
};
|
||||
|
||||
export { useFetchDepinStatusPage };
|
||||
@@ -1,96 +0,0 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { networkService } from "../main";
|
||||
import { createToast } from "../Utils/toastUtils";
|
||||
|
||||
const useSubscribeToDepinDetails = ({ monitorId, isPublic, isPublished, dateRange }) => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [connectionStatus, setConnectionStatus] = useState(undefined);
|
||||
const [retryCount, setRetryCount] = useState(0);
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
const [monitor, setMonitor] = useState(undefined);
|
||||
const [lastUpdateTrigger, setLastUpdateTrigger] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof monitorId === "undefined") {
|
||||
return;
|
||||
}
|
||||
// If this page is public and not published, don't subscribe to details
|
||||
if (isPublic && isPublished === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get initial data
|
||||
|
||||
const fetchInitialData = async () => {
|
||||
try {
|
||||
const res = await networkService.getDistributedUptimeDetails({
|
||||
monitorId,
|
||||
dateRange: dateRange,
|
||||
normalize: true,
|
||||
isPublic,
|
||||
});
|
||||
const responseData = res?.data?.data;
|
||||
|
||||
if (typeof responseData === "undefined") {
|
||||
throw new Error("No data");
|
||||
}
|
||||
setConnectionStatus("up");
|
||||
setLastUpdateTrigger(Date.now());
|
||||
setMonitor(responseData);
|
||||
} catch (error) {
|
||||
setNetworkError(true);
|
||||
setConnectionStatus("down");
|
||||
createToast({
|
||||
body: error.message,
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchInitialData();
|
||||
|
||||
try {
|
||||
const cleanup = networkService.subscribeToDistributedUptimeDetails({
|
||||
monitorId,
|
||||
dateRange: dateRange,
|
||||
normalize: true,
|
||||
onUpdate: (data) => {
|
||||
if (isLoading === true) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
if (networkError === true) {
|
||||
setNetworkError(false);
|
||||
}
|
||||
setLastUpdateTrigger(Date.now());
|
||||
setMonitor(data.monitor);
|
||||
},
|
||||
onOpen: () => {
|
||||
setConnectionStatus("up");
|
||||
setRetryCount(0); // Reset retry count on successful connection
|
||||
},
|
||||
onError: () => {
|
||||
setIsLoading(false);
|
||||
setNetworkError(true);
|
||||
setConnectionStatus("down");
|
||||
},
|
||||
});
|
||||
return cleanup;
|
||||
} catch (error) {
|
||||
setNetworkError(true);
|
||||
}
|
||||
}, [
|
||||
dateRange,
|
||||
monitorId,
|
||||
retryCount,
|
||||
setConnectionStatus,
|
||||
networkError,
|
||||
isLoading,
|
||||
isPublic,
|
||||
isPublished,
|
||||
]);
|
||||
|
||||
return [isLoading, networkError, connectionStatus, monitor, lastUpdateTrigger];
|
||||
};
|
||||
|
||||
export { useSubscribeToDepinDetails };
|
||||
@@ -1,95 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { networkService } from "../main";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useMonitorUtils } from "./useMonitorUtils";
|
||||
import { createToast } from "../Utils/toastUtils";
|
||||
|
||||
const useSubscribeToDepinMonitors = (page, rowsPerPage) => {
|
||||
// Redux
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
|
||||
// Local state
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
const [count, setCount] = useState(undefined);
|
||||
const [monitors, setMonitors] = useState(undefined);
|
||||
|
||||
const theme = useTheme();
|
||||
const { getMonitorWithPercentage } = useMonitorUtils();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchInitialData = async () => {
|
||||
try {
|
||||
const initialDataRes = await networkService.getDistributedUptimeMonitors({
|
||||
teamId: user.teamId,
|
||||
limit: 25,
|
||||
types: ["distributed_http"],
|
||||
page,
|
||||
rowsPerPage,
|
||||
});
|
||||
|
||||
const { count, monitors } = initialDataRes?.data?.data ?? {};
|
||||
const responseData = initialDataRes?.data?.data;
|
||||
if (typeof responseData === "undefined") throw new Error("No data");
|
||||
|
||||
const mappedMonitors = monitors?.map((monitor) =>
|
||||
getMonitorWithPercentage(monitor, theme)
|
||||
);
|
||||
|
||||
setMonitors(mappedMonitors);
|
||||
setCount(count?.monitorsCount ?? 0);
|
||||
} catch (error) {
|
||||
setNetworkError(true);
|
||||
createToast({
|
||||
body: error.message,
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchInitialData();
|
||||
|
||||
try {
|
||||
const cleanup = networkService.subscribeToDistributedUptimeMonitors({
|
||||
teamId: user.teamId,
|
||||
limit: 25,
|
||||
types: [
|
||||
typeof import.meta.env.VITE_DEPIN_TESTING === "undefined"
|
||||
? "distributed_http"
|
||||
: "distributed_test",
|
||||
],
|
||||
page,
|
||||
rowsPerPage,
|
||||
filter: null,
|
||||
field: null,
|
||||
order: null,
|
||||
onUpdate: (data) => {
|
||||
if (isLoading === true) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
const { count, monitors } = data;
|
||||
const mappedMonitors = monitors.map((monitor) =>
|
||||
getMonitorWithPercentage(monitor, theme)
|
||||
);
|
||||
setMonitors(mappedMonitors);
|
||||
setCount(count?.monitorsCount ?? 0);
|
||||
},
|
||||
onError: () => {
|
||||
setIsLoading(false);
|
||||
setNetworkError(true);
|
||||
},
|
||||
});
|
||||
|
||||
return cleanup;
|
||||
} catch (error) {
|
||||
createToast({
|
||||
body: error.message,
|
||||
});
|
||||
setNetworkError(true);
|
||||
}
|
||||
}, [user, getMonitorWithPercentage, theme, isLoading, page, rowsPerPage]);
|
||||
return [monitors, count, isLoading, networkError];
|
||||
};
|
||||
export { useSubscribeToDepinMonitors };
|
||||
@@ -1,31 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import { networkService } from "../../../../main";
|
||||
import { useSelector } from "react-redux";
|
||||
import { createToast } from "../../../../Utils/toastUtils";
|
||||
|
||||
const useCreateDistributedUptimeMonitor = ({ isCreate, monitorId }) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
const createDistributedUptimeMonitor = async ({ form }) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (isCreate) {
|
||||
await networkService.createMonitor({ monitor: form });
|
||||
} else {
|
||||
await networkService.updateMonitor({ monitor: form, monitorId });
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
setNetworkError(true);
|
||||
createToast({ body: error?.response?.data?.msg ?? error.message });
|
||||
return false;
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return [createDistributedUptimeMonitor, isLoading, networkError];
|
||||
};
|
||||
|
||||
export { useCreateDistributedUptimeMonitor };
|
||||
@@ -1,31 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { networkService } from "../../../../main";
|
||||
import { createToast } from "../../../../Utils/toastUtils";
|
||||
|
||||
export const useMonitorFetch = ({ monitorId, isCreate }) => {
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [monitor, setMonitor] = useState(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMonitors = async () => {
|
||||
try {
|
||||
if (isCreate) return;
|
||||
const res = await networkService.getUptimeDetailsById({
|
||||
monitorId: monitorId,
|
||||
normalize: true,
|
||||
});
|
||||
setMonitor(res?.data?.data ?? {});
|
||||
} catch (error) {
|
||||
setNetworkError(true);
|
||||
createToast({ body: error.message });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
fetchMonitors();
|
||||
}, [monitorId, isCreate]);
|
||||
return [monitor, isLoading, networkError];
|
||||
};
|
||||
|
||||
export default useMonitorFetch;
|
||||
@@ -1,349 +0,0 @@
|
||||
// Components
|
||||
import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material";
|
||||
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
import ConfigBox from "../../../Components/ConfigBox";
|
||||
import TextInput from "../../../Components/Inputs/TextInput";
|
||||
import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments";
|
||||
import Radio from "../../../Components/Inputs/Radio";
|
||||
import Checkbox from "../../../Components/Inputs/Checkbox";
|
||||
import Select from "../../../Components/Inputs/Select";
|
||||
import { createToast } from "../../../Utils/toastUtils";
|
||||
|
||||
// Utility
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
import { monitorValidation } from "../../../Validation/validation";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useCreateDistributedUptimeMonitor } from "./Hooks/useCreateDistributedUptimeMonitor";
|
||||
import { useMonitorFetch } from "./Hooks/useMonitorFetch";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
// Constants
|
||||
const MS_PER_MINUTE = 60000;
|
||||
const SELECT_VALUES = [
|
||||
{ _id: 1, name: "1 minute" },
|
||||
{ _id: 2, name: "2 minutes" },
|
||||
{ _id: 3, name: "3 minutes" },
|
||||
{ _id: 4, name: "4 minutes" },
|
||||
{ _id: 5, name: "5 minutes" },
|
||||
];
|
||||
|
||||
const parseUrl = (url) => {
|
||||
try {
|
||||
return new URL(url);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const CreateDistributedUptime = () => {
|
||||
const { monitorId } = useParams();
|
||||
const isCreate = typeof monitorId === "undefined";
|
||||
const { t } = useTranslation();
|
||||
|
||||
let BREADCRUMBS = [
|
||||
{ name: `distributed uptime`, path: "/distributed-uptime" },
|
||||
...(isCreate ? [] : [{ name: "details", path: `/distributed-uptime/${monitorId}` }]),
|
||||
{ name: isCreate ? "create" : "configure", path: `` },
|
||||
];
|
||||
|
||||
// Redux state
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
|
||||
// Local state
|
||||
const [https, setHttps] = useState(true);
|
||||
const [notifications, setNotifications] = useState([]);
|
||||
const [form, setForm] = useState({
|
||||
type: "distributed_http",
|
||||
name: "",
|
||||
url: "",
|
||||
interval: 1,
|
||||
});
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
//utils
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const [createDistributedUptimeMonitor, isLoading, networkError] =
|
||||
useCreateDistributedUptimeMonitor({ isCreate, monitorId });
|
||||
|
||||
const [monitor, monitorIsLoading, monitorNetworkError] = useMonitorFetch({
|
||||
monitorId,
|
||||
isCreate,
|
||||
});
|
||||
|
||||
// Effect to set monitor to fetched monitor
|
||||
useEffect(() => {
|
||||
if (typeof monitor !== "undefined") {
|
||||
const parsedUrl = parseUrl(monitor?.url);
|
||||
const protocol = parsedUrl?.protocol?.replace(":", "") || "";
|
||||
setHttps(protocol === "https");
|
||||
|
||||
const newForm = {
|
||||
name: monitor.name,
|
||||
interval: monitor.interval / MS_PER_MINUTE,
|
||||
url: parsedUrl.host,
|
||||
type: monitor.type,
|
||||
};
|
||||
|
||||
setForm(newForm);
|
||||
}
|
||||
}, [monitor]);
|
||||
|
||||
// Handlers
|
||||
const handleCreateMonitor = async () => {
|
||||
const monitorToSubmit = { ...form };
|
||||
|
||||
// Prepend protocol to url
|
||||
monitorToSubmit.url = `http${https ? "s" : ""}://` + monitorToSubmit.url;
|
||||
|
||||
const { error } = monitorValidation.validate(monitorToSubmit, {
|
||||
abortEarly: false,
|
||||
});
|
||||
if (error) {
|
||||
const newErrors = {};
|
||||
error.details.forEach((err) => {
|
||||
newErrors[err.path[0]] = err.message;
|
||||
});
|
||||
setErrors(newErrors);
|
||||
createToast({ body: "Please check the form for errors." });
|
||||
return;
|
||||
}
|
||||
|
||||
// Append needed fields
|
||||
monitorToSubmit.description = form.name;
|
||||
monitorToSubmit.interval = form.interval * MS_PER_MINUTE;
|
||||
monitorToSubmit.teamId = user.teamId;
|
||||
monitorToSubmit.userId = user._id;
|
||||
monitorToSubmit.notifications = notifications;
|
||||
|
||||
const success = await createDistributedUptimeMonitor({ form: monitorToSubmit });
|
||||
if (success) {
|
||||
createToast({ body: "Monitor created successfully!" });
|
||||
navigate("/distributed-uptime");
|
||||
} else {
|
||||
createToast({ body: "Failed to create monitor." });
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (event) => {
|
||||
let { name, value } = event.target;
|
||||
|
||||
setForm({
|
||||
...form,
|
||||
[name]: value,
|
||||
});
|
||||
const { error } = monitorValidation.validate(
|
||||
{ [name]: value },
|
||||
{ abortEarly: false }
|
||||
);
|
||||
setErrors((prev) => ({
|
||||
...prev,
|
||||
...(error ? { [name]: error.details[0].message } : { [name]: undefined }),
|
||||
}));
|
||||
};
|
||||
|
||||
const handleNotifications = (event, type) => {
|
||||
const { value } = event.target;
|
||||
let currentNotifications = [...notifications];
|
||||
const notificationAlreadyExists = notifications.some((notification) => {
|
||||
if (notification.type === type && notification.address === value) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (notificationAlreadyExists) {
|
||||
currentNotifications = currentNotifications.filter((notification) => {
|
||||
if (notification.type === type && notification.address === value) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
currentNotifications.push({ type, address: value });
|
||||
}
|
||||
setNotifications(currentNotifications);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<Stack
|
||||
component="form"
|
||||
gap={theme.spacing(12)}
|
||||
mt={theme.spacing(6)}
|
||||
onSubmit={() => console.log("submit")}
|
||||
>
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="h1"
|
||||
>
|
||||
<Typography
|
||||
component="span"
|
||||
fontSize="inherit"
|
||||
>
|
||||
{isCreate ? "Create your" : "Edit your"}{" "}
|
||||
</Typography>
|
||||
<Typography
|
||||
component="span"
|
||||
variant="h2"
|
||||
fontSize="inherit"
|
||||
fontWeight="inherit"
|
||||
>
|
||||
{t("monitor")}
|
||||
</Typography>
|
||||
</Typography>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h2">{t("settingsGeneralSettings")}</Typography>
|
||||
<Typography component="p">{t("distributedUptimeCreateSelectURL")}</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(15)}>
|
||||
<TextInput
|
||||
type={"url"}
|
||||
id="monitor-url"
|
||||
startAdornment={<HttpAdornment https={https} />}
|
||||
label="URL to monitor"
|
||||
https={https}
|
||||
placeholder={"www.google.com"}
|
||||
disabled={!isCreate}
|
||||
value={form.url}
|
||||
name="url"
|
||||
onChange={handleChange}
|
||||
error={errors["url"] ? true : false}
|
||||
helperText={errors["url"]}
|
||||
/>
|
||||
<TextInput
|
||||
type="text"
|
||||
id="monitor-name"
|
||||
label="Display name"
|
||||
isOptional={true}
|
||||
placeholder={"Google"}
|
||||
value={form.name}
|
||||
name="name"
|
||||
onChange={handleChange}
|
||||
error={errors["name"] ? true : false}
|
||||
helperText={errors["name"]}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h2">{t("distributedUptimeCreateChecks")}</Typography>
|
||||
<Typography component="p">
|
||||
{t("distributedUptimeCreateChecksDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(12)}>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
<Radio
|
||||
id="monitor-checks-http"
|
||||
title="Website monitoring"
|
||||
desc="Use HTTP(s) to monitor your website or API endpoint."
|
||||
size="small"
|
||||
value="http"
|
||||
name="type"
|
||||
checked={true}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{form.type === "http" || form.type === "distributed_http" ? (
|
||||
<ButtonGroup
|
||||
disabled={!isCreate}
|
||||
sx={{ ml: theme.spacing(16) }}
|
||||
>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={https.toString()}
|
||||
onClick={() => setHttps(true)}
|
||||
>
|
||||
{t("https")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(!https).toString()}
|
||||
onClick={() => setHttps(false)}
|
||||
>
|
||||
{t("http")}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
{errors["type"] ? (
|
||||
<Box className="error-container">
|
||||
<Typography
|
||||
component="p"
|
||||
className="input-error"
|
||||
color={theme.palette.error.contrastText}
|
||||
>
|
||||
{errors["type"]}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h2">
|
||||
{t("distributedUptimeCreateIncidentNotification")}
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
{t("distributedUptimeCreateIncidentDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
<Checkbox
|
||||
id="notify-email-default"
|
||||
label={`Notify via email (to ${user.email})`}
|
||||
isChecked={notifications.some(
|
||||
(notification) => notification.type === "email"
|
||||
)}
|
||||
value={user?.email}
|
||||
onChange={(event) => handleNotifications(event, "email")}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h2">
|
||||
{t("distributedUptimeCreateAdvancedSettings")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(12)}>
|
||||
<Select
|
||||
id="monitor-interval"
|
||||
label="Check frequency"
|
||||
name="interval"
|
||||
value={form.interval}
|
||||
onChange={handleChange}
|
||||
items={SELECT_VALUES}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={() => handleCreateMonitor()}
|
||||
disabled={!Object.values(errors).every((value) => value === undefined)}
|
||||
loading={isLoading}
|
||||
>
|
||||
{isCreate ? "Create monitor" : "Configure monitor"}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateDistributedUptime;
|
||||
@@ -1,50 +0,0 @@
|
||||
// Components
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import { ColContainer } from "../../../../../Components/StandardContainer";
|
||||
import SmartToyIcon from "@mui/icons-material/SmartToy";
|
||||
import Dot from "../../../../../Components/Dot";
|
||||
// Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const MESSAGES = [
|
||||
"I've checked the network status, and we're seeing excellent performance across all regions.",
|
||||
"The network is stable and functioning optimally. All connections are active and stable.",
|
||||
"I've reviewed the network status, and everything looks great. No issues detected.",
|
||||
"The network is up and running smoothly. All connections are active and stable.",
|
||||
"I've checked the network status, and everything is looking good. No issues detected.",
|
||||
];
|
||||
|
||||
const ChatBot = ({ sx }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<ColContainer
|
||||
backgroundColor={theme.palette.chatbot.background}
|
||||
sx={{ ...sx }}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(4)}
|
||||
>
|
||||
<SmartToyIcon sx={{ color: theme.palette.chatbot.textAccent }} />
|
||||
<Typography color={theme.palette.chatbot.textAccent}>Status Bot</Typography>
|
||||
<Dot
|
||||
color={theme.palette.chatbot.textAccent}
|
||||
style={{ opacity: 0.4 }}
|
||||
/>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color={theme.palette.chatbot.textAccent}
|
||||
sx={{ opacity: 0.4 }}
|
||||
>
|
||||
{t("now")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Typography>{MESSAGES[Math.floor(Math.random() * MESSAGES.length)]}</Typography>
|
||||
</ColContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatBot;
|
||||
@@ -1,92 +0,0 @@
|
||||
// Components
|
||||
import { Box, Stack, Button } from "@mui/material";
|
||||
import Image from "../../../../../Components/Image";
|
||||
import SettingsIcon from "../../../../../assets/icons/settings-bold.svg?react";
|
||||
|
||||
//Utils
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Controls = ({ isDeleteOpen, setIsDeleteOpen, isDeleting, monitorId }) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Box>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={() => setIsDeleteOpen(!isDeleteOpen)}
|
||||
loading={isDeleting}
|
||||
>
|
||||
{t("delete")}
|
||||
</Button>
|
||||
</Box>
|
||||
<Box>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={() => {
|
||||
navigate(`/distributed-uptime/configure/${monitorId}`);
|
||||
}}
|
||||
sx={{
|
||||
px: theme.spacing(5),
|
||||
"& svg": {
|
||||
mr: theme.spacing(3),
|
||||
"& path": {
|
||||
stroke: theme.palette.secondary.contrastText,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SettingsIcon /> {t("configure")}
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
Controls.propTypes = {
|
||||
isDeleting: PropTypes.bool,
|
||||
monitorId: PropTypes.string,
|
||||
isDeleteOpen: PropTypes.bool.isRequired,
|
||||
setIsDeleteOpen: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const ControlsHeader = ({ isDeleting, isDeleteOpen, setIsDeleteOpen, monitorId }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Stack
|
||||
alignSelf="flex-start"
|
||||
direction="row"
|
||||
width="100%"
|
||||
gap={theme.spacing(2)}
|
||||
justifyContent="flex-end"
|
||||
alignItems="flex-end"
|
||||
>
|
||||
<Controls
|
||||
isDeleting={isDeleting}
|
||||
isDeleteOpen={isDeleteOpen}
|
||||
setIsDeleteOpen={setIsDeleteOpen}
|
||||
monitorId={monitorId}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
ControlsHeader.propTypes = {
|
||||
monitorId: PropTypes.string,
|
||||
isDeleting: PropTypes.bool,
|
||||
isDeleteOpen: PropTypes.bool.isRequired,
|
||||
setIsDeleteOpen: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default ControlsHeader;
|
||||
@@ -1,98 +0,0 @@
|
||||
// Components
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import PulseDot from "../../../../../Components/Animated/PulseDot";
|
||||
import "flag-icons/css/flag-icons.min.css";
|
||||
import { ColContainer } from "../../../../../Components/StandardContainer";
|
||||
|
||||
// Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const DeviceTicker = ({ data, width = "100%", connectionStatus }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const statusColor = {
|
||||
up: theme.palette.success.main,
|
||||
down: theme.palette.error.main,
|
||||
undefined: theme.palette.warning.main,
|
||||
};
|
||||
|
||||
return (
|
||||
<ColContainer sx={{ height: "100%" }}>
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(4)}
|
||||
>
|
||||
<PulseDot color={statusColor[connectionStatus]} />
|
||||
|
||||
<Typography
|
||||
variant="h1"
|
||||
mb={theme.spacing(8)}
|
||||
sx={{ alignSelf: "center" }}
|
||||
>
|
||||
{connectionStatus === "up" ? "Connected" : "Connecting..."}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<div
|
||||
style={{
|
||||
overflowX: "auto",
|
||||
maxWidth: "100%",
|
||||
// Optional: add a max height if you want vertical scrolling too
|
||||
// maxHeight: '400px',
|
||||
// overflowY: 'auto'
|
||||
}}
|
||||
>
|
||||
<table style={{ width: "100%" }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ textAlign: "left", paddingLeft: theme.spacing(4) }}>
|
||||
<Typography>{t("country")}</Typography>
|
||||
</th>
|
||||
<th style={{ textAlign: "left", paddingLeft: theme.spacing(4) }}>
|
||||
<Typography>{t("city")}</Typography>
|
||||
</th>
|
||||
<th style={{ textAlign: "right", paddingLeft: theme.spacing(4) }}>
|
||||
<Typography>{t("response")}</Typography>
|
||||
</th>
|
||||
<th style={{ textAlign: "right", paddingLeft: theme.spacing(4) }}>
|
||||
<Typography sx={{ whiteSpace: "nowrap" }}>{"UPT BURNED"}</Typography>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.map((dataPoint) => {
|
||||
const countryCode = dataPoint?.countryCode?.toLowerCase() ?? null;
|
||||
const flag = countryCode ? `fi fi-${countryCode}` : null;
|
||||
const city = dataPoint?.city !== "" ? dataPoint?.city : "Unknown";
|
||||
return (
|
||||
<tr key={Math.random()}>
|
||||
<td style={{ paddingLeft: theme.spacing(4) }}>
|
||||
<Typography>
|
||||
{flag ? <span className={flag} /> : null}{" "}
|
||||
{countryCode?.toUpperCase() ?? "N/A"}
|
||||
</Typography>
|
||||
</td>
|
||||
<td style={{ paddingLeft: theme.spacing(4) }}>
|
||||
<Typography>{city}</Typography>
|
||||
</td>
|
||||
<td style={{ textAlign: "right", paddingLeft: theme.spacing(4) }}>
|
||||
<Typography>
|
||||
{Math.floor(dataPoint.responseTime)} {t("ms")}
|
||||
</Typography>
|
||||
</td>
|
||||
<td style={{ textAlign: "right", paddingLeft: theme.spacing(4) }}>
|
||||
<Typography color={theme.palette.warning.main}>
|
||||
+{dataPoint.uptBurnt}
|
||||
</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</ColContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeviceTicker;
|
||||
@@ -1,169 +0,0 @@
|
||||
{
|
||||
"id": "43f36e14-e3f5-43c1-84c0-50a9c80dc5c7",
|
||||
"name": "MapLibre",
|
||||
"zoom": 0.861983335785597,
|
||||
"pitch": 0,
|
||||
"center": [17.6543171043124, 32.9541203267468],
|
||||
"glyphs": "https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf",
|
||||
"layers": [
|
||||
{
|
||||
"id": "background",
|
||||
"type": "background",
|
||||
"paint": {
|
||||
"background-color": "#121217"
|
||||
},
|
||||
"filter": ["all"],
|
||||
"layout": {
|
||||
"visibility": "visible"
|
||||
},
|
||||
"maxzoom": 24
|
||||
},
|
||||
{
|
||||
"id": "coastline",
|
||||
"type": "line",
|
||||
"paint": {
|
||||
"line-blur": 0.5,
|
||||
"line-color": "#000000",
|
||||
"line-width": {
|
||||
"stops": [
|
||||
[0, 2],
|
||||
[6, 6],
|
||||
[14, 9],
|
||||
[22, 18]
|
||||
]
|
||||
}
|
||||
},
|
||||
"filter": ["all"],
|
||||
"layout": {
|
||||
"line-cap": "round",
|
||||
"line-join": "round",
|
||||
"visibility": "visible"
|
||||
},
|
||||
"source": "maplibre",
|
||||
"maxzoom": 24,
|
||||
"minzoom": 0,
|
||||
"source-layer": "countries"
|
||||
},
|
||||
{
|
||||
"id": "countries-fill",
|
||||
"type": "fill",
|
||||
"paint": {
|
||||
"fill-color": "#292929"
|
||||
},
|
||||
"filter": ["all"],
|
||||
"layout": {
|
||||
"visibility": "visible"
|
||||
},
|
||||
"source": "maplibre",
|
||||
"maxzoom": 24,
|
||||
"source-layer": "countries"
|
||||
},
|
||||
{
|
||||
"id": "countries-boundary",
|
||||
"type": "line",
|
||||
"paint": {
|
||||
"line-color": "#484848",
|
||||
"line-width": {
|
||||
"stops": [
|
||||
[1, 1],
|
||||
[6, 2],
|
||||
[14, 6],
|
||||
[22, 12]
|
||||
]
|
||||
},
|
||||
"line-opacity": {
|
||||
"stops": [
|
||||
[3, 0.5],
|
||||
[6, 1]
|
||||
]
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
"line-cap": "round",
|
||||
"line-join": "round",
|
||||
"visibility": "visible"
|
||||
},
|
||||
"source": "maplibre",
|
||||
"maxzoom": 24,
|
||||
"source-layer": "countries"
|
||||
},
|
||||
{
|
||||
"id": "countries-label",
|
||||
"type": "symbol",
|
||||
"paint": {
|
||||
"text-color": "rgba(8, 37, 77, 1)",
|
||||
"text-halo-blur": {
|
||||
"stops": [
|
||||
[2, 0.2],
|
||||
[6, 0]
|
||||
]
|
||||
},
|
||||
"text-halo-color": "rgba(255, 255, 255, 1)",
|
||||
"text-halo-width": {
|
||||
"stops": [
|
||||
[2, 1],
|
||||
[6, 1.6]
|
||||
]
|
||||
}
|
||||
},
|
||||
"filter": ["all"],
|
||||
"layout": {
|
||||
"text-font": ["Open Sans Semibold"],
|
||||
"text-size": {
|
||||
"stops": [
|
||||
[2, 10],
|
||||
[4, 12],
|
||||
[6, 16]
|
||||
]
|
||||
},
|
||||
"text-field": {
|
||||
"stops": [
|
||||
[2, "{ABBREV}"],
|
||||
[4, "{NAME}"]
|
||||
]
|
||||
},
|
||||
"visibility": "visible",
|
||||
"text-max-width": 10,
|
||||
"text-transform": {
|
||||
"stops": [
|
||||
[0, "uppercase"],
|
||||
[2, "none"]
|
||||
]
|
||||
}
|
||||
},
|
||||
"source": "maplibre",
|
||||
"maxzoom": 24,
|
||||
"minzoom": 2,
|
||||
"source-layer": "centroids"
|
||||
},
|
||||
{
|
||||
"id": "data-dots",
|
||||
"type": "circle",
|
||||
"source": "data-dots",
|
||||
"paint": {
|
||||
"circle-radius": 3,
|
||||
"circle-color": ["get", "color"],
|
||||
"circle-opacity": 0.5
|
||||
}
|
||||
}
|
||||
],
|
||||
"bearing": 0,
|
||||
"sources": {
|
||||
"maplibre": {
|
||||
"url": "https://demotiles.maplibre.org/tiles/tiles.json",
|
||||
"type": "vector"
|
||||
},
|
||||
"data-dots": {
|
||||
"type": "geojson",
|
||||
"data": {
|
||||
"type": "FeatureCollection",
|
||||
"features": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"version": 8,
|
||||
"metadata": {
|
||||
"maptiler:copyright": "This style was generated on MapTiler Cloud. Usage is governed by the license terms in https://github.com/maplibre/demotiles/blob/gh-pages/LICENSE",
|
||||
"openmaptiles:version": "3.x"
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import baseStyle from "./DistributedUptimeMapStyle.json";
|
||||
|
||||
const buildStyle = (theme, mode) => {
|
||||
const style = JSON.parse(JSON.stringify(baseStyle));
|
||||
|
||||
if (mode === "dark") {
|
||||
return baseStyle;
|
||||
}
|
||||
|
||||
if (style.layers) {
|
||||
const newLayers = style.layers.map((layer) => {
|
||||
if (layer.id === "background") {
|
||||
layer.paint["background-color"] = theme.palette.map.main;
|
||||
}
|
||||
|
||||
if (layer.id === "countries-fill") {
|
||||
layer.paint["fill-color"] = theme.palette.map.lowContrast;
|
||||
}
|
||||
|
||||
if (layer.id === "coastline") {
|
||||
layer.paint["line-color"] = theme.palette.map.highContrast;
|
||||
}
|
||||
if (layer.id === "countries-boundary") {
|
||||
layer.paint["line-color"] = theme.palette.map.highContrast;
|
||||
}
|
||||
return layer;
|
||||
});
|
||||
style.layers = newLayers;
|
||||
}
|
||||
return style;
|
||||
};
|
||||
|
||||
export default buildStyle;
|
||||
@@ -1,118 +0,0 @@
|
||||
import "maplibre-gl/dist/maplibre-gl.css";
|
||||
import PropTypes from "prop-types";
|
||||
import { useRef, useState, useEffect } from "react";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import maplibregl from "maplibre-gl";
|
||||
import { useSelector } from "react-redux";
|
||||
import buildStyle from "./buildStyle";
|
||||
|
||||
const DistributedUptimeMap = ({
|
||||
width = "100%",
|
||||
checks,
|
||||
height,
|
||||
minHeight = "350px",
|
||||
}) => {
|
||||
const mapContainer = useRef(null);
|
||||
const map = useRef(null);
|
||||
const theme = useTheme();
|
||||
const [mapLoaded, setMapLoaded] = useState(false);
|
||||
const mode = useSelector((state) => state.ui.mode);
|
||||
const initialTheme = useRef(theme);
|
||||
const initialMode = useRef(mode);
|
||||
|
||||
const colorLookup = (avgResponseTime) => {
|
||||
if (avgResponseTime <= 150) {
|
||||
return "#00FF00"; // Green
|
||||
} else if (avgResponseTime <= 250) {
|
||||
return "#FFFF00"; // Yellow
|
||||
} else {
|
||||
return "#FF0000"; // Red
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (mapContainer.current && !map.current) {
|
||||
const initialStyle = buildStyle(initialTheme.current, initialMode.current);
|
||||
|
||||
map.current = new maplibregl.Map({
|
||||
container: mapContainer.current,
|
||||
style: initialStyle,
|
||||
center: [0, 20],
|
||||
zoom: 0.8,
|
||||
attributionControl: false,
|
||||
canvasContextAttributes: {
|
||||
antialias: true,
|
||||
preserveDrawingBuffer: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
map.current.on("load", () => {
|
||||
setMapLoaded(true);
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (map.current) {
|
||||
map.current.remove();
|
||||
map.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const style = buildStyle(theme, mode);
|
||||
if (map.current && mapLoaded) {
|
||||
map.current.setStyle(style);
|
||||
}
|
||||
}, [theme, mode, mapLoaded]);
|
||||
|
||||
useEffect(() => {
|
||||
if (map.current && checks?.length > 0) {
|
||||
// Convert dots to GeoJSON
|
||||
const geojson = {
|
||||
type: "FeatureCollection",
|
||||
features: checks.map((check) => {
|
||||
return {
|
||||
type: "Feature",
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: [check._id.lng, check._id.lat],
|
||||
},
|
||||
properties: {
|
||||
color: theme.palette.accent.main,
|
||||
// color: colorLookup(check.avgResponseTime) || "blue", // Default to blue if no color specified
|
||||
},
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
// Update the source with new dots
|
||||
const source = map.current.getSource("data-dots");
|
||||
if (source) {
|
||||
source.setData(geojson);
|
||||
}
|
||||
}
|
||||
}, [checks, theme, mapLoaded]);
|
||||
return (
|
||||
<div
|
||||
ref={mapContainer}
|
||||
style={{
|
||||
width: width,
|
||||
height: height,
|
||||
minHeight: minHeight,
|
||||
borderRadius: theme.spacing(4),
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
borderStyle: "solid",
|
||||
borderWidth: 1,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
DistributedUptimeMap.propTypes = {
|
||||
checks: PropTypes.array,
|
||||
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
minHeight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
};
|
||||
|
||||
export default DistributedUptimeMap;
|
||||
@@ -1,95 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import {
|
||||
AreaChart,
|
||||
Area,
|
||||
XAxis,
|
||||
Tooltip,
|
||||
CartesianGrid,
|
||||
ResponsiveContainer,
|
||||
} from "recharts";
|
||||
import CustomTick from "../Helpers/Tick";
|
||||
import CustomToolTip from "../Helpers/ToolTip";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const DistributedUptimeResponseAreaChart = ({ checks }) => {
|
||||
const theme = useTheme();
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
return (
|
||||
<ResponsiveContainer
|
||||
width="100%"
|
||||
minWidth={25}
|
||||
height={220}
|
||||
>
|
||||
<AreaChart
|
||||
width="100%"
|
||||
height="100%"
|
||||
data={checks}
|
||||
margin={{
|
||||
top: 10,
|
||||
right: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
onMouseMove={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<CartesianGrid
|
||||
stroke={theme.palette.primary.lowContrast}
|
||||
strokeWidth={1}
|
||||
strokeOpacity={1}
|
||||
fill="transparent"
|
||||
vertical={false}
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="colorUv"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="0"
|
||||
y2="1"
|
||||
>
|
||||
<stop
|
||||
offset="0%"
|
||||
stopColor={theme.palette.accent.darker}
|
||||
stopOpacity={0.8}
|
||||
/>
|
||||
<stop
|
||||
offset="100%"
|
||||
stopColor={theme.palette.accent.main}
|
||||
stopOpacity={0}
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<XAxis
|
||||
stroke={theme.palette.primary.lowContrast}
|
||||
dataKey="_id.date"
|
||||
tick={<CustomTick />}
|
||||
minTickGap={0}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
height={20}
|
||||
/>
|
||||
<Tooltip
|
||||
cursor={{ stroke: theme.palette.primary.lowContrast }}
|
||||
content={<CustomToolTip />}
|
||||
wrapperStyle={{ pointerEvents: "none" }}
|
||||
/>
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="avgResponseTime"
|
||||
stroke={theme.palette.primary.accent}
|
||||
fill="url(#colorUv)"
|
||||
strokeWidth={isHovered ? 2.5 : 1.5}
|
||||
activeDot={{ stroke: theme.palette.background.main, r: 5 }}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
};
|
||||
|
||||
DistributedUptimeResponseAreaChart.propTypes = {
|
||||
checks: PropTypes.array,
|
||||
};
|
||||
|
||||
export default DistributedUptimeResponseAreaChart;
|
||||
@@ -1,95 +0,0 @@
|
||||
import {
|
||||
BarChart,
|
||||
Bar,
|
||||
CartesianGrid,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
} from "recharts";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import PropTypes from "prop-types";
|
||||
import CustomToolTip from "../Helpers/ToolTip";
|
||||
import CustomTick from "../Helpers/Tick";
|
||||
|
||||
const DistributedUptimeResponseBarChart = ({ checks }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<ResponsiveContainer
|
||||
width="100%"
|
||||
minWidth={25}
|
||||
height={220}
|
||||
>
|
||||
<BarChart
|
||||
width="100%"
|
||||
height="100%"
|
||||
data={checks}
|
||||
margin={{
|
||||
top: 10,
|
||||
right: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="colorUv"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="0"
|
||||
y2="1"
|
||||
>
|
||||
<stop
|
||||
offset="0%"
|
||||
stopColor={theme.palette.accent.darker}
|
||||
stopOpacity={0.8}
|
||||
/>
|
||||
<stop
|
||||
offset="100%"
|
||||
stopColor={theme.palette.accent.main}
|
||||
stopOpacity={0}
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid
|
||||
stroke={theme.palette.primary.lowContrast}
|
||||
strokeWidth={1}
|
||||
strokeOpacity={1}
|
||||
fill="transparent"
|
||||
vertical={false}
|
||||
/>
|
||||
<Tooltip
|
||||
cursor={{
|
||||
stroke: theme.palette.primary.lowContrast,
|
||||
fill: "transparent",
|
||||
}}
|
||||
content={<CustomToolTip />}
|
||||
wrapperStyle={{ pointerEvents: "none" }}
|
||||
/>
|
||||
<XAxis
|
||||
stroke={theme.palette.primary.lowContrast}
|
||||
dataKey="_id.date"
|
||||
tick={<CustomTick />}
|
||||
minTickGap={0}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
height={20}
|
||||
/>
|
||||
<Bar
|
||||
maxBarSize={25}
|
||||
dataKey="avgResponseTime"
|
||||
fill="url(#colorUv)"
|
||||
activeBar={{
|
||||
stroke: theme.palette.accent.main,
|
||||
r: 5,
|
||||
}}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
};
|
||||
|
||||
DistributedUptimeResponseBarChart.propTypes = {
|
||||
checks: PropTypes.array,
|
||||
};
|
||||
|
||||
export default DistributedUptimeResponseBarChart;
|
||||
@@ -1,31 +0,0 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { formatDateWithTz } from "../../../../../../Utils/timeUtils";
|
||||
import PropTypes from "prop-types";
|
||||
import { Text } from "recharts";
|
||||
const CustomTick = ({ x, y, payload, index }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const uiTimezone = useSelector((state) => state.ui.timezone);
|
||||
return (
|
||||
<Text
|
||||
x={x}
|
||||
y={y + 10}
|
||||
textAnchor="middle"
|
||||
fill={theme.palette.text.tertiary}
|
||||
fontSize={11}
|
||||
fontWeight={400}
|
||||
>
|
||||
{formatDateWithTz(payload?.value, "h:mm a", uiTimezone)}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
CustomTick.propTypes = {
|
||||
x: PropTypes.number,
|
||||
y: PropTypes.number,
|
||||
payload: PropTypes.object,
|
||||
index: PropTypes.number,
|
||||
};
|
||||
|
||||
export default CustomTick;
|
||||
@@ -1,89 +0,0 @@
|
||||
import { Box, Stack, Typography } from "@mui/material";
|
||||
import { useSelector } from "react-redux";
|
||||
import { formatDateWithTz } from "../../../../../../Utils/timeUtils";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import AccessTimeIcon from "@mui/icons-material/AccessTime";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const CustomToolTip = ({ active, payload, label }) => {
|
||||
const uiTimezone = useSelector((state) => state.ui.timezone);
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
if (active && payload && payload.length) {
|
||||
const responseTime = payload[0]?.payload?.originalAvgResponseTime
|
||||
? payload[0]?.payload?.originalAvgResponseTime
|
||||
: (payload[0]?.payload?.avgResponseTime ?? 0);
|
||||
return (
|
||||
<Stack
|
||||
className="area-tooltip"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
border: 1,
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
py: theme.spacing(2),
|
||||
px: theme.spacing(4),
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.text.tertiary,
|
||||
fontSize: 12,
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)}
|
||||
</Typography>
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="end"
|
||||
gap={theme.spacing(2)}
|
||||
sx={{
|
||||
"& span": {
|
||||
color: theme.palette.text.tertiary,
|
||||
fontSize: 11,
|
||||
fontWeight: 500,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<AccessTimeIcon fontSize="small" />
|
||||
|
||||
<Typography
|
||||
component="span"
|
||||
sx={{ opacity: 0.8 }}
|
||||
>
|
||||
{t("responseTime")}
|
||||
</Typography>
|
||||
<Typography component="span">
|
||||
{Math.floor(responseTime)}
|
||||
<Typography
|
||||
component="span"
|
||||
sx={{ opacity: 0.8 }}
|
||||
>
|
||||
{t("ms")}
|
||||
</Typography>
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
CustomToolTip.propTypes = {
|
||||
active: PropTypes.bool,
|
||||
payload: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
value: PropTypes.number,
|
||||
payload: PropTypes.shape({
|
||||
_id: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
avgResponseTime: PropTypes.number,
|
||||
originalAvgResponseTime: PropTypes.number,
|
||||
}),
|
||||
})
|
||||
),
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
};
|
||||
export default CustomToolTip;
|
||||
@@ -1,53 +0,0 @@
|
||||
//Components
|
||||
import { Stack, Switch, Typography } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import DistributedUptimeResponseAreaChart from "./Area";
|
||||
import DistributedUptimeResponseBarChart from "./Bar";
|
||||
import ChartBox from "../../../../../Components/Charts/ChartBox";
|
||||
import ResponseTimeIcon from "../../../../../assets/icons/response-time-icon.svg?react";
|
||||
|
||||
// Utils
|
||||
import PropTypes from "prop-types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const DistributedUptimeResponseChart = ({ checks }) => {
|
||||
const [chartType, setChartType] = useState("bar");
|
||||
const { t } = useTranslation();
|
||||
let Chart = null;
|
||||
if (chartType === "area") {
|
||||
Chart = DistributedUptimeResponseAreaChart;
|
||||
}
|
||||
if (chartType === "bar") {
|
||||
Chart = DistributedUptimeResponseBarChart;
|
||||
}
|
||||
return (
|
||||
<Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
>
|
||||
<Typography>{t("bar")}</Typography>
|
||||
<Switch
|
||||
color="main"
|
||||
value={chartType}
|
||||
onChange={(e) => setChartType(e.target.checked ? "area" : "bar")}
|
||||
/>
|
||||
<Typography>{t("area")}</Typography>
|
||||
</Stack>
|
||||
<ChartBox
|
||||
icon={<ResponseTimeIcon />}
|
||||
header="Response Times"
|
||||
sx={{ padding: 0 }}
|
||||
>
|
||||
<Chart checks={checks} />
|
||||
</ChartBox>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
DistributedUptimeResponseChart.propTypes = {
|
||||
checks: PropTypes.array,
|
||||
type: PropTypes.string,
|
||||
};
|
||||
|
||||
export default DistributedUptimeResponseChart;
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Stack, Typography, Box } from "@mui/material";
|
||||
import SolanaLogo from "../../../../../assets/icons/solana_logo.svg?react";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Footer = () => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Stack
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
spacing={2}
|
||||
>
|
||||
<Typography variant="h2">{t("distributedUptimeDetailsFooterHeading")}</Typography>
|
||||
<Stack
|
||||
width="100%"
|
||||
direction="row"
|
||||
gap={theme.spacing(2)}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Typography variant="h2">{t("distributedUptimeDetailsFooterBuilt")}</Typography>
|
||||
<SolanaLogo
|
||||
width={15}
|
||||
height={15}
|
||||
/>
|
||||
<Typography variant="h2">{t("distributedUptimeDetailsFooterSolana")}</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
@@ -1,15 +0,0 @@
|
||||
import { useState, useEffect } from "react";
|
||||
const LastUpdate = ({ suffix, lastUpdateTime, trigger }) => {
|
||||
const [elapsedMs, setElapsedMs] = useState(lastUpdateTime);
|
||||
|
||||
useEffect(() => {
|
||||
setElapsedMs(lastUpdateTime);
|
||||
const timer = setInterval(() => {
|
||||
setElapsedMs((prev) => prev + 1000);
|
||||
}, 1000);
|
||||
return () => clearInterval(timer);
|
||||
}, [lastUpdateTime, trigger]);
|
||||
|
||||
return `${Math.floor(elapsedMs / 1000)} ${suffix}`;
|
||||
};
|
||||
export default LastUpdate;
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const MonitorHeader = ({ monitor }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Stack direction="row">
|
||||
<Stack gap={theme.spacing(2)}>
|
||||
<Typography variant="h1">{monitor.name}</Typography>
|
||||
<Typography variant="h2">{t("distributedUptimeDetailsMonitorHeader")}</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
MonitorHeader.propTypes = {
|
||||
monitor: PropTypes.object,
|
||||
};
|
||||
|
||||
export default MonitorHeader;
|
||||
@@ -1,30 +0,0 @@
|
||||
import { LinearProgress } from "@mui/material";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
const NextExpectedCheck = ({ lastUpdateTime, interval, trigger }) => {
|
||||
const [elapsedMs, setElapsedMs] = useState(lastUpdateTime);
|
||||
|
||||
useEffect(() => {
|
||||
setElapsedMs(lastUpdateTime);
|
||||
const timer = setInterval(() => {
|
||||
setElapsedMs((prev) => {
|
||||
const newElapsedMs = prev + 100;
|
||||
return newElapsedMs;
|
||||
});
|
||||
}, 100);
|
||||
return () => clearInterval(timer);
|
||||
}, [interval, trigger]);
|
||||
|
||||
return (
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
color="accent"
|
||||
value={Math.min((elapsedMs / interval) * 100, 100)}
|
||||
sx={{
|
||||
transition: "width 1s linear", // Smooth transition over 1 second
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default NextExpectedCheck;
|
||||
@@ -1,14 +0,0 @@
|
||||
import { Stack, Skeleton } from "@mui/material";
|
||||
|
||||
export const SkeletonLayout = () => {
|
||||
return (
|
||||
<Stack>
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
height={"90vh"}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default SkeletonLayout;
|
||||
@@ -1,60 +0,0 @@
|
||||
// Components
|
||||
import { Stack } from "@mui/material";
|
||||
import InfoBox from "../../../../../Components/InfoBox";
|
||||
import LastUpdate from "../LastUpdate";
|
||||
|
||||
// Utils
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const StatBoxes = ({ monitor, lastUpdateTrigger }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
flexWrap="wrap"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<InfoBox
|
||||
sx={{ flex: 1 }}
|
||||
heading="Avg Response Time"
|
||||
subHeading={`${Math.floor(monitor?.avgResponseTime ?? 0)} ms`}
|
||||
/>
|
||||
<InfoBox
|
||||
sx={{ flex: 1 }}
|
||||
heading="Checking every"
|
||||
subHeading={`${(monitor?.interval ?? 0) / 1000} seconds`}
|
||||
/>
|
||||
<InfoBox
|
||||
sx={{ flex: 1 }}
|
||||
heading={"Last check"}
|
||||
subHeading={
|
||||
<LastUpdate
|
||||
lastUpdateTime={monitor?.timeSinceLastCheck}
|
||||
suffix={"seconds ago"}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<InfoBox
|
||||
sx={{ flex: 1 }}
|
||||
heading="Last server push"
|
||||
subHeading={
|
||||
<LastUpdate
|
||||
suffix={"seconds ago"}
|
||||
lastUpdateTime={0}
|
||||
trigger={lastUpdateTrigger}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
StatBoxes.propTypes = {
|
||||
monitor: PropTypes.object,
|
||||
lastUpdateTrigger: PropTypes.number,
|
||||
};
|
||||
|
||||
export default StatBoxes;
|
||||
@@ -1,94 +0,0 @@
|
||||
// Components
|
||||
import { ColContainer } from "../../../../../Components/StandardContainer";
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import PulseDot from "../../../../../Components/Animated/PulseDot";
|
||||
import LastUpdate from "../LastUpdate";
|
||||
import ChatBot from "../Chatbot";
|
||||
import ShareComponent from "../../../../../Components/ShareComponent";
|
||||
|
||||
// Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const StatusHeader = ({ monitor, connectionStatus, elementToCapture }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const COLOR_MAP = {
|
||||
up: theme.palette.successSecondary.main,
|
||||
down: theme.palette.error.lowContrast,
|
||||
};
|
||||
|
||||
const MSG_MAP = {
|
||||
up: "All Systems Operational",
|
||||
down: "Last Check Failed",
|
||||
};
|
||||
|
||||
const PULSE_COLOR = {
|
||||
up: theme.palette.success.main,
|
||||
down: theme.palette.error.main,
|
||||
};
|
||||
|
||||
let bgColor = COLOR_MAP[connectionStatus];
|
||||
return (
|
||||
<ColContainer backgroundColor={bgColor}>
|
||||
<Stack
|
||||
direction={{ s: "column", md: "row" }}
|
||||
justifyContent="space-between"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<PulseDot color={PULSE_COLOR[connectionStatus]} />
|
||||
<Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<Typography
|
||||
variant="h1"
|
||||
color={theme.palette.success.lowContrast}
|
||||
>
|
||||
{MSG_MAP[connectionStatus]}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
borderRadius={theme.spacing(8)}
|
||||
padding={theme.spacing(4)}
|
||||
backgroundColor={theme.palette.successSecondary.lowContrast}
|
||||
color={theme.palette.success.lowContrast}
|
||||
>
|
||||
{t("distributedUptimeDetailsStatusHeaderUptime")}{" "}
|
||||
{(monitor.uptimePercentage * 100).toFixed(2)}%
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color={theme.palette.success.lowContrast}
|
||||
>
|
||||
{t("distributedUptimeDetailsStatusHeaderLastUpdate")}{" "}
|
||||
<LastUpdate
|
||||
suffix={"seconds ago"}
|
||||
lastUpdateTime={monitor?.timeSinceLastCheck}
|
||||
/>
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<ShareComponent
|
||||
elementToCapture={elementToCapture}
|
||||
fileName={monitor.name}
|
||||
/>
|
||||
</Stack>
|
||||
<ChatBot sx={{ marginTop: theme.spacing(10) }} />
|
||||
</ColContainer>
|
||||
);
|
||||
};
|
||||
|
||||
StatusHeader.propTypes = {
|
||||
monitor: PropTypes.object,
|
||||
connectionStatus: PropTypes.string,
|
||||
};
|
||||
|
||||
export default StatusHeader;
|
||||
@@ -1,25 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import { networkService } from "../../../../main";
|
||||
import { createToast } from "../../../../Utils/toastUtils";
|
||||
|
||||
const useDeleteMonitor = ({ monitorId }) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const deleteMonitor = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await networkService.deleteMonitorById({ monitorId });
|
||||
return true;
|
||||
} catch (error) {
|
||||
createToast({
|
||||
body: error.message,
|
||||
});
|
||||
return false;
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return [deleteMonitor, isLoading];
|
||||
};
|
||||
|
||||
export { useDeleteMonitor };
|
||||
@@ -1,148 +0,0 @@
|
||||
//Components
|
||||
import DistributedUptimeMap from "./Components/DistributedUptimeMap";
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import DeviceTicker from "./Components/DeviceTicker";
|
||||
import ResponseTimeChart from "./Components/DistributedUptimeResponseChart";
|
||||
import NextExpectedCheck from "./Components/NextExpectedCheck";
|
||||
import Footer from "./Components/Footer";
|
||||
import StatBoxes from "./Components/StatBoxes";
|
||||
import MonitorHeader from "./Components/MonitorHeader";
|
||||
import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader";
|
||||
import GenericFallback from "../../../Components/GenericFallback";
|
||||
import MonitorCreateHeader from "../../../Components/MonitorCreateHeader";
|
||||
import SkeletonLayout from "./Components/Skeleton";
|
||||
import ControlsHeader from "./Components/ControlsHeader";
|
||||
import Dialog from "../../../Components/Dialog";
|
||||
//Utils
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
|
||||
import { useSubscribeToDepinDetails } from "../../../Hooks/useSubscribeToDepinDetails";
|
||||
import { useDeleteMonitor } from "./Hooks/useDeleteMonitor";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const DistributedUptimeDetails = () => {
|
||||
const { monitorId } = useParams();
|
||||
// Local State
|
||||
const [dateRange, setDateRange] = useState("recent");
|
||||
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
||||
|
||||
// Utils
|
||||
const theme = useTheme();
|
||||
const isAdmin = useIsAdmin();
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [isLoading, networkError, connectionStatus, monitor, lastUpdateTrigger] =
|
||||
useSubscribeToDepinDetails({ monitorId, dateRange });
|
||||
|
||||
const [deleteMonitor, isDeleting] = useDeleteMonitor({ monitorId });
|
||||
// Constants
|
||||
const BREADCRUMBS = [
|
||||
{ name: "Distributed Uptime", path: "/distributed-uptime" },
|
||||
{ name: "Details", path: `/distributed-uptime/${monitorId}` },
|
||||
];
|
||||
|
||||
if (isLoading) {
|
||||
return <SkeletonLayout />;
|
||||
}
|
||||
|
||||
if (networkError) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{t("networkError")}
|
||||
</Typography>
|
||||
<Typography>{t("checkConnection")}</Typography>
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
if (
|
||||
typeof monitor === "undefined" ||
|
||||
typeof monitor?.totalChecks === "undefined" ||
|
||||
monitor?.totalChecks === 0
|
||||
) {
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<GenericFallback>
|
||||
<Typography>{t("distributedUptimeDetailsNoMonitorHistory")}</Typography>
|
||||
</GenericFallback>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Stack
|
||||
direction="column"
|
||||
gap={theme.spacing(10)}
|
||||
>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<MonitorCreateHeader
|
||||
label="Create status page"
|
||||
isAdmin={isAdmin}
|
||||
path={`/status/distributed/create/${monitorId}`}
|
||||
/>
|
||||
<ControlsHeader
|
||||
isDeleting={isDeleting}
|
||||
isDeleteOpen={isDeleteOpen}
|
||||
setIsDeleteOpen={setIsDeleteOpen}
|
||||
monitorId={monitorId}
|
||||
/>
|
||||
<MonitorHeader monitor={monitor} />
|
||||
<StatBoxes
|
||||
monitor={monitor}
|
||||
lastUpdateTrigger={lastUpdateTrigger}
|
||||
/>
|
||||
<NextExpectedCheck
|
||||
lastUpdateTime={monitor?.timeSinceLastCheck ?? 0}
|
||||
interval={monitor?.interval ?? 0}
|
||||
trigger={lastUpdateTrigger}
|
||||
/>
|
||||
<MonitorTimeFrameHeader
|
||||
dateRange={dateRange}
|
||||
setDateRange={setDateRange}
|
||||
/>
|
||||
|
||||
<ResponseTimeChart checks={monitor?.groupedChecks ?? []} />
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<DistributedUptimeMap
|
||||
checks={monitor?.groupedMapChecks ?? []}
|
||||
height={"100%"}
|
||||
width={"100%"}
|
||||
/>
|
||||
<DeviceTicker
|
||||
width={"25vw"}
|
||||
data={monitor?.latestChecks ?? []}
|
||||
connectionStatus={connectionStatus}
|
||||
/>
|
||||
</Stack>
|
||||
<Footer />
|
||||
<Dialog
|
||||
title="Do you want to delete this monitor?"
|
||||
onConfirm={() => {
|
||||
deleteMonitor();
|
||||
setIsDeleteOpen(false);
|
||||
navigate("/distributed-uptime");
|
||||
}}
|
||||
onCancel={() => {
|
||||
setIsDeleteOpen(false);
|
||||
}}
|
||||
open={isDeleteOpen}
|
||||
confirmationButtonLabel="Yes, delete monitor"
|
||||
description="Once deleted, your monitor cannot be retrieved."
|
||||
isLoading={isDeleting || isLoading}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default DistributedUptimeDetails;
|
||||
@@ -1,93 +0,0 @@
|
||||
// Components
|
||||
import DataTable from "../../../../../Components/Table";
|
||||
import BarChart from "../../../../../Components/Charts/BarChart";
|
||||
import { Box } from "@mui/material";
|
||||
import { StatusLabel } from "../../../../../Components/Label";
|
||||
import Host from "../../../../../Components/Host";
|
||||
|
||||
// Utils
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
// Constants
|
||||
const TYPE_MAP = {
|
||||
distributed_http: "Distributed HTTP",
|
||||
};
|
||||
|
||||
const MonitorTable = ({ isLoading, monitors }) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const { determineState } = useMonitorUtils();
|
||||
const headers = [
|
||||
{
|
||||
id: "name",
|
||||
content: <Box>Host</Box>,
|
||||
render: (row) => (
|
||||
<Host
|
||||
key={row._id}
|
||||
url={row.url}
|
||||
title={row.name}
|
||||
percentageColor={row.percentageColor}
|
||||
percentage={row.percentage}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "status",
|
||||
content: <Box width="max-content"> Status</Box>,
|
||||
render: (row) => {
|
||||
const status = determineState(row?.monitor);
|
||||
return (
|
||||
<StatusLabel
|
||||
status={status}
|
||||
text={status}
|
||||
customStyles={{ textTransform: "capitalize" }}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "responseTime",
|
||||
content: "Response Time",
|
||||
render: (row) => <BarChart checks={row?.monitor?.checks.slice().reverse()} />,
|
||||
},
|
||||
{
|
||||
id: "type",
|
||||
content: "Type",
|
||||
render: (row) => <span>{TYPE_MAP[row.monitor.type]}</span>,
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
content: "Actions",
|
||||
render: (row) => <span>{"TODO"}</span>,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
shouldRender={!isLoading}
|
||||
headers={headers}
|
||||
data={monitors}
|
||||
config={{
|
||||
rowSX: {
|
||||
cursor: "pointer",
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.background.accent,
|
||||
},
|
||||
},
|
||||
onRowClick: (row) => {
|
||||
navigate(`/distributed-uptime/${row._id}`);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
MonitorTable.propTypes = {
|
||||
isLoading: PropTypes.bool,
|
||||
monitors: PropTypes.array,
|
||||
};
|
||||
|
||||
export default MonitorTable;
|
||||
@@ -1,14 +0,0 @@
|
||||
import { Stack, Skeleton } from "@mui/material";
|
||||
|
||||
export const SkeletonLayout = () => {
|
||||
return (
|
||||
<Stack>
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
height={"90vh"}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default SkeletonLayout;
|
||||
@@ -1,102 +0,0 @@
|
||||
// Components
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
import CreateMonitorHeader from "../../../Components/MonitorCreateHeader";
|
||||
import MonitorTable from "./Components/MonitorTable";
|
||||
import Pagination from "../../../Components/Table/TablePagination";
|
||||
import Fallback from "../../../Components/Fallback";
|
||||
import GenericFallback from "../../../Components/GenericFallback";
|
||||
|
||||
// Utils
|
||||
import { useState } from "react";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
|
||||
import { useSubscribeToDepinMonitors } from "../../../Hooks/useSubscribeToDepinMonitors";
|
||||
import SkeletonLayout from "./Components/Skeleton";
|
||||
import { useTranslation } from "react-i18next";
|
||||
// Constants
|
||||
const BREADCRUMBS = [{ name: `Distributed Uptime`, path: "/distributed-uptime" }];
|
||||
|
||||
const DistributedUptimeMonitors = () => {
|
||||
// Local state
|
||||
const [page, setPage] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(10);
|
||||
|
||||
// Utils
|
||||
const theme = useTheme();
|
||||
const isAdmin = useIsAdmin();
|
||||
const { t } = useTranslation();
|
||||
const [monitors, count, isLoading, networkError] = useSubscribeToDepinMonitors(
|
||||
page,
|
||||
rowsPerPage
|
||||
);
|
||||
// Handlers
|
||||
const handleChangePage = (event, newPage) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (event) => {
|
||||
setRowsPerPage(event.target.value);
|
||||
setPage(0);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <SkeletonLayout />;
|
||||
}
|
||||
|
||||
if (networkError) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{t("networkError")}
|
||||
</Typography>
|
||||
<Typography>{t("checkConnection")}</Typography>
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
|
||||
if (count === 0) {
|
||||
return (
|
||||
<Fallback
|
||||
vowelStart={false}
|
||||
title="distributed uptime monitor"
|
||||
checks={[
|
||||
"Check if a server is online from multiple locations",
|
||||
"Detect outages and performance issues in real time",
|
||||
"Reduce false alarms by verifying downtime from different networks",
|
||||
"Provide insights on regional availability and latency",
|
||||
]}
|
||||
link="/distributed-uptime/create"
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<CreateMonitorHeader
|
||||
isAdmin={isAdmin}
|
||||
shouldRender={!isLoading}
|
||||
path="/distributed-uptime/create"
|
||||
/>
|
||||
<MonitorTable
|
||||
isLoading={isLoading}
|
||||
monitors={monitors}
|
||||
/>
|
||||
<Pagination
|
||||
itemCount={count}
|
||||
paginationLabel="monitors"
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
handleChangePage={handleChangePage}
|
||||
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default DistributedUptimeMonitors;
|
||||
@@ -1,22 +0,0 @@
|
||||
const VisuallyHiddenInput = ({ onChange }) => {
|
||||
return (
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={onChange}
|
||||
style={{
|
||||
clip: "rect(0 0 0 0)",
|
||||
clipPath: "inset(50%)",
|
||||
height: 1,
|
||||
overflow: "hidden",
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
whiteSpace: "nowrap",
|
||||
width: 1,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default VisuallyHiddenInput;
|
||||
@@ -1,322 +0,0 @@
|
||||
// Components
|
||||
import { Stack, Typography, Button, Box } from "@mui/material";
|
||||
import ConfigBox from "../../../Components/ConfigBox";
|
||||
import Checkbox from "../../../Components/Inputs/Checkbox";
|
||||
import TextInput from "../../../Components/Inputs/TextInput";
|
||||
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 { 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";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFetchDepinStatusPage } from "../../../Hooks/useFetchDepinStatusPage";
|
||||
const CreateStatus = () => {
|
||||
const theme = useTheme();
|
||||
const { monitorId, url } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
const isCreate = typeof url === "undefined";
|
||||
|
||||
const [createStatusPage, isLoading, networkError] = useCreateStatusPage(isCreate);
|
||||
|
||||
const [statusPageIsLoading, statusPageNetworkError, statusPage, _, isPublished] =
|
||||
useFetchDepinStatusPage({
|
||||
url,
|
||||
timeFrame: 30,
|
||||
isCreate,
|
||||
});
|
||||
|
||||
const [monitors, monitorsIsLoading, monitorsNetworkError] = useMonitorsFetch();
|
||||
|
||||
const BREADCRUMBS = [
|
||||
{ name: "distributed uptime", path: "/distributed-uptime" },
|
||||
{
|
||||
name: "details",
|
||||
path: `/distributed-uptime/${isCreate ? monitorId : statusPage?.monitors[0]}`,
|
||||
},
|
||||
{ name: isCreate ? "create status page" : "edit status page", path: `` },
|
||||
];
|
||||
// Local state
|
||||
const [form, setForm] = useState({
|
||||
type: "distributed",
|
||||
isPublished: false,
|
||||
url: url ?? Math.floor(Math.random() * 1000000).toFixed(0),
|
||||
logo: undefined,
|
||||
companyName: "",
|
||||
monitors: [monitorId],
|
||||
});
|
||||
const [errors, setErrors] = useState({});
|
||||
const [search, setSearch] = useState("");
|
||||
const [selectedMonitors, setSelectedMonitors] = useState([]);
|
||||
|
||||
const handleFormChange = (e) => {
|
||||
const { name, value, checked, type } = e.target;
|
||||
|
||||
// Check for errors
|
||||
const { error } = statusPageValidation.validate(
|
||||
{ [name]: value },
|
||||
{ abortEarly: false }
|
||||
);
|
||||
|
||||
setErrors((prev) => buildErrors(prev, name, error));
|
||||
|
||||
if (type === "checkbox") {
|
||||
setForm({ ...form, [name]: checked });
|
||||
return;
|
||||
}
|
||||
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) => ({
|
||||
...prev,
|
||||
logo: img,
|
||||
}));
|
||||
};
|
||||
const handleSubmit = async () => {
|
||||
let logoToSubmit = undefined;
|
||||
|
||||
// Handle image
|
||||
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") {
|
||||
const success = await createStatusPage({ form: formToSubmit });
|
||||
if (success) {
|
||||
const verb = isCreate ? "created" : "updated";
|
||||
createToast({ body: `Status page ${verb} successfully` });
|
||||
navigate(`/status/distributed/${form.url}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const newErrors = {};
|
||||
error?.details?.forEach((err) => {
|
||||
newErrors[err.path[0]] = err.message;
|
||||
});
|
||||
setErrors((prev) => ({ ...prev, ...newErrors }));
|
||||
};
|
||||
|
||||
// If we are configuring, populate fields
|
||||
useEffect(() => {
|
||||
if (isCreate) return;
|
||||
if (typeof statusPage === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
let newLogo = undefined;
|
||||
if (statusPage.logo) {
|
||||
newLogo = {
|
||||
src: `data:${statusPage.logo.contentType};base64,${statusPage.logo.data}`,
|
||||
name: "logo",
|
||||
type: statusPage.logo.contentType,
|
||||
size: null,
|
||||
};
|
||||
}
|
||||
|
||||
setForm((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
companyName: statusPage?.companyName,
|
||||
isPublished: statusPage?.isPublished,
|
||||
timezone: statusPage?.timezone,
|
||||
monitors: statusPage?.monitors,
|
||||
subMonitors: statusPage?.subMonitors.map((monitor) => monitor._id),
|
||||
color: statusPage?.color,
|
||||
logo: newLogo,
|
||||
};
|
||||
});
|
||||
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)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<Typography variant="h1">
|
||||
<Typography
|
||||
component="span"
|
||||
fontSize="inherit"
|
||||
>
|
||||
{isCreate
|
||||
? t("distributedUptimeStatusCreateYour")
|
||||
: t("distributedUptimeStatusEditYour")}{" "}
|
||||
</Typography>
|
||||
<Typography
|
||||
component="span"
|
||||
variant="h2"
|
||||
fontSize="inherit"
|
||||
fontWeight="inherit"
|
||||
>
|
||||
{t("distributedUptimeStatusCreateStatusPage")}
|
||||
</Typography>
|
||||
</Typography>
|
||||
<ConfigBox>
|
||||
<Stack>
|
||||
<Typography component="h2">
|
||||
{t("distributedUptimeStatusCreateStatusPageAccess")}
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
{t("distributedUptimeStatusCreateStatusPageReady")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack gap={theme.spacing(18)}>
|
||||
<Checkbox
|
||||
id="publish"
|
||||
name="isPublished"
|
||||
label={t("distributedUptimeStatusPublishedLabel")}
|
||||
isChecked={form.isPublished}
|
||||
onChange={handleFormChange}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
<Typography component="h2">
|
||||
{t("distributedUptimeStatusBasicInfoHeader")}
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
{t("distributedUptimeStatusBasicInfoDescription")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack gap={theme.spacing(18)}>
|
||||
<TextInput
|
||||
id="companyName"
|
||||
name="companyName"
|
||||
type="text"
|
||||
label={t("distributedUptimeStatusCompanyNameLabel")}
|
||||
placeholder="Company name"
|
||||
value={form.companyName}
|
||||
onChange={handleFormChange}
|
||||
helperText={errors["companyName"]}
|
||||
error={errors["companyName"] ? true : false}
|
||||
/>
|
||||
<TextInput
|
||||
id="url"
|
||||
name="url"
|
||||
type="url"
|
||||
label={t("distributedUptimeStatusPageAddressLabel")}
|
||||
disabled={!isCreate}
|
||||
value={form.url}
|
||||
onChange={handleFormChange}
|
||||
helperText={errors["url"]}
|
||||
error={errors["url"] ? true : false}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
<Typography component="h2">{t("distributedUptimeStatusLogoHeader")}</Typography>
|
||||
<Typography component="p">
|
||||
{t("distributedUptimeStatusLogoDescription")}{" "}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack
|
||||
gap={theme.spacing(18)}
|
||||
alignItems="center"
|
||||
>
|
||||
<Image
|
||||
src={imgSrc}
|
||||
alt="Logo"
|
||||
minWidth={"300px"}
|
||||
minHeight={"100px"}
|
||||
maxWidth={"300px"}
|
||||
maxHeight={"300px"}
|
||||
placeholder={LogoPlaceholder}
|
||||
/>
|
||||
<Box>
|
||||
<Button
|
||||
component="label"
|
||||
role={undefined}
|
||||
variant="contained"
|
||||
color="accent"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{t("distributedUptimeStatusLogoUploadButton")}
|
||||
<VisuallyHiddenInput onChange={handleImageUpload} />
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Stack>
|
||||
<Typography component="h2">
|
||||
{t("distributedUptimeStatusStandardMonitorsHeader")}
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
{t("distributedUptimeStatusStandardMonitorsDescription")}
|
||||
</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"
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
{t("settingsSave")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateStatus;
|
||||
@@ -1,66 +0,0 @@
|
||||
// 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;
|
||||
@@ -1,14 +0,0 @@
|
||||
import { Stack, Skeleton } from "@mui/material";
|
||||
|
||||
export const SkeletonLayout = () => {
|
||||
return (
|
||||
<Stack>
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
height={"90vh"}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default SkeletonLayout;
|
||||
@@ -1,42 +0,0 @@
|
||||
import { Stack, Button, ButtonGroup } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const TimeFrameHeader = ({ timeFrame, setTimeFrame, sx, ...props }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="flex-end"
|
||||
sx={{ ...sx }}
|
||||
{...props}
|
||||
>
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(timeFrame === 30).toString()}
|
||||
onClick={() => setTimeFrame(30)}
|
||||
>
|
||||
{t("distributedUptimeStatus30Days")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(timeFrame === 60).toString()}
|
||||
onClick={() => setTimeFrame(60)}
|
||||
>
|
||||
{t("distributedUptimeStatus60Days")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(timeFrame === 90).toString()}
|
||||
onClick={() => setTimeFrame(90)}
|
||||
>
|
||||
{t("distributedUptimeStatus90Days")}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default TimeFrameHeader;
|
||||
@@ -1,319 +0,0 @@
|
||||
//Components
|
||||
import DistributedUptimeMap from "../../DistributedUptime/Details/Components/DistributedUptimeMap";
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import DeviceTicker from "../../DistributedUptime/Details/Components/DeviceTicker";
|
||||
import DistributedUptimeResponseChart from "../../DistributedUptime/Details/Components/DistributedUptimeResponseChart";
|
||||
import NextExpectedCheck from "../../DistributedUptime/Details/Components/NextExpectedCheck";
|
||||
import Footer from "../../DistributedUptime/Details/Components/Footer";
|
||||
import StatBoxes from "../../DistributedUptime/Details/Components/StatBoxes";
|
||||
import ControlsHeader from "../../StatusPage/Status/Components/ControlsHeader";
|
||||
import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader";
|
||||
import GenericFallback from "../../../Components/GenericFallback";
|
||||
import Dialog from "../../../Components/Dialog";
|
||||
import SkeletonLayout from "./Components/Skeleton";
|
||||
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";
|
||||
import { RowContainer } from "../../../Components/StandardContainer";
|
||||
|
||||
//Utils
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useFetchDepinStatusPage } from "../../../Hooks/useFetchDepinStatusPage";
|
||||
import { useSubscribeToDepinDetails } from "../../../Hooks/useSubscribeToDepinDetails";
|
||||
import { useStatusPageDelete } from "../../StatusPage/Status/Hooks/useStatusPageDelete";
|
||||
import TimeFrameHeader from "./Components/TimeframeHeader";
|
||||
import SubHeader from "../../../Components/Subheader";
|
||||
import { safelyParseFloat } from "../../../Utils/utils";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { setMode } from "../../../Features/UI/uiSlice";
|
||||
|
||||
// Responsive
|
||||
import { useMediaQuery } from "@mui/material";
|
||||
|
||||
const DistributedUptimeStatus = () => {
|
||||
const { url } = useParams();
|
||||
const location = useLocation();
|
||||
const { t } = useTranslation();
|
||||
const isPublic = location.pathname.startsWith("/status/distributed/public");
|
||||
const elementToCapture = useRef(null);
|
||||
|
||||
// Redux state
|
||||
const mode = useSelector((state) => state.ui.mode);
|
||||
const originalModeRef = useRef(null);
|
||||
|
||||
// Local State
|
||||
const [dateRange, setDateRange] = useState("recent");
|
||||
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
||||
const [timeFrame, setTimeFrame] = useState(30);
|
||||
// Utils
|
||||
const theme = useTheme();
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [
|
||||
statusPageIsLoading,
|
||||
statusPageNetworkError,
|
||||
statusPage,
|
||||
monitorId,
|
||||
isPublished,
|
||||
] = useFetchDepinStatusPage({
|
||||
url,
|
||||
timeFrame,
|
||||
});
|
||||
|
||||
const [isLoading, networkError, connectionStatus, monitor, lastUpdateTrigger] =
|
||||
useSubscribeToDepinDetails({ monitorId, dateRange, isPublic, isPublished });
|
||||
|
||||
const [deleteStatusPage, isDeleting] = useStatusPageDelete(() => {
|
||||
navigate("/distributed-uptime");
|
||||
}, url);
|
||||
// Constants
|
||||
const BREADCRUMBS = [
|
||||
{ name: "Distributed Uptime", path: "/distributed-uptime" },
|
||||
{ name: "details", path: `/distributed-uptime/${monitorId}` },
|
||||
{ name: "status", path: `` },
|
||||
];
|
||||
|
||||
let sx = {};
|
||||
if (isPublic) {
|
||||
sx = {
|
||||
paddingTop: "10vh",
|
||||
paddingRight: "10vw",
|
||||
paddingBottom: "10vh",
|
||||
paddingLeft: "10vw",
|
||||
};
|
||||
}
|
||||
|
||||
// Default to dark mode
|
||||
useEffect(() => {
|
||||
const cleanup = () => {
|
||||
if (originalModeRef.current === null) {
|
||||
originalModeRef.current = mode;
|
||||
}
|
||||
|
||||
if (isPublic) {
|
||||
dispatch(setMode(originalModeRef.current));
|
||||
}
|
||||
};
|
||||
|
||||
if (isPublic) {
|
||||
dispatch(setMode("dark"));
|
||||
}
|
||||
|
||||
window.addEventListener("beforeunload", cleanup);
|
||||
return () => {
|
||||
window.removeEventListener("beforeunload", cleanup);
|
||||
};
|
||||
}, [dispatch, isPublic]);
|
||||
|
||||
// Done loading, a status page doesn't exist
|
||||
if (!statusPageIsLoading && typeof statusPage === "undefined") {
|
||||
return (
|
||||
<Stack sx={sx}>
|
||||
<GenericFallback>
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{t("distributedUptimeStatusPageNotSetUp")}
|
||||
</Typography>
|
||||
<Typography>{t("distributedUptimeStatusContactAdmin")}</Typography>
|
||||
</GenericFallback>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
// Done loading, a status page exists but is not public
|
||||
if (!statusPageIsLoading && isPublic && statusPage.isPublished === false) {
|
||||
return (
|
||||
<Stack sx={sx}>
|
||||
<GenericFallback>
|
||||
<Typography>{t("distributedUptimeStatusPageNotPublic")}</Typography>
|
||||
</GenericFallback>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading || statusPageIsLoading) {
|
||||
return <SkeletonLayout />;
|
||||
}
|
||||
|
||||
if (networkError || statusPageNetworkError) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{t("networkError")}
|
||||
</Typography>
|
||||
<Typography>{t("checkConnection")}</Typography>
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
typeof statusPage === "undefined" ||
|
||||
typeof monitor === "undefined" ||
|
||||
monitor.totalChecks === 0
|
||||
) {
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<GenericFallback>
|
||||
<Typography>{t("distributedUptimeDetailsNoMonitorHistory")}</Typography>
|
||||
</GenericFallback>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack
|
||||
ref={elementToCapture}
|
||||
direction="column"
|
||||
gap={theme.spacing(10)}
|
||||
sx={{
|
||||
...sx,
|
||||
}}
|
||||
>
|
||||
{!isPublic && <Breadcrumbs list={BREADCRUMBS} />}
|
||||
|
||||
<ControlsHeader
|
||||
statusPage={statusPage}
|
||||
isPublic={isPublic}
|
||||
isDeleting={isDeleting}
|
||||
isDeleteOpen={isDeleteOpen}
|
||||
setIsDeleteOpen={setIsDeleteOpen}
|
||||
url={url}
|
||||
type="distributed"
|
||||
/>
|
||||
<StatusHeader
|
||||
monitor={monitor}
|
||||
connectionStatus={connectionStatus}
|
||||
elementToCapture={elementToCapture}
|
||||
/>
|
||||
|
||||
<SubHeader
|
||||
direction={{ s: "column", md: "row" }}
|
||||
headerText={t("distributedStatusHeaderText")}
|
||||
subHeaderText={t("distributedStatusSubHeaderText")}
|
||||
gap={isSmallScreen ? theme.spacing(10) : 0}
|
||||
alignItems={{ s: "flex-start", md: "flex-end" }}
|
||||
>
|
||||
<RowContainer>
|
||||
<Stack>
|
||||
<Typography variant={`body2`}>
|
||||
{t("distributedRightCategoryTitle")}
|
||||
</Typography>
|
||||
<Typography variant={`h2`}>{statusPage.companyName}</Typography>
|
||||
</Stack>
|
||||
</RowContainer>
|
||||
</SubHeader>
|
||||
|
||||
<NextExpectedCheck
|
||||
lastUpdateTime={monitor?.timeSinceLastCheck ?? 0}
|
||||
interval={monitor?.interval ?? 0}
|
||||
trigger={lastUpdateTrigger}
|
||||
/>
|
||||
<MonitorTimeFrameHeader
|
||||
dateRange={dateRange}
|
||||
setDateRange={setDateRange}
|
||||
/>
|
||||
<Stack
|
||||
gap={theme.spacing(8)}
|
||||
direction={{ s: "column", md: "row" }}
|
||||
>
|
||||
<DistributedUptimeMap
|
||||
checks={monitor?.groupedMapChecks ?? []}
|
||||
width={isSmallScreen ? "100%" : "50%"}
|
||||
/>
|
||||
<Stack
|
||||
width={{ s: "100%", md: "50%" }}
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<InfoBox
|
||||
heading={t("distributedUptimeStatusDevices")}
|
||||
subHeading={monitor?.totalChecks ?? 0}
|
||||
icon={PeopleAltOutlinedIcon}
|
||||
alt="Upt Logo"
|
||||
sx={{ width: "50%" }}
|
||||
/>
|
||||
<InfoBox
|
||||
heading={
|
||||
isSmallScreen
|
||||
? t("distributedUptimeStatusUpt")
|
||||
: t("distributedUptimeStatusUptBurned")
|
||||
}
|
||||
subHeading={safelyParseFloat(monitor?.uptBurnt).toFixed(4)}
|
||||
img={UptLogo}
|
||||
alt={t("distributedUptimeStatusUptLogo")}
|
||||
sx={{ width: "50%" }}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<DeviceTicker
|
||||
data={monitor?.latestChecks ?? []}
|
||||
connectionStatus={connectionStatus}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<DistributedUptimeResponseChart checks={monitor?.groupedChecks ?? []} />
|
||||
<StatBoxes
|
||||
monitor={monitor}
|
||||
lastUpdateTrigger={lastUpdateTrigger}
|
||||
/>
|
||||
|
||||
<SubHeader
|
||||
shouldRender={statusPage?.subMonitors?.length > 0}
|
||||
direction={{ s: "column", md: "row" }}
|
||||
headerText={t("distributedStatusServerMonitors")}
|
||||
subHeaderText={t("distributedStatusServerMonitorsDescription")}
|
||||
gap={isSmallScreen ? theme.spacing(10) : 0}
|
||||
alignItems={{ s: "flex-start", md: "flex-end" }}
|
||||
>
|
||||
<TimeFrameHeader
|
||||
timeFrame={timeFrame}
|
||||
setTimeFrame={setTimeFrame}
|
||||
alignSelf="flex-start"
|
||||
/>
|
||||
</SubHeader>
|
||||
|
||||
<MonitorsList
|
||||
monitors={statusPage?.subMonitors}
|
||||
timeFrame={timeFrame}
|
||||
/>
|
||||
<Footer />
|
||||
<Dialog
|
||||
// open={isOpen.deleteStats}
|
||||
title={t("distributedUptimeStatusPageDeleteDialog")}
|
||||
onConfirm={() => {
|
||||
deleteStatusPage();
|
||||
setIsDeleteOpen(false);
|
||||
}}
|
||||
onCancel={() => {
|
||||
setIsDeleteOpen(false);
|
||||
}}
|
||||
open={isDeleteOpen}
|
||||
confirmationButtonLabel={t("distributedUptimeStatusPageDeleteConfirm")}
|
||||
description={t("distributedUptimeStatusPageDeleteDescription")}
|
||||
isLoading={isDeleting || isLoading}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default DistributedUptimeStatus;
|
||||
@@ -22,10 +22,6 @@ const Controls = ({ isDeleteOpen, setIsDeleteOpen, isDeleting, url, type }) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (currentPath.startsWith("/status/distributed/public")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
@@ -48,8 +44,6 @@ const Controls = ({ isDeleteOpen, setIsDeleteOpen, isDeleting, url, type }) => {
|
||||
onClick={() => {
|
||||
if (type === "uptime") {
|
||||
navigate(`/status/uptime/configure/${url}`);
|
||||
} else {
|
||||
navigate(`/status/distributed/configure/${url}`);
|
||||
}
|
||||
}}
|
||||
sx={{
|
||||
|
||||
@@ -23,10 +23,7 @@ const StatusPagesTable = ({ data }) => {
|
||||
onClick: (e, row) => {
|
||||
if (row.isPublished) {
|
||||
e.stopPropagation();
|
||||
const url =
|
||||
row.type === "distributed"
|
||||
? `/status/distributed/public/${row.url}`
|
||||
: `/status/uptime/public/${row.url}`;
|
||||
const url = `/status/uptime/public/${row.url}`;
|
||||
window.open(url, "_blank", "noopener,noreferrer");
|
||||
}
|
||||
},
|
||||
@@ -79,11 +76,7 @@ const StatusPagesTable = ({ data }) => {
|
||||
];
|
||||
|
||||
const handleRowClick = (statusPage) => {
|
||||
if (statusPage.type === "distributed") {
|
||||
navigate(`/status/distributed/${statusPage.url}`);
|
||||
} else if (statusPage.type === "uptime") {
|
||||
navigate(`/status/uptime/${statusPage.url}`);
|
||||
}
|
||||
navigate(`/status/uptime/${statusPage.url}`);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -27,15 +27,6 @@ import Infrastructure from "../Pages/Infrastructure/Monitors";
|
||||
import InfrastructureCreate from "../Pages/Infrastructure/Create";
|
||||
import InfrastructureDetails from "../Pages/Infrastructure/Details";
|
||||
|
||||
// Distributed Uptime
|
||||
import DistributedUptimeMonitors from "../Pages/DistributedUptime/Monitors";
|
||||
import CreateDistributedUptime from "../Pages/DistributedUptime/Create";
|
||||
import DistributedUptimeDetails from "../Pages/DistributedUptime/Details";
|
||||
|
||||
// Distributed Uptime Status
|
||||
import CreateDistributedUptimeStatus from "../Pages/DistributedUptimeStatus/Create";
|
||||
import DistributedUptimeStatus from "../Pages/DistributedUptimeStatus/Status";
|
||||
|
||||
// Server Status
|
||||
import ServerUnreachable from "../Pages/ServerUnreachable";
|
||||
|
||||
@@ -99,39 +90,6 @@ const Routes = () => {
|
||||
path="/uptime/configure/:monitorId/"
|
||||
element={<UptimeConfigure />}
|
||||
/>
|
||||
{/* <Route
|
||||
path="/distributed-uptime"
|
||||
element={
|
||||
<ProtectedDistributedUptimeRoute>
|
||||
<DistributedUptimeMonitors />{" "}
|
||||
</ProtectedDistributedUptimeRoute>
|
||||
}
|
||||
/> */}
|
||||
|
||||
{/* <Route
|
||||
path="/distributed-uptime/create"
|
||||
element={
|
||||
<ProtectedDistributedUptimeRoute>
|
||||
<CreateDistributedUptime />
|
||||
</ProtectedDistributedUptimeRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/distributed-uptime/configure/:monitorId"
|
||||
element={
|
||||
<ProtectedDistributedUptimeRoute>
|
||||
<CreateDistributedUptime />
|
||||
</ProtectedDistributedUptimeRoute>
|
||||
}
|
||||
/> */}
|
||||
{/* <Route
|
||||
path="/distributed-uptime/:monitorId"
|
||||
element={
|
||||
<ProtectedDistributedUptimeRoute>
|
||||
<DistributedUptimeDetails />
|
||||
</ProtectedDistributedUptimeRoute>
|
||||
}
|
||||
/> */}
|
||||
|
||||
<Route
|
||||
path="pagespeed"
|
||||
@@ -180,43 +138,16 @@ const Routes = () => {
|
||||
element={<Status />}
|
||||
/>
|
||||
|
||||
{/* <Route
|
||||
path="/status/distributed/:url"
|
||||
element={
|
||||
<ProtectedDistributedUptimeRoute>
|
||||
<DistributedUptimeStatus />
|
||||
</ProtectedDistributedUptimeRoute>
|
||||
}
|
||||
/> */}
|
||||
|
||||
<Route
|
||||
path="status/uptime/create"
|
||||
element={<CreateStatus />}
|
||||
/>
|
||||
|
||||
{/* <Route
|
||||
path="/status/distributed/create/:monitorId"
|
||||
element={
|
||||
<ProtectedDistributedUptimeRoute>
|
||||
<CreateDistributedUptimeStatus />
|
||||
</ProtectedDistributedUptimeRoute>
|
||||
}
|
||||
/> */}
|
||||
|
||||
<Route
|
||||
path="status/uptime/configure/:url"
|
||||
element={<CreateStatus />}
|
||||
/>
|
||||
|
||||
{/* <Route
|
||||
path="/status/distributed/configure/:url"
|
||||
element={
|
||||
<ProtectedDistributedUptimeRoute>
|
||||
<CreateDistributedUptimeStatus />
|
||||
</ProtectedDistributedUptimeRoute>
|
||||
}
|
||||
/> */}
|
||||
|
||||
<Route
|
||||
path="integrations"
|
||||
element={<Integrations />}
|
||||
@@ -283,10 +214,6 @@ const Routes = () => {
|
||||
path="/status/uptime/public/:url"
|
||||
element={<Status />}
|
||||
/>
|
||||
{/* <Route
|
||||
path="/status/distributed/public/:url"
|
||||
element={<DistributedUptimeStatus />}
|
||||
/> */}
|
||||
|
||||
<Route
|
||||
path="/server-unreachable"
|
||||
|
||||
@@ -847,7 +847,7 @@ class NetworkService {
|
||||
*/
|
||||
async fetchGithubLatestRelease() {
|
||||
return this.axiosInstance.get(
|
||||
"https://api.github.com/repos/bluewave-labs/bluewave-uptime/releases/latest",
|
||||
"https://api.github.com/repos/bluewave-labs/checkmate/releases/latest",
|
||||
{
|
||||
headers: {
|
||||
Authorization: null, // No authorization header for this request
|
||||
@@ -856,141 +856,6 @@ class NetworkService {
|
||||
);
|
||||
}
|
||||
|
||||
getDistributedUptimeMonitors(config) {
|
||||
const { teamId, limit, types, page, rowsPerPage, filter, field, order } = config;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (limit) params.append("limit", limit);
|
||||
if (types) {
|
||||
types.forEach((type) => {
|
||||
params.append("type", type);
|
||||
});
|
||||
}
|
||||
if (page) params.append("page", page);
|
||||
if (rowsPerPage) params.append("rowsPerPage", rowsPerPage);
|
||||
if (filter) params.append("filter", filter);
|
||||
if (field) params.append("field", field);
|
||||
if (order) params.append("order", order);
|
||||
|
||||
if (this.eventSource) {
|
||||
this.eventSource.close();
|
||||
}
|
||||
|
||||
const url = `${this.axiosInstance.defaults.baseURL}/distributed-uptime/monitors/${teamId}/initial?${params.toString()}`;
|
||||
return this.axiosInstance.get(url);
|
||||
}
|
||||
|
||||
subscribeToDistributedUptimeMonitors(config) {
|
||||
const {
|
||||
teamId,
|
||||
onUpdate,
|
||||
onError,
|
||||
onOpen,
|
||||
limit,
|
||||
types,
|
||||
page,
|
||||
rowsPerPage,
|
||||
filter,
|
||||
field,
|
||||
order,
|
||||
} = config;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (limit) params.append("limit", limit);
|
||||
if (types) {
|
||||
types.forEach((type) => {
|
||||
params.append("type", type);
|
||||
});
|
||||
}
|
||||
if (page) params.append("page", page);
|
||||
if (rowsPerPage) params.append("rowsPerPage", rowsPerPage);
|
||||
if (filter) params.append("filter", filter);
|
||||
if (field) params.append("field", field);
|
||||
if (order) params.append("order", order);
|
||||
|
||||
if (this.eventSource) {
|
||||
this.eventSource.close();
|
||||
}
|
||||
|
||||
const url = `${this.axiosInstance.defaults.baseURL}/distributed-uptime/monitors/${teamId}?${params.toString()}`;
|
||||
this.eventSource = new EventSource(url);
|
||||
|
||||
this.eventSource.onopen = () => {
|
||||
onOpen?.();
|
||||
};
|
||||
|
||||
this.eventSource.addEventListener("open", (e) => {});
|
||||
|
||||
this.eventSource.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
onUpdate(data);
|
||||
};
|
||||
|
||||
this.eventSource.onerror = (error) => {
|
||||
console.error("Monitor stream error:", error);
|
||||
onError?.();
|
||||
this.eventSource.close();
|
||||
};
|
||||
|
||||
// Returns a cleanup function
|
||||
return () => {
|
||||
if (this.eventSource) {
|
||||
this.eventSource.close();
|
||||
this.eventSource = null;
|
||||
}
|
||||
return () => {
|
||||
console.log("Nothing to cleanup");
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
getDistributedUptimeDetails(config) {
|
||||
const params = new URLSearchParams();
|
||||
const { monitorId, dateRange, normalize, isPublic } = config;
|
||||
if (dateRange) params.append("dateRange", dateRange);
|
||||
if (normalize) params.append("normalize", normalize);
|
||||
let url;
|
||||
if (isPublic) {
|
||||
url = `${this.axiosInstance.defaults.baseURL}/distributed-uptime/monitors/details/public/${monitorId}/initial?${params.toString()}`;
|
||||
} else {
|
||||
url = `${this.axiosInstance.defaults.baseURL}/distributed-uptime/monitors/details/${monitorId}/initial?${params.toString()}`;
|
||||
}
|
||||
return this.axiosInstance.get(url);
|
||||
}
|
||||
|
||||
subscribeToDistributedUptimeDetails(config) {
|
||||
const params = new URLSearchParams();
|
||||
const { monitorId, onUpdate, onOpen, onError, dateRange, normalize } = config;
|
||||
if (dateRange) params.append("dateRange", dateRange);
|
||||
if (normalize) params.append("normalize", normalize);
|
||||
|
||||
const url = `${this.axiosInstance.defaults.baseURL}/distributed-uptime/monitors/details/${monitorId}?${params.toString()}`;
|
||||
this.eventSource = new EventSource(url);
|
||||
|
||||
this.eventSource.onopen = (e) => {
|
||||
onOpen?.();
|
||||
};
|
||||
|
||||
this.eventSource.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
onUpdate(data);
|
||||
};
|
||||
|
||||
this.eventSource.onerror = (error) => {
|
||||
console.error("Monitor stream error:", error);
|
||||
onError?.();
|
||||
this.eventSource.close();
|
||||
};
|
||||
return () => {
|
||||
if (this.eventSource) {
|
||||
this.eventSource.close();
|
||||
this.eventSource = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async getStatusPage() {
|
||||
return this.axiosInstance.get(`/status-page`, {
|
||||
headers: {
|
||||
@@ -1010,20 +875,6 @@ class NetworkService {
|
||||
},
|
||||
});
|
||||
}
|
||||
async getDistributedStatusPageByUrl(config) {
|
||||
const { 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: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async getStatusPagesByTeamId(config) {
|
||||
const { teamId } = config;
|
||||
|
||||
@@ -239,7 +239,7 @@ const logoImageValidation = joi
|
||||
.optional(); // Make entire object optional
|
||||
|
||||
const statusPageValidation = joi.object({
|
||||
type: joi.string().valid("uptime", "distributed").required(),
|
||||
type: joi.string().valid("uptime").required(),
|
||||
isPublished: joi.bool(),
|
||||
companyName: joi
|
||||
.string()
|
||||
|
||||
@@ -65,8 +65,6 @@
|
||||
"settingsAppearanceDescription": "Switch between light and dark mode, or change user interface language",
|
||||
"settingsThemeMode": "Theme Mode",
|
||||
"settingsLanguage": "Language",
|
||||
"settingsDistributedUptime": "Distributed uptime",
|
||||
"settingsDistributedUptimeDescription": "Enable/disable distributed uptime monitoring.",
|
||||
"settingsEnabled": "Enabled",
|
||||
"settingsDisabled": "Disabled",
|
||||
"settingsHistoryAndMonitoring": "History of monitoring",
|
||||
@@ -86,8 +84,6 @@
|
||||
"settingsRemoveAllMonitorsButton": "Remove all monitors",
|
||||
"settingsRemoveAllMonitorsDialogTitle": "Do you want to remove all monitors?",
|
||||
"settingsRemoveAllMonitorsDialogConfirm": "Yes, remove all monitors",
|
||||
"settingsWallet": "Wallet",
|
||||
"settingsWalletDescription": "Connect your wallet here. This is required for the Distributed Uptime monitor to connect to multiple nodes globally.",
|
||||
"settingsAbout": "About",
|
||||
"settingsDevelopedBy": "Developed by Bluewave Labs.",
|
||||
"settingsSave": "Save",
|
||||
@@ -136,10 +132,6 @@
|
||||
"distributedUptimeCreateIncidentDescription": "When there is an incident, notify users.",
|
||||
"distributedUptimeCreateAdvancedSettings": "Advanced settings",
|
||||
"distributedUptimeDetailsNoMonitorHistory": "There is no check history for this monitor yet.",
|
||||
"distributedUptimeDetailsFooterHeading": "Made with ❤️ by UpRock & Bluewave Labs",
|
||||
"distributedUptimeDetailsFooterBuilt": "Built on",
|
||||
"distributedUptimeDetailsFooterSolana": "Solana",
|
||||
"distributedUptimeDetailsMonitorHeader": "Distributed Uptime Monitoring powered by DePIN",
|
||||
"distributedUptimeDetailsStatusHeaderUptime": "Uptime:",
|
||||
"distributedUptimeDetailsStatusHeaderLastUpdate": "Last updated",
|
||||
"notifications": {
|
||||
@@ -488,7 +480,6 @@
|
||||
"uptime": "Uptime",
|
||||
"pagespeed": "Pagespeed",
|
||||
"infrastructure": "Infrastructure",
|
||||
"distributedUptime": "Distributed Uptime",
|
||||
"incidents": "Incidents",
|
||||
"statusPages": "Status pages",
|
||||
"maintenance": "Maintenance",
|
||||
|
||||
@@ -87,7 +87,6 @@
|
||||
"settingsRemoveAllMonitorsDialogTitle": "Você deseja remover todos os monitores?",
|
||||
"settingsRemoveAllMonitorsDialogConfirm": "Sim, remova todos os monitores",
|
||||
"settingsWallet": "Carteira",
|
||||
"settingsWalletDescription": "Conecte sua carteira aqui. Isso é necessário para que o Distributed Uptime Monitor se conecte a vários nós globalmente.",
|
||||
"settingsAbout": "Sobre",
|
||||
"settingsDevelopedBy": "Desenvolvido pela Bluewave Labs.",
|
||||
"settingsSave": "Salvar",
|
||||
|
||||
@@ -65,8 +65,6 @@
|
||||
"settingsAppearanceDescription": "Переключение между светлым и темным режимом или изменение языка пользовательского интерфейса",
|
||||
"settingsThemeMode": "Тема",
|
||||
"settingsLanguage": "Язык",
|
||||
"settingsDistributedUptime": "Distributed uptime",
|
||||
"settingsDistributedUptimeDescription": "Включить/выключить distributed uptime monitoring.",
|
||||
"settingsEnabled": "Включено",
|
||||
"settingsDisabled": "Выключено",
|
||||
"settingsHistoryAndMonitoring": "История и мониторинг",
|
||||
@@ -86,8 +84,6 @@
|
||||
"settingsRemoveAllMonitorsButton": "Удалить все демонстрационные мониторы",
|
||||
"settingsRemoveAllMonitorsDialogTitle": "Хотите удалить все мониторы?",
|
||||
"settingsRemoveAllMonitorsDialogConfirm": "Да, очистить все мониторы",
|
||||
"settingsWallet": "Кошелёк",
|
||||
"settingsWalletDescription": "Подключите свой кошелек здесь. Это необходимо для того, чтобы монитор Distributed Uptime мог подключиться к нескольким узлам по всему миру.",
|
||||
"settingsAbout": "О",
|
||||
"settingsDevelopedBy": "Developed by Bluewave Labs.",
|
||||
"settingsSave": "Сохранить",
|
||||
@@ -139,7 +135,6 @@
|
||||
"distributedUptimeDetailsFooterHeading": "Made with ❤️ by UpRock & Bluewave Labs",
|
||||
"distributedUptimeDetailsFooterBuilt": "Built on",
|
||||
"distributedUptimeDetailsFooterSolana": "Solana",
|
||||
"distributedUptimeDetailsMonitorHeader": "Distributed Uptime Monitoring powered by DePIN",
|
||||
"distributedUptimeDetailsStatusHeaderUptime": "Аптайм:",
|
||||
"distributedUptimeDetailsStatusHeaderLastUpdate": "Последнее обновление",
|
||||
"notifications": {
|
||||
|
||||
@@ -4,25 +4,11 @@ const SERVICE_NAME = "diagnosticController";
|
||||
class DiagnosticController {
|
||||
constructor(db) {
|
||||
this.db = db;
|
||||
this.getDistributedUptimeDbExecutionStats =
|
||||
this.getDistributedUptimeDbExecutionStats.bind(this);
|
||||
this.getMonitorsByTeamIdExecutionStats =
|
||||
this.getMonitorsByTeamIdExecutionStats.bind(this);
|
||||
this.getDbStats = this.getDbStats.bind(this);
|
||||
}
|
||||
|
||||
async getDistributedUptimeDbExecutionStats(req, res, next) {
|
||||
try {
|
||||
const data = await this.db.getDistributedUptimeDbExecutionStats(req);
|
||||
return res.success({
|
||||
msg: "OK",
|
||||
data,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "getDbExecutionStats"));
|
||||
}
|
||||
}
|
||||
|
||||
async getMonitorsByTeamIdExecutionStats(req, res, next) {
|
||||
try {
|
||||
const data = await this.db.getMonitorsByTeamIdExecutionStats(req);
|
||||
|
||||
@@ -1,339 +0,0 @@
|
||||
import { handleError } from "./controllerUtils.js";
|
||||
import Monitor from "../db/models/Monitor.js";
|
||||
import DistributedUptimeCheck from "../db/models/DistributedUptimeCheck.js";
|
||||
const SERVICE_NAME = "DistributedUptimeQueueController";
|
||||
|
||||
class DistributedUptimeController {
|
||||
constructor({ db, http, statusService, logger }) {
|
||||
this.db = db;
|
||||
this.http = http;
|
||||
this.statusService = statusService;
|
||||
this.logger = logger;
|
||||
this.resultsCallback = this.resultsCallback.bind(this);
|
||||
this.getDistributedUptimeMonitors = this.getDistributedUptimeMonitors.bind(this);
|
||||
this.subscribeToDistributedUptimeMonitors =
|
||||
this.subscribeToDistributedUptimeMonitors.bind(this);
|
||||
|
||||
this.subscribeToDistributedUptimeMonitorDetails =
|
||||
this.subscribeToDistributedUptimeMonitorDetails.bind(this);
|
||||
this.getDistributedUptimeMonitorDetails =
|
||||
this.getDistributedUptimeMonitorDetails.bind(this);
|
||||
}
|
||||
|
||||
async resultsCallback(req, res, next) {
|
||||
try {
|
||||
const { id, result } = req.body;
|
||||
// Calculate response time
|
||||
const {
|
||||
first_byte_took,
|
||||
body_read_took,
|
||||
dns_took,
|
||||
conn_took,
|
||||
connect_took,
|
||||
tls_took,
|
||||
status_code,
|
||||
error,
|
||||
upt_burnt,
|
||||
} = result;
|
||||
|
||||
// Calculate response time
|
||||
const responseTime = first_byte_took / 1_000_000;
|
||||
if (!isFinite(responseTime) || responseTime <= 0 || responseTime > 30000) {
|
||||
this.logger.info({
|
||||
message: `Unreasonable response time detected: ${responseTime}ms from first_byte_took: ${first_byte_took}ns`,
|
||||
service: SERVICE_NAME,
|
||||
method: "resultsCallback",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate if server is up or down
|
||||
const isErrorStatus = status_code >= 400;
|
||||
const hasError = error !== "";
|
||||
|
||||
const status = isErrorStatus || hasError ? false : true;
|
||||
|
||||
// Build response
|
||||
const distributedUptimeResponse = {
|
||||
monitorId: id,
|
||||
type: "distributed_http",
|
||||
payload: result,
|
||||
status,
|
||||
code: status_code,
|
||||
responseTime,
|
||||
first_byte_took,
|
||||
body_read_took,
|
||||
dns_took,
|
||||
conn_took,
|
||||
connect_took,
|
||||
tls_took,
|
||||
upt_burnt,
|
||||
};
|
||||
if (error) {
|
||||
const code = status_code || this.NETWORK_ERROR;
|
||||
distributedUptimeResponse.code = code;
|
||||
distributedUptimeResponse.message =
|
||||
this.http.STATUS_CODES[code] || "Network Error";
|
||||
} else {
|
||||
distributedUptimeResponse.message = this.http.STATUS_CODES[status_code];
|
||||
}
|
||||
|
||||
await this.statusService.updateStatus(distributedUptimeResponse);
|
||||
|
||||
res.status(200).json({ message: "OK" });
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "resultsCallback"));
|
||||
}
|
||||
}
|
||||
|
||||
async getDistributedUptimeMonitors(req, res, next) {
|
||||
try {
|
||||
const monitors = await this.db.getMonitorsWithChecksByTeamId(req);
|
||||
return res.success({
|
||||
msg: "OK",
|
||||
data: monitors,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "getDistributedUptimeMonitors"));
|
||||
}
|
||||
}
|
||||
|
||||
async subscribeToDistributedUptimeMonitors(req, res, next) {
|
||||
try {
|
||||
res.setHeader("Content-Type", "text/event-stream");
|
||||
res.setHeader("Cache-Control", "no-cache");
|
||||
res.setHeader("Connection", "keep-alive");
|
||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
// Disable compression
|
||||
req.headers["accept-encoding"] = "identity";
|
||||
res.removeHeader("Content-Encoding");
|
||||
const BATCH_DELAY = 1000;
|
||||
let batchTimeout = null;
|
||||
let opInProgress = false;
|
||||
let monitorStream = null;
|
||||
let checksStream = null;
|
||||
|
||||
// Do things here
|
||||
const notifyChange = async () => {
|
||||
if (opInProgress) {
|
||||
// Get data
|
||||
const { count, monitors } = await this.db.getMonitorsWithChecksByTeamId(req);
|
||||
res.write(`data: ${JSON.stringify({ count, monitors })}\n\n`);
|
||||
opInProgress = false;
|
||||
}
|
||||
batchTimeout = null;
|
||||
};
|
||||
|
||||
const handleChange = () => {
|
||||
opInProgress = true;
|
||||
if (batchTimeout) clearTimeout(batchTimeout);
|
||||
batchTimeout = setTimeout(notifyChange, BATCH_DELAY);
|
||||
};
|
||||
|
||||
const createMonitorStream = () => {
|
||||
if (monitorStream) {
|
||||
try {
|
||||
monitorStream.close();
|
||||
} catch (error) {
|
||||
this.logger.error({
|
||||
message: "Error closing monitor stream",
|
||||
service: SERVICE_NAME,
|
||||
method: "subscribeToDistributedUptimeMonitors",
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
monitorStream = Monitor.watch(
|
||||
[{ $match: { operationType: { $in: ["insert", "update", "delete"] } } }],
|
||||
{ fullDocument: "updateLookup" }
|
||||
);
|
||||
|
||||
monitorStream.on("change", handleChange);
|
||||
monitorStream.on("error", (error) => {
|
||||
this.logger.error({
|
||||
message: "Error in monitor stream",
|
||||
service: SERVICE_NAME,
|
||||
method: "subscribeToDistributedUptimeMonitors",
|
||||
stack: error.stack,
|
||||
});
|
||||
createMonitorStream();
|
||||
});
|
||||
monitorStream.on("close", () => {
|
||||
monitorStream = null;
|
||||
});
|
||||
};
|
||||
|
||||
const createChecksStream = () => {
|
||||
if (checksStream) {
|
||||
try {
|
||||
checksStream.close();
|
||||
} catch (error) {
|
||||
this.logger.error({
|
||||
message: "Error closing checks stream",
|
||||
service: SERVICE_NAME,
|
||||
method: "subscribeToDistributedUptimeMonitors",
|
||||
details: error,
|
||||
});
|
||||
}
|
||||
}
|
||||
checksStream = DistributedUptimeCheck.watch(
|
||||
[{ $match: { operationType: { $in: ["insert", "update", "delete"] } } }],
|
||||
{ fullDocument: "updateLookup" }
|
||||
);
|
||||
checksStream.on("change", handleChange);
|
||||
checksStream.on("error", (error) => {
|
||||
this.logger.error({
|
||||
message: "Error in checks stream",
|
||||
service: SERVICE_NAME,
|
||||
method: "subscribeToDistributedUptimeMonitors",
|
||||
stack: error.stack,
|
||||
});
|
||||
createChecksStream();
|
||||
});
|
||||
checksStream.on("close", () => {
|
||||
checksStream = null;
|
||||
});
|
||||
};
|
||||
|
||||
createMonitorStream();
|
||||
createChecksStream();
|
||||
|
||||
req.on("close", () => {
|
||||
if (batchTimeout) {
|
||||
clearTimeout(batchTimeout);
|
||||
}
|
||||
monitorStream.close();
|
||||
checksStream.close();
|
||||
clearInterval(keepAlive);
|
||||
});
|
||||
|
||||
// Keep connection alive
|
||||
const keepAlive = setInterval(() => {
|
||||
res.write(": keepalive\n\n");
|
||||
}, 10000);
|
||||
|
||||
// Clean up on close
|
||||
req.on("close", () => {
|
||||
clearInterval(keepAlive);
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error({
|
||||
message: "Error in subscribeToDistributedUptimeMonitors",
|
||||
service: SERVICE_NAME,
|
||||
method: "subscribeToDistributedUptimeMonitors",
|
||||
stack: error.stack,
|
||||
});
|
||||
next(handleError(error, SERVICE_NAME, "subscribeToDistributedUptimeMonitors"));
|
||||
}
|
||||
}
|
||||
|
||||
async getDistributedUptimeMonitorDetails(req, res, next) {
|
||||
try {
|
||||
const monitor = await this.db.getDistributedUptimeDetailsById(req);
|
||||
return res.success({
|
||||
msg: "OK",
|
||||
data: monitor,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "getDistributedUptimeMonitorDetails"));
|
||||
}
|
||||
}
|
||||
|
||||
async subscribeToDistributedUptimeMonitorDetails(req, res, next) {
|
||||
try {
|
||||
res.setHeader("Content-Type", "text/event-stream");
|
||||
res.setHeader("Cache-Control", "no-cache");
|
||||
res.setHeader("Connection", "keep-alive");
|
||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
|
||||
// disable compression
|
||||
req.headers["accept-encoding"] = "identity";
|
||||
res.removeHeader("Content-Encoding");
|
||||
|
||||
const BATCH_DELAY = 1000;
|
||||
let batchTimeout = null;
|
||||
let opInProgress = false;
|
||||
let checksStream = null;
|
||||
// Do things here
|
||||
const notifyChange = async () => {
|
||||
try {
|
||||
if (opInProgress) {
|
||||
// Get data
|
||||
const monitor = await this.db.getDistributedUptimeDetailsById(req);
|
||||
res.write(`data: ${JSON.stringify({ monitor })}\n\n`);
|
||||
opInProgress = false;
|
||||
}
|
||||
batchTimeout = null;
|
||||
} catch (error) {
|
||||
opInProgress = false;
|
||||
batchTimeout = null;
|
||||
this.logger.error({
|
||||
message: "Error in notifyChange",
|
||||
service: SERVICE_NAME,
|
||||
method: "subscribeToDistributedUptimeMonitorDetails",
|
||||
stack: error.stack,
|
||||
});
|
||||
next(handleError(error, SERVICE_NAME, "getDistributedUptimeMonitorDetails"));
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = () => {
|
||||
opInProgress = true;
|
||||
if (batchTimeout) clearTimeout(batchTimeout);
|
||||
batchTimeout = setTimeout(notifyChange, BATCH_DELAY);
|
||||
};
|
||||
|
||||
const createCheckStream = () => {
|
||||
if (checksStream) {
|
||||
try {
|
||||
checksStream.close();
|
||||
} catch (error) {
|
||||
this.logger.error({
|
||||
message: "Error closing checks stream",
|
||||
service: SERVICE_NAME,
|
||||
method: "subscribeToDistributedUptimeMonitorDetails",
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
checksStream = DistributedUptimeCheck.watch(
|
||||
[{ $match: { operationType: { $in: ["insert", "update", "delete"] } } }],
|
||||
{ fullDocument: "updateLookup" }
|
||||
);
|
||||
|
||||
checksStream.on("change", handleChange);
|
||||
checksStream.on("error", (error) => {
|
||||
this.logger.error({
|
||||
message: "Error in checks stream",
|
||||
service: SERVICE_NAME,
|
||||
method: "subscribeToDistributedUptimeMonitorDetails",
|
||||
stack: error.stack,
|
||||
});
|
||||
createCheckStream();
|
||||
});
|
||||
checksStream.on("close", () => {
|
||||
checksStream = null;
|
||||
});
|
||||
};
|
||||
|
||||
createCheckStream();
|
||||
|
||||
// Handle client disconnect
|
||||
req.on("close", () => {
|
||||
if (batchTimeout) {
|
||||
clearTimeout(batchTimeout);
|
||||
}
|
||||
checksStream.close();
|
||||
clearInterval(keepAlive);
|
||||
});
|
||||
|
||||
// Keep connection alive
|
||||
const keepAlive = setInterval(() => {
|
||||
res.write(": keepalive\n\n");
|
||||
}, 10000);
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "getDistributedUptimeMonitorDetails"));
|
||||
}
|
||||
}
|
||||
}
|
||||
export default DistributedUptimeController;
|
||||
@@ -22,7 +22,6 @@ import logger from "../utils/logger.js";
|
||||
import { handleError, handleValidationError } from "./controllerUtils.js";
|
||||
import axios from "axios";
|
||||
import seedDb from "../db/mongo/utils/seedDb.js";
|
||||
import { seedDistributedTest } from "../db/mongo/utils/seedDb.js";
|
||||
const SERVICE_NAME = "monitorController";
|
||||
import pkg from "papaparse";
|
||||
|
||||
@@ -431,10 +430,6 @@ class MonitorController {
|
||||
name: "deleteHardwareChecks",
|
||||
fn: () => this.db.deleteHardwareChecksByMonitorId(monitor._id),
|
||||
},
|
||||
{
|
||||
name: "deleteDistributedUptimeChecks",
|
||||
fn: () => this.db.deleteDistributedChecksByMonitorId(monitor._id),
|
||||
},
|
||||
|
||||
// TODO We don't actually want to delete the status page if there are other monitors in it
|
||||
// We actually just want to remove the monitor being deleted from the status page.
|
||||
@@ -743,11 +738,8 @@ class MonitorController {
|
||||
const token = getTokenFromHeaders(req.headers);
|
||||
const { jwtSecret } = this.settingsService.getSettings();
|
||||
const { _id, teamId } = jwt.verify(token, jwtSecret);
|
||||
if (type === "distributed_test") {
|
||||
await seedDistributedTest(_id, teamId);
|
||||
} else {
|
||||
await seedDb(_id, teamId);
|
||||
}
|
||||
|
||||
await seedDb(_id, teamId);
|
||||
res.success({ msg: "Database seeded" });
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "seedDb"));
|
||||
|
||||
@@ -71,29 +71,6 @@ class StatusPageController {
|
||||
}
|
||||
};
|
||||
|
||||
getDistributedStatusPageByUrl = async (req, res, next) => {
|
||||
try {
|
||||
await getStatusPageParamValidation.validateAsync(req.params);
|
||||
await getStatusPageQueryValidation.validateAsync(req.query);
|
||||
} catch (error) {
|
||||
next(handleValidationError(error, SERVICE_NAME));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const statusPage = await this.db.getDistributedStatusPageByUrl({
|
||||
url: req.params.url,
|
||||
daysToShow: req.params.timeFrame,
|
||||
});
|
||||
return res.success({
|
||||
msg: this.stringService.statusPageByUrl,
|
||||
data: statusPage,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "getDistributedStatusPageByUrl"));
|
||||
}
|
||||
};
|
||||
|
||||
getStatusPageByUrl = async (req, res, next) => {
|
||||
try {
|
||||
await getStatusPageParamValidation.validateAsync(req.params);
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
import mongoose from "mongoose";
|
||||
|
||||
import { BaseCheckSchema } from "./Check.js";
|
||||
|
||||
// {
|
||||
// "id": "12123",
|
||||
// "result": {
|
||||
// "task_arrived": "2025-01-13T19:21:37.463466602Z",
|
||||
// "dns_start": "2025-01-14T00:21:33.1801319+05:00",
|
||||
// "dns_end": "2025-01-14T00:21:33.4582552+05:00",
|
||||
// "conn_start": "2025-01-14T00:21:33.1801319+05:00",
|
||||
// "conn_end": "2025-01-14T00:21:33.7076318+05:00",
|
||||
// "connect_start": "2025-01-14T00:21:33.4582552+05:00",
|
||||
// "connect_end": "2025-01-14T00:21:33.541899+05:00",
|
||||
// "tls_hand_shake_start": "2025-01-14T00:21:33.541899+05:00",
|
||||
// "tls_hand_shake_end": "2025-01-14T00:21:33.7076318+05:00",
|
||||
// "body_read_start": "2025-01-14T00:21:34.1894707+05:00",
|
||||
// "body_read_end": "2025-01-14T00:21:34.1894707+05:00",
|
||||
// "wrote_request": "2025-01-14T00:21:33.7076318+05:00",
|
||||
// "got_first_response_byte": "2025-01-14T00:21:34.1327652+05:00",
|
||||
// "first_byte_took": 425133400,
|
||||
// "body_read_took": 56030000,
|
||||
// "dns_took": 278123300,
|
||||
// "conn_took": 527499900,
|
||||
// "connect_took": 83643800,
|
||||
// "tls_took": 165732800,
|
||||
// "sni_name": "uprock.com",
|
||||
// "status_code": 200,
|
||||
// "body_size": 19320,
|
||||
// "request_header_size": 95,
|
||||
// "response_header_size": 246,
|
||||
// "response_headers": "X-Vercel-Id: bom1::iad1::sm87v-1736796096856-aec270c01f23\nDate: Mon, 13 Jan 2025 19:21:37 GMT\nServer: Vercel\nStrict-Transport-Security: max-age=63072000\nVary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url\nX-Matched-Path: /\nX-Powered-By: Next.js\nX-Vercel-Cache: MISS\nAge: 0\nCache-Control: private, no-cache, no-store, max-age=0, must-revalidate\nContent-Type: text/html; charset=utf-8",
|
||||
// "error": "",
|
||||
// "device_id": "d5f578e143a2cd603dd6bf5f846a86a538bde4a8fbe2ad1fca284ad9f033daf8",
|
||||
// "ip_address": "223.123.19.0",
|
||||
// "proof": "",
|
||||
// "created_at": "2025-01-13T19:21:37.463466912Z",
|
||||
// "continent": "AS",
|
||||
// "country_code": "PK",
|
||||
// "city": "Muzaffargarh",
|
||||
// "upt_burnt" : "0.01",
|
||||
// "location": {
|
||||
// "lat": 71.0968,
|
||||
// "lng": 30.0208
|
||||
// },
|
||||
// "payload": {
|
||||
// "callback": "https://webhook.site/2a15b0af-545a-4ac2-b913-153b97592d7a",
|
||||
// "x": "y"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
const LocationSchema = new mongoose.Schema(
|
||||
{
|
||||
lat: { type: Number, required: true },
|
||||
lng: { type: Number, required: true },
|
||||
},
|
||||
{ _id: false }
|
||||
);
|
||||
|
||||
const DistributedUptimeCheckSchema = mongoose.Schema(
|
||||
{
|
||||
...BaseCheckSchema.obj,
|
||||
first_byte_took: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
body_read_took: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
dns_took: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
conn_took: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
connect_took: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
tls_took: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
location: {
|
||||
type: LocationSchema,
|
||||
required: false,
|
||||
},
|
||||
continent: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
countryCode: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
city: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
uptBurnt: {
|
||||
type: mongoose.Schema.Types.Decimal128,
|
||||
required: false,
|
||||
},
|
||||
count: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
DistributedUptimeCheckSchema.pre("save", function (next) {
|
||||
if (this.isModified("uptBurnt") && typeof this.uptBurnt === "string") {
|
||||
this.uptBurnt = mongoose.Types.Decimal128.fromString(this.uptBurnt);
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
DistributedUptimeCheckSchema.index({ createdAt: 1 });
|
||||
DistributedUptimeCheckSchema.index({ monitorId: 1, updatedAt: 1 });
|
||||
DistributedUptimeCheckSchema.index({ monitorId: 1, updatedAt: -1 });
|
||||
DistributedUptimeCheckSchema.index(
|
||||
{
|
||||
monitorId: 1,
|
||||
createdAt: -1,
|
||||
city: 1,
|
||||
"location.lat": 1,
|
||||
"location.lng": 1,
|
||||
responseTime: 1,
|
||||
},
|
||||
{ background: true }
|
||||
);
|
||||
export default mongoose.model("DistributedUptimeCheck", DistributedUptimeCheckSchema);
|
||||
@@ -28,16 +28,7 @@ const MonitorSchema = mongoose.Schema(
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: [
|
||||
"http",
|
||||
"ping",
|
||||
"pagespeed",
|
||||
"hardware",
|
||||
"docker",
|
||||
"port",
|
||||
"distributed_http",
|
||||
"distributed_test",
|
||||
],
|
||||
enum: ["http", "ping", "pagespeed", "hardware", "docker", "port"],
|
||||
},
|
||||
ignoreTlsErrors: {
|
||||
type: Boolean,
|
||||
|
||||
@@ -18,7 +18,7 @@ const StatusPageSchema = mongoose.Schema(
|
||||
type: String,
|
||||
required: true,
|
||||
default: "uptime",
|
||||
enum: ["uptime", "distributed"],
|
||||
enum: ["uptime"],
|
||||
},
|
||||
companyName: {
|
||||
type: String,
|
||||
|
||||
@@ -43,11 +43,6 @@ import * as hardwareCheckModule from "./modules/hardwareCheckModule.js";
|
||||
|
||||
import * as checkModule from "./modules/checkModule.js";
|
||||
|
||||
//****************************************
|
||||
// Distributed Checks
|
||||
//****************************************
|
||||
import * as distributedCheckModule from "./modules/distributedCheckModule.js";
|
||||
|
||||
//****************************************
|
||||
// Maintenance Window
|
||||
//****************************************
|
||||
@@ -85,7 +80,6 @@ class MongoDB {
|
||||
Object.assign(this, pageSpeedCheckModule);
|
||||
Object.assign(this, hardwareCheckModule);
|
||||
Object.assign(this, checkModule);
|
||||
Object.assign(this, distributedCheckModule);
|
||||
Object.assign(this, maintenanceWindowModule);
|
||||
Object.assign(this, notificationModule);
|
||||
Object.assign(this, settingsModule);
|
||||
|
||||
@@ -2,7 +2,6 @@ import Check from "../../models/Check.js";
|
||||
import Monitor from "../../models/Monitor.js";
|
||||
import HardwareCheck from "../../models/HardwareCheck.js";
|
||||
import PageSpeedCheck from "../../models/PageSpeedCheck.js";
|
||||
import DistributedUptimeCheck from "../../models/DistributedUptimeCheck.js";
|
||||
import User from "../../models/User.js";
|
||||
import logger from "../../../utils/logger.js";
|
||||
import { ObjectId } from "mongodb";
|
||||
@@ -111,8 +110,6 @@ const getChecksByMonitor = async (req) => {
|
||||
port: Check,
|
||||
pagespeed: PageSpeedCheck,
|
||||
hardware: HardwareCheck,
|
||||
distributed_http: DistributedUptimeCheck,
|
||||
distributed_test: DistributedUptimeCheck,
|
||||
};
|
||||
|
||||
const Model = checkModels[type];
|
||||
@@ -202,12 +199,7 @@ const getChecksByTeam = async (req) => {
|
||||
pipeline: [{ $match: matchStage }],
|
||||
},
|
||||
},
|
||||
{
|
||||
$unionWith: {
|
||||
coll: "distributeduptimechecks",
|
||||
pipeline: [{ $match: matchStage }],
|
||||
},
|
||||
},
|
||||
|
||||
{ $sort: { createdAt: sortOrder } },
|
||||
{
|
||||
$facet: {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import DistributedUptimeCheck from "../../models/DistributedUptimeCheck.js";
|
||||
import Monitor from "../../models/Monitor.js";
|
||||
import { ObjectId } from "mongodb";
|
||||
|
||||
@@ -12,53 +11,6 @@ import {
|
||||
} from "./monitorModuleQueries.js";
|
||||
import { getDateRange } from "./monitorModule.js";
|
||||
|
||||
const getDistributedUptimeDbExecutionStats = async (req) => {
|
||||
try {
|
||||
const { monitorId } = req?.params ?? {};
|
||||
if (typeof monitorId === "undefined") {
|
||||
throw new Error();
|
||||
}
|
||||
const monitor = await Monitor.findById(monitorId);
|
||||
if (monitor === null || monitor === undefined) {
|
||||
throw new Error(this.stringService.dbFindMonitorById(monitorId));
|
||||
}
|
||||
|
||||
const { dateRange } = req.query;
|
||||
const dates = getDateRange(dateRange);
|
||||
const formatLookup = {
|
||||
recent: "%Y-%m-%dT%H:%M:00Z",
|
||||
day: {
|
||||
$concat: [
|
||||
{ $dateToString: { format: "%Y-%m-%dT%H:", date: "$createdAt" } },
|
||||
{
|
||||
$cond: [{ $lt: [{ $minute: "$createdAt" }, 30] }, "00:00Z", "30:00Z"],
|
||||
},
|
||||
],
|
||||
},
|
||||
week: "%Y-%m-%dT%H:00:00Z",
|
||||
month: "%Y-%m-%dT00:00:00Z",
|
||||
};
|
||||
|
||||
const dateString = formatLookup[dateRange];
|
||||
|
||||
const dePINDetailsByDateRangeStats = await DistributedUptimeCheck.aggregate(
|
||||
buildDePINDetailsByDateRange(monitor, dates, dateString)
|
||||
).explain("executionStats");
|
||||
const latestChecksStats = await DistributedUptimeCheck.aggregate(
|
||||
buildDePINLatestChecks(monitor)
|
||||
).explain("executionStats");
|
||||
|
||||
return {
|
||||
dePINDetailsByDateRangeStats,
|
||||
latestChecksStats,
|
||||
};
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getAllMonitorsWithUptimeStats";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const getMonitorsByTeamIdExecutionStats = async (req) => {
|
||||
try {
|
||||
let { limit, type, page, rowsPerPage, filter, field, order } = req.query;
|
||||
@@ -104,4 +56,4 @@ const getMonitorsByTeamIdExecutionStats = async (req) => {
|
||||
}
|
||||
};
|
||||
|
||||
export { getDistributedUptimeDbExecutionStats, getMonitorsByTeamIdExecutionStats };
|
||||
export { getMonitorsByTeamIdExecutionStats };
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
import DistributedUptimeCheck from "../../models/DistributedUptimeCheck.js";
|
||||
import { ObjectId } from "mongodb";
|
||||
|
||||
const SERVICE_NAME = "distributedCheckModule";
|
||||
|
||||
const createDistributedCheck = async (checkData) => {
|
||||
try {
|
||||
if (typeof checkData.monitorId === "string") {
|
||||
checkData.monitorId = ObjectId.createFromHexString(checkData.monitorId);
|
||||
}
|
||||
const check = await DistributedUptimeCheck.findOneAndUpdate(
|
||||
{
|
||||
monitorId: checkData.monitorId,
|
||||
city: checkData.city,
|
||||
},
|
||||
[
|
||||
{
|
||||
$set: {
|
||||
...checkData,
|
||||
|
||||
responseTime: {
|
||||
$cond: {
|
||||
if: { $ifNull: ["$count", false] },
|
||||
then: {
|
||||
$cond: {
|
||||
// Check if the new value is an outlier (3x the current average)
|
||||
if: {
|
||||
$and: [
|
||||
{ $gt: ["$responseTime", 0] },
|
||||
{
|
||||
$gt: [
|
||||
checkData.responseTime,
|
||||
{ $multiply: ["$responseTime", 3] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
then: "$responseTime", // Keep the current value if it's an outlier
|
||||
else: {
|
||||
// Normal case - calculate new average
|
||||
$round: [
|
||||
{
|
||||
$divide: [
|
||||
{
|
||||
$add: [
|
||||
{ $multiply: ["$responseTime", "$count"] },
|
||||
checkData.responseTime,
|
||||
],
|
||||
},
|
||||
{ $add: ["$count", 1] },
|
||||
],
|
||||
},
|
||||
2,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
else: checkData.responseTime,
|
||||
},
|
||||
},
|
||||
count: { $add: [{ $ifNull: ["$count", 0] }, 1] },
|
||||
},
|
||||
},
|
||||
],
|
||||
{
|
||||
upsert: true,
|
||||
new: true,
|
||||
runValidators: true,
|
||||
}
|
||||
);
|
||||
return check;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "createCheck";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const createDistributedChecks = async (checksData) => {
|
||||
try {
|
||||
if (!Array.isArray(checksData) || checksData.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bulkOps = checksData.map((checkData) => {
|
||||
if (typeof checkData.monitorId === "string") {
|
||||
checkData.monitorId = ObjectId.createFromHexString(checkData.monitorId);
|
||||
}
|
||||
|
||||
return {
|
||||
updateOne: {
|
||||
filter: {
|
||||
monitorId: checkData.monitorId,
|
||||
city: checkData.city,
|
||||
},
|
||||
update: [
|
||||
{
|
||||
$set: {
|
||||
...checkData,
|
||||
responseTime: {
|
||||
$cond: {
|
||||
if: { $ifNull: ["$count", false] },
|
||||
then: {
|
||||
$round: [
|
||||
{
|
||||
$divide: [
|
||||
{
|
||||
$add: [
|
||||
{ $multiply: ["$responseTime", "$count"] },
|
||||
checkData.responseTime,
|
||||
],
|
||||
},
|
||||
{ $add: ["$count", 1] },
|
||||
],
|
||||
},
|
||||
2,
|
||||
],
|
||||
},
|
||||
else: checkData.responseTime,
|
||||
},
|
||||
},
|
||||
count: { $add: [{ $ifNull: ["$count", 0] }, 1] },
|
||||
},
|
||||
},
|
||||
],
|
||||
upsert: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
await DistributedUptimeCheck.bulkWrite(bulkOps, {
|
||||
ordered: false,
|
||||
});
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "createDistributedChecks";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const deleteDistributedChecksByMonitorId = async (monitorId) => {
|
||||
try {
|
||||
const result = await DistributedUptimeCheck.deleteMany({ monitorId });
|
||||
return result.deletedCount;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "deleteDistributedChecksByMonitorId";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
createDistributedCheck,
|
||||
createDistributedChecks,
|
||||
deleteDistributedChecksByMonitorId,
|
||||
};
|
||||
@@ -3,7 +3,6 @@ import MonitorStats from "../../models/MonitorStats.js";
|
||||
import Check from "../../models/Check.js";
|
||||
import PageSpeedCheck from "../../models/PageSpeedCheck.js";
|
||||
import HardwareCheck from "../../models/HardwareCheck.js";
|
||||
import DistributedUptimeCheck from "../../models/DistributedUptimeCheck.js";
|
||||
import Notification from "../../models/Notification.js";
|
||||
import { NormalizeData, NormalizeDataUptimeDetails } from "../../../utils/dataUtils.js";
|
||||
import ServiceRegistry from "../../../service/serviceRegistry.js";
|
||||
@@ -385,72 +384,6 @@ const getUptimeDetailsById = async (req) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getDistributedUptimeDetailsById = async (req) => {
|
||||
try {
|
||||
const { monitorId } = req?.params ?? {};
|
||||
if (typeof monitorId === "undefined") {
|
||||
throw new Error();
|
||||
}
|
||||
const monitor = await Monitor.findById(monitorId);
|
||||
if (monitor === null || monitor === undefined) {
|
||||
throw new Error(this.stringService.dbFindMonitorById(monitorId));
|
||||
}
|
||||
|
||||
const { dateRange, normalize } = req.query;
|
||||
const dates = getDateRange(dateRange);
|
||||
const formatLookup = {
|
||||
recent: "%Y-%m-%dT%H:%M:00Z",
|
||||
day: {
|
||||
$concat: [
|
||||
{ $dateToString: { format: "%Y-%m-%dT%H:", date: "$updatedAt" } },
|
||||
{
|
||||
$cond: [{ $lt: [{ $minute: "$updatedAt" }, 30] }, "00:00Z", "30:00Z"],
|
||||
},
|
||||
],
|
||||
},
|
||||
week: "%Y-%m-%dT%H:00:00Z",
|
||||
month: "%Y-%m-%dT00:00:00Z",
|
||||
};
|
||||
|
||||
const dateString = formatLookup[dateRange];
|
||||
|
||||
const monitorStatsResult = await MonitorStats.aggregate(
|
||||
buildMonitorStatsPipeline(monitor)
|
||||
);
|
||||
const monitorStats = monitorStatsResult[0];
|
||||
const dePINDetailsByDateRange = await DistributedUptimeCheck.aggregate(
|
||||
buildDePINDetailsByDateRange(monitor, dates, dateString)
|
||||
);
|
||||
const latestChecks = await DistributedUptimeCheck.aggregate(
|
||||
buildDePINLatestChecks(monitor)
|
||||
);
|
||||
|
||||
const checkData = dePINDetailsByDateRange[0];
|
||||
const normalizedGroupChecks = NormalizeDataUptimeDetails(
|
||||
checkData.groupedChecks,
|
||||
10,
|
||||
100
|
||||
);
|
||||
const data = {
|
||||
...monitor.toObject(),
|
||||
latestChecks,
|
||||
totalChecks: monitorStats?.totalChecks,
|
||||
avgResponseTime: monitorStats?.avgResponseTime,
|
||||
uptimePercentage: monitorStats?.uptimePercentage,
|
||||
timeSinceLastCheck: monitorStats?.timeSinceLastCheck,
|
||||
uptBurnt: monitorStats?.uptBurnt,
|
||||
groupedChecks: normalizedGroupChecks,
|
||||
groupedMapChecks: checkData.groupedMapChecks,
|
||||
};
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getDistributedUptimeDetailsById";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get stats by monitor ID
|
||||
* @async
|
||||
@@ -876,7 +809,6 @@ export {
|
||||
getMonitorsAndSummaryByTeamId,
|
||||
getMonitorsWithChecksByTeamId,
|
||||
getUptimeDetailsById,
|
||||
getDistributedUptimeDetailsById,
|
||||
createMonitor,
|
||||
createBulkMonitors,
|
||||
deleteMonitor,
|
||||
|
||||
@@ -613,8 +613,6 @@ const buildMonitorsWithChecksByTeamIdPipeline = ({
|
||||
checksCollection = "pagespeedchecks";
|
||||
} else if (type === "hardware") {
|
||||
checksCollection = "hardwarechecks";
|
||||
} else if (type === "distributed_http" || type === "distributed_test") {
|
||||
checksCollection = "distributeduptimechecks";
|
||||
}
|
||||
monitorsPipeline.push({
|
||||
$lookup: {
|
||||
@@ -702,8 +700,6 @@ const buildFilteredMonitorsByTeamIdPipeline = ({
|
||||
checksCollection = "pagespeedchecks";
|
||||
} else if (type === "hardware") {
|
||||
checksCollection = "hardwarechecks";
|
||||
} else if (type === "distributed_http" || type === "distributed_test") {
|
||||
checksCollection = "distributeduptimechecks";
|
||||
}
|
||||
pipeline.push({
|
||||
$lookup: {
|
||||
@@ -861,26 +857,6 @@ const buildGetMonitorsByTeamIdPipeline = (req) => {
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(limit
|
||||
? [
|
||||
{
|
||||
$lookup: {
|
||||
from: "distributeduptimechecks",
|
||||
let: { monitorId: "$_id" },
|
||||
pipeline: [
|
||||
{
|
||||
$match: {
|
||||
$expr: { $eq: ["$monitorId", "$$monitorId"] },
|
||||
},
|
||||
},
|
||||
{ $sort: { createdAt: -1 } },
|
||||
...(limit ? [{ $limit: limit }] : []),
|
||||
],
|
||||
as: "distributeduptimechecks",
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
{
|
||||
$addFields: {
|
||||
@@ -899,14 +875,6 @@ const buildGetMonitorsByTeamIdPipeline = (req) => {
|
||||
case: { $eq: ["$type", "hardware"] },
|
||||
then: "$hardwarechecks",
|
||||
},
|
||||
{
|
||||
case: { $eq: ["$type", "distributed_http"] },
|
||||
then: "$distributeduptimechecks",
|
||||
},
|
||||
{
|
||||
case: { $eq: ["$type", "distributed_test"] },
|
||||
then: "$distributeduptimechecks",
|
||||
},
|
||||
],
|
||||
default: [],
|
||||
},
|
||||
|
||||
@@ -61,105 +61,6 @@ const updateStatusPage = async (statusPageData, image) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getDistributedStatusPageByUrl = async ({ url, daysToShow = 30 }) => {
|
||||
const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
|
||||
try {
|
||||
const statusPage = await StatusPage.findOne({ url }).lean();
|
||||
|
||||
if (!statusPage) {
|
||||
const error = new Error(stringService.statusPageNotFound);
|
||||
error.status = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
// No sub monitors, return status page
|
||||
if (statusPage.subMonitors.length === 0) {
|
||||
return statusPage;
|
||||
}
|
||||
// Sub monitors, return status page with sub monitors
|
||||
const daysAgo = new Date();
|
||||
daysAgo.setDate(daysAgo.getDate() - daysToShow);
|
||||
|
||||
const subMonitors = await Monitor.aggregate([
|
||||
{ $match: { _id: { $in: statusPage.subMonitors } } },
|
||||
{
|
||||
$addFields: {
|
||||
orderIndex: { $indexOfArray: [statusPage.subMonitors, "$_id"] },
|
||||
},
|
||||
},
|
||||
|
||||
// Return 30 days of checks by default
|
||||
{
|
||||
$lookup: {
|
||||
from: "checks",
|
||||
let: { monitorId: "$_id" },
|
||||
pipeline: [
|
||||
{
|
||||
$match: {
|
||||
$expr: {
|
||||
$and: [
|
||||
{ $eq: ["$monitorId", "$$monitorId"] },
|
||||
{ $gte: ["$updatedAt", daysAgo] },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: {
|
||||
$dateToString: { format: "%Y-%m-%d", date: "$updatedAt" },
|
||||
},
|
||||
responseTime: {
|
||||
$avg: "$responseTime",
|
||||
},
|
||||
trueCount: {
|
||||
$sum: {
|
||||
$cond: [{ $eq: ["$status", true] }, 1, 0],
|
||||
},
|
||||
},
|
||||
totalCount: {
|
||||
$sum: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
responseTime: 1,
|
||||
upPercentage: {
|
||||
$cond: [
|
||||
{ $eq: ["$totalCount", 0] },
|
||||
0,
|
||||
{ $multiply: [{ $divide: ["$trueCount", "$totalCount"] }, 100] },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$sort: { _id: -1 },
|
||||
},
|
||||
],
|
||||
as: "checks",
|
||||
},
|
||||
},
|
||||
{ $sort: { orderIndex: 1 } },
|
||||
{ $project: { orderIndex: 0 } },
|
||||
]);
|
||||
|
||||
const normalizedSubMonitors = subMonitors.map((monitor) => {
|
||||
return {
|
||||
...monitor,
|
||||
checks: NormalizeData(monitor.checks, 10, 100),
|
||||
};
|
||||
});
|
||||
return { ...statusPage, subMonitors: normalizedSubMonitors };
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getDistributedStatusPageByUrl";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusPageByUrl = async (url, type) => {
|
||||
// TODO This is deprecated, can remove and have controller call getStatusPage
|
||||
try {
|
||||
@@ -306,7 +207,6 @@ export {
|
||||
getStatusPagesByTeamId,
|
||||
getStatusPage,
|
||||
getStatusPageByUrl,
|
||||
getDistributedStatusPageByUrl,
|
||||
deleteStatusPage,
|
||||
deleteStatusPagesByMonitorId,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import Monitor from "../../models/Monitor.js";
|
||||
import Check from "../../models/Check.js";
|
||||
import DistributedUptimeCheck from "../../models/DistributedUptimeCheck.js";
|
||||
import logger from "../../../utils/logger.js";
|
||||
|
||||
const generateRandomUrl = () => {
|
||||
@@ -73,105 +72,4 @@ const seedDb = async (userId, teamId) => {
|
||||
}
|
||||
};
|
||||
|
||||
const generateDistributedChecks = (monitorId, teamId, count = 2880) => {
|
||||
const checks = [];
|
||||
const endTime = new Date();
|
||||
const startTime = new Date(endTime - 48 * 60 * 60 * 1000);
|
||||
|
||||
// Sample locations for variety
|
||||
const locations = [
|
||||
{
|
||||
city: "New York",
|
||||
countryCode: "US",
|
||||
continent: "NA",
|
||||
location: { lat: 40.7128, lng: -74.006 },
|
||||
},
|
||||
{
|
||||
city: "London",
|
||||
countryCode: "GB",
|
||||
continent: "EU",
|
||||
location: { lat: 51.5074, lng: -0.1278 },
|
||||
},
|
||||
{
|
||||
city: "Singapore",
|
||||
countryCode: "SG",
|
||||
continent: "AS",
|
||||
location: { lat: 1.3521, lng: 103.8198 },
|
||||
},
|
||||
];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const timestamp = new Date(startTime.getTime() + i * 60 * 1000);
|
||||
const location = locations[Math.floor(Math.random() * locations.length)];
|
||||
const status = Math.random() > 0.05; // 95% success rate
|
||||
|
||||
checks.push({
|
||||
monitorId,
|
||||
teamId,
|
||||
status,
|
||||
responseTime: Math.floor(Math.random() * 1000), // Random response time between 0-1000ms
|
||||
first_byte_took: Math.floor(Math.random() * 300000), // 0-300ms
|
||||
body_read_took: Math.floor(Math.random() * 100000), // 0-100ms
|
||||
dns_took: Math.floor(Math.random() * 100000), // 0-100ms
|
||||
conn_took: Math.floor(Math.random() * 200000), // 0-200ms
|
||||
connect_took: Math.floor(Math.random() * 150000), // 0-150ms
|
||||
tls_took: Math.floor(Math.random() * 200000), // 0-200ms
|
||||
location: location.location,
|
||||
continent: location.continent,
|
||||
countryCode: location.countryCode,
|
||||
city: location.city,
|
||||
uptBurnt: "0.01", // Will be converted to Decimal128 by the schema
|
||||
createdAt: timestamp,
|
||||
updatedAt: timestamp,
|
||||
});
|
||||
}
|
||||
|
||||
return checks;
|
||||
};
|
||||
|
||||
export const seedDistributedTest = async (userId, teamId) => {
|
||||
try {
|
||||
logger.info({
|
||||
message: "Deleting all test monitors and checks",
|
||||
service: "DB",
|
||||
method: "seedDistributedTest",
|
||||
});
|
||||
|
||||
const testMonitors = await Monitor.find({
|
||||
type: "distributed_test",
|
||||
});
|
||||
|
||||
testMonitors.forEach(async (monitor) => {
|
||||
await DistributedUptimeCheck.deleteMany({ monitorId: monitor._id });
|
||||
await Monitor.deleteOne({ _id: monitor._id });
|
||||
});
|
||||
|
||||
logger.info({
|
||||
message: "Adding test monitors and checks",
|
||||
service: "DB",
|
||||
method: "seedDistributedTest",
|
||||
});
|
||||
const monitor = await Monitor.create({
|
||||
name: "Distributed Test",
|
||||
url: "https://distributed-test.com",
|
||||
type: "distributed_test",
|
||||
userId,
|
||||
teamId,
|
||||
interval: 60000,
|
||||
active: false,
|
||||
});
|
||||
const checks = generateDistributedChecks(monitor._id, teamId, 2800);
|
||||
await DistributedUptimeCheck.insertMany(checks);
|
||||
return monitor;
|
||||
} catch (error) {
|
||||
logger.error({
|
||||
message: "Error seeding distributed test",
|
||||
service: "DB",
|
||||
method: "seedDistributedTest",
|
||||
stack: error.stack,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default seedDb;
|
||||
|
||||
@@ -36,9 +36,6 @@ import StatusPageController from "./controllers/statusPageController.js";
|
||||
import QueueRoutes from "./routes/queueRoute.js";
|
||||
import QueueController from "./controllers/queueController.js";
|
||||
|
||||
import DistributedUptimeRoutes from "./routes/distributedUptimeRoute.js";
|
||||
import DistributedUptimeController from "./controllers/distributedUptimeController.js";
|
||||
|
||||
import NotificationRoutes from "./routes/notificationRoute.js";
|
||||
import NotificationController from "./controllers/notificationController.js";
|
||||
|
||||
@@ -300,13 +297,6 @@ const startApp = async () => {
|
||||
ServiceRegistry.get(StatusService.SERVICE_NAME)
|
||||
);
|
||||
|
||||
const distributedUptimeController = new DistributedUptimeController({
|
||||
db: ServiceRegistry.get(MongoDB.SERVICE_NAME),
|
||||
http,
|
||||
statusService: ServiceRegistry.get(StatusService.SERVICE_NAME),
|
||||
logger,
|
||||
});
|
||||
|
||||
const diagnosticController = new DiagnosticController(
|
||||
ServiceRegistry.get(MongoDB.SERVICE_NAME)
|
||||
);
|
||||
@@ -322,9 +312,7 @@ const startApp = async () => {
|
||||
);
|
||||
const queueRoutes = new QueueRoutes(queueController);
|
||||
const statusPageRoutes = new StatusPageRoutes(statusPageController);
|
||||
const distributedUptimeRoutes = new DistributedUptimeRoutes(
|
||||
distributedUptimeController
|
||||
);
|
||||
|
||||
const notificationRoutes = new NotificationRoutes(notificationController);
|
||||
const diagnosticRoutes = new DiagnosticRoutes(diagnosticController);
|
||||
// Middleware
|
||||
@@ -375,7 +363,6 @@ const startApp = async () => {
|
||||
app.use("/api/v1/checks", verifyJWT, checkRoutes.getRouter());
|
||||
app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRoutes.getRouter());
|
||||
app.use("/api/v1/queue", verifyJWT, queueRoutes.getRouter());
|
||||
app.use("/api/v1/distributed-uptime", distributedUptimeRoutes.getRouter());
|
||||
app.use("/api/v1/status-page", statusPageRoutes.getRouter());
|
||||
app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter());
|
||||
app.use("/api/v1/diagnostic", verifyJWT, diagnosticRoutes.getRouter());
|
||||
|
||||
@@ -7,11 +7,6 @@ class DiagnosticRoutes {
|
||||
this.initRoutes();
|
||||
}
|
||||
initRoutes() {
|
||||
this.router.get(
|
||||
"/db/execution-stats/:monitorId",
|
||||
this.diagnosticController.getDistributedUptimeDbExecutionStats
|
||||
);
|
||||
|
||||
this.router.get(
|
||||
"/db/get-monitors-by-team-id/:teamId",
|
||||
this.diagnosticController.getMonitorsByTeamIdExecutionStats
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { Router } from "express";
|
||||
import { verifyJWT } from "../middleware/verifyJWT.js";
|
||||
class DistributedUptimeRoutes {
|
||||
constructor(distributedUptimeController) {
|
||||
this.router = Router();
|
||||
this.distributedUptimeController = distributedUptimeController;
|
||||
this.initRoutes();
|
||||
}
|
||||
initRoutes() {
|
||||
this.router.post("/callback", this.distributedUptimeController.resultsCallback);
|
||||
|
||||
this.router.get(
|
||||
"/monitors/:teamId/initial",
|
||||
verifyJWT,
|
||||
this.distributedUptimeController.getDistributedUptimeMonitors
|
||||
);
|
||||
|
||||
this.router.get(
|
||||
"/monitors/:teamId",
|
||||
this.distributedUptimeController.subscribeToDistributedUptimeMonitors
|
||||
);
|
||||
|
||||
this.router.get(
|
||||
"/monitors/details/:monitorId/initial",
|
||||
verifyJWT,
|
||||
this.distributedUptimeController.getDistributedUptimeMonitorDetails
|
||||
);
|
||||
this.router.get(
|
||||
"/monitors/details/public/:monitorId/initial",
|
||||
this.distributedUptimeController.getDistributedUptimeMonitorDetails
|
||||
);
|
||||
|
||||
this.router.get(
|
||||
"/monitors/details/:monitorId",
|
||||
this.distributedUptimeController.subscribeToDistributedUptimeMonitorDetails
|
||||
);
|
||||
}
|
||||
|
||||
getRouter() {
|
||||
return this.router;
|
||||
}
|
||||
}
|
||||
|
||||
export default DistributedUptimeRoutes;
|
||||
@@ -18,10 +18,6 @@ class StatusPageRoutes {
|
||||
this.statusPageController.getStatusPagesByTeamId
|
||||
);
|
||||
this.router.get("/:url", this.statusPageController.getStatusPageByUrl);
|
||||
this.router.get(
|
||||
"/distributed/:url",
|
||||
this.statusPageController.getDistributedStatusPageByUrl
|
||||
);
|
||||
|
||||
this.router.post(
|
||||
"/",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const QUEUE_NAMES = ["uptime", "pagespeed", "hardware", "distributed"];
|
||||
const QUEUE_NAMES = ["uptime", "pagespeed", "hardware"];
|
||||
const SERVICE_NAME = "JobQueue";
|
||||
const HEALTH_CHECK_INTERVAL = 10 * 60 * 1000; // 10 minutes
|
||||
const QUEUE_LOOKUP = {
|
||||
@@ -8,7 +8,6 @@ const QUEUE_LOOKUP = {
|
||||
port: "uptime",
|
||||
docker: "uptime",
|
||||
pagespeed: "pagespeed",
|
||||
distributed_http: "distributed",
|
||||
};
|
||||
const getSchedulerId = (monitor) => `scheduler:${monitor.type}:${monitor._id}`;
|
||||
|
||||
|
||||
@@ -256,13 +256,6 @@ class JobQueueHelper {
|
||||
await job.updateProgress(30);
|
||||
const monitor = job.data;
|
||||
const networkResponse = await this.networkService.getStatus(monitor);
|
||||
if (
|
||||
job.data.type === "distributed_http" ||
|
||||
job.data.type === "distributed_test"
|
||||
) {
|
||||
await job.updateProgress(100);
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the network response is not found, we're done
|
||||
if (!networkResponse) {
|
||||
|
||||
@@ -29,10 +29,6 @@ class PulseQueueHelper {
|
||||
}
|
||||
|
||||
const networkResponse = await this.networkService.getStatus(monitor);
|
||||
if (monitor.type === "distributed_http" || monitor.type === "distributed_test") {
|
||||
await job.updateProgress(100);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!networkResponse) {
|
||||
throw new Error("No network response");
|
||||
|
||||
@@ -7,7 +7,6 @@ const TYPE_MAP = {
|
||||
docker: "checks",
|
||||
pagespeed: "pagespeedChecks",
|
||||
hardware: "hardwareChecks",
|
||||
distributed_http: "distributedChecks",
|
||||
};
|
||||
|
||||
class BufferService {
|
||||
@@ -19,13 +18,11 @@ class BufferService {
|
||||
checks: [],
|
||||
pagespeedChecks: [],
|
||||
hardwareChecks: [],
|
||||
distributedChecks: [],
|
||||
};
|
||||
this.OPERATION_MAP = {
|
||||
checks: this.db.createChecks,
|
||||
pagespeedChecks: this.db.createPageSpeedChecks,
|
||||
hardwareChecks: this.db.createHardwareChecks,
|
||||
distributedChecks: this.db.createDistributedChecks,
|
||||
};
|
||||
|
||||
this.scheduleNextFlush();
|
||||
|
||||
@@ -23,8 +23,6 @@ class NetworkService {
|
||||
this.TYPE_HARDWARE = "hardware";
|
||||
this.TYPE_DOCKER = "docker";
|
||||
this.TYPE_PORT = "port";
|
||||
this.TYPE_DISTRIBUTED_HTTP = "distributed_http";
|
||||
this.TYPE_DISTRIBUTED_TEST = "distributed_test";
|
||||
this.SERVICE_NAME = SERVICE_NAME;
|
||||
this.NETWORK_ERROR = 5000;
|
||||
this.PING_ERROR = 5001;
|
||||
@@ -414,34 +412,6 @@ class NetworkService {
|
||||
}
|
||||
}
|
||||
|
||||
async requestDistributedHttp(monitor) {
|
||||
try {
|
||||
const CALLBACK_URL = process.env.CALLBACK_URL;
|
||||
|
||||
const response = await this.axios.post(
|
||||
UPROCK_ENDPOINT,
|
||||
{
|
||||
id: monitor._id,
|
||||
url: monitor.url,
|
||||
callback: `${CALLBACK_URL}/api/v1/distributed-uptime/callback`,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-checkmate-key": process.env.UPROCK_API_KEY,
|
||||
},
|
||||
}
|
||||
);
|
||||
if (response.data.success === false) {
|
||||
throw new Error(response.data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
error.service = this.SERVICE_NAME;
|
||||
error.method = "requestDistributedHttp";
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles unsupported job types by throwing an error with details.
|
||||
*
|
||||
@@ -517,10 +487,6 @@ class NetworkService {
|
||||
return await this.requestDocker(monitor);
|
||||
case this.TYPE_PORT:
|
||||
return await this.requestPort(monitor);
|
||||
case this.TYPE_DISTRIBUTED_HTTP:
|
||||
return await this.requestDistributedHttp(monitor);
|
||||
case this.TYPE_DISTRIBUTED_TEST:
|
||||
return;
|
||||
default:
|
||||
return this.handleUnsupportedType(type);
|
||||
}
|
||||
|
||||
@@ -209,23 +209,6 @@ class StatusService {
|
||||
tls_took,
|
||||
};
|
||||
|
||||
if (type === "distributed_http") {
|
||||
if (typeof payload === "undefined") {
|
||||
return undefined;
|
||||
}
|
||||
check.continent = payload.continent;
|
||||
check.countryCode = payload.country_code;
|
||||
check.city = payload.city;
|
||||
check.location = payload.location;
|
||||
check.uptBurnt = payload.upt_burnt;
|
||||
check.first_byte_took = payload.first_byte_took;
|
||||
check.body_read_took = payload.body_read_took;
|
||||
check.dns_took = payload.dns_took;
|
||||
check.conn_took = payload.conn_took;
|
||||
check.connect_took = payload.connect_took;
|
||||
check.tls_took = payload.tls_took;
|
||||
}
|
||||
|
||||
if (type === "pagespeed") {
|
||||
if (typeof payload === "undefined") {
|
||||
this.logger.warn({
|
||||
|
||||
@@ -303,18 +303,7 @@ const getChecksParamValidation = joi.object({
|
||||
});
|
||||
|
||||
const getChecksQueryValidation = joi.object({
|
||||
type: joi
|
||||
.string()
|
||||
.valid(
|
||||
"http",
|
||||
"ping",
|
||||
"pagespeed",
|
||||
"hardware",
|
||||
"docker",
|
||||
"port",
|
||||
"distributed_http",
|
||||
"distributed_test"
|
||||
),
|
||||
type: joi.string().valid("http", "ping", "pagespeed", "hardware", "docker", "port"),
|
||||
sortOrder: joi.string().valid("asc", "desc"),
|
||||
limit: joi.number(),
|
||||
dateRange: joi.string().valid("recent", "hour", "day", "week", "month", "all"),
|
||||
@@ -446,14 +435,14 @@ const getStatusPageParamValidation = joi.object({
|
||||
});
|
||||
|
||||
const getStatusPageQueryValidation = joi.object({
|
||||
type: joi.string().valid("uptime", "distributed").required(),
|
||||
type: joi.string().valid("uptime").required(),
|
||||
timeFrame: joi.number().optional(),
|
||||
});
|
||||
|
||||
const createStatusPageBodyValidation = joi.object({
|
||||
userId: joi.string().required(),
|
||||
teamId: joi.string().required(),
|
||||
type: joi.string().valid("uptime", "distributed").required(),
|
||||
type: joi.string().valid("uptime").required(),
|
||||
companyName: joi.string().required(),
|
||||
url: joi
|
||||
.string()
|
||||
|
||||
Reference in New Issue
Block a user