diff --git a/Client/package-lock.json b/Client/package-lock.json
index 3deeca1bc..556caf1bc 100644
--- a/Client/package-lock.json
+++ b/Client/package-lock.json
@@ -37,7 +37,6 @@
"react-router": "^6.23.0",
"react-router-dom": "^6.23.1",
"react-toastify": "^10.0.5",
- "react-world-flags": "^1.6.0",
"recharts": "2.15.1",
"redux-persist": "6.0.0",
"vite-plugin-svgr": "^4.2.0"
@@ -2368,14 +2367,6 @@
"@svgr/core": "*"
}
},
- "node_modules/@trysound/sax": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
- "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
- "engines": {
- "node": ">=10.13.0"
- }
- },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -2880,11 +2871,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/boolbase": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
- "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
- },
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -3077,14 +3063,6 @@
"node": ">= 0.8"
}
},
- "node_modules/commander": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
- "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
- "engines": {
- "node": ">= 10"
- }
- },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -3137,74 +3115,6 @@
"tiny-invariant": "^1.0.6"
}
},
- "node_modules/css-select": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
- "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
- "dependencies": {
- "boolbase": "^1.0.0",
- "css-what": "^6.1.0",
- "domhandler": "^5.0.2",
- "domutils": "^3.0.1",
- "nth-check": "^2.0.1"
- },
- "funding": {
- "url": "https://github.com/sponsors/fb55"
- }
- },
- "node_modules/css-tree": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
- "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
- "dependencies": {
- "mdn-data": "2.0.30",
- "source-map-js": "^1.0.1"
- },
- "engines": {
- "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
- }
- },
- "node_modules/css-what": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
- "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
- "engines": {
- "node": ">= 6"
- },
- "funding": {
- "url": "https://github.com/sponsors/fb55"
- }
- },
- "node_modules/csso": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz",
- "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==",
- "dependencies": {
- "css-tree": "~2.2.0"
- },
- "engines": {
- "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
- "npm": ">=7.0.0"
- }
- },
- "node_modules/csso/node_modules/css-tree": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz",
- "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==",
- "dependencies": {
- "mdn-data": "2.0.28",
- "source-map-js": "^1.0.1"
- },
- "engines": {
- "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
- "npm": ">=7.0.0"
- }
- },
- "node_modules/csso/node_modules/mdn-data": {
- "version": "2.0.28",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz",
- "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="
- },
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@@ -3529,57 +3439,6 @@
"csstype": "^3.0.2"
}
},
- "node_modules/dom-serializer": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
- "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
- "dependencies": {
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.2",
- "entities": "^4.2.0"
- },
- "funding": {
- "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
- }
- },
- "node_modules/domelementtype": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
- "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fb55"
- }
- ]
- },
- "node_modules/domhandler": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
- "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
- "dependencies": {
- "domelementtype": "^2.3.0"
- },
- "engines": {
- "node": ">= 4"
- },
- "funding": {
- "url": "https://github.com/fb55/domhandler?sponsor=1"
- }
- },
- "node_modules/domutils": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
- "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
- "dependencies": {
- "dom-serializer": "^2.0.0",
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.3"
- },
- "funding": {
- "url": "https://github.com/fb55/domutils?sponsor=1"
- }
- },
"node_modules/dot-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
@@ -5474,11 +5333,6 @@
"node": ">= 0.4"
}
},
- "node_modules/mdn-data": {
- "version": "2.0.30",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
- "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="
- },
"node_modules/memoize-one": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
@@ -5600,17 +5454,6 @@
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
"license": "MIT"
},
- "node_modules/nth-check": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
- "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
- "dependencies": {
- "boolbase": "^1.0.0"
- },
- "funding": {
- "url": "https://github.com/fb55/nth-check?sponsor=1"
- }
- },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -5938,9 +5781,9 @@
}
},
"node_modules/prettier": {
- "version": "3.4.2",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
- "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.0.tgz",
+ "integrity": "sha512-quyMrVt6svPS7CjQ9gKb3GLEX/rl3BCL2oa/QkNcXv4YNVBC9olt3s+H7ukto06q7B1Qz46PbrKLO34PR6vXcA==",
"dev": true,
"license": "MIT",
"bin": {
@@ -6222,19 +6065,6 @@
"react-dom": ">=16.6.0"
}
},
- "node_modules/react-world-flags": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/react-world-flags/-/react-world-flags-1.6.0.tgz",
- "integrity": "sha512-eutSeAy5YKoVh14js/JUCSlA6EBk1n4k+bDaV+NkNB50VhnG+f4QDTpYycnTUTsZ5cqw/saPmk0Z4Fa0VVZ1Iw==",
- "dependencies": {
- "svg-country-flags": "^1.2.10",
- "svgo": "^3.0.2",
- "world-countries": "^5.0.0"
- },
- "peerDependencies": {
- "react": ">=0.14"
- }
- },
"node_modules/recharts": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.1.tgz",
@@ -6862,41 +6692,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/svg-country-flags": {
- "version": "1.2.10",
- "resolved": "https://registry.npmjs.org/svg-country-flags/-/svg-country-flags-1.2.10.tgz",
- "integrity": "sha512-xrqwo0TYf/h2cfPvGpjdSuSguUbri4vNNizBnwzoZnX0xGo3O5nGJMlbYEp7NOYcnPGBm6LE2axqDWSB847bLw=="
- },
"node_modules/svg-parser": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz",
"integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==",
"license": "MIT"
},
- "node_modules/svgo": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz",
- "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==",
- "dependencies": {
- "@trysound/sax": "0.2.0",
- "commander": "^7.2.0",
- "css-select": "^5.1.0",
- "css-tree": "^2.3.1",
- "css-what": "^6.1.0",
- "csso": "^5.0.5",
- "picocolors": "^1.0.0"
- },
- "bin": {
- "svgo": "bin/svgo"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/svgo"
- }
- },
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -7330,11 +7131,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/world-countries": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/world-countries/-/world-countries-5.0.0.tgz",
- "integrity": "sha512-wAfOT9Y5i/xnxNOdKJKXdOCw9Q3yQLahBUeuRol+s+o20F6h2a4tLEbJ1lBCYwEQ30Sf9Meqeipk1gib3YwF5w=="
- },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
diff --git a/Client/package.json b/Client/package.json
index edf247160..d4c26e1c5 100644
--- a/Client/package.json
+++ b/Client/package.json
@@ -40,7 +40,6 @@
"react-router": "^6.23.0",
"react-router-dom": "^6.23.1",
"react-toastify": "^10.0.5",
- "react-world-flags": "^1.6.0",
"recharts": "2.15.1",
"redux-persist": "6.0.0",
"vite-plugin-svgr": "^4.2.0"
diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx
index 0b959a392..a1ead3cf9 100644
--- a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx
+++ b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx
@@ -73,9 +73,12 @@ const CustomToolTip = ({ active, payload, label, dateRange }) => {
component="span"
sx={{ opacity: 0.8 }}
>
- Response Time
- {" "}
-
+ Response time:
+
+
{Math.floor(responseTime)}
{
if (shouldRender === false) {
@@ -28,11 +31,21 @@ const Image = ({
src = `data:image/png;base64,${base64}`;
}
+ if (
+ typeof src === "undefined" &&
+ typeof base64 === "undefined" &&
+ typeof placeholder !== "undefined"
+ ) {
+ src = placeholder;
+ }
+
return (
Click to upload
diff --git a/Client/src/Components/LanguageSelector.jsx b/Client/src/Components/LanguageSelector.jsx
index e1a19c231..9f60a5b42 100644
--- a/Client/src/Components/LanguageSelector.jsx
+++ b/Client/src/Components/LanguageSelector.jsx
@@ -2,7 +2,7 @@ import { useState } from "react";
import { useTranslation } from "react-i18next";
import { Box, MenuItem, Select, Stack } from "@mui/material";
import { useTheme } from "@emotion/react";
-import Flag from "react-world-flags";
+import "flag-icons/css/flag-icons.min.css";
import { useSelector, useDispatch } from "react-redux";
import { setLanguage } from "../Features/Settings/uiSlice";
@@ -81,55 +81,59 @@ const LanguageSelector = () => {
},
}}
>
- {languages.map((lang) => (
-
+ );
+ })}
);
};
diff --git a/Client/src/Components/MonitorCreateHeader/index.jsx b/Client/src/Components/MonitorCreateHeader/index.jsx
index 01a102b7d..565dca032 100644
--- a/Client/src/Components/MonitorCreateHeader/index.jsx
+++ b/Client/src/Components/MonitorCreateHeader/index.jsx
@@ -3,7 +3,12 @@ import { useNavigate } from "react-router-dom";
import PropTypes from "prop-types";
import SkeletonLayout from "./skeleton";
-const CreateMonitorHeader = ({ isAdmin, shouldRender = true, path }) => {
+const CreateMonitorHeader = ({
+ isAdmin,
+ label = "Create new",
+ shouldRender = true,
+ path,
+}) => {
const navigate = useNavigate();
if (!isAdmin) return null;
if (!shouldRender) return ;
@@ -18,7 +23,7 @@ const CreateMonitorHeader = ({ isAdmin, shouldRender = true, path }) => {
color="accent"
onClick={() => navigate(path)}
>
- Create new
+ {label}
);
@@ -30,4 +35,5 @@ CreateMonitorHeader.propTypes = {
isAdmin: PropTypes.bool.isRequired,
shouldRender: PropTypes.bool,
path: PropTypes.string.isRequired,
+ label: PropTypes.string,
};
diff --git a/Client/src/Hooks/useMonitorUtils.js b/Client/src/Hooks/useMonitorUtils.js
index 1e235af68..fc1966b69 100644
--- a/Client/src/Hooks/useMonitorUtils.js
+++ b/Client/src/Hooks/useMonitorUtils.js
@@ -22,10 +22,7 @@ const useMonitorUtils = () => {
}
return {
- id: monitor._id,
- name: monitor.name,
- url: monitor.url,
- title: monitor.name,
+ ...monitor,
percentage: uptimePercentage,
percentageColor,
monitor: monitor,
diff --git a/Client/src/Pages/DistributedUptime/CreateStatus/Components/VisuallyHiddenInput/index.jsx b/Client/src/Pages/DistributedUptime/CreateStatus/Components/VisuallyHiddenInput/index.jsx
new file mode 100644
index 000000000..fd002d2a4
--- /dev/null
+++ b/Client/src/Pages/DistributedUptime/CreateStatus/Components/VisuallyHiddenInput/index.jsx
@@ -0,0 +1,22 @@
+const VisuallyHiddenInput = ({ onChange }) => {
+ return (
+
+ );
+};
+
+export default VisuallyHiddenInput;
diff --git a/Client/src/Pages/DistributedUptime/CreateStatus/index.jsx b/Client/src/Pages/DistributedUptime/CreateStatus/index.jsx
new file mode 100644
index 000000000..d244bfe38
--- /dev/null
+++ b/Client/src/Pages/DistributedUptime/CreateStatus/index.jsx
@@ -0,0 +1,217 @@
+// 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";
+// Utils
+import { useTheme } from "@emotion/react";
+import { useState } from "react";
+import { useParams } from "react-router-dom";
+import { useCreateStatusPage } from "../../StatusPage/Create/Hooks/useCreateStatusPage";
+import { useLocation } from "react-router-dom";
+import { statusPageValidation } from "../../../Validation/validation";
+import { buildErrors } from "../../../Validation/error";
+import { createToast } from "../../../Utils/toastUtils";
+import { useNavigate } from "react-router-dom";
+
+const CreateStatus = () => {
+ const theme = useTheme();
+ const { monitorId } = useParams();
+ const navigate = useNavigate();
+ const location = useLocation();
+ const isCreate = location.pathname.startsWith("/distributed-uptime/status/create");
+ const [createStatusPage, isLoading, networkError] = useCreateStatusPage(isCreate);
+ const BREADCRUMBS = [
+ { name: "distributed uptime", path: "/distributed-uptime" },
+ { name: "details", path: `/distributed-uptime/${monitorId}` },
+ { name: "create status page", path: `` },
+ ];
+ // Local state
+ const [form, setForm] = useState({
+ isPublished: false,
+ url: Math.floor(Math.random() * 1000000).toFixed(0),
+ logo: undefined,
+ companyName: "",
+ monitors: [monitorId],
+ });
+ const [errors, setErrors] = 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 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") {
+ logoToSubmit = {
+ src: URL.createObjectURL(form.logo),
+ name: form.logo.name,
+ type: form.logo.type,
+ size: form.logo.size,
+ };
+ }
+ 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) {
+ createToast({ body: "Status page created successfully" });
+ navigate(`/distributed-uptime/status/${form.url}`);
+ }
+ return;
+ }
+ const newErrors = {};
+ error?.details?.forEach((err) => {
+ newErrors[err.path[0]] = err.message;
+ });
+ setErrors((prev) => ({ ...prev, ...newErrors }));
+ };
+
+ return (
+
+
+
+
+ Create your{" "}
+
+
+ status page
+
+
+
+
+ Access
+
+ If your status page is ready, you can mark it as published.
+
+
+
+
+
+
+
+
+ Basic Information
+
+ Define company name and the subdomain that your status page points to.
+
+
+
+
+
+
+
+
+
+ Logo
+ Upload a logo for your status page
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default CreateStatus;
diff --git a/Client/src/Pages/DistributedUptime/Details/Components/DeviceTicker/index.jsx b/Client/src/Pages/DistributedUptime/Details/Components/DeviceTicker/index.jsx
index 22ef5596d..dc5604a47 100644
--- a/Client/src/Pages/DistributedUptime/Details/Components/DeviceTicker/index.jsx
+++ b/Client/src/Pages/DistributedUptime/Details/Components/DeviceTicker/index.jsx
@@ -1,7 +1,7 @@
import { Stack, Typography, List, ListItem } from "@mui/material";
import { useTheme } from "@emotion/react";
import PulseDot from "../../../../../Components/Animated/PulseDot";
-import "/node_modules/flag-icons/css/flag-icons.min.css";
+import "flag-icons/css/flag-icons.min.css";
const BASE_BOX_PADDING_VERTICAL = 16;
const BASE_BOX_PADDING_HORIZONTAL = 8;
diff --git a/Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/index.jsx b/Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/index.jsx
index 99e591750..f4e6a2b19 100644
--- a/Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/index.jsx
+++ b/Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/index.jsx
@@ -6,7 +6,7 @@ import maplibregl from "maplibre-gl";
import { useSelector } from "react-redux";
import buildStyle from "./buildStyle";
-const DistributedUptimeMap = ({ width = "100%", height = "100%", checks }) => {
+const DistributedUptimeMap = ({ width = "100%", checks }) => {
const mapContainer = useRef(null);
const map = useRef(null);
const theme = useTheme();
@@ -87,7 +87,6 @@ const DistributedUptimeMap = ({ width = "100%", height = "100%", checks }) => {
ref={mapContainer}
style={{
width: width,
- height: height,
}}
/>
);
diff --git a/Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/index.jsx b/Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/index.jsx
index 7a4c4b210..b57d0f9dc 100644
--- a/Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/index.jsx
+++ b/Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/index.jsx
@@ -24,73 +24,70 @@ const CustomToolTip = ({ active, payload, label }) => {
? payload[0]?.payload?.originalAvgResponseTime
: (payload[0]?.payload?.avgResponseTime ?? 0);
return (
- }
- header="Response Times"
- sx={{ padding: 0 }}
+
-
-
+
+
+
- {formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)}
-
-
-
-
+ Response time
+
+
+ {Math.floor(responseTime)}
- Response Time
- {" "}
-
- {Math.floor(responseTime)}
-
- {" "}
- ms
-
+ {" "}
+ ms
-
-
- {/* Display original value */}
+
+
-
+ {/* Display original value */}
+
);
}
return null;
@@ -143,75 +140,81 @@ const DistributedUptimeResponseChart = ({ checks }) => {
if (checks.length === 0) return null;
return (
- }
+ header="Response Times"
+ sx={{ padding: 0 }}
>
- setIsHovered(true)}
- onMouseLeave={() => setIsHovered(false)}
+ minWidth={25}
+ height={220}
>
-
-
-
-
-
-
-
- }
- minTickGap={0}
- axisLine={false}
- tickLine={false}
- height={20}
- />
- }
- wrapperStyle={{ pointerEvents: "none" }}
- />
-
-
-
+ setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
+ >
+
+
+
+
+
+
+
+ }
+ minTickGap={0}
+ axisLine={false}
+ tickLine={false}
+ height={20}
+ />
+ }
+ wrapperStyle={{ pointerEvents: "none" }}
+ />
+
+
+
+
);
};
diff --git a/Client/src/Pages/DistributedUptime/Details/Components/Skeleton/index.jsx b/Client/src/Pages/DistributedUptime/Details/Components/Skeleton/index.jsx
new file mode 100644
index 000000000..233fc3c06
--- /dev/null
+++ b/Client/src/Pages/DistributedUptime/Details/Components/Skeleton/index.jsx
@@ -0,0 +1,14 @@
+import { Stack, Skeleton } from "@mui/material";
+
+export const SkeletonLayout = () => {
+ return (
+
+
+
+ );
+};
+
+export default SkeletonLayout;
diff --git a/Client/src/Pages/DistributedUptime/Details/Hooks/useSubscribeToDetails.jsx b/Client/src/Pages/DistributedUptime/Details/Hooks/useSubscribeToDetails.jsx
index 409e9cf89..9ee7fa873 100644
--- a/Client/src/Pages/DistributedUptime/Details/Hooks/useSubscribeToDetails.jsx
+++ b/Client/src/Pages/DistributedUptime/Details/Hooks/useSubscribeToDetails.jsx
@@ -45,7 +45,7 @@ const getRandomDevice = () => {
model: randomModel,
};
};
-const useSubscribeToDetails = ({ monitorId, dateRange }) => {
+const useSubscribeToDetails = ({ monitorId, isPublic, isPublished, dateRange }) => {
const [isLoading, setIsLoading] = useState(true);
const [connectionStatus, setConnectionStatus] = useState(undefined);
const [retryCount, setRetryCount] = useState(0);
@@ -58,6 +58,14 @@ const useSubscribeToDetails = ({ monitorId, dateRange }) => {
const prevDateRangeRef = useRef(dateRange);
useEffect(() => {
+ if (typeof monitorId === "undefined") {
+ return;
+ }
+ // If this page is public and not published, don't subscribe to details
+ if (isPublic && isPublished === false) {
+ return;
+ }
+
try {
const cleanup = networkService.subscribeToDistributedUptimeDetails({
authToken,
@@ -65,6 +73,9 @@ const useSubscribeToDetails = ({ monitorId, dateRange }) => {
dateRange: dateRange,
normalize: true,
onUpdate: (data) => {
+ if (isLoading === true) {
+ setIsLoading(false);
+ }
if (networkError === true) {
setNetworkError(false);
}
@@ -84,6 +95,7 @@ const useSubscribeToDetails = ({ monitorId, dateRange }) => {
setRetryCount(0); // Reset retry count on successful connection
},
onError: () => {
+ setIsLoading(false);
setNetworkError(true);
setConnectionStatus("down");
},
@@ -91,8 +103,6 @@ const useSubscribeToDetails = ({ monitorId, dateRange }) => {
return cleanup;
} catch (error) {
setNetworkError(true);
- } finally {
- setIsLoading(false);
}
}, [
authToken,
@@ -102,6 +112,7 @@ const useSubscribeToDetails = ({ monitorId, dateRange }) => {
setConnectionStatus,
networkError,
devices,
+ isLoading,
]);
useEffect(() => {
@@ -116,4 +127,4 @@ const useSubscribeToDetails = ({ monitorId, dateRange }) => {
return [isLoading, networkError, connectionStatus, monitor, lastUpdateTrigger];
};
-export default useSubscribeToDetails;
+export { useSubscribeToDetails };
diff --git a/Client/src/Pages/DistributedUptime/Details/index.jsx b/Client/src/Pages/DistributedUptime/Details/index.jsx
index ab39ec2ec..27a596af4 100644
--- a/Client/src/Pages/DistributedUptime/Details/index.jsx
+++ b/Client/src/Pages/DistributedUptime/Details/index.jsx
@@ -10,21 +10,23 @@ 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";
//Utils
import { useTheme } from "@mui/material/styles";
import { useState } from "react";
import { useParams } from "react-router-dom";
-import useSubscribeToDetails from "./Hooks/useSubscribeToDetails";
+import { useIsAdmin } from "../../../Hooks/useIsAdmin";
+import { useSubscribeToDetails } from "./Hooks/useSubscribeToDetails";
const DistributedUptimeDetails = () => {
const { monitorId } = useParams();
-
// Local State
const [dateRange, setDateRange] = useState("day");
// Utils
const theme = useTheme();
+ const isAdmin = useIsAdmin();
const [isLoading, networkError, connectionStatus, monitor, lastUpdateTrigger] =
useSubscribeToDetails({ monitorId, dateRange });
// Constants
@@ -33,6 +35,10 @@ const DistributedUptimeDetails = () => {
{ name: "Details", path: `/distributed-uptime/${monitorId}` },
];
+ if (isLoading) {
+ return ;
+ }
+
if (networkError) {
return (
@@ -65,6 +71,11 @@ const DistributedUptimeDetails = () => {
gap={theme.spacing(10)}
>
+
{
},
},
onRowClick: (row) => {
- navigate(`/distributed-uptime/${row.id}`);
+ navigate(`/distributed-uptime/${row._id}`);
},
}}
/>
diff --git a/Client/src/Pages/DistributedUptime/Monitors/Components/Skeleton/index.jsx b/Client/src/Pages/DistributedUptime/Monitors/Components/Skeleton/index.jsx
new file mode 100644
index 000000000..233fc3c06
--- /dev/null
+++ b/Client/src/Pages/DistributedUptime/Monitors/Components/Skeleton/index.jsx
@@ -0,0 +1,14 @@
+import { Stack, Skeleton } from "@mui/material";
+
+export const SkeletonLayout = () => {
+ return (
+
+
+
+ );
+};
+
+export default SkeletonLayout;
diff --git a/Client/src/Pages/DistributedUptime/Monitors/Hooks/useSubscribeToMonitors.jsx b/Client/src/Pages/DistributedUptime/Monitors/Hooks/useSubscribeToMonitors.jsx
index 1a1db1453..428791a3e 100644
--- a/Client/src/Pages/DistributedUptime/Monitors/Hooks/useSubscribeToMonitors.jsx
+++ b/Client/src/Pages/DistributedUptime/Monitors/Hooks/useSubscribeToMonitors.jsx
@@ -32,6 +32,10 @@ const useSubscribeToMonitors = () => {
field: null,
order: null,
onUpdate: (data) => {
+ if (isLoading === true) {
+ setIsLoading(false);
+ }
+
const res = data.monitors;
const { monitors, filteredMonitors, summary } = res;
const mappedMonitors = filteredMonitors.map((monitor) =>
@@ -41,6 +45,9 @@ const useSubscribeToMonitors = () => {
setMonitorsSummary(summary);
setFilteredMonitors(mappedMonitors);
},
+ onError: () => {
+ setIsLoading(false);
+ },
});
return cleanup;
@@ -49,8 +56,6 @@ const useSubscribeToMonitors = () => {
body: error.message,
});
setNetworkError(true);
- } finally {
- setIsLoading(false);
}
}, [authToken, user, getMonitorWithPercentage, theme]);
return [isLoading, networkError, monitors, monitorsSummary, filteredMonitors];
diff --git a/Client/src/Pages/DistributedUptime/Monitors/index.jsx b/Client/src/Pages/DistributedUptime/Monitors/index.jsx
index a3466b845..159d265a0 100644
--- a/Client/src/Pages/DistributedUptime/Monitors/index.jsx
+++ b/Client/src/Pages/DistributedUptime/Monitors/index.jsx
@@ -10,6 +10,7 @@ import GenericFallback from "../../../Components/GenericFallback";
import { useTheme } from "@mui/material/styles";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
import { useSubscribeToMonitors } from "./Hooks/useSubscribeToMonitors";
+import SkeletonLayout from "./Components/Skeleton";
// Constants
const BREADCRUMBS = [{ name: `Distributed Uptime`, path: "/distributed-uptime" }];
@@ -21,6 +22,10 @@ const DistributedUptimeMonitors = () => {
const [isLoading, networkError, monitors, monitorsSummary, filteredMonitors] =
useSubscribeToMonitors();
+ if (isLoading) {
+ return ;
+ }
+
if (networkError) {
return (
diff --git a/Client/src/Pages/DistributedUptime/Status/Components/Skeleton/index.jsx b/Client/src/Pages/DistributedUptime/Status/Components/Skeleton/index.jsx
new file mode 100644
index 000000000..233fc3c06
--- /dev/null
+++ b/Client/src/Pages/DistributedUptime/Status/Components/Skeleton/index.jsx
@@ -0,0 +1,14 @@
+import { Stack, Skeleton } from "@mui/material";
+
+export const SkeletonLayout = () => {
+ return (
+
+
+
+ );
+};
+
+export default SkeletonLayout;
diff --git a/Client/src/Pages/DistributedUptime/Status/Hooks/useStatusPageFetchByUrl.jsx b/Client/src/Pages/DistributedUptime/Status/Hooks/useStatusPageFetchByUrl.jsx
new file mode 100644
index 000000000..583fed69d
--- /dev/null
+++ b/Client/src/Pages/DistributedUptime/Status/Hooks/useStatusPageFetchByUrl.jsx
@@ -0,0 +1,37 @@
+import { useState, useEffect } from "react";
+import { networkService } from "../../../../main";
+import { createToast } from "../../../../Utils/toastUtils";
+import { useSelector } from "react-redux";
+
+const useStatusPageFetchByUrl = ({ url }) => {
+ 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);
+ useEffect(() => {
+ const fetchStatusPageByUrl = async () => {
+ try {
+ const response = await networkService.getStatusPageByUrl({ authToken, url });
+ if (!response?.data?.data) return;
+ const statusPage = response.data.data;
+ setStatusPage(statusPage);
+ setMonitorId(statusPage?.monitors[0]);
+ setIsPublished(statusPage?.isPublished);
+ } catch (error) {
+ setNetworkError(true);
+ createToast({
+ body: error.message,
+ });
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ fetchStatusPageByUrl();
+ }, [authToken, url]);
+
+ return [isLoading, networkError, statusPage, monitorId, isPublished];
+};
+
+export { useStatusPageFetchByUrl };
diff --git a/Client/src/Pages/DistributedUptime/Status/index.jsx b/Client/src/Pages/DistributedUptime/Status/index.jsx
new file mode 100644
index 000000000..d91761e06
--- /dev/null
+++ b/Client/src/Pages/DistributedUptime/Status/index.jsx
@@ -0,0 +1,194 @@
+//Components
+import DistributedUptimeMap from "../Details/Components/DistributedUptimeMap";
+import Breadcrumbs from "../../../Components/Breadcrumbs";
+import { Stack, Typography } from "@mui/material";
+import DeviceTicker from "../Details/Components/DeviceTicker";
+import DistributedUptimeResponseChart from "../Details/Components/DistributedUptimeResponseChart";
+import NextExpectedCheck from "../Details/Components/NextExpectedCheck";
+import Footer from "../Details/Components/Footer";
+import StatBoxes from "../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";
+//Utils
+import { useTheme } from "@mui/material/styles";
+import { useState } from "react";
+import { useParams } from "react-router-dom";
+import { useSubscribeToDetails } from "../Details/Hooks/useSubscribeToDetails";
+import { useStatusPageFetchByUrl } from "./Hooks/useStatusPageFetchByUrl";
+import { useStatusPageDelete } from "../../StatusPage/Status/Hooks/useStatusPageDelete";
+import { useNavigate } from "react-router-dom";
+import { useLocation } from "react-router-dom";
+const DistributedUptimeStatus = () => {
+ const { url } = useParams();
+ const location = useLocation();
+ const isPublic = location.pathname.startsWith("/distributed-uptime/status/public");
+
+ // Local State
+ const [dateRange, setDateRange] = useState("day");
+ const [isDeleteOpen, setIsDeleteOpen] = useState(false);
+ // Utils
+ const theme = useTheme();
+ const navigate = useNavigate();
+ const [
+ statusPageIsLoading,
+ statusPageNetworkError,
+ statusPage,
+ monitorId,
+ isPublished,
+ ] = useStatusPageFetchByUrl({
+ url,
+ });
+
+ const [isLoading, networkError, connectionStatus, monitor, lastUpdateTrigger] =
+ useSubscribeToDetails({ 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",
+ };
+ }
+
+ // Done loading, a status page doesn't exist
+ if (!statusPageIsLoading && typeof statusPage === "undefined") {
+ return (
+
+
+
+ A public status page is not set up.
+
+ Please contact to your administrator
+
+
+ );
+ }
+
+ // Done loading, a status page exists but is not public
+ if (!statusPageIsLoading && statusPage.isPublished === false) {
+ return (
+
+
+ This status page is not public.
+
+
+ );
+ }
+
+ if (isLoading || statusPageIsLoading) {
+ return ;
+ }
+
+ if (networkError || statusPageNetworkError) {
+ return (
+
+
+ Network error
+
+ Please check your connection
+
+ );
+ }
+
+ if (
+ typeof statusPage === "undefined" ||
+ typeof monitor === "undefined" ||
+ monitor.totalChecks === 0
+ ) {
+ return (
+
+
+
+ There is no check history for this monitor yet.
+
+
+ );
+ }
+
+ return (
+
+ {!isPublic && }
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default DistributedUptimeStatus;
diff --git a/Client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx b/Client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx
index 3f1832889..6f030b2e5 100644
--- a/Client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx
+++ b/Client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx
@@ -126,7 +126,7 @@ const MonitorsTable = ({ shouldRender, monitors, isAdmin, handleActionMenuDelete
transition: "background-color .3s ease",
},
},
- onRowClick: (row) => openDetails(row.id),
+ onRowClick: (row) => openDetails(row._id),
}}
/>
);
diff --git a/Client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx b/Client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx
index 11733fc61..113f0bafc 100644
--- a/Client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx
+++ b/Client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx
@@ -14,9 +14,15 @@ const Controls = ({ isDeleteOpen, setIsDeleteOpen, isDeleting }) => {
const location = useLocation();
const currentPath = location.pathname;
const navigate = useNavigate();
+
if (currentPath === "/status/public") {
return null;
}
+
+ if (currentPath.startsWith("/distributed-uptime/status/public")) {
+ return null;
+ }
+
return (
{statusPage?.companyName}
diff --git a/Client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx b/Client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx
index ff7a18f97..7f4af1e0c 100644
--- a/Client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx
+++ b/Client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx
@@ -34,7 +34,7 @@ const MonitorsList = ({ monitors = [] }) => {
gap={theme.spacing(20)}
>
-
+
{
+const useStatusPageDelete = (fetchStatusPage, url = "/status/public") => {
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const { authToken } = useSelector((state) => state.auth);
-
const deleteStatusPage = async () => {
try {
setIsLoading(true);
- await networkService.deleteStatusPage({ authToken });
+ await networkService.deleteStatusPage({ authToken, url });
fetchStatusPage?.();
return true;
} catch (error) {
diff --git a/Client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx b/Client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx
index d2b2eae2a..013e32dd9 100644
--- a/Client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx
+++ b/Client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx
@@ -18,11 +18,13 @@ const useStatusPageFetch = (isCreate = false) => {
const response = await networkService.getStatusPage({ authToken });
if (!response?.data?.data) return;
const { statusPage, monitors } = response.data.data;
+
setStatusPage(statusPage);
const monitorsWithPercentage = monitors.map((monitor) =>
getMonitorWithPercentage(monitor, theme)
);
+
setMonitors(monitorsWithPercentage);
} catch (error) {
// If there is a 404, status page is not found
@@ -35,7 +37,7 @@ const useStatusPageFetch = (isCreate = false) => {
} finally {
setIsLoading(false);
}
- }, [authToken, theme]);
+ }, [authToken, theme, getMonitorWithPercentage]);
useEffect(() => {
if (isCreate === true) {
diff --git a/Client/src/Pages/StatusPage/Status/index.jsx b/Client/src/Pages/StatusPage/Status/index.jsx
index e27bc8184..25a794b28 100644
--- a/Client/src/Pages/StatusPage/Status/index.jsx
+++ b/Client/src/Pages/StatusPage/Status/index.jsx
@@ -32,6 +32,7 @@ const PublicStatus = () => {
const currentPath = location.pathname;
let sx = { paddingLeft: theme.spacing(20), paddingRight: theme.spacing(20) };
let link = undefined;
+
// Public status page
if (currentPath === "/status/public") {
sx = {
@@ -139,7 +140,6 @@ const PublicStatus = () => {
{link}