Merge pull request #1795 from bluewave-labs/feat/du-status
feat: DePIN status page
This commit is contained in:
12 files changed
+490
-61
No files matched your search
Generated
+45
@@ -28,6 +28,7 @@
|
||||
"axios": "^1.7.4",
|
||||
"dayjs": "1.11.13",
|
||||
"flag-icons": "7.3.2",
|
||||
"html2canvas": "^1.4.1",
|
||||
"i18next": "^24.2.2",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"joi": "17.13.3",
|
||||
@@ -9381,6 +9382,14 @@
|
||||
"integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/base64-arraybuffer": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
|
||||
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
@@ -10443,6 +10452,14 @@
|
||||
"tiny-invariant": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/css-line-break": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
@@ -12441,6 +12458,18 @@
|
||||
"void-elements": "3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html2canvas": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
|
||||
"dependencies": {
|
||||
"css-line-break": "^2.1.0",
|
||||
"text-segmentation": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||
@@ -17849,6 +17878,14 @@
|
||||
"resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz",
|
||||
"integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg=="
|
||||
},
|
||||
"node_modules/text-segmentation": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
|
||||
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/text-table": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||
@@ -18379,6 +18416,14 @@
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/utrie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
|
||||
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
|
||||
"dependencies": {
|
||||
"base64-arraybuffer": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"axios": "^1.7.4",
|
||||
"dayjs": "1.11.13",
|
||||
"flag-icons": "7.3.2",
|
||||
"html2canvas": "^1.4.1",
|
||||
"i18next": "^24.2.2",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"joi": "17.13.3",
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { RowContainer } from "../StandardContainer";
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import Image from "../Image";
|
||||
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
|
||||
const InfoBox = ({
|
||||
img,
|
||||
icon: Icon,
|
||||
alt,
|
||||
heading,
|
||||
headingLevel = 2,
|
||||
subHeading,
|
||||
subHeadingLevel = "",
|
||||
sx,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<RowContainer sx={{ ...sx }}>
|
||||
{img && (
|
||||
<Image
|
||||
src={img}
|
||||
height={"30px"}
|
||||
width={"30px"}
|
||||
alt={alt}
|
||||
sx={{ marginRight: theme.spacing(8) }}
|
||||
/>
|
||||
)}
|
||||
{Icon && (
|
||||
<Icon sx={{ width: "30px", height: "30px", marginRight: theme.spacing(8) }} />
|
||||
)}
|
||||
<Stack>
|
||||
<Typography variant={`h${headingLevel}`}>{heading}</Typography>
|
||||
<Typography variant={subHeadingLevel ? `h${subHeadingLevel}` : "body1"}>
|
||||
{subHeading}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</RowContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfoBox;
|
||||
@@ -0,0 +1,67 @@
|
||||
import html2canvas from "html2canvas";
|
||||
import ShareIcon from "@mui/icons-material/Share";
|
||||
import { Button } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
|
||||
const ShareComponent = ({ elementToCapture, fileName = "screenshot" }) => {
|
||||
const theme = useTheme();
|
||||
const captureAndShare = async () => {
|
||||
try {
|
||||
// Temporarily apply styles directly to the element
|
||||
const originalBackground = elementToCapture.current.style.background;
|
||||
const originalPadding = elementToCapture.current.style.padding;
|
||||
|
||||
elementToCapture.current.style.background = `radial-gradient(circle, ${theme.palette.gradient.color1}, ${theme.palette.gradient.color2}, ${theme.palette.gradient.color3}, ${theme.palette.gradient.color4}, ${theme.palette.gradient.color5})`;
|
||||
elementToCapture.current.style.padding = `${theme.spacing(20)}`;
|
||||
|
||||
// Capture the element directly
|
||||
const canvas = await html2canvas(elementToCapture.current, {
|
||||
useCORS: true,
|
||||
scale: 2,
|
||||
allowTaint: true,
|
||||
backgroundColor: null,
|
||||
});
|
||||
|
||||
// Restore original styles
|
||||
elementToCapture.current.style.background = originalBackground;
|
||||
elementToCapture.current.style.padding = originalPadding;
|
||||
|
||||
const imageBlob = await new Promise((resolve) =>
|
||||
canvas.toBlob(resolve, "image/png")
|
||||
);
|
||||
|
||||
const file = new File([imageBlob], `${fileName}.png`, {
|
||||
type: "image/png",
|
||||
});
|
||||
|
||||
if (navigator.share) {
|
||||
await navigator.share({
|
||||
files: [file],
|
||||
title: "Screenshot",
|
||||
text: "Check out this screenshot!",
|
||||
});
|
||||
} else {
|
||||
const url = URL.createObjectURL(imageBlob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `${fileName}.png`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<ShareIcon sx={{ color: theme.palette.success.main }} />}
|
||||
color="success"
|
||||
onClick={captureAndShare}
|
||||
>
|
||||
Share
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShareComponent;
|
||||
@@ -0,0 +1,51 @@
|
||||
// Components
|
||||
import { Stack } from "@mui/material";
|
||||
|
||||
// Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
|
||||
const Container = ({ children, direction, backgroundColor, sx }) => {
|
||||
const theme = useTheme();
|
||||
let bgColor =
|
||||
typeof backgroundColor !== "undefined"
|
||||
? backgroundColor
|
||||
: theme.palette.background.main;
|
||||
return (
|
||||
<Stack
|
||||
direction={direction}
|
||||
padding={theme.spacing(8)}
|
||||
gap={theme.spacing(2)}
|
||||
border={1}
|
||||
borderColor={theme.palette.primary.lowContrast}
|
||||
borderRadius={theme.spacing(4)}
|
||||
backgroundColor={bgColor}
|
||||
sx={{ ...sx }}
|
||||
>
|
||||
{children}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const ColContainer = ({ children, backgroundColor, sx }) => {
|
||||
return (
|
||||
<Container
|
||||
direction="column"
|
||||
backgroundColor={backgroundColor}
|
||||
sx={{ ...sx }}
|
||||
>
|
||||
{children}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export const RowContainer = ({ children, backgroundColor, sx }) => {
|
||||
return (
|
||||
<Container
|
||||
direction="row"
|
||||
backgroundColor={backgroundColor}
|
||||
sx={{ ...sx }}
|
||||
>
|
||||
{children}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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";
|
||||
|
||||
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();
|
||||
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 }}
|
||||
>
|
||||
Now
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Typography>{MESSAGES[Math.floor(Math.random() * MESSAGES.length)]}</Typography>
|
||||
</ColContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatBot;
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Stack, Typography, List, ListItem } from "@mui/material";
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import PulseDot from "../../../../../Components/Animated/PulseDot";
|
||||
import "flag-icons/css/flag-icons.min.css";
|
||||
import { ColContainer } from "../../../../../Components/StandardContainer";
|
||||
|
||||
const BASE_BOX_PADDING_VERTICAL = 16;
|
||||
const BASE_BOX_PADDING_HORIZONTAL = 8;
|
||||
@@ -14,21 +15,9 @@ const DeviceTicker = ({ data, width = "100%", connectionStatus }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction="column"
|
||||
gap={theme.spacing(2)}
|
||||
width={width}
|
||||
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,
|
||||
}}
|
||||
>
|
||||
<ColContainer>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent={"center"}
|
||||
gap={theme.spacing(4)}
|
||||
>
|
||||
<PulseDot color={statusColor[connectionStatus]} />
|
||||
@@ -41,30 +30,53 @@ const DeviceTicker = ({ data, width = "100%", connectionStatus }) => {
|
||||
{connectionStatus === "up" ? "Connected" : "No connection"}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<List>
|
||||
{data.slice(Math.max(data.length - 5, 0)).map((dataPoint) => {
|
||||
const countryCode = dataPoint?.countryCode?.toLowerCase() ?? null;
|
||||
const flag = countryCode ? `fi fi-${countryCode}` : null;
|
||||
return (
|
||||
<ListItem key={Math.random()}>
|
||||
<Stack direction="column">
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(4)}
|
||||
>
|
||||
{flag && <span className={flag} />}
|
||||
<Typography variant="h2">{dataPoint?.city || "Unknown"}</Typography>
|
||||
</Stack>
|
||||
<Typography variant="p">{`Response time: ${Math.floor(dataPoint?.responseTime ?? 0)} ms`}</Typography>
|
||||
<Typography variant="p">{`UPT burned: ${dataPoint.uptBurnt}`}</Typography>
|
||||
<Typography variant="p">{`${dataPoint?.device?.manufacturer} ${dataPoint?.device?.model}`}</Typography>
|
||||
</Stack>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</Stack>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ textAlign: "left" }}>
|
||||
<Typography>COUNTRY</Typography>
|
||||
</th>
|
||||
<th style={{ textAlign: "left" }}>
|
||||
<Typography>CITY</Typography>
|
||||
</th>
|
||||
<th style={{ textAlign: "right" }}>
|
||||
<Typography>RESPONSE</Typography>
|
||||
</th>
|
||||
<th style={{ textAlign: "right" }}>
|
||||
<Typography>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={{ padding: theme.spacing(4) }}>
|
||||
<Typography>
|
||||
{flag ? <span className={flag} /> : null}{" "}
|
||||
{countryCode?.toUpperCase() ?? "N/A"}
|
||||
</Typography>
|
||||
</td>
|
||||
<td>
|
||||
<Typography>{city}</Typography>
|
||||
</td>
|
||||
<td style={{ textAlign: "right" }}>
|
||||
<Typography>{Math.floor(dataPoint.responseTime)} ms</Typography>
|
||||
</td>
|
||||
<td style={{ textAlign: "right" }}>
|
||||
<Typography color={theme.palette.warning.main}>
|
||||
+{dataPoint.uptBurnt}
|
||||
</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</ColContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -28,12 +28,17 @@ const DistributedUptimeMap = ({ width = "100%", checks }) => {
|
||||
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", () => {
|
||||
@@ -87,6 +92,10 @@ const DistributedUptimeMap = ({ width = "100%", checks }) => {
|
||||
ref={mapContainer}
|
||||
style={{
|
||||
width: width,
|
||||
borderRadius: theme.spacing(4),
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
borderStyle: "solid",
|
||||
borderWidth: 1,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Components
|
||||
import { Stack } from "@mui/material";
|
||||
import StatBox from "../../../../../Components/StatBox";
|
||||
import InfoBox from "../../../../../Components/InfoBox";
|
||||
import LastUpdate from "../LastUpdate";
|
||||
import UptLogo from "../../../../../assets/icons/upt_logo.png";
|
||||
|
||||
@@ -14,17 +14,21 @@ const StatBoxes = ({ monitor, lastUpdateTrigger }) => {
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<StatBox
|
||||
<InfoBox
|
||||
sx={{ flex: 1 }}
|
||||
heading="Avg Response Time"
|
||||
subHeading={`${Math.floor(monitor?.avgResponseTime ?? 0)} ms`}
|
||||
/>
|
||||
<StatBox
|
||||
<InfoBox
|
||||
sx={{ flex: 1 }}
|
||||
heading="Checking every"
|
||||
subHeading={`${(monitor?.interval ?? 0) / 1000} seconds`}
|
||||
/>
|
||||
<StatBox
|
||||
<InfoBox
|
||||
sx={{ flex: 1 }}
|
||||
heading={"Last check"}
|
||||
subHeading={
|
||||
<LastUpdate
|
||||
@@ -33,7 +37,8 @@ const StatBoxes = ({ monitor, lastUpdateTrigger }) => {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<StatBox
|
||||
<InfoBox
|
||||
sx={{ flex: 1 }}
|
||||
heading="Last server push"
|
||||
subHeading={
|
||||
<LastUpdate
|
||||
@@ -43,13 +48,6 @@ const StatBoxes = ({ monitor, lastUpdateTrigger }) => {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<StatBox
|
||||
heading="UPT Burned"
|
||||
subHeading={monitor?.totalUptBurnt ?? 0}
|
||||
img={UptLogo}
|
||||
alt="Upt Logo"
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
// 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";
|
||||
|
||||
const StatusHeader = ({ monitor, connectionStatus, elementToCapture }) => {
|
||||
const theme = useTheme();
|
||||
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="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}
|
||||
>
|
||||
Uptime: {(monitor.totalUptime * 100).toFixed(2)}%
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color={theme.palette.success.lowContrast}
|
||||
>
|
||||
Last updated{" "}
|
||||
<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;
|
||||
@@ -12,9 +12,14 @@ 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";
|
||||
|
||||
//Utils
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useState } from "react";
|
||||
import { useState, useRef } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useSubscribeToDetails } from "../../DistributedUptime/Details/Hooks/useSubscribeToDetails";
|
||||
import { useStatusPageFetchByUrl } from "./Hooks/useStatusPageFetchByUrl";
|
||||
@@ -26,6 +31,7 @@ const DistributedUptimeStatus = () => {
|
||||
const location = useLocation();
|
||||
const isPublic = location.pathname.startsWith("/status/distributed/public");
|
||||
|
||||
const elementToCapture = useRef(null);
|
||||
// Local State
|
||||
const [dateRange, setDateRange] = useState("day");
|
||||
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
||||
@@ -130,9 +136,12 @@ const DistributedUptimeStatus = () => {
|
||||
|
||||
return (
|
||||
<Stack
|
||||
ref={elementToCapture}
|
||||
direction="column"
|
||||
gap={theme.spacing(10)}
|
||||
sx={sx}
|
||||
sx={{
|
||||
...sx,
|
||||
}}
|
||||
>
|
||||
{!isPublic && <Breadcrumbs list={BREADCRUMBS} />}
|
||||
<ControlsHeader
|
||||
@@ -144,10 +153,13 @@ const DistributedUptimeStatus = () => {
|
||||
url={url}
|
||||
type="distributed"
|
||||
/>
|
||||
<StatBoxes
|
||||
|
||||
<StatusHeader
|
||||
monitor={monitor}
|
||||
lastUpdateTrigger={lastUpdateTrigger}
|
||||
connectionStatus={connectionStatus}
|
||||
elementToCapture={elementToCapture}
|
||||
/>
|
||||
|
||||
<NextExpectedCheck
|
||||
lastUpdateTime={monitor?.timeSinceLastCheck ?? 0}
|
||||
interval={monitor?.interval ?? 0}
|
||||
@@ -157,7 +169,6 @@ const DistributedUptimeStatus = () => {
|
||||
dateRange={dateRange}
|
||||
setDateRange={setDateRange}
|
||||
/>
|
||||
<DistributedUptimeResponseChart checks={monitor?.groupedChecks ?? []} />
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(8)}
|
||||
@@ -165,14 +176,43 @@ const DistributedUptimeStatus = () => {
|
||||
<DistributedUptimeMap
|
||||
checks={monitor?.groupedMapChecks ?? []}
|
||||
height={"100%"}
|
||||
width={"100%"}
|
||||
/>
|
||||
<DeviceTicker
|
||||
width={"25vw"}
|
||||
data={monitor?.latestChecks ?? []}
|
||||
connectionStatus={connectionStatus}
|
||||
width={"50%"}
|
||||
/>
|
||||
<Stack
|
||||
width={"50%"}
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<InfoBox
|
||||
heading="Devices"
|
||||
subHeading={monitor?.totalChecks ?? 0}
|
||||
icon={PeopleAltOutlinedIcon}
|
||||
alt="Upt Logo"
|
||||
sx={{ width: "50%" }}
|
||||
/>
|
||||
<InfoBox
|
||||
heading="UPT Burned"
|
||||
subHeading={monitor?.totalUptBurnt ?? 0}
|
||||
img={UptLogo}
|
||||
alt="Upt Logo"
|
||||
sx={{ width: "50%" }}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<DeviceTicker
|
||||
data={monitor?.latestChecks ?? []}
|
||||
connectionStatus={connectionStatus}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<DistributedUptimeResponseChart checks={monitor?.groupedChecks ?? []} />
|
||||
<StatBoxes
|
||||
monitor={monitor}
|
||||
lastUpdateTrigger={lastUpdateTrigger}
|
||||
/>
|
||||
<Footer />
|
||||
<Dialog
|
||||
// open={isOpen.deleteStats}
|
||||
|
||||
@@ -200,6 +200,20 @@ const newSemanticColors = {
|
||||
dark: newColors.green200,
|
||||
},
|
||||
},
|
||||
successSecondary: {
|
||||
main: {
|
||||
light: undefined,
|
||||
dark: "#073C2D",
|
||||
},
|
||||
contrastText: {
|
||||
light: undefined,
|
||||
dark: "#34D399",
|
||||
},
|
||||
lowContrast: {
|
||||
light: undefined,
|
||||
dark: "#094434",
|
||||
},
|
||||
},
|
||||
warning: {
|
||||
main: {
|
||||
light: newColors.orange700,
|
||||
@@ -266,6 +280,16 @@ const newSemanticColors = {
|
||||
dark: undefined,
|
||||
},
|
||||
},
|
||||
chatbot: {
|
||||
background: {
|
||||
light: "#112B2B",
|
||||
dark: "#112B2B",
|
||||
},
|
||||
textAccent: {
|
||||
light: "#C084FC",
|
||||
dark: "#C084FC",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export { typographyLevels, semanticColors as colors, newSemanticColors };
|
||||
Reference in new issue
Block a user