Merge pull request #1712 from bluewave-labs/fix/fe/du-ui-fixes-and-refactor

fix: fe/du UI fixes and refactor
This commit is contained in:
Alexander Holliday authored and GitHub committed 2025-02-06 19:30:07 -08:00
commit 7d2b2af036
17 files changed
+601 -574

No files matched your search

@@ -2,16 +2,16 @@ import { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
import { createToast } from "../../../../../Utils/toastUtils";
import { logger } from "../../../../../Utils/Logger";
import { createToast } from "../../Utils/toastUtils";
import { logger } from "../../Utils/Logger";
import { IconButton, Menu, MenuItem } from "@mui/material";
import {
deleteUptimeMonitor,
pauseUptimeMonitor,
} from "../../../../../Features/UptimeMonitors/uptimeMonitorsSlice";
import Settings from "../../../../../assets/icons/settings-bold.svg?react";
} from "../../Features/UptimeMonitors/uptimeMonitorsSlice";
import Settings from "../../assets/icons/settings-bold.svg?react";
import PropTypes from "prop-types";
import Dialog from "../../../../../Components/Dialog";
import Dialog from "../../Components/Dialog";
const ActionsMenu = ({
monitor,
@@ -1,7 +1,7 @@
import { Stack, Typography } from "@mui/material";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import Dot from "../../../../../Components/Dot";
import Dot from "../Dot";
/**
* Host component.
* This subcomponent receives a params object and displays the host details.
+45
View File
@@ -0,0 +1,45 @@
import { useCallback } from "react";
const useMonitorUtils = () => {
const getMonitorWithPercentage = useCallback((monitor, theme) => {
let uptimePercentage = "";
let percentageColor = "";
if (monitor.uptimePercentage !== undefined) {
uptimePercentage =
monitor.uptimePercentage === 0
? "0"
: (monitor.uptimePercentage * 100).toFixed(2);
percentageColor =
monitor.uptimePercentage < 0.25
? theme.palette.error.main
: monitor.uptimePercentage < 0.5
? theme.palette.warning.main
: monitor.uptimePercentage < 0.75
? theme.palette.success.main
: theme.palette.success.main;
}
return {
id: monitor._id,
name: monitor.name,
url: monitor.url,
title: monitor.name,
percentage: uptimePercentage,
percentageColor,
monitor: monitor,
};
}, []);
const determineState = useCallback((monitor) => {
if (typeof monitor === "undefined") return "pending";
if (monitor.isActive === false) return "paused";
if (monitor?.status === undefined) return "pending";
return monitor?.status == true ? "up" : "down";
}, []);
return { getMonitorWithPercentage, determineState };
};
export { useMonitorUtils };
@@ -8,6 +8,8 @@ import {
ResponsiveContainer,
Text,
} from "recharts";
import ChartBox from "../../../../../Components/Charts/ChartBox";
import ResponseTimeIcon from "../../../../../assets/icons/response-time-icon.svg?react";
import { Box, Stack, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useState } from "react";
@@ -22,67 +24,73 @@ const CustomToolTip = ({ active, payload, label }) => {
? payload[0]?.payload?.originalAvgResponseTime
: (payload[0]?.payload?.avgResponseTime ?? 0);
return (
<Box
className="area-tooltip"
sx={{
backgroundColor: theme.palette.background.main,
border: 1,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
py: theme.spacing(2),
px: theme.spacing(4),
}}
<ChartBox
icon={<ResponseTimeIcon />}
header="Response Times"
sx={{ padding: 0 }}
>
<Typography
<Box
className="area-tooltip"
sx={{
color: theme.palette.text.tertiary,
fontSize: 12,
fontWeight: 500,
backgroundColor: theme.palette.background.main,
border: 1,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
py: theme.spacing(2),
px: theme.spacing(4),
}}
>
{formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)}
</Typography>
<Box mt={theme.spacing(1)}>
<Box
display="inline-block"
width={theme.spacing(4)}
height={theme.spacing(4)}
backgroundColor={theme.palette.primary.main}
sx={{ borderRadius: "50%" }}
/>
<Stack
display="inline-flex"
direction="row"
justifyContent="space-between"
ml={theme.spacing(3)}
<Typography
sx={{
"& span": {
color: theme.palette.text.tertiary,
fontSize: 11,
fontWeight: 500,
},
color: theme.palette.text.tertiary,
fontSize: 12,
fontWeight: 500,
}}
>
<Typography
component="span"
sx={{ opacity: 0.8 }}
{formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)}
</Typography>
<Box mt={theme.spacing(1)}>
<Box
display="inline-block"
width={theme.spacing(4)}
height={theme.spacing(4)}
backgroundColor={theme.palette.primary.main}
sx={{ borderRadius: "50%" }}
/>
<Stack
display="inline-flex"
direction="row"
justifyContent="space-between"
ml={theme.spacing(3)}
sx={{
"& span": {
color: theme.palette.text.tertiary,
fontSize: 11,
fontWeight: 500,
},
}}
>
Response Time
</Typography>{" "}
<Typography component="span">
{Math.floor(responseTime)}
<Typography
component="span"
sx={{ opacity: 0.8 }}
>
{" "}
ms
Response Time
</Typography>{" "}
<Typography component="span">
{Math.floor(responseTime)}
<Typography
component="span"
sx={{ opacity: 0.8 }}
>
{" "}
ms
</Typography>
</Typography>
</Typography>
</Stack>
</Stack>
</Box>
{/* Display original value */}
</Box>
{/* Display original value */}
</Box>
</ChartBox>
);
}
return null;
@@ -0,0 +1,23 @@
import { Stack, Typography } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import PropTypes from "prop-types";
const MonitorHeader = ({ monitor }) => {
const theme = useTheme();
return (
<Stack direction="row">
<Stack gap={theme.spacing(2)}>
<Typography variant="h1">{monitor.name}</Typography>
<Typography variant="h2">
Distributed Uptime Monitoring powered by DePIN
</Typography>
</Stack>
</Stack>
);
};
MonitorHeader.propTypes = {
monitor: PropTypes.object,
};
export default MonitorHeader;
@@ -0,0 +1,62 @@
// Components
import { Stack } from "@mui/material";
import StatBox from "../../../../../Components/StatBox";
import LastUpdate from "../LastUpdate";
import UptLogo from "../../../../../assets/icons/upt_logo.png";
// Utils
import { useTheme } from "@mui/material/styles";
import PropTypes from "prop-types";
const StatBoxes = ({ monitor, lastUpdateTrigger }) => {
const theme = useTheme();
return (
<Stack
direction="row"
gap={theme.spacing(8)}
>
<StatBox
heading="Avg Response Time"
subHeading={`${Math.floor(monitor?.avgResponseTime ?? 0)} ms`}
/>
<StatBox
heading="Checking every"
subHeading={`${(monitor?.interval ?? 0) / 1000} seconds`}
/>
<StatBox
heading={"Last check"}
subHeading={
<LastUpdate
lastUpdateTime={monitor?.timeSinceLastCheck ?? 0}
suffix={"seconds ago"}
/>
}
/>
<StatBox
heading="Last server push"
subHeading={
<LastUpdate
suffix={"seconds ago"}
lastUpdateTime={0}
trigger={lastUpdateTrigger}
/>
}
/>
<StatBox
heading="UPT Burned"
subHeading={monitor?.totalUptBurnt ?? 0}
img={UptLogo}
alt="Upt Logo"
/>
</Stack>
);
};
StatBoxes.propTypes = {
monitor: PropTypes.object,
lastUpdateTrigger: PropTypes.number,
};
export default StatBoxes;
@@ -0,0 +1,119 @@
import { useState, useEffect, useRef } from "react";
import { networkService } from "../../../../main";
import { useSelector } from "react-redux";
const getRandomDevice = () => {
const manufacturers = {
Apple: ["iPhone 15 Pro Max", "iPhone 15", "iPhone 14 Pro", "iPhone 14", "iPhone 13"],
Samsung: [
"Galaxy S23 Ultra",
"Galaxy S23+",
"Galaxy S23",
"Galaxy Z Fold5",
"Galaxy Z Flip5",
],
Google: ["Pixel 8 Pro", "Pixel 8", "Pixel 7a", "Pixel 7", "Pixel 6a"],
OnePlus: [
"OnePlus 11",
"OnePlus 10T",
"OnePlus Nord 3",
"OnePlus 10 Pro",
"OnePlus Nord 2T",
],
Xiaomi: ["13 Pro", "13", "Redmi Note 12", "POCO F5", "Redmi 12"],
Huawei: ["P60 Pro", "Mate X3", "Nova 11", "P50 Pro", "Mate 50"],
Sony: ["Xperia 1 V", "Xperia 5 V", "Xperia 10 V", "Xperia Pro-I", "Xperia 1 IV"],
Motorola: ["Edge 40 Pro", "Edge 40", "G84", "G54", "Razr 40 Ultra"],
ASUS: [
"ROG Phone 7",
"Zenfone 10",
"ROG Phone 6",
"Zenfone 9",
"ROG Phone 7 Ultimate",
],
};
const manufacturerNames = Object.keys(manufacturers);
const randomManufacturer =
manufacturerNames[Math.floor(Math.random() * manufacturerNames.length)];
const models = manufacturers[randomManufacturer];
const randomModel = models[Math.floor(Math.random() * models.length)];
return {
manufacturer: randomManufacturer,
model: randomModel,
};
};
const useSubscribeToDetails = ({ monitorId, 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);
const [devices, setDevices] = useState(Array.from({ length: 5 }, getRandomDevice));
const authToken = useSelector((state) => state.auth.token);
const prevDateRangeRef = useRef(dateRange);
useEffect(() => {
try {
const cleanup = networkService.subscribeToDistributedUptimeDetails({
authToken,
monitorId,
dateRange: dateRange,
normalize: true,
onUpdate: (data) => {
if (networkError === true) {
setNetworkError(false);
}
setLastUpdateTrigger(Date.now());
const latestChecksWithDevice = data?.monitor?.latestChecks.map((check, idx) => {
check.device = devices[idx];
return check;
});
const monitorWithDevice = {
...data.monitor,
latestChecks: latestChecksWithDevice,
};
setMonitor(monitorWithDevice);
},
onOpen: () => {
setConnectionStatus("up");
setRetryCount(0); // Reset retry count on successful connection
},
onError: () => {
setNetworkError(true);
setConnectionStatus("down");
},
});
return cleanup;
} catch (error) {
setNetworkError(true);
} finally {
setIsLoading(false);
}
}, [
authToken,
dateRange,
monitorId,
retryCount,
setConnectionStatus,
networkError,
devices,
]);
useEffect(() => {
const hasDateRangeChanged = prevDateRangeRef.current !== dateRange;
prevDateRangeRef.current = dateRange; // Update the ref to the current dateRange
if (!hasDateRangeChanged) {
setDevices(Array.from({ length: 5 }, getRandomDevice));
}
}, [dateRange]);
return [isLoading, networkError, connectionStatus, monitor, lastUpdateTrigger];
};
export default useSubscribeToDetails;
@@ -1,324 +1,102 @@
//Components
import DistributedUptimeMap from "./Components/DistributedUptimeMap";
import Breadcrumbs from "../../../Components/Breadcrumbs";
import { Stack, Typography, Box, Button, ButtonGroup } from "@mui/material";
import ChartBox from "../../../Components/Charts/ChartBox";
import StatBox from "../../../Components/StatBox";
import ResponseTimeIcon from "../../../assets/icons/response-time-icon.svg?react";
import { Stack, Typography } from "@mui/material";
import DeviceTicker from "./Components/DeviceTicker";
import DistributedUptimeResponseChart from "./Components/DistributedUptimeResponseChart";
import UptLogo from "../../../assets/icons/upt_logo.png";
import LastUpdate from "./Components/LastUpdate";
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";
//Utils
import { networkService } from "../../../main";
import { useSelector } from "react-redux";
import { useTheme } from "@mui/material/styles";
import { useEffect, useState, useCallback, useRef } from "react";
import { useState } from "react";
import { useParams } from "react-router-dom";
//Constants
const BASE_BOX_PADDING_VERTICAL = 8;
const BASE_BOX_PADDING_HORIZONTAL = 8;
const MAX_RETRIES = 10;
const RETRY_DELAY = 5000;
function getRandomDevice() {
const manufacturers = {
Apple: ["iPhone 15 Pro Max", "iPhone 15", "iPhone 14 Pro", "iPhone 14", "iPhone 13"],
Samsung: [
"Galaxy S23 Ultra",
"Galaxy S23+",
"Galaxy S23",
"Galaxy Z Fold5",
"Galaxy Z Flip5",
],
Google: ["Pixel 8 Pro", "Pixel 8", "Pixel 7a", "Pixel 7", "Pixel 6a"],
OnePlus: [
"OnePlus 11",
"OnePlus 10T",
"OnePlus Nord 3",
"OnePlus 10 Pro",
"OnePlus Nord 2T",
],
Xiaomi: ["13 Pro", "13", "Redmi Note 12", "POCO F5", "Redmi 12"],
Huawei: ["P60 Pro", "Mate X3", "Nova 11", "P50 Pro", "Mate 50"],
Sony: ["Xperia 1 V", "Xperia 5 V", "Xperia 10 V", "Xperia Pro-I", "Xperia 1 IV"],
Motorola: ["Edge 40 Pro", "Edge 40", "G84", "G54", "Razr 40 Ultra"],
ASUS: [
"ROG Phone 7",
"Zenfone 10",
"ROG Phone 6",
"Zenfone 9",
"ROG Phone 7 Ultimate",
],
};
const manufacturerNames = Object.keys(manufacturers);
const randomManufacturer =
manufacturerNames[Math.floor(Math.random() * manufacturerNames.length)];
const models = manufacturers[randomManufacturer];
const randomModel = models[Math.floor(Math.random() * models.length)];
return {
manufacturer: randomManufacturer,
model: randomModel,
};
}
// export const StatBox = ({ heading, value, img, altTxt }) => {
// const theme = useTheme();
// return (
// <Stack
// direction="row"
// width={"25%"}
// justifyContent="center"
// sx={{
// padding: `${theme.spacing(BASE_BOX_PADDING_VERTICAL)} ${theme.spacing(BASE_BOX_PADDING_HORIZONTAL)}`,
// backgroundColor: theme.palette.background.main,
// border: 1,
// borderStyle: "solid",
// borderColor: theme.palette.primary.lowContrast,
// }}
// >
// {img && (
// <img
// style={{ marginRight: theme.spacing(8) }}
// height={30}
// width={30}
// src={img}
// alt={altTxt}
// />
// )}
// <Stack direction="column">
// <Typography variant="h2">{heading}</Typography>
// <Typography>{value}</Typography>
// </Stack>
// </Stack>
// );
// };
import useSubscribeToDetails from "./Hooks/useSubscribeToDetails";
const DistributedUptimeDetails = () => {
// Redux State
const { authToken } = useSelector((state) => state.auth);
const { mode } = useSelector((state) => state.ui);
const { monitorId } = useParams();
// Local State
// const [hoveredUptimeData, setHoveredUptimeData] = useState(null);
// const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null);
const [retryCount, setRetryCount] = useState(0);
const [connectionStatus, setConnectionStatus] = useState("down");
const [lastUpdateTrigger, setLastUpdateTrigger] = useState(Date.now());
const [dateRange, setDateRange] = useState("day");
const [monitor, setMonitor] = useState(null);
const [devices, setDevices] = useState([]);
// Refs
const prevDateRangeRef = useRef(dateRange);
// Utils
const theme = useTheme();
const { monitorId } = useParams();
const [isLoading, networkError, connectionStatus, monitor, lastUpdateTrigger] =
useSubscribeToDetails({ monitorId, dateRange });
// Constants
const BREADCRUMBS = [
{ name: "Distributed Uptime", path: "/distributed-uptime" },
{ name: "Details", path: `/distributed-uptime/${monitorId}` },
];
useEffect(() => {
const hasDateRangeChanged = prevDateRangeRef.current !== dateRange;
prevDateRangeRef.current = dateRange; // Update the ref to the current dateRange
if (networkError) {
return (
<GenericFallback>
<Typography
variant="h1"
marginY={theme.spacing(4)}
color={theme.palette.primary.contrastTextTertiary}
>
Network error
</Typography>
<Typography>Please check your connection</Typography>
</GenericFallback>
);
}
if (!hasDateRangeChanged) {
setDevices(Array.from({ length: 5 }, getRandomDevice));
}
}, [dateRange]);
const connectToService = useCallback(() => {
return networkService.subscribeToDistributedUptimeDetails({
authToken,
monitorId,
dateRange: dateRange,
normalize: true,
onUpdate: (data) => {
setLastUpdateTrigger(Date.now());
const latestChecksWithDevice = data?.monitor?.latestChecks.map((check, idx) => {
check.device = devices[idx];
return check;
});
const monitorWithDevice = {
...data.monitor,
latestChecks: latestChecksWithDevice,
};
setMonitor(monitorWithDevice);
},
onOpen: () => {
setConnectionStatus("up");
setRetryCount(0); // Reset retry count on successful connection
},
onError: () => {
setConnectionStatus("down");
console.log("Error, attempting reconnect...");
if (retryCount < MAX_RETRIES) {
setTimeout(() => {
setRetryCount((prev) => prev + 1);
connectToService();
}, RETRY_DELAY);
} else {
console.log("Max retries reached");
}
},
});
}, [authToken, monitorId, dateRange, retryCount, devices]);
useEffect(() => {
const devices = Array.from({ length: 5 }, getRandomDevice);
const cleanup = connectToService(devices);
return cleanup;
}, [connectToService]);
if (typeof monitor === "undefined" || monitor.totalChecks === 0) {
return (
<Stack gap={theme.spacing(10)}>
<Breadcrumbs list={BREADCRUMBS} />
<GenericFallback>
<Typography>There is no check history for this monitor yet.</Typography>
</GenericFallback>
</Stack>
);
}
return (
monitor && (
<Stack
direction="column"
gap={theme.spacing(10)}
>
<Breadcrumbs list={BREADCRUMBS} />
<MonitorHeader monitor={monitor} />
<StatBoxes
monitor={monitor}
lastUpdateTrigger={lastUpdateTrigger}
/>
<NextExpectedCheck
lastUpdateTime={monitor?.timeSinceLastCheck ?? 0}
interval={monitor?.interval ?? 0}
trigger={lastUpdateTrigger}
/>
<MonitorTimeFrameHeader
dateRange={dateRange}
setDateRange={setDateRange}
/>
<DistributedUptimeResponseChart checks={monitor?.groupedChecks ?? []} />
<Stack
direction="column"
direction="row"
gap={theme.spacing(8)}
>
<Breadcrumbs list={BREADCRUMBS} />
{monitor?.url !== "https://jup.ag/" &&
monitor?.url !== "https://explorer.solana.com/" && (
<Box>
<Typography
component="h1"
variant="h1"
>
{monitor.name}
</Typography>
</Box>
)}
<Stack
direction="row"
alignItems="center"
gap={theme.spacing(8)}
>
<Typography variant="h2">
Distributed Uptime Monitoring powered by DePIN
</Typography>
</Stack>
<Stack
direction="row"
gap={theme.spacing(8)}
>
<StatBox
heading="Avg Response Time"
subHeading={`${Math.floor(monitor?.avgResponseTime ?? 0)} ms`}
/>
<StatBox
heading="Checking every"
subHeading={`${(monitor?.interval ?? 0) / 1000} seconds`}
/>
<StatBox
heading={"Last check"}
subHeading={
<LastUpdate
lastUpdateTime={monitor?.timeSinceLastCheck ?? 0}
suffix={"seconds ago"}
/>
}
/>
<StatBox
heading="Last server push"
subHeading={
<LastUpdate
suffix={"seconds ago"}
lastUpdateTime={0}
trigger={lastUpdateTrigger}
/>
}
/>
<StatBox
heading="UPT Burned"
subHeading={monitor?.totalUptBurnt ?? 0}
img={UptLogo}
alt="Upt Logo"
/>
</Stack>
<Box sx={{ width: "100%" }}>
<NextExpectedCheck
lastUpdateTime={monitor?.timeSinceLastCheck ?? 0}
interval={monitor?.interval ?? 0}
trigger={lastUpdateTrigger}
/>
</Box>
<Stack
direction="row"
justifyContent="space-between"
alignItems="flex-end"
gap={theme.spacing(4)}
mb={theme.spacing(8)}
>
<Typography variant="body2">
Showing statistics for past{" "}
{dateRange === "day"
? "24 hours"
: dateRange === "week"
? "7 days"
: "30 days"}
.
</Typography>
<ButtonGroup sx={{ height: 32 }}>
<Button
variant="group"
filled={(dateRange === "day").toString()}
onClick={() => setDateRange("day")}
>
Day
</Button>
<Button
variant="group"
filled={(dateRange === "week").toString()}
onClick={() => setDateRange("week")}
>
Week
</Button>
<Button
variant="group"
filled={(dateRange === "month").toString()}
onClick={() => setDateRange("month")}
>
Month
</Button>
</ButtonGroup>
</Stack>
<ChartBox
icon={<ResponseTimeIcon />}
header="Response Times"
sx={{ padding: 0 }}
>
<DistributedUptimeResponseChart checks={monitor?.groupedChecks ?? []} />
</ChartBox>
<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 />
<DistributedUptimeMap
checks={monitor?.groupedMapChecks ?? []}
height={"100%"}
width={"100%"}
/>
<DeviceTicker
width={"25vw"}
data={monitor?.latestChecks ?? []}
connectionStatus={connectionStatus}
/>
</Stack>
)
<Footer />
</Stack>
);
};
@@ -0,0 +1,94 @@
// 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.title}
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;
@@ -0,0 +1,58 @@
import { useEffect, useState } from "react";
import { networkService } from "../../../../main";
import { useSelector } from "react-redux";
import { useTheme } from "@emotion/react";
import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils";
import { createToast } from "../../../../Utils/toastUtils";
const useSubscribeToMonitors = () => {
// Redux
const { authToken, user } = useSelector((state) => state.auth);
// Local state
const [isLoading, setIsLoading] = useState(true);
const [networkError, setNetworkError] = useState(false);
const [monitors, setMonitors] = useState(undefined);
const [monitorsSummary, setMonitorsSummary] = useState(undefined);
const [filteredMonitors, setFilteredMonitors] = useState(undefined);
const theme = useTheme();
const { getMonitorWithPercentage } = useMonitorUtils();
useEffect(() => {
try {
const cleanup = networkService.subscribeToDistributedUptimeMonitors({
authToken: authToken,
teamId: user.teamId,
limit: 25,
types: ["distributed_http"],
page: 0,
rowsPerPage: 10,
filter: null,
field: null,
order: null,
onUpdate: (data) => {
const res = data.monitors;
const { monitors, filteredMonitors, summary } = res;
const mappedMonitors = filteredMonitors.map((monitor) =>
getMonitorWithPercentage(monitor, theme)
);
setMonitors(monitors);
setMonitorsSummary(summary);
setFilteredMonitors(mappedMonitors);
},
});
return cleanup;
} catch (error) {
createToast({
body: error.message,
});
setNetworkError(true);
} finally {
setIsLoading(false);
}
}, [authToken, user, getMonitorWithPercentage, theme]);
return [isLoading, networkError, monitors, monitorsSummary, filteredMonitors];
};
export { useSubscribeToMonitors };
@@ -1,185 +1,69 @@
// Components
import { Stack, Box, Button } from "@mui/material";
import DataTable from "../../../Components/Table";
import { Stack, Typography } from "@mui/material";
import Breadcrumbs from "../../../Components/Breadcrumbs";
import Host from "../../Uptime/Monitors/Components/Host";
import BarChart from "../../../Components/Charts/BarChart";
import ActionsMenu from "../../Uptime/Monitors/Components/ActionsMenu";
import { StatusLabel } from "../../../Components/Label";
// Utils
import { networkService } from "../../../main";
import { useTheme } from "@mui/material/styles";
import { useNavigate } from "react-router-dom";
import useUtils from "../../Uptime/Monitors/Hooks/useUtils";
import { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import CreateMonitorHeader from "../../../Components/MonitorCreateHeader";
import MonitorTable from "./Components/MonitorTable";
import Fallback from "../../../Components/Fallback";
import GenericFallback from "../../../Components/GenericFallback";
// Utils
import { useTheme } from "@mui/material/styles";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
import { useSubscribeToMonitors } from "./Hooks/useSubscribeToMonitors";
// Constants
const BREADCRUMBS = [{ name: `Distributed Uptime`, path: "/distributed-uptime" }];
const TYPE_MAP = {
distributed_http: "Distributed HTTP",
};
const DistributedUptimeMonitors = () => {
// Redux state
const { authToken, user } = useSelector((state) => state.auth);
// Local state
const [monitors, setMonitors] = useState([]);
const [filteredMonitors, setFilteredMonitors] = useState([]);
const [monitorsSummary, setMonitorsSummary] = useState({});
// Utils
const { determineState } = useUtils();
const theme = useTheme();
const navigate = useNavigate();
const headers = [
{
id: "name",
content: <Box>Host</Box>,
render: (row) => (
<Host
key={row._id}
url={row.url}
title={row.title}
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) => (
<ActionsMenu
monitor={row.monitor}
isAdmin={true}
/>
),
},
];
const isAdmin = useIsAdmin();
const [isLoading, networkError, monitors, monitorsSummary, filteredMonitors] =
useSubscribeToMonitors();
const getMonitorWithPercentage = (monitor, theme) => {
let uptimePercentage = "";
let percentageColor = "";
if (monitor.uptimePercentage !== undefined) {
uptimePercentage =
monitor.uptimePercentage === 0
? "0"
: (monitor.uptimePercentage * 100).toFixed(2);
percentageColor =
monitor.uptimePercentage < 0.25
? theme.palette.error.main
: monitor.uptimePercentage < 0.5
? theme.palette.warning.main
: monitor.uptimePercentage < 0.75
? theme.palette.success.main
: theme.palette.success.main;
}
return {
id: monitor._id,
name: monitor.name,
url: monitor.url,
title: monitor.name,
percentage: uptimePercentage,
percentageColor,
monitor: monitor,
};
};
useEffect(() => {
const cleanup = networkService.subscribeToDistributedUptimeMonitors({
authToken: authToken,
teamId: user.teamId,
limit: 25,
types: ["distributed_http"],
page: 0,
rowsPerPage: 10,
filter: null,
field: null,
order: null,
onUpdate: (data) => {
const res = data.monitors;
const { monitors, filteredMonitors, summary } = res;
const mappedMonitors = filteredMonitors.map((monitor) =>
getMonitorWithPercentage(monitor, theme)
);
setMonitors(monitors);
setFilteredMonitors(mappedMonitors);
setMonitorsSummary(summary);
},
});
return cleanup;
}, [user.teamId, authToken, theme]);
return (
<Stack
direction="column"
gap={theme.spacing(8)}
>
<Breadcrumbs list={BREADCRUMBS} />
<Stack
direction="row"
justifyContent="end"
alignItems="center"
mt={theme.spacing(5)}
gap={theme.spacing(6)}
>
<Button
variant="contained"
color="primary"
onClick={() => {
navigate("/distributed-uptime/create");
}}
sx={{ fontWeight: 500, whiteSpace: "nowrap" }}
if (networkError) {
return (
<GenericFallback>
<Typography
variant="h1"
marginY={theme.spacing(4)}
color={theme.palette.primary.contrastTextTertiary}
>
Create new
</Button>
</Stack>
{monitors.length > 0 && (
<DataTable
headers={headers}
data={filteredMonitors}
config={{
rowSX: {
cursor: "pointer",
"&:hover": {
backgroundColor: theme.palette.background.accent,
},
},
onRowClick: (row) => {
navigate(`/distributed-uptime/${row.id}`);
},
}}
/>
)}
Network error
</Typography>
<Typography>Please check your connection</Typography>
</GenericFallback>
);
}
if (typeof monitorsSummary !== "undefined" && monitorsSummary.totalMonitors === 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={filteredMonitors}
/>
</Stack>
);
};
@@ -1,6 +1,6 @@
// Components
import DataTable from "../../../../../Components/Table";
import Host from "../../../../Uptime/Monitors/Components/Host";
import Host from "../../../../../Components/Host";
import { StatusLabel } from "../../../../../Components/Label";
import { Stack } from "@mui/material";
import { InfrastructureMenu } from "../MonitorsTableMenu";
@@ -12,6 +12,7 @@ import CustomGauge from "../../../../../Components/Charts/CustomGauge";
import { useTheme } from "@emotion/react";
import useUtils from "../../../../Uptime/Monitors/Hooks/useUtils";
import { useNavigate } from "react-router-dom";
import PropTypes from "prop-types";
const MonitorsTable = ({ shouldRender, monitors, isAdmin, handleActionMenuDelete }) => {
// Utils
@@ -131,4 +132,11 @@ const MonitorsTable = ({ shouldRender, monitors, isAdmin, handleActionMenuDelete
);
};
MonitorsTable.propTypes = {
shouldRender: PropTypes.bool,
monitors: PropTypes.array,
isAdmin: PropTypes.bool,
handleActionMenuDelete: PropTypes.func,
};
export default MonitorsTable;
@@ -1,6 +1,6 @@
// Components
import { Stack, Box } from "@mui/material";
import Host from "../../../../Uptime/Monitors/Components/Host";
import Host from "../../../../../Components/Host";
import StatusPageBarChart from "../../../../../Components/Charts/StatusPageBarChart";
import { StatusLabel } from "../../../../../Components/Label";
@@ -3,30 +3,7 @@ import { networkService } from "../../../../main";
import { useSelector } from "react-redux";
import { createToast } from "../../../../Utils/toastUtils";
import { useTheme } from "@emotion/react";
const getMonitorWithPercentage = (monitor, theme) => {
let uptimePercentage = "";
let percentageColor = "";
if (monitor.uptimePercentage !== undefined) {
uptimePercentage =
monitor.uptimePercentage === 0 ? "0" : (monitor.uptimePercentage * 100).toFixed(2);
percentageColor =
monitor.uptimePercentage < 0.25
? theme.palette.error.main
: monitor.uptimePercentage < 0.5
? theme.palette.warning.main
: monitor.uptimePercentage < 0.75
? theme.palette.success.main
: theme.palette.success.main;
}
return {
...monitor,
percentage: uptimePercentage,
percentageColor,
};
};
import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils";
const useStatusPageFetch = (isCreate = false) => {
const [isLoading, setIsLoading] = useState(true);
@@ -35,7 +12,7 @@ const useStatusPageFetch = (isCreate = false) => {
const [monitors, setMonitors] = useState(undefined);
const { authToken } = useSelector((state) => state.auth);
const theme = useTheme();
const { getMonitorWithPercentage } = useMonitorUtils();
const fetchStatusPage = useCallback(async () => {
try {
const response = await networkService.getStatusPage({ authToken });
@@ -3,10 +3,10 @@ import { Box, Stack } from "@mui/material";
import DataTable from "../../../../../Components/Table";
import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded";
import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded";
import Host from "../Host";
import Host from "../../../../../Components/Host";
import { StatusLabel } from "../../../../../Components/Label";
import BarChart from "../../../../../Components/Charts/BarChart";
import ActionsMenu from "../ActionsMenu";
import ActionsMenu from "../../../../../Components/ActionsMenu";
import LoadingSpinner from "../LoadingSpinner";
import TableSkeleton from "../../../../../Components/Table/skeleton";
@@ -2,34 +2,7 @@ import { useEffect, useState } from "react";
import { networkService } from "../../../../main";
import { createToast } from "../../../../Utils/toastUtils";
import { useTheme } from "@emotion/react";
const getMonitorWithPercentage = (monitor, theme) => {
let uptimePercentage = "";
let percentageColor = "";
if (monitor.uptimePercentage !== undefined) {
uptimePercentage =
monitor.uptimePercentage === 0 ? "0" : (monitor.uptimePercentage * 100).toFixed(2);
percentageColor =
monitor.uptimePercentage < 0.25
? theme.palette.error.main
: monitor.uptimePercentage < 0.5
? theme.palette.warning.main
: monitor.uptimePercentage < 0.75
? theme.palette.success.main
: theme.palette.success.main;
}
return {
id: monitor._id,
name: monitor.name,
url: monitor.url,
title: monitor.name,
percentage: uptimePercentage,
percentageColor,
monitor: monitor,
};
};
import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils";
export const useMonitorFetch = ({
authToken,
@@ -49,7 +22,7 @@ export const useMonitorFetch = ({
const [networkError, setNetworkError] = useState(false);
const theme = useTheme();
const { getMonitorWithPercentage } = useMonitorUtils();
useEffect(() => {
const fetchMonitors = async () => {
try {
+1 -3
View File
@@ -905,9 +905,7 @@ class NetworkService {
onOpen?.();
};
this.eventSource.addEventListener("open", (e) => {
console.log("getDistributedUptimeMonitors connection opened:");
});
this.eventSource.addEventListener("open", (e) => {});
this.eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);