From db66ba849b264e99e3a2696e12efb01f5e3ef90d Mon Sep 17 00:00:00 2001
From: Daniel C <122758391+danielcj2@users.noreply.github.com>
Date: Sun, 28 Jul 2024 16:51:47 -0400
Subject: [PATCH] Page Speed (#470)
* Added page speed route and fallback UI
* Added first batch of components and route to create a page-speed monitor
* Added basic styling
* Added checkboxes and removed old component
* Added monitor card and some styling
* Moved time functions to separate file and added formatDate function
* Added status label
* Changed sidebar item priority and small styling fix
* Small styling changes
---
Client/src/App.jsx | 10 ++
Client/src/Components/Checkbox/Checkbox.css | 36 -----
Client/src/Components/Checkbox/Checkbox.jsx | 13 --
.../Checkbox/CustomizableCheckbox.jsx | 24 ---
Client/src/Components/Fallback/index.jsx | 2 +-
.../src/Components/Inputs/Checkbox/index.css | 1 -
Client/src/Components/Inputs/Field/index.jsx | 2 +-
Client/src/Components/Sidebar/index.jsx | 2 +-
Client/src/Pages/Integrations/index.css | 6 +
Client/src/Pages/Integrations/index.jsx | 12 +-
Client/src/Pages/Monitors/Configure/index.jsx | 28 +---
Client/src/Pages/Monitors/Details/index.jsx | 46 +-----
.../Pages/PageSpeed/CreatePageSpeed/index.css | 49 ++++++
.../Pages/PageSpeed/CreatePageSpeed/index.jsx | 106 ++++++++++++
Client/src/Pages/PageSpeed/index.css | 30 ++++
Client/src/Pages/PageSpeed/index.jsx | 153 ++++++++++++++++++
Client/src/Utils/timeUtils.js | 59 +++++++
17 files changed, 423 insertions(+), 156 deletions(-)
delete mode 100644 Client/src/Components/Checkbox/Checkbox.css
delete mode 100644 Client/src/Components/Checkbox/Checkbox.jsx
delete mode 100644 Client/src/Components/Checkbox/CustomizableCheckbox.jsx
create mode 100644 Client/src/Pages/PageSpeed/CreatePageSpeed/index.css
create mode 100644 Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx
create mode 100644 Client/src/Pages/PageSpeed/index.css
create mode 100644 Client/src/Pages/PageSpeed/index.jsx
create mode 100644 Client/src/Utils/timeUtils.js
diff --git a/Client/src/App.jsx b/Client/src/App.jsx
index 1443be30c..a5b649e8b 100644
--- a/Client/src/App.jsx
+++ b/Client/src/App.jsx
@@ -22,6 +22,8 @@ import Details from "./Pages/Monitors/Details";
import Maintenance from "./Pages/Maintenance";
import withAdminCheck from "./HOC/withAdminCheck";
import Configure from "./Pages/Monitors/Configure";
+import PageSpeed from "./Pages/PageSpeed";
+import CreatePageSpeed from "./Pages/PageSpeed/CreatePageSpeed";
function App() {
const AdminCheckedRegister = withAdminCheck(Register);
@@ -83,6 +85,14 @@ function App() {
path="account/team"
element={}
/>
+ }
+ />
+ }
+ />
} />
diff --git a/Client/src/Components/Checkbox/Checkbox.css b/Client/src/Components/Checkbox/Checkbox.css
deleted file mode 100644
index e78fff6da..000000000
--- a/Client/src/Components/Checkbox/Checkbox.css
+++ /dev/null
@@ -1,36 +0,0 @@
-.checkbox-holder {
- height: var(--env-var-img-width-1);
- display: flex;
- align-items: center;
-}
-
-.checkbox {
- width: var(--env-var-font-size-medium);
- height: var(--env-var-font-size-medium);
- margin: 0;
- padding: 0;
- cursor: pointer;
-}
-
-.custom-checkbox {
- width: var(--env-var-img-width-1);
- height: var(--env-var-img-width-1);
- margin: 0;
- padding: 0;
- cursor: pointer;
-}
-
-.checkbox-label {
- margin-left: 5px;
- font-size: var(--env-var-font-size-medium);
- color: var(--env-var-color-5);
- font-weight: 600;
- cursor: pointer;
-}
-
-.custom-checkbox-label {
- margin-left: var(--env-var-font-size-large);
- font-size: var(--env-var-font-size-medium);
- color: var(--env-var-color-5);
- font-weight: 500;
-}
diff --git a/Client/src/Components/Checkbox/Checkbox.jsx b/Client/src/Components/Checkbox/Checkbox.jsx
deleted file mode 100644
index 228091401..000000000
--- a/Client/src/Components/Checkbox/Checkbox.jsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import React from "react";
-import "./Checkbox.css";
-
-const CheckBox = () => {
- return (
-
-
-
-
- );
-};
-
-export default CheckBox;
diff --git a/Client/src/Components/Checkbox/CustomizableCheckbox.jsx b/Client/src/Components/Checkbox/CustomizableCheckbox.jsx
deleted file mode 100644
index e39ea134b..000000000
--- a/Client/src/Components/Checkbox/CustomizableCheckbox.jsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from "react";
-import "./Checkbox.css";
-import PropsTypes from "prop-types";
-
-const CustomizableCheckbox = (props) => {
- return (
-
-
-
-
- );
-};
-
-CustomizableCheckbox.propsTypes = {
- title: PropsTypes.string.isRequired,
-};
-
-export default CustomizableCheckbox;
diff --git a/Client/src/Components/Fallback/index.jsx b/Client/src/Components/Fallback/index.jsx
index 943c3aa5e..164753104 100644
--- a/Client/src/Components/Fallback/index.jsx
+++ b/Client/src/Components/Fallback/index.jsx
@@ -29,7 +29,7 @@ const Fallback = ({ title, checks, link = "/" }) => {
gap={theme.gap.xl}
>
-
+
A {title} is used to:
diff --git a/Client/src/Components/Inputs/Checkbox/index.css b/Client/src/Components/Inputs/Checkbox/index.css
index 320c75f34..0d2285955 100644
--- a/Client/src/Components/Inputs/Checkbox/index.css
+++ b/Client/src/Components/Inputs/Checkbox/index.css
@@ -7,7 +7,6 @@
.checkbox-wrapper span.MuiTypography-root {
font-size: var(--env-var-font-size-medium);
color: var(--env-var-color-2);
- line-height: 1;
}
.checkbox-wrapper .MuiButtonBase-root {
margin-right: var(--env-var-spacing-1);
diff --git a/Client/src/Components/Inputs/Field/index.jsx b/Client/src/Components/Inputs/Field/index.jsx
index 3c09ef434..acf6877be 100644
--- a/Client/src/Components/Inputs/Field/index.jsx
+++ b/Client/src/Components/Inputs/Field/index.jsx
@@ -94,7 +94,7 @@ const Field = ({
borderRight: `solid 1px ${theme.palette.section.borderColor}`,
}}
>
-
+
{https ? "https" : "http"}://
diff --git a/Client/src/Components/Sidebar/index.jsx b/Client/src/Components/Sidebar/index.jsx
index 41dd734f0..020831313 100644
--- a/Client/src/Components/Sidebar/index.jsx
+++ b/Client/src/Components/Sidebar/index.jsx
@@ -25,9 +25,9 @@ const menu = [
{ name: "Monitors", path: "monitors", icon: },
{ name: "Incidents", path: "incidents", icon: },
{ name: "Status pages", path: "status", icon: },
- { name: "Integrations", path: "integrations", icon: },
{ name: "Maintenance", path: "maintenance", icon: },
{ name: "Page speed", path: "page-speed", icon: },
+ { name: "Integrations", path: "integrations", icon: },
{ name: "Support", path: "support", icon: },
{ name: "Settings", path: "settings", icon: },
];
diff --git a/Client/src/Pages/Integrations/index.css b/Client/src/Pages/Integrations/index.css
index ea444bc4a..c05b44212 100644
--- a/Client/src/Pages/Integrations/index.css
+++ b/Client/src/Pages/Integrations/index.css
@@ -12,3 +12,9 @@
.integrations button {
height: var(--env-var-height-2);
}
+
+.integrations .MuiGrid-item > .MuiStack-root {
+ border: solid 1px var(--env-var-color-6);
+ border-radius: var(--env-var-radius-1);
+ background-color: var(--env-var-color-8);
+}
diff --git a/Client/src/Pages/Integrations/index.jsx b/Client/src/Pages/Integrations/index.jsx
index 3ccf96585..376ef6657 100644
--- a/Client/src/Pages/Integrations/index.jsx
+++ b/Client/src/Pages/Integrations/index.jsx
@@ -23,11 +23,7 @@ const IntegrationsComponent = ({ url, header, info, onClick }) => {
direction="row"
justifyContent="space-between"
gap={theme.gap.large}
- p={theme.gap.large}
- border={1}
- borderColor={theme.palette.divider}
- borderRadius={`${theme.shape.borderRadius}px`}
- sx={{ backgroundColor: theme.palette.otherColors.white }}
+ p={theme.gap.ml}
>
{
return (
Integrations
- Connect Uptime Genie to your favorite service
-
+
+ Connect Uptime Genie to your favorite service
+
+
{integrations.map((integration, index) => (
{
- const seconds = Math.floor(ms / 1000);
- const minutes = Math.floor(seconds / 60);
- const hours = Math.floor(minutes / 60);
- const days = Math.floor(hours / 24);
-
- let time = "";
- if (days > 0) {
- time += `${days} day${days !== 1 ? "s" : ""}`;
- return time;
- }
- if (hours > 0) {
- time += `${hours} hour${hours !== 1 ? "s" : ""}`;
- return time;
- }
- if (minutes > 0) {
- time += `${minutes} minute${minutes !== 1 ? "s" : ""}`;
- return time;
- }
- if (seconds > 0) {
- time += `${seconds} second${seconds !== 1 ? "s" : ""}`;
- return time;
- }
-
- return time;
-};
+import { formatDurationRounded } from "../../../Utils/timeUtils";
/**
* Helper function to get duration since last check
diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx
index 18ff56e6a..f233eee77 100644
--- a/Client/src/Pages/Monitors/Details/index.jsx
+++ b/Client/src/Pages/Monitors/Details/index.jsx
@@ -13,53 +13,9 @@ import WestRoundedIcon from "@mui/icons-material/WestRounded";
import GreenCheck from "../../../assets/icons/checkbox-green.svg?react";
import RedCheck from "../../../assets/icons/checkbox-red.svg?react";
import SettingsIcon from "../../../assets/icons/settings.svg?react";
+import { formatDuration, formatDurationRounded } from "../../../Utils/timeUtils";
import "./index.css";
-const formatDuration = (ms) => {
- const seconds = Math.floor(ms / 1000);
- const minutes = Math.floor(seconds / 60);
- const hours = Math.floor(minutes / 60);
- const days = Math.floor(hours / 24);
-
- let dateStr = "";
-
- days && (dateStr += `${days}d `);
- hours && (dateStr += `${hours % 24}h `);
- minutes && (dateStr += `${minutes % 60}m `);
- seconds && (dateStr += `${seconds % 60}s `);
-
- dateStr === "" && (dateStr = "0s");
-
- return dateStr;
-};
-
-const formatDurationRounded = (ms) => {
- const seconds = Math.floor(ms / 1000);
- const minutes = Math.floor(seconds / 60);
- const hours = Math.floor(minutes / 60);
- const days = Math.floor(hours / 24);
-
- let time = "";
- if (days > 0) {
- time += `${days} day${days !== 1 ? "s" : ""}`;
- return time;
- }
- if (hours > 0) {
- time += `${hours} hour${hours !== 1 ? "s" : ""}`;
- return time;
- }
- if (minutes > 0) {
- time += `${minutes} minute${minutes !== 1 ? "s" : ""}`;
- return time;
- }
- if (seconds > 0) {
- time += `${seconds} second${seconds !== 1 ? "s" : ""}`;
- return time;
- }
-
- return time;
-};
-
const StatBox = ({ title, value }) => {
return (
diff --git a/Client/src/Pages/PageSpeed/CreatePageSpeed/index.css b/Client/src/Pages/PageSpeed/CreatePageSpeed/index.css
new file mode 100644
index 000000000..1ae450362
--- /dev/null
+++ b/Client/src/Pages/PageSpeed/CreatePageSpeed/index.css
@@ -0,0 +1,49 @@
+.create-page-speed > .MuiStack-root {
+ position: relative;
+ margin: auto;
+ margin-top: var(--env-var-default-2);
+ background-color: var(--env-var-color-8);
+ width: 100%;
+ max-width: 500px;
+ box-shadow: var(--env-var-shadow-1);
+ border-radius: var(--env-var-radius-1);
+ padding: var(--env-var-default-2);
+}
+.create-page-speed h1.MuiTypography-root,
+.create-page-speed h2.MuiTypography-root,
+.create-page-speed p.MuiTypography-root {
+ color: var(--env-var-color-5);
+}
+.create-page-speed h1.MuiTypography-root,
+.create-page-speed h2.MuiTypography-root {
+ font-weight: 600;
+}
+.create-page-speed h2.MuiTypography-root {
+ font-size: var(--env-var-font-size-medium-plus);
+}
+.create-page-speed p.MuiTypography-root,
+.create-page-speed button {
+ font-size: var(--env-var-font-size-medium);
+}
+.create-page-speed .MuiBox-root > .field + p.MuiTypography-root {
+ font-size: var(--env-var-font-size-small-plus);
+ opacity: 0.6;
+}
+.create-page-speed .checkbox-wrapper {
+ margin-bottom: 1px;
+}
+
+.create-page-speed button:not(.MuiIconButton-root) {
+ height: var(--env-var-height-2);
+}
+.create-page-speed .MuiIconButton-root {
+ position: absolute;
+ top: 0;
+ right: 0;
+ margin: var(--env-var-spacing-1);
+}
+
+body:has(.create-page-speed) .select-dropdown .MuiMenuItem-root,
+.create-page-speed .select-wrapper .select-component > .MuiSelect-select {
+ text-transform: lowercase;
+}
diff --git a/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx b/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx
new file mode 100644
index 000000000..12c224902
--- /dev/null
+++ b/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx
@@ -0,0 +1,106 @@
+import { Box, IconButton, Stack, Typography } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import CloseRoundedIcon from "@mui/icons-material/CloseRounded";
+import { useNavigate } from "react-router";
+import Field from "../../../Components/Inputs/Field";
+import Select from "../../../Components/Inputs/Select";
+import Button from "../../../Components/Button";
+import Checkbox from "../../../Components/Inputs/Checkbox";
+import "./index.css";
+
+const CreatePageSpeed = () => {
+ const theme = useTheme();
+ const navigate = useNavigate();
+
+ const frequencies = [
+ { _id: 1, name: "1 minute" },
+ { _id: 2, name: "2 minutes" },
+ { _id: 3, name: "3 minutes" },
+ { _id: 4, name: "4 minutes" },
+ { _id: 5, name: "5 minutes" },
+ ];
+
+ return (
+
+
+ navigate("/page-speed")}
+ sx={{
+ p: "5px",
+ opacity: 0.6,
+ "&:focus": { outline: "none" },
+ }}
+ >
+
+
+
+ Create a page speed monitor
+ console.log("disabled")}
+ error=""
+ />
+ console.log("disabled")}
+ error=""
+ />
+
+
+ Incidents notifications
+ When there is a new incident,
+
+
+
+
+ console.log("disabled")}
+ error=""
+ />
+
+ You can separate multiple emails with a comma
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default CreatePageSpeed;
diff --git a/Client/src/Pages/PageSpeed/index.css b/Client/src/Pages/PageSpeed/index.css
new file mode 100644
index 000000000..d199f077c
--- /dev/null
+++ b/Client/src/Pages/PageSpeed/index.css
@@ -0,0 +1,30 @@
+.page-speed h1.MuiTypography-root,
+.page-speed span.MuiTypography-root,
+.page-speed p.MuiTypography-root,
+.page-speed .label {
+ color: var(--env-var-color-5);
+}
+.page-speed h2.MuiTypography-root {
+ color: var(--env-var-color-3);
+ line-height: 1.1;
+}
+.page-speed h1.MuiTypography-root,
+.page-speed h2.MuiTypography-root {
+ font-size: var(--env-var-font-size-large);
+ font-weight: 600;
+}
+.page-speed span.MuiTypography-root,
+.page-speed p.MuiTypography-root {
+ font-size: var(--env-var-font-size-medium);
+}
+.page-speed .MuiGrid-item > .MuiStack-root {
+ border: solid 1px var(--env-var-color-6);
+ border-radius: var(--env-var-radius-1);
+ background-color: var(--env-var-color-8);
+}
+.page-speed .label {
+ padding: 0 5px;
+ height: 22px;
+ font-size: var(--env-var-font-size-small);
+ border-color: var(--env-var-color-29);
+}
diff --git a/Client/src/Pages/PageSpeed/index.jsx b/Client/src/Pages/PageSpeed/index.jsx
new file mode 100644
index 000000000..362de1531
--- /dev/null
+++ b/Client/src/Pages/PageSpeed/index.jsx
@@ -0,0 +1,153 @@
+import { Box, Grid, Stack, Typography } from "@mui/material";
+import Fallback from "../../Components/Fallback";
+import { useTheme } from "@emotion/react";
+import PageSpeedIcon from "../../assets/icons/page-speed.svg?react";
+
+import "./index.css";
+import { formatDate, formatDurationRounded } from "../../Utils/timeUtils";
+import { StatusLabel } from "../../Components/Label";
+
+const Card = ({ data }) => {
+ const theme = useTheme();
+
+ /**
+ * Helper function to get duration since last check or the last date checked
+ * @param {Array} checks Array of check objects.
+ * @param {boolean} duration Whether the function should return the duration since last checked or the date itself
+ * @returns {number} Timestamp of the most recent check.
+ */
+ const getLastChecked = (checks, duration = true) => {
+ if (!checks || checks.length === 0) {
+ return 0; // Handle case when no checks are available
+ }
+
+ // Data is sorted oldest -> newest, so last check is the most recent
+ if (!duration) {
+ return new Date(checks[checks.length - 1].createdAt);
+ }
+ return new Date() - new Date(checks[checks.length - 1].createdAt);
+ };
+
+ return (
+
+
+
+
+
+
+ {data.name}
+
+ {/* TODO - fix prop-type error */}
+
+
+ {data.url}
+
+
+ Last checked:{" "}
+
+ {formatDate(getLastChecked(data.checks, false))}{" "}
+
+ ({formatDurationRounded(getLastChecked(data.checks))} ago)
+
+
+
+
+
+ );
+};
+
+const PageSpeed = () => {
+ const theme = useTheme();
+
+ // sample data, remove later
+ let monitors = [
+ {
+ success: true,
+ msg: 'Got monitor for 66a3d58ecd42ab3ed1171cf1 successfully"',
+ data: [
+ {
+ _id: "66a3ef558943628c59aabf00",
+ userId: "66a3d58ecd42ab3ed1171cf1",
+ name: "Google",
+ description: "Google",
+ status: true,
+ type: "pagespeed",
+ url: "https://www.google.com",
+ isActive: true,
+ interval: 10000,
+ createdAt: "2024-07-26T18:47:49.212Z",
+ updatedAt: "2024-07-26T18:47:49.212Z",
+ __v: 0,
+ checks: [
+ {
+ _id: "66a3f2266a073a2ff8dd0f7f",
+ monitorId: "66a3ef558943628c59aabf00",
+ status: true,
+ accessibility: 90,
+ bestPractices: 93,
+ seo: 92,
+ performance: 93,
+ createdAt: "2024-07-26T18:59:50.103Z",
+ updatedAt: "2024-07-26T18:59:50.103Z",
+ __v: 0,
+ },
+ {
+ _id: "66a3f2226a073a2ff8dd0f7d",
+ monitorId: "66a3ef558943628c59aabf00",
+ status: true,
+ accessibility: 90,
+ bestPractices: 93,
+ seo: 92,
+ performance: 87,
+ createdAt: "2024-07-26T18:59:46.280Z",
+ updatedAt: "2024-07-26T18:59:46.280Z",
+ __v: 0,
+ },
+ ],
+ },
+ ],
+ },
+ ];
+
+ return (
+
+ {monitors ? (
+
+ All page speed monitors
+
+ Click on one of the monitors to get more site speed information.
+
+
+ {monitors[0].data?.map((monitor) => (
+
+ ))}
+
+
+ ) : (
+
+ )}
+
+ );
+};
+
+export default PageSpeed;
diff --git a/Client/src/Utils/timeUtils.js b/Client/src/Utils/timeUtils.js
new file mode 100644
index 000000000..37a821605
--- /dev/null
+++ b/Client/src/Utils/timeUtils.js
@@ -0,0 +1,59 @@
+export const formatDuration = (ms) => {
+ const seconds = Math.floor(ms / 1000);
+ const minutes = Math.floor(seconds / 60);
+ const hours = Math.floor(minutes / 60);
+ const days = Math.floor(hours / 24);
+
+ let dateStr = "";
+
+ days && (dateStr += `${days}d `);
+ hours && (dateStr += `${hours % 24}h `);
+ minutes && (dateStr += `${minutes % 60}m `);
+ seconds && (dateStr += `${seconds % 60}s `);
+
+ dateStr === "" && (dateStr = "0s");
+
+ return dateStr;
+};
+
+export const formatDurationRounded = (ms) => {
+ const seconds = Math.floor(ms / 1000);
+ const minutes = Math.floor(seconds / 60);
+ const hours = Math.floor(minutes / 60);
+ const days = Math.floor(hours / 24);
+
+ let time = "";
+ if (days > 0) {
+ time += `${days} day${days !== 1 ? "s" : ""}`;
+ return time;
+ }
+ if (hours > 0) {
+ time += `${hours} hour${hours !== 1 ? "s" : ""}`;
+ return time;
+ }
+ if (minutes > 0) {
+ time += `${minutes} minute${minutes !== 1 ? "s" : ""}`;
+ return time;
+ }
+ if (seconds > 0) {
+ time += `${seconds} second${seconds !== 1 ? "s" : ""}`;
+ return time;
+ }
+
+ return time;
+};
+
+export const formatDate = (date) => {
+ const options = {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ hour: "numeric",
+ minute: "numeric",
+ second: "numeric",
+ hour12: true,
+ };
+
+ // Return the date using the specified options
+ return date.toLocaleString("en-US", options);
+};