8 files changed
+185
-60
No files matched your search
Generated
+50
@@ -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",
|
||||
@@ -9384,6 +9385,15 @@
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"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",
|
||||
@@ -10446,6 +10456,15 @@
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
@@ -12444,6 +12463,19 @@
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"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",
|
||||
@@ -17888,6 +17920,15 @@
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"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",
|
||||
@@ -18418,6 +18459,15 @@
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"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,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;
|
||||
@@ -5,7 +5,6 @@ import { useTheme } from "@mui/material/styles";
|
||||
import maplibregl from "maplibre-gl";
|
||||
import { useSelector } from "react-redux";
|
||||
import buildStyle from "./buildStyle";
|
||||
import { ColContainer } from "../../../../../Components/StandardContainer";
|
||||
|
||||
const DistributedUptimeMap = ({ width = "100%", checks }) => {
|
||||
const mapContainer = useRef(null);
|
||||
@@ -29,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", () => {
|
||||
|
||||
@@ -4,19 +4,17 @@ 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 }) => {
|
||||
console.log(monitor);
|
||||
|
||||
const StatusHeader = ({ monitor, connectionStatus, elementToCapture }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const COLOR_MAP = {
|
||||
up: theme.palette.distributedStatusHeader.up,
|
||||
down: theme.palette.distributedStatusHeader.down,
|
||||
up: theme.palette.successSecondary.main,
|
||||
down: theme.palette.error.lowContrast,
|
||||
};
|
||||
|
||||
const MSG_MAP = {
|
||||
@@ -35,43 +33,51 @@ const StatusHeader = ({ monitor, connectionStatus }) => {
|
||||
<ColContainer backgroundColor={bgColor}>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<PulseDot color={PULSE_COLOR[connectionStatus]} />
|
||||
<Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<Typography
|
||||
variant="h1"
|
||||
color={theme.palette.distributedStatusHeader.textPrimary}
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<PulseDot color={PULSE_COLOR[connectionStatus]} />
|
||||
<Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
{MSG_MAP[connectionStatus]}
|
||||
</Typography>
|
||||
<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"
|
||||
borderRadius={theme.spacing(8)}
|
||||
padding={theme.spacing(4)}
|
||||
backgroundColor={
|
||||
theme.palette.distributedStatusHeader.textPrimaryBackground
|
||||
}
|
||||
color={theme.palette.distributedStatusHeader.textPrimary}
|
||||
color={theme.palette.success.lowContrast}
|
||||
>
|
||||
Uptime: {(monitor.totalUptime * 100).toFixed(2)}%
|
||||
Last updated{" "}
|
||||
<LastUpdate
|
||||
suffix={"seconds ago"}
|
||||
lastUpdateTime={monitor.timeSinceLastCheck}
|
||||
/>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color={theme.palette.distributedStatusHeader.textSecondary}
|
||||
>
|
||||
Last updated{" "}
|
||||
<LastUpdate
|
||||
suffix={"seconds ago"}
|
||||
lastUpdateTime={monitor.timeSinceLastCheck}
|
||||
/>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<ShareComponent
|
||||
elementToCapture={elementToCapture}
|
||||
fileName={monitor.name}
|
||||
/>
|
||||
</Stack>
|
||||
<ChatBot sx={{ marginTop: theme.spacing(10) }} />
|
||||
</ColContainer>
|
||||
|
||||
@@ -19,7 +19,7 @@ import StatusHeader from "../../DistributedUptime/Details/Components/StatusHeade
|
||||
|
||||
//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";
|
||||
@@ -31,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);
|
||||
@@ -135,9 +136,12 @@ const DistributedUptimeStatus = () => {
|
||||
|
||||
return (
|
||||
<Stack
|
||||
ref={elementToCapture}
|
||||
direction="column"
|
||||
gap={theme.spacing(10)}
|
||||
sx={sx}
|
||||
sx={{
|
||||
...sx,
|
||||
}}
|
||||
>
|
||||
{!isPublic && <Breadcrumbs list={BREADCRUMBS} />}
|
||||
<ControlsHeader
|
||||
@@ -153,6 +157,7 @@ const DistributedUptimeStatus = () => {
|
||||
<StatusHeader
|
||||
monitor={monitor}
|
||||
connectionStatus={connectionStatus}
|
||||
elementToCapture={elementToCapture}
|
||||
/>
|
||||
|
||||
<NextExpectedCheck
|
||||
|
||||
@@ -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,
|
||||
@@ -267,29 +281,6 @@ const newSemanticColors = {
|
||||
},
|
||||
},
|
||||
|
||||
distributedStatusHeader: {
|
||||
up: {
|
||||
light: "#073C2D",
|
||||
dark: "#073C2D",
|
||||
},
|
||||
down: {
|
||||
light: newColors.red700,
|
||||
dark: newColors.red700,
|
||||
},
|
||||
textPrimary: {
|
||||
light: "#34D399",
|
||||
dark: "#34D399",
|
||||
},
|
||||
textPrimaryBackground: {
|
||||
light: "#094434",
|
||||
dark: "#094434",
|
||||
},
|
||||
textSecondary: {
|
||||
light: "#22946C",
|
||||
dark: "#22946C",
|
||||
},
|
||||
},
|
||||
|
||||
chatbot: {
|
||||
background: {
|
||||
light: "#112B2B",
|
||||
|
||||
@@ -118,6 +118,7 @@ const baseTheme = (palette) => ({
|
||||
},
|
||||
},
|
||||
],
|
||||
height: 34,
|
||||
fontWeight: 400,
|
||||
borderRadius: 4,
|
||||
boxShadow: "none",
|
||||
|
||||
Reference in new issue
Block a user