diff --git a/Client/src/Components/Charts/ChartBox/index.jsx b/Client/src/Components/Charts/ChartBox/index.jsx
new file mode 100644
index 000000000..eed3efd0d
--- /dev/null
+++ b/Client/src/Components/Charts/ChartBox/index.jsx
@@ -0,0 +1,90 @@
+import { Stack, Typography } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import IconBox from "../../IconBox";
+import PropTypes from "prop-types";
+
+const ChartBox = ({
+ children,
+ icon,
+ header,
+ height = "300px",
+ justifyContent = "space-between",
+ Legend,
+ borderRadiusRight = 4,
+}) => {
+ const theme = useTheme();
+ return (
+
+ span": {
+ color: theme.palette.primary.contrastText,
+ fontSize: 20,
+ "& span": {
+ opacity: 0.8,
+ marginLeft: 2,
+ fontSize: 15,
+ },
+ },
+
+ "& tspan, & text": {
+ fill: theme.palette.primary.contrastTextTertiary,
+ },
+ "& path": {
+ transition: "fill 300ms ease, stroke-width 400ms ease",
+ },
+ }}
+ >
+
+ {icon}
+ {header}
+
+ {children}
+
+ {Legend && Legend}
+
+ );
+};
+
+export default ChartBox;
+
+ChartBox.propTypes = {
+ children: PropTypes.node,
+ icon: PropTypes.node.isRequired,
+ header: PropTypes.string.isRequired,
+ height: PropTypes.string,
+};
diff --git a/Client/src/Components/Charts/LegendBox/index.jsx b/Client/src/Components/Charts/LegendBox/index.jsx
new file mode 100644
index 000000000..ac14669e3
--- /dev/null
+++ b/Client/src/Components/Charts/LegendBox/index.jsx
@@ -0,0 +1,42 @@
+import { Stack, Typography } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import IconBox from "../../IconBox";
+import PropTypes from "prop-types";
+
+const LegendBox = ({ children, icon, header, sx }) => {
+ const theme = useTheme();
+ return (
+
+
+ {icon}
+ {header}
+
+ {children}
+
+ );
+};
+
+LegendBox.propTypes = {
+ children: PropTypes.node,
+ icon: PropTypes.node,
+ header: PropTypes.string,
+};
+
+export default LegendBox;
diff --git a/Client/src/Components/Fallback/index.jsx b/Client/src/Components/Fallback/index.jsx
index 48d9e2198..e5029d276 100644
--- a/Client/src/Components/Fallback/index.jsx
+++ b/Client/src/Components/Fallback/index.jsx
@@ -32,12 +32,14 @@ const Fallback = ({ title, checks, link = "/", isAdmin, vowelStart = false }) =>
border={1}
borderColor={theme.palette.primary.lowContrast}
borderRadius={theme.shape.borderRadius}
- borderStyle="dashed"
backgroundColor={theme.palette.primary.main}
overflow="hidden"
+ sx={{
+ borderStyle: "dashed",
+ }}
>
@@ -68,7 +70,7 @@ const Fallback = ({ title, checks, link = "/", isAdmin, vowelStart = false }) =>
>
{vowelStart ? "An" : "A"} {title} is used to:
- {checks.map((check, index) => (
+ {checks?.map((check, index) => (
option.id}
+ getOptionKey={(option) => {
+ return option._id;
+ }}
renderOption={(props, option) => {
const { key, ...optionProps } = props;
return (
@@ -196,6 +198,7 @@ const Search = ({
};
Search.propTypes = {
+ label: PropTypes.string,
id: PropTypes.string,
multiple: PropTypes.bool,
options: PropTypes.array.isRequired,
diff --git a/Client/src/Pages/Uptime/Details/Components/ConfigButton/index.jsx b/Client/src/Components/MonitorStatusHeader/ConfigButton/index.jsx
similarity index 91%
rename from Client/src/Pages/Uptime/Details/Components/ConfigButton/index.jsx
rename to Client/src/Components/MonitorStatusHeader/ConfigButton/index.jsx
index 467655abf..fc6407f31 100644
--- a/Client/src/Pages/Uptime/Details/Components/ConfigButton/index.jsx
+++ b/Client/src/Components/MonitorStatusHeader/ConfigButton/index.jsx
@@ -1,7 +1,7 @@
import { Button, Box } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
-import SettingsIcon from "../../../../../assets/icons/settings-bold.svg?react";
+import SettingsIcon from "../../../assets/icons/settings-bold.svg?react";
import PropTypes from "prop-types";
const ConfigButton = ({ shouldRender, monitorId }) => {
const theme = useTheme();
diff --git a/Client/src/Pages/Uptime/Details/Components/MonitorHeader/index.jsx b/Client/src/Components/MonitorStatusHeader/index.jsx
similarity index 78%
rename from Client/src/Pages/Uptime/Details/Components/MonitorHeader/index.jsx
rename to Client/src/Components/MonitorStatusHeader/index.jsx
index 53d4c4dfa..6a7188775 100644
--- a/Client/src/Pages/Uptime/Details/Components/MonitorHeader/index.jsx
+++ b/Client/src/Components/MonitorStatusHeader/index.jsx
@@ -1,17 +1,16 @@
import { Stack, Typography } from "@mui/material";
-import PulseDot from "../../../../../Components/Animated/PulseDot";
-import Dot from "../../../../../Components/Dot";
+import PulseDot from "../Animated/PulseDot";
+import Dot from "../Dot";
import { useTheme } from "@emotion/react";
-import useUtils from "../../../Monitors/Hooks/useUtils";
-import { formatDurationRounded } from "../../../../../Utils/timeUtils";
-import ConfigButton from "../ConfigButton";
+import useUtils from "../../Pages/Uptime/Monitors/Hooks/useUtils";
+import { formatDurationRounded } from "../../Utils/timeUtils";
+import ConfigButton from "./ConfigButton";
import SkeletonLayout from "./skeleton";
import PropTypes from "prop-types";
const MonitorHeader = ({ shouldRender = true, isAdmin, monitor }) => {
const theme = useTheme();
const { statusColor, statusMsg, determineState } = useUtils();
- console.log(shouldRender);
if (!shouldRender) {
return ;
}
diff --git a/Client/src/Pages/Uptime/Details/Components/MonitorHeader/skeleton.jsx b/Client/src/Components/MonitorStatusHeader/skeleton.jsx
similarity index 100%
rename from Client/src/Pages/Uptime/Details/Components/MonitorHeader/skeleton.jsx
rename to Client/src/Components/MonitorStatusHeader/skeleton.jsx
diff --git a/Client/src/Components/StatusBoxes/index.jsx b/Client/src/Components/StatusBoxes/index.jsx
new file mode 100644
index 000000000..6e09d0cba
--- /dev/null
+++ b/Client/src/Components/StatusBoxes/index.jsx
@@ -0,0 +1,28 @@
+// Components
+import { Stack } from "@mui/material";
+import SkeletonLayout from "./skeleton";
+// Utils
+import { useTheme } from "@mui/material/styles";
+import PropTypes from "prop-types";
+const StatusBoxes = ({ shouldRender, children }) => {
+ const theme = useTheme();
+ if (!shouldRender) {
+ return ;
+ }
+
+ return (
+
+ {children}
+
+ );
+};
+
+StatusBoxes.propTypes = {
+ shouldRender: PropTypes.bool,
+ children: PropTypes.node,
+};
+
+export default StatusBoxes;
diff --git a/Client/src/Components/StatusBoxes/skeleton.jsx b/Client/src/Components/StatusBoxes/skeleton.jsx
new file mode 100644
index 000000000..b0f04ff3f
--- /dev/null
+++ b/Client/src/Components/StatusBoxes/skeleton.jsx
@@ -0,0 +1,31 @@
+import { Stack, Skeleton } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import PropTypes from "prop-types";
+const SkeletonLayout = ({ numBoxes }) => {
+ const theme = useTheme();
+ return (
+
+ {Array.from({ length: numBoxes }).map((_, index) => {
+ const width = `${100 / numBoxes}%`;
+ return (
+
+ );
+ })}
+
+ );
+};
+
+SkeletonLayout.propTypes = {
+ numBoxes: PropTypes.number,
+};
+
+export default SkeletonLayout;
diff --git a/Client/src/Pages/PageSpeed/Details/Charts/AreaChart.jsx b/Client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx
similarity index 95%
rename from Client/src/Pages/PageSpeed/Details/Charts/AreaChart.jsx
rename to Client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx
index a854eb094..40fec0ab6 100644
--- a/Client/src/Pages/PageSpeed/Details/Charts/AreaChart.jsx
+++ b/Client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx
@@ -11,7 +11,7 @@ import {
import { useTheme } from "@emotion/react";
import { useMemo, useState } from "react";
import { Box, Stack, Typography } from "@mui/material";
-import { formatDateWithTz } from "../../../../Utils/timeUtils";
+import { formatDateWithTz } from "../../../../../Utils/timeUtils";
import { useSelector } from "react-redux";
const config = {
@@ -211,10 +211,13 @@ CustomTick.propTypes = {
* @returns {JSX.Element} The area chart component.
*/
-const PagespeedDetailsAreaChart = ({ data, interval, metrics }) => {
+const PageSpeedAreaChart = ({ data, monitor, metrics }) => {
const theme = useTheme();
const [isHovered, setIsHovered] = useState(false);
- const memoizedData = useMemo(() => processDataWithGaps(data, interval), [data[0]]);
+ const memoizedData = useMemo(
+ () => processDataWithGaps(data, monitor.interval),
+ [data[0]]
+ );
const filteredConfig = Object.keys(config).reduce((result, key) => {
if (metrics[key]) {
@@ -310,7 +313,7 @@ const PagespeedDetailsAreaChart = ({ data, interval, metrics }) => {
);
};
-PagespeedDetailsAreaChart.propTypes = {
+PageSpeedAreaChart.propTypes = {
data: PropTypes.arrayOf(
PropTypes.shape({
createdAt: PropTypes.string.isRequired,
@@ -320,8 +323,8 @@ PagespeedDetailsAreaChart.propTypes = {
seo: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
})
).isRequired,
- interval: PropTypes.number.isRequired,
+ monitor: PropTypes.object.isRequired,
metrics: PropTypes.object,
};
-export default PagespeedDetailsAreaChart;
+export default PageSpeedAreaChart;
diff --git a/Client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx b/Client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx
new file mode 100644
index 000000000..1d3d5ddef
--- /dev/null
+++ b/Client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx
@@ -0,0 +1,56 @@
+import { Box, Typography, Divider } from "@mui/material";
+import Checkbox from "../../../../../Components/Inputs/Checkbox";
+import MetricsIcon from "../../../../../assets/icons/ruler-icon.svg?react";
+import LegendBox from "../../../../../Components/Charts/LegendBox";
+
+import { useTheme } from "@emotion/react";
+
+const AreaChartLegend = ({ metrics, handleMetrics }) => {
+ const theme = useTheme();
+ return (
+ }
+ header="Metrics"
+ >
+
+
+ Shown
+
+
+
+ handleMetrics("accessibility")}
+ />
+
+ handleMetrics("bestPractices")}
+ />
+
+ handleMetrics("performance")}
+ />
+
+ handleMetrics("seo")}
+ />
+
+
+ );
+};
+
+export default AreaChartLegend;
diff --git a/Client/src/Pages/PageSpeed/Details/Charts/PieChart.jsx b/Client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx
similarity index 99%
rename from Client/src/Pages/PageSpeed/Details/Charts/PieChart.jsx
rename to Client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx
index 193841e7e..aad697f9f 100644
--- a/Client/src/Pages/PageSpeed/Details/Charts/PieChart.jsx
+++ b/Client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx
@@ -115,6 +115,10 @@ const weights = {
const PieChart = ({ audits }) => {
const theme = useTheme();
+ const [highlightedItem, setHighLightedItem] = useState(null);
+ const [expand, setExpand] = useState(false);
+
+ if (typeof audits === "undefined") return null;
/**
* Retrieves color properties based on the performance value.
@@ -216,8 +220,6 @@ const PieChart = ({ audits }) => {
const pieData = getPieData(audits);
const colorMap = getColors(performance);
- const [highlightedItem, setHighLightedItem] = useState(null);
- const [expand, setExpand] = useState(false);
return (
setExpand(false)}>
{expand ? (
diff --git a/Client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx b/Client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx
new file mode 100644
index 000000000..c7612821f
--- /dev/null
+++ b/Client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx
@@ -0,0 +1,96 @@
+import { Stack, Box, Typography } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import LegendBox from "../../../../../Components/Charts/LegendBox";
+import SpeedometerIcon from "../../../../../assets/icons/speedometer-icon.svg?react";
+import PropTypes from "prop-types";
+
+const PieChartLegend = ({ audits }) => {
+ const theme = useTheme();
+
+ if (typeof audits === "undefined") return null;
+ return (
+ }
+ header="Performance metrics"
+ sx={{ flex: 1 }}
+ >
+ {Object.keys(audits).map((key) => {
+ if (key === "_id") return;
+
+ let audit = audits[key];
+ let score = audit.score * 100;
+ let bg =
+ score >= 90
+ ? theme.palette.success.main
+ : score >= 50
+ ? theme.palette.warning.main
+ : score >= 0
+ ? theme.palette.error.main
+ : theme.palette.tertiary.main;
+
+ // Find the position where the number ends and the unit begins
+ const match = audit.displayValue.match(/(\d+\.?\d*)\s*([a-zA-Z]+)/);
+ let value;
+ let unit;
+ if (match) {
+ value = match[1];
+ match[2] === "s" ? (unit = "seconds") : (unit = match[2]);
+ } else {
+ value = audit.displayValue;
+ }
+
+ return (
+
+
+
+ {audit.title}
+
+
+ {value}
+
+ {unit}
+
+
+
+
+
+ );
+ })}
+
+ );
+};
+
+PieChartLegend.propTypes = {
+ audits: PropTypes.object,
+};
+
+export default PieChartLegend;
diff --git a/Client/src/Pages/PageSpeed/Details/Components/EmptyView/index.jsx b/Client/src/Pages/PageSpeed/Details/Components/EmptyView/index.jsx
new file mode 100644
index 000000000..e69de29bb
diff --git a/Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx b/Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx
new file mode 100644
index 000000000..c818d04a3
--- /dev/null
+++ b/Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx
@@ -0,0 +1,59 @@
+import ChartBox from "../../../../../Components/Charts/ChartBox";
+import AreaChart from "../Charts/AreaChart";
+import AreaChartLegend from "../Charts/AreaChartLegend";
+import SkeletonLayout from "./skeleton";
+import ScoreIcon from "../../../../../assets/icons/monitor-graph-line.svg?react";
+import { Stack } from "@mui/material";
+import PropTypes from "prop-types";
+
+import { useTheme } from "@emotion/react";
+
+const PageSpeedAreaChart = ({ shouldRender, monitor, metrics, handleMetrics }) => {
+ const theme = useTheme();
+
+ if (!shouldRender) {
+ return ;
+ }
+
+ if (typeof monitor === "undefined") {
+ return null;
+ }
+
+ const data = monitor?.checks ? [...monitor.checks].reverse() : [];
+
+ return (
+
+ }
+ header="Score history"
+ height="100%"
+ borderRadiusRight={16}
+ Legend={
+
+ }
+ >
+
+
+
+ );
+};
+
+PageSpeedAreaChart.propTypes = {
+ shouldRender: PropTypes.bool,
+ monitor: PropTypes.object,
+ metrics: PropTypes.object,
+ handleMetrics: PropTypes.func,
+};
+
+export default PageSpeedAreaChart;
diff --git a/Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx b/Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx
new file mode 100644
index 000000000..b793908d4
--- /dev/null
+++ b/Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx
@@ -0,0 +1,17 @@
+import { Box, Skeleton } from "@mui/material";
+
+const SkeletonLayout = () => {
+ return (
+
+
+
+ );
+};
+
+export default SkeletonLayout;
diff --git a/Client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx b/Client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx
new file mode 100644
index 000000000..5bce18df6
--- /dev/null
+++ b/Client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx
@@ -0,0 +1,41 @@
+import StatusBoxes from "../../../../../Components/StatusBoxes";
+import StatBox from "../../../../../Components/StatBox";
+import { Typography } from "@mui/material";
+import { getHumanReadableDuration } from "../../../../../Utils/timeUtils";
+
+const PageSpeedStatusBoxes = ({ shouldRender, monitor }) => {
+ const { time: uptimeDuration, units: uptimeUnits } = getHumanReadableDuration(
+ monitor?.uptimeDuration
+ );
+
+ const { time: lastCheckTime, units: lastCheckUnits } = getHumanReadableDuration(
+ monitor?.lastChecked
+ );
+
+ return (
+
+
+ {uptimeDuration}
+ {uptimeUnits}
+ ago
+ >
+ }
+ />
+
+ {lastCheckTime}
+ {lastCheckUnits}
+ ago
+ >
+ }
+ />
+
+ );
+};
+
+export default PageSpeedStatusBoxes;
diff --git a/Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx b/Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx
new file mode 100644
index 000000000..2db40e126
--- /dev/null
+++ b/Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx
@@ -0,0 +1,55 @@
+import ChartBox from "../../../../../Components/Charts/ChartBox";
+import PerformanceIcon from "../../../../../assets/icons/performance-report.svg?react";
+import PieChart from "../Charts/PieChart";
+import { Typography } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import PieChartLegend from "../Charts/PieChartLegend";
+import SkeletonLayout from "./skeleton";
+const PerformanceReport = ({ shouldRender, audits }) => {
+ console.log("shouldRender", shouldRender);
+ const theme = useTheme();
+
+ if (!shouldRender) {
+ return ;
+ }
+
+ if (typeof audits === "undefined") {
+ return null;
+ }
+
+ return (
+ }
+ header="Performance report"
+ Legend={}
+ borderRadiusRight={16}
+ >
+
+
+ Values are estimated and may vary.{" "}
+
+ See calculator
+
+
+
+ );
+};
+
+export default PerformanceReport;
diff --git a/Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx b/Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx
new file mode 100644
index 000000000..b793908d4
--- /dev/null
+++ b/Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx
@@ -0,0 +1,17 @@
+import { Box, Skeleton } from "@mui/material";
+
+const SkeletonLayout = () => {
+ return (
+
+
+
+ );
+};
+
+export default SkeletonLayout;
diff --git a/Client/src/Pages/PageSpeed/Details/Hooks/useMonitorFetch.jsx b/Client/src/Pages/PageSpeed/Details/Hooks/useMonitorFetch.jsx
new file mode 100644
index 000000000..340d4cf5e
--- /dev/null
+++ b/Client/src/Pages/PageSpeed/Details/Hooks/useMonitorFetch.jsx
@@ -0,0 +1,40 @@
+import { useEffect, useState } from "react";
+import { networkService } from "../../../../main";
+import { logger } from "../../../../Utils/Logger";
+import { useNavigate } from "react-router-dom";
+const useMonitorFetch = ({ authToken, monitorId }) => {
+ const navigate = useNavigate();
+
+ const [monitor, setMonitor] = useState(undefined);
+ const [audits, setAudits] = useState(undefined);
+ const [isLoading, setIsLoading] = useState(true);
+ useEffect(() => {
+ const fetchMonitor = async () => {
+ try {
+ setIsLoading(true);
+ const res = await networkService.getStatsByMonitorId({
+ authToken: authToken,
+ monitorId: monitorId,
+ sortOrder: "desc",
+ limit: 50,
+ dateRange: "day",
+ numToDisplay: null,
+ normalize: null,
+ });
+ setMonitor(res?.data?.data ?? undefined);
+ setAudits(res?.data?.data?.checks?.[0]?.audits ?? undefined);
+ } catch (error) {
+ logger.error(logger);
+ navigate("/not-found", { replace: true });
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ fetchMonitor();
+ }, [authToken, monitorId, navigate]);
+
+ return { monitor, audits, isLoading };
+};
+
+export { useMonitorFetch };
diff --git a/Client/src/Pages/PageSpeed/Details/index.css b/Client/src/Pages/PageSpeed/Details/index.css
deleted file mode 100644
index 0d0699b3d..000000000
--- a/Client/src/Pages/PageSpeed/Details/index.css
+++ /dev/null
@@ -1,18 +0,0 @@
-.page-speed-details button.MuiButtonBase-root {
- min-height: 34px;
-}
-
-.page-speed-details .MuiPieArcLabel-root {
- font-size: var(--env-var-font-size-small-plus);
- transition: fill 300ms ease;
-}
-.page-speed-details .MuiPieArcLabel-faded {
- fill: rgba(0, 0, 0, 0.3);
-}
-.page-speed-details .pie-label,
-.page-speed-details .pie-value-label {
- transition: all 400ms ease;
-}
-.page-speed-details .MuiPieArc-root {
- stroke: inherit;
-}
diff --git a/Client/src/Pages/PageSpeed/Details/index.jsx b/Client/src/Pages/PageSpeed/Details/index.jsx
index 4c4bceb1b..43eb0ed7b 100644
--- a/Client/src/Pages/PageSpeed/Details/index.jsx
+++ b/Client/src/Pages/PageSpeed/Details/index.jsx
@@ -1,75 +1,37 @@
-import PropTypes from "prop-types";
-import { Box, Button, Divider, Stack, Tooltip, Typography } from "@mui/material";
-import { useEffect, useState } from "react";
-import { useTheme } from "@emotion/react";
-import { useNavigate, useParams } from "react-router-dom";
-import { useSelector } from "react-redux";
-import { formatDurationRounded, formatDurationSplit } from "../../../Utils/timeUtils";
-import { ChartBox } from "./styled";
-import { logger } from "../../../Utils/Logger";
-import { networkService } from "../../../main";
-import SkeletonLayout from "./skeleton";
-import SettingsIcon from "../../../assets/icons/settings-bold.svg?react";
-import SpedometerIcon from "../../../assets/icons/spedometer-icon.svg?react";
-import MetricsIcon from "../../../assets/icons/ruler-icon.svg?react";
-import ScoreIcon from "../../../assets/icons/monitor-graph-line.svg?react";
-import PerformanceIcon from "../../../assets/icons/performance-report.svg?react";
+// Components
+import { Stack, Typography } from "@mui/material";
import Breadcrumbs from "../../../Components/Breadcrumbs";
-import PulseDot from "../../../Components/Animated/PulseDot";
-import PagespeedDetailsAreaChart from "./Charts/AreaChart";
-import Checkbox from "../../../Components/Inputs/Checkbox";
-import PieChart from "./Charts/PieChart";
-import useUtils from "../../Uptime/Monitors/Hooks/useUtils";
-import "./index.css";
+import MonitorStatusHeader from "../../../Components/MonitorStatusHeader";
+import PageSpeedStatusBoxes from "./Components/PageSpeedStatusBoxes";
+import PageSpeedAreaChart from "./Components/PageSpeedAreaChart";
+import PerformanceReport from "./Components/PerformanceReport";
+import Fallback from "../../../Components/Fallback";
+// Utils
+import { useTheme } from "@emotion/react";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
-import StatBox from "../../../Components/StatBox";
-import IconBox from "../../../Components/IconBox";
+import { useParams } from "react-router-dom";
+import { useSelector } from "react-redux";
+import { useMonitorFetch } from "./Hooks/useMonitorFetch";
+import { useState } from "react";
+// Constants
+const BREADCRUMBS = [
+ { name: "pagespeed", path: "/pagespeed" },
+ { name: "details", path: `` },
+ // { name: "details", path: `/pagespeed/${monitorId}` }, // Not needed?
+];
const PageSpeedDetails = () => {
const theme = useTheme();
- const navigate = useNavigate();
const isAdmin = useIsAdmin();
- const { statusColor, pagespeedStatusMsg, determineState } = useUtils();
- const [monitor, setMonitor] = useState({});
- const [audits, setAudits] = useState({});
const { monitorId } = useParams();
const { authToken } = useSelector((state) => state.auth);
- useEffect(() => {
- const fetchMonitor = async () => {
- try {
- const res = await networkService.getStatsByMonitorId({
- authToken: authToken,
- monitorId: monitorId,
- sortOrder: "desc",
- limit: 50,
- dateRange: "day",
- numToDisplay: null,
- normalize: null,
- });
- setMonitor(res?.data?.data ?? {});
- setAudits(res?.data?.data?.checks?.[0]?.audits ?? {});
- } catch (error) {
- logger.error(logger);
- navigate("/not-found", { replace: true });
- }
- };
+ const { monitor, audits, isLoading } = useMonitorFetch({
+ authToken,
+ monitorId,
+ });
- fetchMonitor();
- }, [monitorId, authToken, navigate]);
-
- let loading = Object.keys(monitor).length === 0;
- const data = monitor?.checks ? [...monitor.checks].reverse() : [];
-
- const splitDuration = (duration) => {
- const { time, format } = formatDurationSplit(duration);
- return (
- <>
- {time}
- {format}
- >
- );
- };
+ console.log(monitor, audits, isLoading);
const [metrics, setMetrics] = useState({
accessibility: true,
@@ -77,371 +39,41 @@ const PageSpeedDetails = () => {
performance: true,
seo: true,
});
+
const handleMetrics = (id) => {
setMetrics((prev) => ({ ...prev, [id]: !prev[id] }));
};
return (
-
- {loading ? (
-
- ) : (
- <>
-
-
-
-
- {monitor.name}
-
-
-
-
-
-
-
-
- {monitor?.url}
-
-
- Checking every {formatDurationRounded(monitor?.interval)}.
-
-
-
- {isAdmin && (
-
- )}
-
-
-
- {splitDuration(monitor?.uptimeDuration)}
- ago
- >
- }
- />
-
- {splitDuration(monitor?.lastChecked)}
- ago
- >
- }
- />
-
-
-
- Showing statistics for past 24 hours.
-
-
-
-
-
-
- Score history
-
-
-
-
-
-
-
-
-
- Metrics
-
-
-
-
- Shown
-
-
-
- handleMetrics("accessibility")}
- />
-
- handleMetrics("bestPractices")}
- />
-
- handleMetrics("performance")}
- />
-
- handleMetrics("seo")}
- />
-
-
-
-
-
-
-
-
-
-
- Performance report
-
-
-
-
- Values are estimated and may vary.{" "}
-
- See calculator
-
-
-
-
-
-
-
-
- Performance Metrics
-
-
- {Object.keys(audits).map((key) => {
- if (key === "_id") return;
-
- let audit = audits[key];
- let score = audit.score * 100;
- let bg =
- score >= 90
- ? theme.palette.success.main
- : score >= 50
- ? theme.palette.warning.main
- : score >= 0
- ? theme.palette.error.contrastText
- : theme.palette.tertiary.main;
-
- // Find the position where the number ends and the unit begins
- const match = audit.displayValue.match(/(\d+\.?\d*)\s*([a-zA-Z]+)/);
- let value;
- let unit;
- if (match) {
- value = match[1];
- match[2] === "s" ? (unit = "seconds") : (unit = match[2]);
- } else {
- value = audit.displayValue;
- }
-
- return (
-
-
-
- {audit.title}
-
-
- {value}
-
- {unit}
-
-
-
-
-
- );
- })}
-
-
-
- >
- )}
+
+
+
+
+
+ Showing statistics for past 24 hours.
+
+
+
);
};
-PageSpeedDetails.propTypes = {
- isAdmin: PropTypes.bool,
- push: PropTypes.func,
-};
-
export default PageSpeedDetails;
diff --git a/Client/src/Pages/PageSpeed/Details/skeleton.jsx b/Client/src/Pages/PageSpeed/Details/skeleton.jsx
deleted file mode 100644
index 1d55a5039..000000000
--- a/Client/src/Pages/PageSpeed/Details/skeleton.jsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import { Box, Skeleton, Stack } from "@mui/material";
-
-/**
- * Renders a skeleton layout.
- *
- * @returns {JSX.Element}
- */
-const SkeletonLayout = () => {
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-};
-
-export default SkeletonLayout;
diff --git a/Client/src/Pages/PageSpeed/Details/styled.jsx b/Client/src/Pages/PageSpeed/Details/styled.jsx
deleted file mode 100644
index 291d0c6f2..000000000
--- a/Client/src/Pages/PageSpeed/Details/styled.jsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import { Stack, styled } from "@mui/material";
-
-export const ChartBox = styled(Stack)(({ theme }) => ({
- display: "grid",
- minHeight: 300,
- minWidth: 250,
- border: 1,
- borderStyle: "solid",
- borderColor: theme.palette.primary.lowContrast,
- borderRadius: 4,
- borderTopRightRadius: 16,
- borderBottomRightRadius: 16,
- backgroundColor: theme.palette.primary.main,
- "& h2": {
- color: theme.palette.primary.contrastTextSecondary,
- fontSize: 15,
- fontWeight: 500,
- },
- "& p": { color: theme.palette.primary.contrastTextTertiary },
- "& > :nth-of-type(1)": {
- gridColumn: 1,
- gridRow: 1,
- height: "fit-content",
- paddingTop: theme.spacing(8),
- paddingLeft: theme.spacing(8),
- },
- "& > :nth-of-type(2)": { gridColumn: 1, gridRow: 2 },
- "& > :nth-of-type(3)": {
- gridColumn: 2,
- gridRow: "span 2",
- padding: theme.spacing(8),
- borderLeft: 1,
- borderLeftStyle: "solid",
- borderLeftColor: theme.palette.primary.lowContrast,
- borderRadius: 16,
- backgroundColor: theme.palette.primary.main,
- background: `linear-gradient(325deg, ${theme.palette.tertiary.main} 20%, ${theme.palette.primary.main} 45%)`,
- },
- "& path": {
- transition: "stroke-width 400ms ease",
- },
-}));
diff --git a/Client/src/Pages/PageSpeed/Monitors/index.jsx b/Client/src/Pages/PageSpeed/Monitors/index.jsx
index 228e522ff..e921bc15a 100644
--- a/Client/src/Pages/PageSpeed/Monitors/index.jsx
+++ b/Client/src/Pages/PageSpeed/Monitors/index.jsx
@@ -1,6 +1,6 @@
// Components
import Breadcrumbs from "../../../Components/Breadcrumbs";
-import { Stack, Box } from "@mui/material";
+import { Stack } from "@mui/material";
import CreateMonitorHeader from "../../../Components/CreateMonitorHeader";
import MonitorCountHeader from "../../../Components/MonitorCountHeader";
import MonitorGrid from "./Components/MonitorGrid";
diff --git a/Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx b/Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx
index d9e581267..5565529fb 100644
--- a/Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx
+++ b/Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx
@@ -1,6 +1,6 @@
// Components
import { Stack, Typography, Box } from "@mui/material";
-import ChartBox from "../Charts/ChartBox";
+import ChartBox from "../../../../../Components/Charts/ChartBox";
import UptimeIcon from "../../../../../assets/icons/uptime-icon.svg?react";
import IncidentsIcon from "../../../../../assets/icons/incidents.svg?react";
import AverageResponseIcon from "../../../../../assets/icons/average-response-icon.svg?react";
@@ -40,7 +40,10 @@ const ChartBoxes = ({
icon={}
header="Uptime"
>
-
+
Total Checks
@@ -129,6 +132,7 @@ const ChartBoxes = ({
export default ChartBoxes;
ChartBoxes.propTypes = {
+ shouldRender: PropTypes.bool,
monitor: PropTypes.object.isRequired,
dateRange: PropTypes.string.isRequired,
uiTimezone: PropTypes.string.isRequired,
diff --git a/Client/src/Pages/Uptime/Details/Components/Charts/ChartBox.jsx b/Client/src/Pages/Uptime/Details/Components/Charts/ChartBox.jsx
deleted file mode 100644
index 3d4d73421..000000000
--- a/Client/src/Pages/Uptime/Details/Components/Charts/ChartBox.jsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import { Stack, Typography } from "@mui/material";
-import { useTheme } from "@emotion/react";
-import IconBox from "../../../../../Components/IconBox";
-import PropTypes from "prop-types";
-const ChartBox = ({ children, icon, header, height = "300px" }) => {
- const theme = useTheme();
- return (
- span": {
- color: theme.palette.primary.contrastText,
- fontSize: 20,
- "& span": {
- opacity: 0.8,
- marginLeft: 2,
- fontSize: 15,
- },
- },
- "& .MuiStack-root": {
- flexDirection: "row",
- gap: theme.spacing(6),
- },
- "& .MuiStack-root:first-of-type": {
- alignItems: "center",
- },
- "& tspan, & text": {
- fill: theme.palette.primary.contrastTextTertiary,
- },
- "& path": {
- transition: "fill 300ms ease, stroke-width 400ms ease",
- },
- }}
- >
-
- {icon}
- {header}
-
-
- {children}
-
- );
-};
-
-export default ChartBox;
-
-ChartBox.propTypes = {
- children: PropTypes.node,
- icon: PropTypes.node.isRequired,
- header: PropTypes.string.isRequired,
- height: PropTypes.string,
-};
diff --git a/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx b/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx
index 79e9473ef..8be1125c2 100644
--- a/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx
+++ b/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx
@@ -1,4 +1,4 @@
-import ChartBox from "./ChartBox";
+import ChartBox from "../../../../../Components/Charts/ChartBox";
import MonitorDetailsAreaChart from "../../../../../Components/Charts/MonitorDetailsAreaChart";
import ResponseTimeIcon from "../../../../../assets/icons/response-time-icon.svg?react";
import SkeletonLayout from "./ResponseTimeChartSkeleton";
diff --git a/Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx b/Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx
index 56ea8eca7..1f5a19203 100644
--- a/Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx
+++ b/Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx
@@ -1,4 +1,4 @@
-import ChartBox from "../Charts/ChartBox";
+import ChartBox from "../../../../../Components/Charts/ChartBox";
import PropTypes from "prop-types";
import HistoryIcon from "../../../../../assets/icons/history-icon.svg?react";
import Table from "../../../../../Components/Table";
diff --git a/Client/src/Pages/Uptime/Details/Components/StatusBoxes/skeleton.jsx b/Client/src/Pages/Uptime/Details/Components/StatusBoxes/skeleton.jsx
deleted file mode 100644
index 993b410c8..000000000
--- a/Client/src/Pages/Uptime/Details/Components/StatusBoxes/skeleton.jsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { Stack, Skeleton } from "@mui/material";
-import { useTheme } from "@emotion/react";
-const SkeletonLayout = () => {
- const theme = useTheme();
- return (
-
-
-
-
-
- );
-};
-
-export default SkeletonLayout;
diff --git a/Client/src/Pages/Uptime/Details/Components/StatusBoxes/index.jsx b/Client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx
similarity index 73%
rename from Client/src/Pages/Uptime/Details/Components/StatusBoxes/index.jsx
rename to Client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx
index 3f483889b..ad4620396 100644
--- a/Client/src/Pages/Uptime/Details/Components/StatusBoxes/index.jsx
+++ b/Client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx
@@ -1,20 +1,13 @@
-// Components
-import { Stack, Typography } from "@mui/material";
+import StatusBoxes from "../../../../../Components/StatusBoxes";
import StatBox from "../../../../../Components/StatBox";
-import SkeletonLayout from "./skeleton";
-// Utils
-import { useTheme } from "@mui/material/styles";
-import useUtils from "../../../Monitors/Hooks/useUtils";
import { getHumanReadableDuration } from "../../../../../Utils/timeUtils";
-import PropTypes from "prop-types";
-const StatusBoxes = ({ shouldRender, monitor, certificateExpiry }) => {
- // Utils
+import { useTheme } from "@mui/material/styles";
+import { Typography } from "@mui/material";
+import useUtils from "../../../Monitors/Hooks/useUtils";
+
+const UptimeStatusBoxes = ({ shouldRender, monitor, certificateExpiry }) => {
const theme = useTheme();
const { determineState } = useUtils();
-
- if (!shouldRender) {
- return ;
- }
const { time: streakTime, units: streakUnits } = getHumanReadableDuration(
monitor?.uptimeStreak
);
@@ -22,12 +15,8 @@ const StatusBoxes = ({ shouldRender, monitor, certificateExpiry }) => {
const { time: lastCheckTime, units: lastCheckUnits } = getHumanReadableDuration(
monitor?.timeSinceLastCheck
);
-
return (
-
+
{
}
/>
-
+
);
};
-StatusBoxes.propTypes = {
- shouldRender: PropTypes.bool,
- monitor: PropTypes.object,
- certificateExpiry: PropTypes.string,
-};
-
-export default StatusBoxes;
+export default UptimeStatusBoxes;
diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx
index 81265eb56..33583b0cb 100644
--- a/Client/src/Pages/Uptime/Details/index.jsx
+++ b/Client/src/Pages/Uptime/Details/index.jsx
@@ -1,11 +1,11 @@
// Components
import Breadcrumbs from "../../../Components/Breadcrumbs";
-import MonitorHeader from "./Components/MonitorHeader";
-import StatusBoxes from "./Components/StatusBoxes";
+import MonitorStatusHeader from "../../../Components/MonitorStatusHeader";
import TimeFramePicker from "./Components/TimeFramePicker";
import ChartBoxes from "./Components/ChartBoxes";
import ResponseTimeChart from "./Components/Charts/ResponseTimeChart";
import ResponseTable from "./Components/ResponseTable";
+import UptimeStatusBoxes from "./Components/UptimeStatusBoxes";
// MUI Components
import { Stack } from "@mui/material";
@@ -80,12 +80,12 @@ const UptimeDetails = () => {
return (
-
- {
flex={1}
py={theme.spacing(8)}
>
-
+ Uptime monitors
+
-
+
{
});
const totalMonitors = monitorsSummary?.totalMonitors ?? 0;
- if (!isLoading && monitors?.length === 0) {
+ if (!isLoading && !monitorsAreLoading && monitors?.length === 0) {
return (