From f8eb6e9bc2f08a4e32b4a56eb95ab25dd53b9e3e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 20 Feb 2025 19:00:06 -0800 Subject: [PATCH 1/7] add InfoBox --- src/Components/InfoBox/index.jsx | 42 ++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/Components/InfoBox/index.jsx diff --git a/src/Components/InfoBox/index.jsx b/src/Components/InfoBox/index.jsx new file mode 100644 index 000000000..6797d6c9c --- /dev/null +++ b/src/Components/InfoBox/index.jsx @@ -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 ( + + {img && ( + {alt} + )} + {Icon && ( + + )} + + {heading} + + {subHeading} + + + + ); +}; + +export default InfoBox; From 84c67f6ab33f68a92c633bffd07a7c1a97b16024 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 20 Feb 2025 19:00:15 -0800 Subject: [PATCH 2/7] add share component --- src/Components/ShareComponent/index.jsx | 67 +++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/Components/ShareComponent/index.jsx diff --git a/src/Components/ShareComponent/index.jsx b/src/Components/ShareComponent/index.jsx new file mode 100644 index 000000000..073ed2243 --- /dev/null +++ b/src/Components/ShareComponent/index.jsx @@ -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 ( + + ); +}; + +export default ShareComponent; From 0669beea74a27d65ca63dc5fd2015e0f5865e370 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 20 Feb 2025 19:01:30 -0800 Subject: [PATCH 3/7] add html2canvas --- package-lock.json | 45 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 46 insertions(+) diff --git a/package-lock.json b/package-lock.json index 4cafc5cf5..d6e39edd3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 9751fe0d3..3646f406c 100644 --- a/package.json +++ b/package.json @@ -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", From b44ac7449cd041676db0e4a7995299bb18b04912 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 20 Feb 2025 19:01:48 -0800 Subject: [PATCH 4/7] add secondary success color, add chatbot colors --- src/Utils/Theme/constants.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/Utils/Theme/constants.js b/src/Utils/Theme/constants.js index 3d1a14703..36ab0a0f4 100644 --- a/src/Utils/Theme/constants.js +++ b/src/Utils/Theme/constants.js @@ -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 }; From e22920ebf908ef61627254039d61eb3d5837628b Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 20 Feb 2025 19:02:01 -0800 Subject: [PATCH 5/7] add a standard container --- src/Components/StandardContainer/index.jsx | 51 ++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/Components/StandardContainer/index.jsx diff --git a/src/Components/StandardContainer/index.jsx b/src/Components/StandardContainer/index.jsx new file mode 100644 index 000000000..c736b3ecc --- /dev/null +++ b/src/Components/StandardContainer/index.jsx @@ -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 ( + + {children} + + ); +}; + +export const ColContainer = ({ children, backgroundColor, sx }) => { + return ( + + {children} + + ); +}; + +export const RowContainer = ({ children, backgroundColor, sx }) => { + return ( + + {children} + + ); +}; From 7ea0e0134ce121d50ccd6d685d5cda26722d5772 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 20 Feb 2025 19:02:24 -0800 Subject: [PATCH 6/7] add status header and chatbot --- .../Details/Components/Chatbot/index.jsx | 48 ++++++++++ .../Details/Components/DeviceTicker/index.jsx | 88 ++++++++++-------- .../Components/DistributedUptimeMap/index.jsx | 9 ++ .../Details/Components/StatBoxes/index.jsx | 22 ++--- .../Details/Components/StatusHeader/index.jsx | 92 +++++++++++++++++++ 5 files changed, 209 insertions(+), 50 deletions(-) create mode 100644 src/Pages/DistributedUptime/Details/Components/Chatbot/index.jsx create mode 100644 src/Pages/DistributedUptime/Details/Components/StatusHeader/index.jsx diff --git a/src/Pages/DistributedUptime/Details/Components/Chatbot/index.jsx b/src/Pages/DistributedUptime/Details/Components/Chatbot/index.jsx new file mode 100644 index 000000000..00af46f6b --- /dev/null +++ b/src/Pages/DistributedUptime/Details/Components/Chatbot/index.jsx @@ -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 ( + + + + Status Bot + + + Now + + + {MESSAGES[Math.floor(Math.random() * MESSAGES.length)]} + + ); +}; + +export default ChatBot; diff --git a/src/Pages/DistributedUptime/Details/Components/DeviceTicker/index.jsx b/src/Pages/DistributedUptime/Details/Components/DeviceTicker/index.jsx index dc5604a47..9194eefe5 100644 --- a/src/Pages/DistributedUptime/Details/Components/DeviceTicker/index.jsx +++ b/src/Pages/DistributedUptime/Details/Components/DeviceTicker/index.jsx @@ -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 ( - + @@ -41,30 +30,53 @@ const DeviceTicker = ({ data, width = "100%", connectionStatus }) => { {connectionStatus === "up" ? "Connected" : "No connection"} - - {data.slice(Math.max(data.length - 5, 0)).map((dataPoint) => { - const countryCode = dataPoint?.countryCode?.toLowerCase() ?? null; - const flag = countryCode ? `fi fi-${countryCode}` : null; - return ( - - - - {flag && } - {dataPoint?.city || "Unknown"} - - {`Response time: ${Math.floor(dataPoint?.responseTime ?? 0)} ms`} - {`UPT burned: ${dataPoint.uptBurnt}`} - {`${dataPoint?.device?.manufacturer} ${dataPoint?.device?.model}`} - - - ); - })} - - + + + + + + + + + + + {data.map((dataPoint) => { + const countryCode = dataPoint?.countryCode?.toLowerCase() ?? null; + const flag = countryCode ? `fi fi-${countryCode}` : null; + const city = dataPoint?.city !== "" ? dataPoint?.city : "Unknown"; + return ( + + + + + + + ); + })} + +
+ COUNTRY + + CITY + + RESPONSE + + UPT BURNED +
+ + {flag ? : null}{" "} + {countryCode?.toUpperCase() ?? "N/A"} + + + {city} + + {Math.floor(dataPoint.responseTime)} ms + + + +{dataPoint.uptBurnt} + +
+ ); }; diff --git a/src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/index.jsx b/src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/index.jsx index f4e6a2b19..7617666eb 100644 --- a/src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/index.jsx +++ b/src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/index.jsx @@ -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, }} /> ); diff --git a/src/Pages/DistributedUptime/Details/Components/StatBoxes/index.jsx b/src/Pages/DistributedUptime/Details/Components/StatBoxes/index.jsx index a4fd3b03d..df0492bd2 100644 --- a/src/Pages/DistributedUptime/Details/Components/StatBoxes/index.jsx +++ b/src/Pages/DistributedUptime/Details/Components/StatBoxes/index.jsx @@ -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 ( - - - { /> } /> - { /> } /> - - ); }; diff --git a/src/Pages/DistributedUptime/Details/Components/StatusHeader/index.jsx b/src/Pages/DistributedUptime/Details/Components/StatusHeader/index.jsx new file mode 100644 index 000000000..5db44a559 --- /dev/null +++ b/src/Pages/DistributedUptime/Details/Components/StatusHeader/index.jsx @@ -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 ( + + + + + + + + {MSG_MAP[connectionStatus]} + + + Uptime: {(monitor.totalUptime * 100).toFixed(2)}% + + + + Last updated{" "} + + + + + + + + + ); +}; + +StatusHeader.propTypes = { + monitor: PropTypes.object, + connectionStatus: PropTypes.string, +}; + +export default StatusHeader; From 28a970788568f093fe60e9cc29841e24280f68a5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 20 Feb 2025 19:02:33 -0800 Subject: [PATCH 7/7] update status page --- .../DistributedUptimeStatus/Status/index.jsx | 62 +++++++++++++++---- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/src/Pages/DistributedUptimeStatus/Status/index.jsx b/src/Pages/DistributedUptimeStatus/Status/index.jsx index ef77b3032..a1c6b473f 100644 --- a/src/Pages/DistributedUptimeStatus/Status/index.jsx +++ b/src/Pages/DistributedUptimeStatus/Status/index.jsx @@ -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 ( {!isPublic && } { url={url} type="distributed" /> - + { dateRange={dateRange} setDateRange={setDateRange} /> - { - + + + + + + + + + +