Merge pull request #535 from bluewave-labs/develop

Update master
This commit is contained in:
Alexander Holliday authored and GitHub committed 2024-08-02 14:24:17 -07:00
commit cc25e07a75
42 files changed
+599 -279

No files matched your search

+2 -2
View File
@@ -5,7 +5,7 @@
<meta charset="UTF-8" />
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Uptime Genie</title>
<title>BlueWave Uptime</title>
</head>
<body>
@@ -13,4 +13,4 @@
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
</html>
+2 -1
View File
@@ -35,7 +35,8 @@ const Avatar = ({ src, small, sx }) => {
src ? src : user?.avatarImage ? image : "/static/images/avatar/2.jpg"
}
sx={{
fontSize: small ? "13px" : "20px",
fontSize: small ? "13px" : "22px",
fontWeight: 400,
display: "inline-flex",
"&::before": {
content: `""`,
+20 -21
View File
@@ -1,36 +1,31 @@
.MuiTable-root .host-row {
display: flex;
align-items: center;
}
.MuiTable-root .host-row a {
.MuiTable-root .host a {
width: var(--env-var-img-width-2);
height: var(--env-var-img-width-2);
color: var(--env-var-color-5);
margin-right: 10px;
margin-bottom: 2px;
}
.MuiTable-root .host-row a svg {
.MuiTable-root .host a svg {
width: var(--env-var-font-size-large);
height: var(--env-var-font-size-large);
}
.MuiTable-root .host-row .host-percentage {
font-size: var(--env-var-font-size-small);
}
.MuiTable-root .label {
border-color: var(--env-var-color-4);
border-radius: var(--env-var-radius-2);
padding: calc(var(--env-var-spacing-1) / 2);
}
.MuiTable-root .host-name {
.MuiTable-root .host div:nth-child(2) {
width: fit-content;
margin-right: 10px;
font-weight: 700;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.MuiTable-root .host div:nth-child(2) span {
font-size: var(--env-var-font-size-small);
margin-left: 10px;
}
.MuiTable-root .label {
line-height: 1;
border-radius: var(--env-var-radius-2);
padding: 7px;
font-size: var(--env-var-font-size-small-plus);
}
.MuiPaper-root:has(table.MuiTable-root) {
box-shadow: none;
@@ -85,7 +80,11 @@
.MuiPaper-root + .MuiPagination-root ul {
justify-content: center;
}
.MuiPaper-root + .MuiPagination-root ul li button {
.MuiPaper-root
+ .MuiPagination-root
ul
li:not(:first-child):not(:last-child)
button {
font-size: var(--env-var-font-size-medium);
color: var(--env-var-color-5);
font-weight: 500;
@@ -123,6 +122,6 @@
.MuiPaper-root + .MuiPagination-root .MuiPaginationItem-root.Mui-selected {
background-color: var(--env-var-color-15);
}
.MuiPaper-root + .MuiPagination-root div.MuiPaginationItem-root{
.MuiPaper-root + .MuiPagination-root div.MuiPaginationItem-root {
user-select: none;
}
+3 -1
View File
@@ -135,7 +135,9 @@ const BasicTable = ({ data, paginated, reversed, rowsPerPage = 5 }) => {
{displayData.map((row) => {
return (
<TableRow
sx={{ cursor: row.handleClick ? "pointer" : "default" }}
sx={{
cursor: row.handleClick ? "pointer" : "default",
}}
key={row.id}
onClick={row.handleClick ? row.handleClick : null}
>
+2 -1
View File
@@ -72,7 +72,8 @@ const Button = ({
onClick={onClick}
disableRipple
sx={{
lineHeight: 1,
lineHeight: 1.5,
fontWeight: 400,
boxShadow: "none",
textTransform: "none",
"&:focus": {
@@ -8,7 +8,12 @@ const ResponseTimeChart = ({ checks = [] }) => {
return (
<div className="chart-container">
<ResponsiveContainer width="100%" height="100%">
<BarChart width={150} height={40} data={normalizedChecks}>
<BarChart
width={150}
height={40}
data={normalizedChecks}
style={{ cursor: "pointer" }}
>
<Bar maxBarSize={10} dataKey="responseTime">
{normalizedChecks.map((check, index) => (
<Cell
@@ -1,7 +1,6 @@
import React from "react";
import "./serverStatus.css";
import PropTypes from "prop-types";
import { useTheme } from "@mui/material";
/**
* @component
@@ -12,16 +11,8 @@ import { useTheme } from "@mui/material";
* @returns {JSX.Element} - Renders the server status component
*/
const ServerStatus = ({ title, value, state }) => {
const theme = useTheme();
const fontLookup = {
default: theme.font.default.font,
};
const fontFamily = fontLookup["default"];
return (
<div className="server-status-tile" style={{ fontFamily: fontFamily }}>
<div className="server-status-tile">
<div className="server-status-tile-title">{title}</div>
<div className={`server-status-tile-value ` + " " + state}>{value}</div>
</div>
@@ -24,6 +24,7 @@
.field h3.MuiTypography-root span.field-optional {
opacity: 0.6;
margin-left: 4px;
font-weight: 400;
}
.field h5.MuiTypography-root {
position: relative;
@@ -15,6 +15,9 @@
font-size: var(--env-var-font-size-medium);
color: var(--env-var-color-5);
}
.select-wrapper h3.MuiTypography-root{
font-weight: 500;
}
.MuiInputBase-root:has(> .MuiSelect-select) fieldset {
border-color: var(--env-var-color-29);
}
+1
View File
@@ -134,6 +134,7 @@ const StatusLabel = ({ status, text, customStyles }) => {
<BaseLabel
label={text}
styles={{
color: dotColor,
backgroundColor: bgColor,
borderColor: borderColor,
...customStyles,
+5 -1
View File
@@ -145,7 +145,10 @@ function NavBar() {
</IconButton>
</Tooltip>
<Menu
sx={{ mt: theme.spacing(5.5) }}
sx={{
mt: theme.spacing(5.5),
borderRadius: "4px",
}}
id="menu-appbar"
anchorEl={anchorElUser}
anchorOrigin={{
@@ -172,6 +175,7 @@ function NavBar() {
fontSize="var(--env-var-font-size-medium)"
textAlign="center"
marginLeft="8px"
sx={{ fontWeight: 400, color: "#344054" }}
>
{setting}
</Typography>
@@ -34,7 +34,7 @@ import Select from "../../Inputs/Select";
const TeamPanel = () => {
const theme = useTheme();
const { authToken } = useSelector((state) => state.auth);
const { authToken, user } = useSelector((state) => state.auth);
//TODO
const [orgStates, setOrgStates] = useState({
name: "Bluewave Labs",
@@ -65,7 +65,7 @@ const TeamPanel = () => {
};
fetchTeam();
}, []);
}, [user]);
useEffect(() => {
let team = members;
+2 -4
View File
@@ -61,7 +61,7 @@ export const update = createAsyncThunk(
form.deleteProfileImage &&
fd.append("deleteProfileImage", form.deleteProfileImage);
const res = await axiosInstance.post(`/auth/user/${user._id}`, fd, {
const res = await axiosInstance.put(`/auth/user/${user._id}`, fd, {
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "multipart/form-data",
@@ -185,9 +185,7 @@ const handleForgotRejected = (state, action) => {
const handleNewPasswordRejected = (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to reset password.";
state.msg = action.payload ? action.payload.msg : "Failed to reset password.";
};
const authSlice = createSlice({
@@ -77,9 +77,9 @@ export const updateUptimeMonitor = createAsyncThunk(
name: monitor.name,
description: monitor.description,
interval: monitor.interval,
notifications: monitor.notifications
notifications: monitor.notifications,
};
const res = await axiosInstance.post(
const res = await axiosInstance.put(
`/monitors/edit/${monitor._id}`,
updatedFields,
{
@@ -104,16 +104,12 @@ export const deleteUptimeMonitor = createAsyncThunk(
async (data, thunkApi) => {
try {
const { authToken, monitor } = data;
const res = await axiosInstance.post(
`/monitors/delete/${monitor._id}`,
{},
{
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
},
}
);
const res = await axiosInstance.delete(`/monitors/${monitor._id}`, {
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
},
});
return res.data;
} catch (error) {
if (error.response && error.response.data) {
+2 -3
View File
@@ -31,7 +31,7 @@
.account .MuiButtonGroup-root button {
border-color: var(--env-var-color-16);
}
.account button{
.account button:not(.MuiIconButton-root) {
height: 34px;
}
.account [class$="-form"] {
@@ -70,7 +70,6 @@
align-items: center;
}
/* for testing, will remove later */
@media only screen and (max-width: 1600px) {
.edit-profile-form__wrapper .MuiStack-root:not(.row-stack),
@@ -106,4 +105,4 @@
}
.edit-team-form .MuiTableBody-root .MuiTableCell-root {
padding: var(--env-var-spacing-1-plus) var(--env-var-spacing-2) !important;
}
}
+3 -6
View File
@@ -51,15 +51,12 @@ const Account = ({ open = "profile" }) => {
minWidth: "fit-content",
minHeight: 0,
paddingLeft: "0",
paddingY: "10px",
marginRight: "20px",
paddingY: theme.gap.small,
fontWeight: 400,
marginRight: theme.gap.ml,
"&:focus": {
outline: "none",
},
"& .MuiTouchRipple-root": {
pointerEvents: "none",
display: "none",
},
}}
/>
))}
+4 -4
View File
@@ -158,9 +158,9 @@ const Login = () => {
}
label="Remember me"
/>
<span onClick={() => navigate("/forgot-password")}>
<Typography component="span" onClick={() => navigate("/forgot-password")}>
Forgot password
</span>
</Typography>
</Stack>
</Stack>
<Stack gap={theme.gap.ml} mt={theme.gap.large}>
@@ -179,13 +179,13 @@ const Login = () => {
<Typography component="p" sx={{ alignSelf: "center" }}>
Don't have an account?
</Typography>
<span
<Typography component="span"
onClick={() => {
navigate("/register");
}}
>
Sign up
</span>
</Typography>
</Stack>
</Stack>
</form>
+1 -1
View File
@@ -103,7 +103,7 @@
/* ///// */
.login-page
.MuiStack-root:not(:has(> .MuiButtonBase-root))
span:not(.MuiTypography-root):not(.field-required) {
> span:not(.field-required):not(.input-error) {
color: var(--env-var-color-3);
cursor: pointer;
font-weight: 600;
+7 -3
View File
@@ -1,12 +1,16 @@
.incidents h1.MuiTypography-root {
.incidents h1.MuiTypography-root,
.incidents p.MuiTypography-root {
color: var(--env-var-color-5);
}
.incidents h1.MuiTypography-root {
font-size: var(--env-var-font-size-large);
font-weight: 600;
}
.incidents button.MuiButtonBase-root {
.incidents button.MuiButtonBase-root,
.incidents p.MuiTypography-root {
font-size: var(--env-var-font-size-medium);
}
.incidents button.MuiButtonBase-root {
height: 34px;
border-color: var(--env-var-color-16);
}
@@ -14,4 +18,4 @@
/* remove later */
.incidents .MuiSelect-select {
padding: 0 10px;
}
}
+87 -44
View File
@@ -1,18 +1,20 @@
import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import { Typography, ButtonGroup, Stack } from "@mui/material";
import { Typography, ButtonGroup, Stack, Box } from "@mui/material";
import WestRoundedIcon from "@mui/icons-material/WestRounded";
import Button from "../../Components/Button";
import axiosInstance from "../../Utils/axiosConfig";
import BasicTable from "../../Components/BasicTable";
import { StatusLabel } from "../../Components/Label";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router";
import Select from "../../Components/Inputs/Select";
import "./index.css";
import Select from "../../Components/Inputs/Select";
const Incidents = () => {
const theme = useTheme();
const navigate = useNavigate();
const authState = useSelector((state) => state.auth);
const [monitors, setMonitors] = useState({});
const [selectedMonitor, setSelectedMonitor] = useState("0");
@@ -58,13 +60,19 @@ const Incidents = () => {
setSelectedMonitor(event.target.value);
};
let incidents = [];
let hasAnyIncidents = false;
let hasSpecificIncidents = false;
if (selectedMonitor === "0") {
Object.values(monitors).forEach((monitor) => {
incidents = incidents.concat(monitor.checks);
});
if (incidents.length !== 0) hasAnyIncidents = true;
hasSpecificIncidents = hasAnyIncidents;
} else {
const monitor = monitors[selectedMonitor];
incidents = monitor.checks;
if (incidents.length !== 0) hasSpecificIncidents = true;
hasAnyIncidents = true;
}
incidents = incidents
.sort((a, b) => {
@@ -92,48 +100,83 @@ const Incidents = () => {
data.rows = incidents;
return (
<Stack className="incidents" gap={theme.gap.large}>
<Stack direction="row" alignItems="center" gap={theme.gap.medium}>
<Typography component="h1">Incident history for: </Typography>
<Select
id="incidents-select-monitor"
placeholder="All servers"
value={selectedMonitor}
onChange={handleSelect}
items={Object.values(monitors)}
/>
<ButtonGroup sx={{ ml: "auto" }}>
<Button
level="secondary"
label="All"
onClick={() => setFilter("all")}
sx={{
backgroundColor:
filter === "all" && theme.palette.otherColors.fillGray,
}}
/>
<Button
level="secondary"
label="Down"
onClick={() => setFilter("down")}
sx={{
backgroundColor:
filter === "down" && theme.palette.otherColors.fillGray,
}}
/>
<Button
level="secondary"
label="Cannot Resolve"
onClick={() => setFilter("resolve")}
sx={{
backgroundColor:
filter === "resolve" && theme.palette.otherColors.fillGray,
}}
/>
</ButtonGroup>
<>
<Stack className="incidents" gap={theme.gap.large}>
{hasAnyIncidents ? (
<>
<Stack direction="row" alignItems="center" gap={theme.gap.medium}>
<Typography component="h1">Incident history for: </Typography>
<Select
id="incidents-select-monitor"
placeholder="All servers"
value={selectedMonitor}
onChange={handleSelect}
items={Object.values(monitors)}
/>
{hasAnyIncidents && hasSpecificIncidents && (
<ButtonGroup sx={{ ml: "auto" }}>
<Button
level="secondary"
label="All"
onClick={() => setFilter("all")}
sx={{
backgroundColor:
filter === "all" && theme.palette.otherColors.fillGray,
}}
/>
<Button
level="secondary"
label="Down"
onClick={() => setFilter("down")}
sx={{
backgroundColor:
filter === "down" && theme.palette.otherColors.fillGray,
}}
/>
<Button
level="secondary"
label="Cannot Resolve"
onClick={() => setFilter("resolve")}
sx={{
backgroundColor:
filter === "resolve" &&
theme.palette.otherColors.fillGray,
}}
/>
</ButtonGroup>
)}
</Stack>
{hasAnyIncidents && hasSpecificIncidents ? (
<BasicTable data={data} paginated={true} rowsPerPage={12} />
) : (
<Typography alignSelf="center" mt={theme.gap.xxl}>
The monitor you have selected, has no recorded incidents yet.
</Typography>
)}
</>
) : (
<Stack alignItems="center" mt={theme.gap.xxl} gap={theme.gap.small}>
<Typography>No incidents, yet.</Typography>
<Button
level="tertiary"
label="Back"
animate="slideLeft"
img={<WestRoundedIcon />}
onClick={() => navigate(-1)}
sx={{
backgroundColor: theme.palette.otherColors.fillGray,
mb: theme.gap.medium,
px: theme.gap.ml,
"& svg.MuiSvgIcon-root": {
mr: theme.gap.small,
fill: theme.palette.otherColors.slateGray,
},
}}
/>
</Stack>
)}
</Stack>
<BasicTable data={data} paginated={true} rowsPerPage={12} />
</Stack>
</>
);
};
+1 -1
View File
@@ -111,7 +111,7 @@ const Integrations = () => {
<Stack className="integrations" gap={theme.gap.xs}>
<Typography component="h1">Integrations</Typography>
<Typography mb={theme.gap.large}>
Connect Uptime Genie to your favorite service
Connect BlueWave Uptime to your favorite service.
</Typography>
<Grid container spacing={theme.gap.large}>
{integrations.map((integration, index) => (
+10 -2
View File
@@ -22,7 +22,9 @@ import "./index.css";
const StatBox = ({ title, value }) => {
return (
<Box className="stat-box">
<Typography variant="h6">{title}</Typography>
<Typography variant="h6" sx={{ fontWeight: 400 }}>
{title}
</Typography>
<Typography variant="h4">{value}</Typography>
</Box>
);
@@ -64,7 +66,13 @@ const DetailsPage = () => {
data: [
{
id: idx,
data: <StatusLabel status={status} text={status} />,
data: (
<StatusLabel
status={status}
text={status}
customStyles={{ textTransform: "capitalize" }}
/>
),
},
{ id: idx + 1, data: new Date(check.createdAt).toLocaleString() },
{ id: idx + 2, data: check.statusCode },
+24
View File
@@ -31,3 +31,27 @@
margin-left: 10px;
line-height: 0.8;
}
body:has(.monitors) .actions-menu .MuiPaper-root {
box-shadow: var(--env-var-shadow-1);
border: solid 1px var(--env-var-color-6);
border-radius: var(--env-var-radius-1);
gap: 1px;
}
body:has(.monitors) .actions-menu .MuiList-root {
padding: 6px 8px;
min-width: 100px;
}
body:has(.monitors) .actions-menu li.MuiButtonBase-root {
font-size: var(--env-var-font-size-medium);
color: var(--env-var-color-5);
padding: 5px 8px;
border-radius: var(--env-var-radius-1);
}
body:has(.monitors) .actions-menu li.MuiButtonBase-root:last-of-type {
color: var(--env-var-color-26);
}
body:has(.monitors) .actions-menu .MuiModal-root p.MuiTypography-root {
font-size: var(--env-var-font-size-medium);
color: var(--env-var-color-2);
}
+158 -17
View File
@@ -1,17 +1,30 @@
import "./index.css";
import { useEffect } from "react";
import { useEffect, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { getUptimeMonitorsByUserId } from "../../Features/UptimeMonitors/uptimeMonitorsSlice";
import {
deleteUptimeMonitor,
getUptimeMonitorsByUserId,
} from "../../Features/UptimeMonitors/uptimeMonitorsSlice";
import { useNavigate } from "react-router-dom";
import Button from "../../Components/Button";
import ServerStatus from "../../Components/Charts/Servers/ServerStatus";
import { useTheme } from "@emotion/react";
import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded";
import OpenInNewPage from "../../assets/icons/open-in-new-page.svg?react";
import Settings from "../../assets/icons/settings-bold.svg?react";
import BasicTable from "../../Components/BasicTable";
import { StatusLabel } from "../../Components/Label";
import { createToast } from "../../Utils/toastUtils";
import ResponseTimeChart from "../../Components/Charts/ResponseTimeChart";
import { Box, Stack, Typography } from "@mui/material";
import {
Box,
IconButton,
Menu,
MenuItem,
Modal,
Stack,
Typography,
} from "@mui/material";
/**
* Host component.
@@ -27,18 +40,24 @@ import { Box, Stack, Typography } from "@mui/material";
*/
const Host = ({ params }) => {
return (
<div className="host-row">
<a href={params.url} target="_blank" rel="noreferrer">
<Stack direction="row" alignItems="center" className="host">
<a
href={params.url}
target="_blank"
rel="noreferrer"
onClick={(event) => {
event.stopPropagation();
}}
>
<OpenInNewPage />
</a>
<div className="host-name">{params.title}</div>
<div
className="host-percentage"
style={{ color: params.percentageColor }}
>
{params.precentage}%
</div>
</div>
<Box>
{params.title}
<Typography component="span" sx={{ color: params.percentageColor }}>
{params.precentage}%
</Typography>
</Box>
</Stack>
);
};
@@ -47,7 +66,39 @@ const Monitors = () => {
const navigate = useNavigate();
const monitorState = useSelector((state) => state.uptimeMonitors);
const authState = useSelector((state) => state.auth);
const dispatch = useDispatch();
const dispatch = useDispatch({});
const [anchorEl, setAnchorEl] = useState(null);
const [actions, setActions] = useState({});
const openMenu = (event, id, url) => {
event.preventDefault();
setAnchorEl(event.currentTarget);
setActions({ id: id, url: url });
};
const closeMenu = () => {
setAnchorEl(null);
setActions({});
};
const [isOpen, setIsOpen] = useState(false);
const openRemove = () => {
setAnchorEl(null);
setIsOpen(true);
};
const handleRemove = async (event) => {
event.preventDefault();
let monitor = { _id: actions.id };
const action = await dispatch(
deleteUptimeMonitor({ authToken: authState.authToken, monitor })
);
if (action.meta.requestStatus === "fulfilled") {
setIsOpen(false); // close modal
dispatch(getUptimeMonitorsByUserId(authState.authToken));
createToast({ body: "Monitor deleted successfully." });
} else {
createToast({ body: "Failed to delete monitor." });
}
};
useEffect(() => {
dispatch(getUptimeMonitorsByUserId(authState.authToken));
@@ -65,12 +116,12 @@ const Monitors = () => {
{
id: 2,
name: (
<>
<Box width="max-content">
Status
<span>
<ArrowDownwardRoundedIcon />
</span>
</>
</Box>
),
},
{ id: 3, name: "Response Time" },
@@ -97,6 +148,7 @@ const Monitors = () => {
return {
id: monitor._id,
// disabled for now
handleClick: () => navigate(`/monitors/${monitor._id}`),
data: [
{ id: idx, data: <Host params={params} /> },
@@ -117,7 +169,27 @@ const Monitors = () => {
<span style={{ textTransform: "uppercase" }}>{monitor.type}</span>
),
},
{ id: idx + 4, data: "TODO" },
{
id: idx + 4,
data: (
<>
<IconButton
aria-label="monitor actions"
onClick={(event) => {
event.stopPropagation();
openMenu(event, monitor._id, monitor.url);
}}
sx={{
"&:focus": {
outline: "none",
},
}}
>
<Settings />
</IconButton>
</>
),
},
],
};
});
@@ -163,6 +235,75 @@ const Monitors = () => {
</Stack>
<BasicTable data={data} paginated={true} />
</Stack>
<Menu
className="actions-menu"
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={closeMenu}
>
<MenuItem
onClick={() => {
window.open(actions.url, "_blank", "noreferrer");
}}
>
Open site
</MenuItem>
<MenuItem onClick={() => navigate(`/monitors/${actions.id}`)}>
Details
</MenuItem>
{/* TODO - pass monitor id to Incidents page */}
<MenuItem disabled>Incidents</MenuItem>
<MenuItem onClick={() => navigate(`/monitors/configure/${actions.id}`)}>
Configure
</MenuItem>
<MenuItem onClick={openRemove}>Remove</MenuItem>
</Menu>
<Modal
aria-labelledby="modal-delete-monitor"
aria-describedby="delete-monitor-confirmation"
open={isOpen}
onClose={() => setIsOpen(false)}
disablePortal
>
<Stack
gap={theme.gap.xs}
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: "white",
border: "solid 1px #f2f2f2",
borderRadius: `${theme.shape.borderRadius}px`,
boxShadow: 24,
p: "30px",
"&:focus": {
outline: "none",
},
}}
>
<Typography id="modal-delete-monitor" component="h2">
Really delete this monitor?
</Typography>
<Typography id="delete-monitor-confirmation">
Once deleted, this monitor cannot be retrieved back.
</Typography>
<Stack
direction="row"
gap={theme.gap.small}
mt={theme.gap.large}
justifyContent="flex-end"
>
<Button
level="tertiary"
label="Cancel"
onClick={() => setIsOpen(false)}
/>
<Button level="error" label="Delete" onClick={handleRemove} />
</Stack>
</Stack>
</Modal>
</Stack>
);
};
@@ -1,37 +1,31 @@
.create-page-speed form {
position: relative;
margin: auto;
margin-top: var(--env-var-default-2);
.create-page-speed form > .MuiStack-root:not(:last-of-type) {
background-color: var(--env-var-color-8);
width: 100%;
max-width: 500px;
box-shadow: var(--env-var-shadow-1);
border: solid 1px var(--env-var-color-6);
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 h1.MuiTypography-root {
font-size: var(--env-var-font-size-large);
color: var(--env-var-color-1);
}
.create-page-speed p.MuiTypography-root,
.create-page-speed h2.MuiTypography-root > span.MuiTypography-root {
.create-page-speed h3.MuiTypography-root {
color: var(--env-var-color-5);
}
.create-page-speed h1.MuiTypography-root,
.create-page-speed h2.MuiTypography-root {
.create-page-speed h3.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,
.create-page-speed h2.MuiTypography-root > span.MuiTypography-root {
.create-page-speed h3.MuiTypography-root,
.create-page-speed h3.MuiTypography-root > span.MuiTypography-root {
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);
}
.create-page-speed .MuiBox-root > .field + p.MuiTypography-root,
.create-page-speed h2.MuiTypography-root > span.MuiTypography-root {
.create-page-speed h3.MuiTypography-root > span.MuiTypography-root {
opacity: 0.6;
}
.create-page-speed .checkbox-wrapper {
@@ -47,17 +41,18 @@
pointer-events: none;
}
.create-page-speed button:not(.MuiIconButton-root) {
.create-page-speed button {
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;
}
.create-page-speed .field,
.create-page-speed .section-disabled,
.create-page-speed .select-wrapper,
.create-page-speed h3.MuiTypography-root {
flex: 1;
}
@@ -1,9 +1,8 @@
import { Box, IconButton, Stack, Typography } from "@mui/material";
import { Box, Stack, Typography } from "@mui/material";
import { useState } from "react";
import { useTheme } from "@emotion/react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router";
import CloseRoundedIcon from "@mui/icons-material/CloseRounded";
import Field from "../../../Components/Inputs/Field";
import Select from "../../../Components/Inputs/Select";
import Button from "../../../Components/Button";
@@ -11,6 +10,7 @@ import Checkbox from "../../../Components/Inputs/Checkbox";
import { monitorValidation } from "../../../Validation/validation";
import { createToast } from "../../../Utils/toastUtils";
import { createPageSpeed } from "../../../Features/PageSpeedMonitor/pageSpeedMonitorSlice";
import WestRoundedIcon from "@mui/icons-material/WestRounded";
import "./index.css";
const CreatePageSpeed = () => {
@@ -23,10 +23,11 @@ const CreatePageSpeed = () => {
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" },
{ _id: 1440, name: "1 day" },
{ _id: 2880, name: "2 days" },
{ _id: 4320, name: "3 days" },
{ _id: 7200, name: "5 days" },
{ _id: 10080, name: "1 week" },
];
const [form, setForm] = useState({
@@ -96,102 +97,114 @@ const CreatePageSpeed = () => {
return (
<Box className="create-page-speed">
<Button
level="tertiary"
label="Back"
animate="slideLeft"
img={<WestRoundedIcon />}
onClick={() => navigate("/page-speed")}
sx={{
backgroundColor: "#f4f4f4",
mb: theme.gap.large,
px: theme.gap.ml,
"& svg.MuiSvgIcon-root": {
mr: theme.gap.small,
fill: theme.palette.otherColors.slateGray,
},
}}
/>
<form
noValidate
spellCheck="false"
onSubmit={handleCreate}
style={{ display: "flex", flexDirection: "column", gap: theme.gap.xl }}
style={{
display: "flex",
flexDirection: "column",
gap: theme.gap.large,
// TODO
maxWidth: "1000px",
}}
>
<IconButton
aria-label="close modal"
onClick={() => navigate("/page-speed")}
sx={{
p: "5px",
opacity: 0.6,
"&:focus": { outline: "none" },
}}
>
<CloseRoundedIcon />
</IconButton>
<Stack gap={theme.gap.large}>
<Typography component="h1">Create a page speed monitor</Typography>
<Field
type="text"
id="monitor-name"
label="Monitor friendly name"
placeholder="Example monitor"
isOptional={true}
value={form.name}
onChange={(event) => handleChange(event, "name")}
error={errors.name}
/>
<Field
type="url"
id="monitor-url"
label="URL"
placeholder="random.website.com"
value={form.url}
onChange={(event) => handleChange(event, "url")}
error={errors.url}
/>
<Select
id="monitor-frequency"
label="Check frequency"
items={frequencies}
value={form.interval}
onChange={(event) => handleChange(event, "interval")}
/>
</Stack>
<Stack gap={theme.gap.small}>
<Typography component="h2">
Incidents notifications{" "}
<Typography component="span">(coming soon)</Typography>
</Typography>
<Box className="section-disabled">
<Typography mb={theme.gap.small}>
When there is a new incident,
<Typography component="h1">Create a page speed monitor</Typography>
<Stack gap={theme.gap.xl}>
<Stack direction="row">
<Typography component="h3">Monitor friendly name</Typography>
<Field
type="text"
id="monitor-name"
placeholder="Example monitor"
value={form.name}
onChange={(event) => handleChange(event, "name")}
error={errors.name}
/>
</Stack>
<Stack direction="row">
<Typography component="h3">URL</Typography>
<Field
type="url"
id="monitor-url"
placeholder="random.website.com"
value={form.url}
onChange={(event) => handleChange(event, "url")}
error={errors.url}
/>
</Stack>
<Stack direction="row">
<Typography component="h3">Check frequency</Typography>
<Select
id="monitor-frequency"
items={frequencies}
value={form.interval}
onChange={(event) => handleChange(event, "interval")}
/>
</Stack>
<Stack direction="row">
<Typography component="h3">
Incidents notifications{" "}
<Typography component="span">(coming soon)</Typography>
</Typography>
<Checkbox
id="notify-sms"
label="Notify via SMS (coming soon)"
isChecked={false}
isDisabled={true}
/>
<Checkbox
id="notify-email"
label="Notify via email (to gorkem.cetin@bluewavelabs.ca)"
isChecked={false}
/>
<Checkbox
id="notify-emails"
label="Notify via email to following emails"
isChecked={false}
/>
<Box mx={`calc(${theme.gap.ml} * 2)`}>
<Field
id="notify-emails-list"
placeholder="notifications@gmail.com"
value=""
onChange={() => console.log("disabled")}
error=""
/>
<Typography mt={theme.gap.small}>
You can separate multiple emails with a comma
<Stack className="section-disabled">
<Typography mb={theme.gap.small}>
When there is a new incident,
</Typography>
</Box>
</Box>
<Checkbox
id="notify-sms"
label="Notify via SMS (coming soon)"
isChecked={false}
isDisabled={true}
/>
<Checkbox
id="notify-email"
label="Notify via email (to gorkem.cetin@bluewavelabs.ca)"
isChecked={false}
/>
<Checkbox
id="notify-emails"
label="Notify via email to following emails"
isChecked={false}
/>
<Box mx={`calc(${theme.gap.ml} * 2)`}>
<Field
id="notify-emails-list"
placeholder="notifications@gmail.com"
value=""
onChange={() => console.log("disabled")}
error=""
/>
<Typography mt={theme.gap.small}>
You can separate multiple emails with a comma
</Typography>
</Box>
</Stack>
</Stack>
</Stack>
<Stack direction="row" justifyContent="flex-end" gap={theme.gap.small}>
<Button
level="tertiary"
label="Cancel"
onClick={() => navigate("/page-speed")}
/>
<Stack direction="row" justifyContent="flex-end">
<Button
type="submit"
level="primary"
label="Create"
onClick={handleCreate}
sx={{ px: theme.gap.large, mt: theme.gap.large }}
/>
</Stack>
</form>
+9 -4
View File
@@ -1,7 +1,6 @@
.page-speed h1.MuiTypography-root,
.page-speed span.MuiTypography-root,
.page-speed p.MuiTypography-root,
.page-speed .label {
.page-speed p.MuiTypography-root {
color: var(--env-var-color-5);
}
.page-speed h2.MuiTypography-root {
@@ -26,8 +25,14 @@
background-color: var(--env-var-color-8);
}
.page-speed .label {
padding: 0 5px;
padding: 7px;
height: 22px;
font-size: var(--env-var-font-size-small);
border-color: var(--env-var-color-29);
}
.page-speed:not(:has([class*="fallback__"])) button {
height: 34px;
align-self: flex-end;
}
.page-speed [class*="fallback__"] h1 {
margin-left: var(--env-var-spacing-3);
}
+21 -5
View File
@@ -8,6 +8,8 @@ import { getPageSpeedByUserId } from "../../Features/PageSpeedMonitor/pageSpeedM
import PageSpeedIcon from "../../assets/icons/page-speed.svg?react";
import Fallback from "../../Components/Fallback";
import "./index.css";
import Button from "../../Components/Button";
import { useNavigate } from "react-router";
const Card = ({ data }) => {
const theme = useTheme();
@@ -63,6 +65,7 @@ const Card = ({ data }) => {
const PageSpeed = () => {
const theme = useTheme();
const dispatch = useDispatch();
const navigate = useNavigate();
const { authToken } = useSelector((state) => state.auth);
const { monitors } = useSelector((state) => state.pageSpeedMonitors);
@@ -72,12 +75,25 @@ const PageSpeed = () => {
return (
<Box className="page-speed">
{monitors ? (
{monitors?.length !== 0 ? (
<Stack gap={theme.gap.xs}>
<Typography component="h1">All page speed monitors</Typography>
<Typography mb={theme.gap.large}>
Click on one of the monitors to get more site speed information.
</Typography>
<Stack
direction="row"
justifyContent="space-between"
mb={theme.gap.large}
>
<Box>
<Typography component="h1">All page speed monitors</Typography>
<Typography mt={theme.gap.xs}>
Click on one of the monitors to get more site speed information.
</Typography>
</Box>
<Button
level="primary"
label="Create new"
onClick={() => navigate("/page-speed/create")}
/>
</Stack>
<Grid container spacing={theme.gap.large}>
{monitors?.map((monitor) => (
<Card data={monitor} key={`monitor-${monitor._id}`} />
+1 -1
View File
@@ -96,7 +96,7 @@ const Settings = () => {
<Typography component="h1">About</Typography>
</Box>
<Box>
<Typography component="h2">Uptime Genie v1.0.0</Typography>
<Typography component="h2">BlueWave Uptime v1.0.0</Typography>
<Typography
sx={{ mt: theme.gap.xs, mb: theme.gap.medium, opacity: 0.6 }}
>
+8 -11
View File
@@ -33,9 +33,11 @@ const shadow =
"0px 4px 24px -4px rgba(16, 24, 40, 0.08), 0px 3px 3px -3px rgba(16, 24, 40, 0.03)";
// Global Font Family
const fontFamilyDefault = '"Roboto", "Helvetica", "Arial", sans-serif';
const fontFamilyDefault =
'"Inter","system-ui", "Avenir", "Helvetica", "Arial", sans-serif';
const theme = createTheme({
typography: { fontFamily: fontFamilyDefault, fontSize: 13 },
palette: {
primary: {
main: primaryMain,
@@ -78,11 +80,6 @@ const theme = createTheme({
fillGray: otherColorsFillGray,
},
},
font: {
default: {
font: fontFamilyDefault,
},
},
shape: {
borderRadius: 4,
borderThick: 2,
@@ -121,19 +118,19 @@ const theme = createTheme({
//add more as needed
label: {
up: {
borderColor: sectionBorder,
borderColor: "#D4F4E1",
bgColor: "#ecfdf3",
dotColor: "#17B26A",
},
down: {
borderColor: sectionBorder,
borderColor: "#fbd1d1",
bgColor: "#f9eced",
dotColor: labelRed,
dotColor: "#f04438",
},
"cannot resolve": {
borderColor: sectionBorder,
borderColor: "#e2eaf7",
bgColor: "#f2f4f7",
dotColor: labelGray,
dotColor: "#4e5ba6",
},
},
});
@@ -0,0 +1,4 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 11.4C10.3255 11.4 11.4 10.3255 11.4 9C11.4 7.67452 10.3255 6.6 9 6.6C7.67452 6.6 6.6 7.67452 6.6 9C6.6 10.3255 7.67452 11.4 9 11.4Z" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14.3818 11.1818C14.285 11.4012 14.2561 11.6445 14.2989 11.8804C14.3417 12.1164 14.4542 12.3341 14.6218 12.5055L14.6655 12.5491C14.8007 12.6842 14.908 12.8446 14.9812 13.0212C15.0544 13.1978 15.0921 13.387 15.0921 13.5782C15.0921 13.7693 15.0544 13.9586 14.9812 14.1352C14.908 14.3118 14.8007 14.4722 14.6655 14.6073C14.5304 14.7425 14.3699 14.8498 14.1934 14.923C14.0168 14.9962 13.8275 15.0339 13.6364 15.0339C13.4452 15.0339 13.2559 14.9962 13.0794 14.923C12.9028 14.8498 12.7424 14.7425 12.6073 14.6073L12.5636 14.5636C12.3922 14.396 12.1745 14.2835 11.9386 14.2407C11.7027 14.1979 11.4594 14.2268 11.24 14.3236C11.0249 14.4158 10.8414 14.5689 10.7122 14.764C10.583 14.9591 10.5137 15.1878 10.5127 15.4218V15.5455C10.5127 15.9312 10.3595 16.3012 10.0867 16.574C9.81392 16.8468 9.44395 17 9.05818 17C8.67241 17 8.30244 16.8468 8.02966 16.574C7.75688 16.3012 7.60364 15.9312 7.60364 15.5455V15.48C7.59801 15.2393 7.52009 15.0058 7.38001 14.81C7.23993 14.6141 7.04417 14.4649 6.81818 14.3818C6.59882 14.285 6.3555 14.2561 6.11957 14.2989C5.88365 14.3417 5.66595 14.4542 5.49455 14.6218L5.45091 14.6655C5.31582 14.8007 5.1554 14.908 4.97882 14.9812C4.80224 15.0544 4.61297 15.0921 4.42182 15.0921C4.23067 15.0921 4.04139 15.0544 3.86481 14.9812C3.68824 14.908 3.52782 14.8007 3.39273 14.6655C3.25749 14.5304 3.1502 14.3699 3.077 14.1934C3.00381 14.0168 2.96613 13.8275 2.96613 13.6364C2.96613 13.4452 3.00381 13.2559 3.077 13.0794C3.1502 12.9028 3.25749 12.7424 3.39273 12.6073L3.43636 12.5636C3.60403 12.3922 3.7165 12.1745 3.75928 11.9386C3.80205 11.7027 3.77317 11.4594 3.67636 11.24C3.58417 11.0249 3.4311 10.8414 3.23597 10.7122C3.04085 10.583 2.81221 10.5137 2.57818 10.5127H2.45455C2.06878 10.5127 1.69881 10.3595 1.42603 10.0867C1.15325 9.81392 1 9.44395 1 9.05818C1 8.67241 1.15325 8.30244 1.42603 8.02966C1.69881 7.75688 2.06878 7.60364 2.45455 7.60364H2.52C2.76072 7.59801 2.99419 7.52009 3.19004 7.38001C3.38589 7.23993 3.53507 7.04417 3.61818 6.81818C3.71499 6.59882 3.74387 6.3555 3.70109 6.11957C3.65832 5.88365 3.54585 5.66595 3.37818 5.49455L3.33455 5.45091C3.19931 5.31582 3.09202 5.1554 3.01882 4.97882C2.94562 4.80224 2.90795 4.61297 2.90795 4.42182C2.90795 4.23067 2.94562 4.04139 3.01882 3.86481C3.09202 3.68824 3.19931 3.52782 3.33455 3.39273C3.46963 3.25749 3.63005 3.1502 3.80663 3.077C3.98321 3.00381 4.17249 2.96613 4.36364 2.96613C4.55479 2.96613 4.74406 3.00381 4.92064 3.077C5.09722 3.1502 5.25764 3.25749 5.39273 3.39273L5.43636 3.43636C5.60777 3.60403 5.82547 3.7165 6.06139 3.75928C6.29731 3.80205 6.54064 3.77317 6.76 3.67636H6.81818C7.03329 3.58417 7.21674 3.4311 7.34596 3.23597C7.47518 3.04085 7.54452 2.81221 7.54545 2.57818V2.45455C7.54545 2.06878 7.6987 1.69881 7.97148 1.42603C8.24426 1.15325 8.61423 1 9 1C9.38577 1 9.75574 1.15325 10.0285 1.42603C10.3013 1.69881 10.4545 2.06878 10.4545 2.45455V2.52C10.4555 2.75403 10.5248 2.98267 10.654 3.17779C10.7833 3.37291 10.9667 3.52599 11.1818 3.61818C11.4012 3.71499 11.6445 3.74387 11.8804 3.70109C12.1164 3.65832 12.3341 3.54585 12.5055 3.37818L12.5491 3.33455C12.6842 3.19931 12.8446 3.09202 13.0212 3.01882C13.1978 2.94562 13.387 2.90795 13.5782 2.90795C13.7693 2.90795 13.9586 2.94562 14.1352 3.01882C14.3118 3.09202 14.4722 3.19931 14.6073 3.33455C14.7425 3.46963 14.8498 3.63005 14.923 3.80663C14.9962 3.98321 15.0339 4.17249 15.0339 4.36364C15.0339 4.55479 14.9962 4.74406 14.923 4.92064C14.8498 5.09722 14.7425 5.25764 14.6073 5.39273L14.5636 5.43636C14.396 5.60777 14.2835 5.82547 14.2407 6.06139C14.1979 6.29731 14.2268 6.54064 14.3236 6.76V6.81818C14.4158 7.03329 14.5689 7.21674 14.764 7.34596C14.9591 7.47518 15.1878 7.54452 15.4218 7.54545H15.5455C15.9312 7.54545 16.3012 7.6987 16.574 7.97148C16.8468 8.24426 17 8.61423 17 9C17 9.38577 16.8468 9.75574 16.574 10.0285C16.3012 10.3013 15.9312 10.4545 15.5455 10.4545H15.48C15.246 10.4555 15.0173 10.5248 14.8222 10.654C14.6271 10.7833 14.474 10.9667 14.3818 11.1818Z" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

+2
View File
@@ -1,3 +1,5 @@
@import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap");
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
font-weight: 400;
+3
View File
@@ -439,7 +439,9 @@ const deleteUserController = async (req, res, next) => {
const decodedToken = jwt.decode(token);
const { _id, email } = decodedToken;
// TODO fix this hack
const decodedTokenCastedAsRequest = {
query: {},
params: {
userId: _id,
},
@@ -480,6 +482,7 @@ const deleteUserController = async (req, res, next) => {
await req.jobQueue.deleteJob(monitor);
await req.db.deleteChecks(monitor._id);
await req.db.deleteAlertByMonitorId(monitor._id);
await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
await req.db.deleteNotificationsByMonitorId(monitor._id);
})
);
+1
View File
@@ -182,6 +182,7 @@ const deleteMonitor = async (req, res, next) => {
await req.jobQueue.deleteJob(monitor);
await req.db.deleteChecks(monitor._id);
await req.db.deleteAlertByMonitorId(monitor._id);
await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
await req.db.deleteNotificationsByMonitorId(monitor._id);
/**
+2 -2
View File
@@ -79,7 +79,7 @@ const {
const {
createPageSpeedCheck,
getPageSpeedChecks,
deletePageSpeedChecks,
deletePageSpeedChecksByMonitorId,
} = require("./modules/pageSpeedCheckModule");
//****************************************
@@ -157,7 +157,7 @@ module.exports = {
deleteMonitorsByUserId,
createPageSpeedCheck,
getPageSpeedChecks,
deletePageSpeedChecks,
deletePageSpeedChecksByMonitorId,
createMaintenanceWindow,
getMaintenanceWindowsByUserId,
getMaintenanceWindowsByMonitorId,
@@ -48,7 +48,7 @@ const getPageSpeedChecks = async (monitorId) => {
* @throws {Error}
*/
const deletePageSpeedChecks = async (monitorId) => {
const deletePageSpeedChecksByMonitorId = async (monitorId) => {
try {
const result = await PageSpeedCheck.deleteMany({ monitorId });
return result.deletedCount;
@@ -60,5 +60,5 @@ const deletePageSpeedChecks = async (monitorId) => {
module.exports = {
createPageSpeedCheck,
getPageSpeedChecks,
deletePageSpeedChecks,
deletePageSpeedChecksByMonitorId,
};
+38
View File
@@ -1,5 +1,39 @@
const mongoose = require("mongoose");
const AuditSchema = mongoose.Schema({
id: { type: String, required: true },
title: { type: String, required: true },
description: { type: String, required: true },
score: { type: Number, required: true },
scoreDisplayMode: { type: String, required: true },
displayValue: { type: String, required: true },
numericValue: { type: Number, required: true },
numericUnit: { type: String, required: true },
});
const AuditsSchema = mongoose.Schema({
cls: {
type: AuditSchema,
required: true,
},
si: {
type: AuditSchema,
required: true,
},
fcp: {
type: AuditSchema,
required: true,
},
lcp: {
type: AuditSchema,
required: true,
},
tbt: {
type: AuditSchema,
required: true,
},
});
/**
* Mongoose schema for storing metrics from Google Lighthouse.
* @typedef {Object} PageSpeedCheck
@@ -37,6 +71,10 @@ const PageSpeedCheck = mongoose.Schema(
type: Number,
required: true,
},
audits: {
type: AuditsSchema,
required: true,
},
},
{
timestamps: true,
+1 -1
View File
@@ -23,7 +23,7 @@ const {
//Auth routes
router.post("/register", upload.single("profileImage"), registerController);
router.post("/login", loginController);
router.post(
router.put(
"/user/:userId",
upload.single("profileImage"),
verifyJWT,
+3 -3
View File
@@ -8,12 +8,12 @@ router.get("/:monitorId", monitorController.getMonitorById);
router.get("/user/:userId", monitorController.getMonitorsByUserId);
router.post("/", monitorController.createMonitor);
router.post(
"/delete/:monitorId",
router.delete(
"/:monitorId",
verifyOwnership(Monitor, "monitorId"),
monitorController.deleteMonitor
);
router.post(
router.put(
"/edit/:monitorId",
verifyOwnership(Monitor, "monitorId"),
monitorController.editMonitor
+3
View File
@@ -210,6 +210,9 @@ class JobQueue {
*/
async addJob(jobName, payload) {
try {
// Execute job immediately
await this.queue.add(jobName, payload);
await this.queue.add(jobName, payload, {
repeat: {
every: payload.interval,
+24
View File
@@ -129,6 +129,22 @@ class NetworkService {
);
const pageSpeedResults = response.data;
const categories = pageSpeedResults.lighthouseResult?.categories;
const audits = pageSpeedResults.lighthouseResult?.audits;
const {
"cumulative-layout-shift": cls,
"speed-index": si,
"first-contentful-paint": fcp,
"largest-contentful-paint": lcp,
"total-blocking-time": tbt,
} = audits;
// Weights
// First Contentful Paint 10%
// Speed Index 10%
// Largest Contentful Paint 25%
// Total Blocking Time 30%
// Cumulative Layout Shift 25%
const checkData = {
monitorId: job.data._id,
status: true,
@@ -136,7 +152,15 @@ class NetworkService {
bestPractices: (categories["best-practices"]?.score || 0) * 100,
seo: (categories.seo?.score || 0) * 100,
performance: (categories.performance?.score || 0) * 100,
audits: {
cls,
si,
fcp,
lcp,
tbt,
},
};
this.logAndStoreCheck(checkData, this.db.createPageSpeedCheck);
} catch (error) {
const checkData = {
+1
View File
@@ -0,0 +1 @@
uptime.bluewavelabs.ca