Merge pull request #719 from bluewave-labs/feat/dashboard-monitors

Monitors dashboard
This commit is contained in:
Alexander Holliday authored and GitHub committed 2024-08-24 17:53:13 -07:00
commit e8a4d2f2ff
15 files changed
+348 -132

No files matched your search

+14 -2
View File
@@ -5,8 +5,7 @@
white-space: nowrap;
}
.MuiTable-root .host span {
font-size: var(--env-var-font-size-small);
margin-left: 8px;
font-size: 11px;
}
.MuiTable-root .label {
@@ -180,3 +179,16 @@ body:has(.pagination-dropdown) p:has(+ .MuiTablePagination-root) {
body:has(.pagination-dropdown) p:has(+ .MuiTablePagination-root) {
font-size: var(--env-var-font-size-small-plus);
}
.monitors .MuiTable-root .MuiTableHead-root .MuiTableCell-root {
text-transform: uppercase;
color: var(--env-var-color-5);
opacity: 0.8;
font-size: var(--env-var-font-size-small-plus);
font-weight: 400;
}
.monitors .MuiTableCell-root:not(:first-of-type):not(:last-of-type),
.monitors .MuiTableCell-root:not(:first-of-type):not(:last-of-type) {
padding-left: var(--env-var-spacing-1);
padding-right: var(--env-var-spacing-1);
}
+5 -1
View File
@@ -233,6 +233,7 @@ const BasicTable = ({ data, paginated, reversed, table }) => {
direction="row"
alignItems="center"
justifyContent="space-between"
px={theme.gap.small}
>
<Typography sx={{ opacity: 0.7 }}>
Showing {getRange()} of {data.rows.length} monitor(s)
@@ -277,7 +278,10 @@ const BasicTable = ({ data, paginated, reversed, table }) => {
},
},
}}
sx={{ mt: theme.gap.medium }}
sx={{
mt: theme.gap.medium,
color: theme.palette.otherColors.bluishGray,
}}
/>
</Stack>
)}
@@ -1,28 +0,0 @@
import React from "react";
import "./serverStatus.css";
import PropTypes from "prop-types";
/**
* @component
* @param {Object} props
* @param {string} props.title - The title text for the server status (required)
* @param {number} props.value - The value text to be displayed (required)
* @param {string} props.state - The state of the server (e.g., "online", "offline", "warning") (required)
* @returns {JSX.Element} - Renders the server status component
*/
const ServerStatus = ({ title, value, state }) => {
return (
<div className="server-status-tile">
<div className="server-status-tile-title">{title}</div>
<div className={`server-status-tile-value ` + " " + state}>{value}</div>
</div>
);
};
ServerStatus.propTypes = {
title: PropTypes.string.isRequired,
value: PropTypes.number.isRequired,
state: PropTypes.string.isRequired,
};
export default ServerStatus;
@@ -1,46 +0,0 @@
:root {
--spacing-general-0: 12px 18px;
--spacing-general-1: 20px;
--color-border-0: #eaecf0;
--border-radius-0: 4px;
--font-size-0: 1rem;
--font-size-2: 36px;
--color-up: #17b26a;
--color-down: #f04438;
--color-pause: #fdb022;
}
.server-status-tile {
min-width: 100px;
width: calc(100% - 310px);
padding: var(--spacing-general-0);
border: 1px solid var(--color-border-0);
border-radius: var(--border-radius-0);
background-color: var(--env-var-color-8);
}
.server-status-tile-title {
font-size: var(--font-size-0);
font-weight: 500;
margin-bottom: calc(var(--spacing-general-1) / 2);
color: var(--env-var-color-5);
opacity: 0.6;
}
.server-status-tile-value {
font-size: var(--font-size-2);
font-weight: bolder;
}
.up {
color: var(--color-up);
}
.down {
color: var(--color-down);
}
.pause {
color: var(--color-pause);
}
+3
View File
@@ -15,6 +15,9 @@ aside span.MuiTypography-root {
font-size: var(--env-var-font-size-medium);
line-height: 1;
}
aside .MuiStack-root + span.MuiTypography-root {
font-size: var(--env-var-font-size-medium-plus);
}
aside .MuiListSubheader-root {
font-size: var(--env-var-font-size-small);
font-weight: 500;
+2
View File
@@ -194,6 +194,7 @@ function Sidebar() {
sx={{
pt: theme.gap.small,
px: collapsed ? theme.gap.xs : theme.gap.small,
backgroundColor: "transparent"
}}
>
Menu
@@ -394,6 +395,7 @@ function Sidebar() {
sx={{
pt: theme.gap.small,
px: collapsed ? 0 : theme.gap.small,
backgroundColor: "transparent"
}}
>
Other
+1 -1
View File
@@ -20,7 +20,7 @@
height: calc(100vh - var(--env-var-spacing-2) * 2);
max-width: var(--env-var-side-bar-width);
border: 1px solid var(--color-border-0);
border: 1px solid var(--env-var-color-6);
border-radius: var(--env-var-radius-1);
background-color: var(--env-var-color-8);
@@ -1,13 +1,11 @@
.create-monitor h1.MuiTypography-root {
font-size: var(--env-var-font-size-large-plus);
color: var(--env-var-color-1);
}
.create-monitor h1.MuiTypography-root,
.create-monitor h2.MuiTypography-root {
font-weight: 600;
font-weight: 500;
}
.create-monitor h2.MuiTypography-root {
font-size: var(--env-var-font-size-large);
font-weight: 600;
}
.create-monitor p.MuiTypography-root,
.create-monitor button.MuiButtonBase-root {
@@ -157,9 +157,20 @@ const CreateMonitor = () => {
noValidate
spellCheck="false"
gap={theme.gap.large}
mt={theme.gap.mlplus}
mt={theme.gap.medium}
>
<Typography component="h1">Create new monitor</Typography>
<Typography component="h1">
<Typography
component="span"
fontSize="inherit"
color={theme.palette.otherColors.bluishGray}
>
Create your{" "}
</Typography>
<Typography component="span" fontSize="inherit" fontWeight="inherit">
monitor
</Typography>
</Typography>
<Stack className="config-box">
<Box>
<Typography component="h2">General settings</Typography>
@@ -389,9 +400,9 @@ const CreateMonitor = () => {
/> */}
<Stack direction="row" justifyContent="flex-end">
<Button
id="create-new-monitor-btn"
id="create-monitor-btn"
level="primary"
label="Create new monitor"
label="Create monitor"
onClick={handleCreateMonitor}
disabled={Object.keys(errors).length !== 0 && true}
/>
+8 -6
View File
@@ -1,18 +1,20 @@
.monitors h1.MuiTypography-root,
.monitors h2.MuiTypography-root {
color: var(--env-var-color-1);
font-weight: 600;
}
.monitors h1.MuiTypography-root {
font-size: var(--env-var-font-size-large-plus);
color: var(--env-var-color-1);
font-weight: 500;
}
.monitors h2.MuiTypography-root {
font-size: var(--env-var-font-size-large);
color: var(--env-var-color-5);
font-weight: 500;
}
.monitors p.MuiTypography-root {
font-size: var(--env-var-font-size-medium);
color: var(--env-var-color-5);
}
.monitors h1.MuiTypography-root + p.MuiTypography-root {
font-size: var(--env-var-font-size-medium-plus);
}
.monitors .MuiStack-root > button:not(.MuiIconButton-root) {
font-size: var(--env-var-font-size-medium);
height: var(--env-var-height-2);
@@ -31,7 +33,7 @@
border-radius: 50%;
font-size: var(--env-var-font-size-medium);
font-weight: 500;
color: var(--env-var-color-5);
color: var(--env-var-color-1);
margin-left: 10px;
line-height: 0.8;
}
+224 -39
View File
@@ -1,4 +1,3 @@
import "./index.css";
import { useEffect, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import {
@@ -6,11 +5,7 @@ import {
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 BasicTable from "../../Components/BasicTable";
import { StatusLabel } from "../../Components/Label";
import { createToast } from "../../Utils/toastUtils";
import {
@@ -23,10 +18,17 @@ import {
Stack,
Typography,
} from "@mui/material";
import BasicTable from "../../Components/BasicTable";
import Button from "../../Components/Button";
import Settings from "../../assets/icons/settings-bold.svg?react";
import PropTypes from "prop-types";
import BarChart from "../../Components/Charts/BarChart";
import Breadcrumbs from "../../Components/Breadcrumbs";
import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded";
import background from "../../assets/Images/background-grid.svg";
import Arrow from "../../assets/icons/top-right-arrow.svg?react";
import ClockSnooze from "../../assets/icons/clock-snooze.svg?react";
import "./index.css";
const ActionsMenu = ({ monitor }) => {
const [anchorEl, setAnchorEl] = useState(null);
@@ -141,7 +143,10 @@ const ActionsMenu = ({ monitor }) => {
aria-labelledby="modal-delete-monitor"
aria-describedby="delete-monitor-confirmation"
open={isOpen}
onClose={() => setIsOpen(false)}
onClose={(e) => {
e.stopPropagation();
setIsOpen(false);
}}
disablePortal
>
<Stack
@@ -177,7 +182,10 @@ const ActionsMenu = ({ monitor }) => {
<Button
level="tertiary"
label="Cancel"
onClick={() => setIsOpen(false)}
onClick={(e) => {
e.stopPropagation();
setIsOpen(false);
}}
/>
<Button
level="error"
@@ -212,12 +220,39 @@ ActionsMenu.propTypes = {
*/
const Host = ({ params }) => {
return (
<Stack direction="row" alignItems="baseline" className="host">
{params.title}
<Typography component="span" sx={{ color: params.percentageColor }}>
<Box className="host">
<Box
display="inline-block"
position="relative"
sx={{
fontWeight: 500,
"&:before": {
position: "absolute",
content: `""`,
width: "4px",
height: "4px",
borderRadius: "50%",
backgroundColor: "gray",
opacity: 0.8,
right: "-10px",
top: "42%",
},
}}
>
{params.title}
</Box>
<Typography
component="span"
sx={{
color: params.percentageColor,
fontWeight: 500,
ml: "15px",
}}
>
{params.percentage}%
</Typography>
</Stack>
<Box sx={{ opacity: 0.6 }}>{params.url}</Box>
</Box>
);
};
@@ -229,6 +264,113 @@ Host.propTypes = {
}).isRequired,
};
/**
* StatusBox component displays a status box with a title and value.
* The icon and color change based on the status title.
*
* @param {Object} props
* @param {string} props.title - The status title, which determines the icon and color.
* @param {number|string} props.value - The value to be displayed inside the box.
* @returns {JSX.Element} The rendered StatusBox component.
*/
const StatusBox = ({ title, value }) => {
const theme = useTheme();
let sharedStyles = { position: "absolute", right: 8, opacity: 0.5 };
let color;
let icon;
if (title === "up") {
color = theme.pie.green.stroke;
icon = (
<Box sx={{ ...sharedStyles, top: 8 }}>
<Arrow />
</Box>
);
} else if (title === "down") {
color = theme.pie.red.stroke;
icon = (
<Box sx={{ ...sharedStyles, transform: "rotate(180deg)", top: 5 }}>
<Arrow />
</Box>
);
} else if (title === "paused") {
color = theme.pie.yellow.stroke;
icon = (
<Box sx={{ ...sharedStyles, top: 12, right: 12 }}>
<ClockSnooze />
</Box>
);
}
return (
<Box
position="relative"
flex={1}
border={1}
borderColor={theme.palette.otherColors.graishWhite}
borderRadius={`${theme.shape.borderRadius}px`}
backgroundColor={theme.palette.otherColors.white}
px={theme.gap.large}
py={theme.gap.ml}
overflow="hidden"
sx={{
"&:hover": {
backgroundColor: "#f9fafb",
},
"&:after": {
position: "absolute",
content: `""`,
backgroundImage: `url(${background})`,
width: "400px",
height: "200px",
top: "-10%",
left: "5%",
zIndex: 10000,
pointerEvents: "none",
},
}}
>
<Box
textTransform="uppercase"
fontSize={15}
letterSpacing={0.5}
color={theme.palette.otherColors.bluishGray}
mb={theme.gap.ml}
sx={{ opacity: 0.6 }}
>
{title}
</Box>
{icon}
<Stack
direction="row"
alignItems="flex-start"
fontSize={36}
fontWeight={600}
color={color}
gap="2px"
>
{value}
<Typography
component="span"
fontSize={20}
fontWeight={300}
color={theme.palette.otherColors.bluishGray}
sx={{ opacity: 0.3 }}
>
#
</Typography>
</Stack>
</Box>
);
};
StatusBox.propTypes = {
title: PropTypes.oneOf(["up", "down", "paused"]).isRequired,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
};
/**
* Renders a skeleton layout.
*
@@ -301,6 +443,7 @@ const Monitors = () => {
data.rows = monitorState.monitors.map((monitor, idx) => {
const params = {
url: monitor.url,
title: monitor.name,
percentage: 100,
percentageColor:
@@ -348,34 +491,73 @@ const Monitors = () => {
let loading = monitorState.isLoading && monitorState.monitors.length === 0;
const now = new Date();
const hour = now.getHours();
let greeting = "";
let emoji = "";
if (hour < 12) {
greeting = "morning";
emoji = "🌅";
} else if (hour < 18) {
greeting = "afternoon";
emoji = "🌞";
} else {
greeting = "evening";
emoji = "🌙";
}
return (
<Stack className="monitors" gap={theme.gap.large}>
{loading ? (
<SkeletonLayout />
) : (
<>
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
>
<Typography
component="h1"
sx={{ lineHeight: 1, alignSelf: "flex-end" }}
<Box>
<Breadcrumbs list={[{ name: `monitors`, path: "/monitors" }]} />
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
mt={theme.gap.medium}
>
Hello, {authState.user.firstName}
</Typography>
{monitorState.monitors?.length !== 0 && (
<Button
level="primary"
label="Create monitor"
onClick={() => {
navigate("/monitors/create");
}}
sx={{ fontWeight: 500 }}
/>
)}
</Stack>
<Box>
<Typography component="h1" lineHeight={1}>
<Typography
component="span"
fontSize="inherit"
color={theme.palette.otherColors.bluishGray}
>
Good {greeting},{" "}
</Typography>
<Typography
component="span"
fontSize="inherit"
fontWeight="inherit"
>
{authState.user.firstName} {emoji}
</Typography>
</Typography>
<Typography
sx={{ opacity: 0.8 }}
lineHeight={1}
fontWeight={300}
>
Here’s an overview of your uptime monitors.
</Typography>
</Box>
{monitorState.monitors?.length !== 0 && (
<Button
level="primary"
label="Create monitor"
onClick={() => {
navigate("/monitors/create");
}}
sx={{ fontWeight: 500, alignSelf: "flex-end" }}
/>
)}
</Stack>
</Box>
{monitorState.monitors?.length === 0 ? (
<Stack
alignItems="center"
@@ -406,13 +588,14 @@ const Monitors = () => {
direction="row"
justifyContent="space-between"
>
<ServerStatus title="Up" value={up} state="up" />
<ServerStatus title="Down" value={down} state="down" />
<ServerStatus title="Paused" value={0} state="pause" />
<StatusBox title="up" value={up} />
<StatusBox title="down" value={down} />
<StatusBox title="paused" value={0} />
</Stack>
<Box
flex={1}
p={theme.gap.lgplus}
py={theme.gap.large}
px={theme.gap.lgplus}
border={1}
borderColor={theme.palette.otherColors.graishWhite}
backgroundColor={theme.palette.otherColors.white}
@@ -420,8 +603,10 @@ const Monitors = () => {
borderRadius: `${theme.shape.borderRadius}px`,
}}
>
<Stack direction="row" alignItems="center" mb={theme.gap.large}>
<Typography component="h2">Current monitors</Typography>
<Stack direction="row" alignItems="center" mb={theme.gap.ml}>
<Typography component="h2" letterSpacing={-0.5}>
Actively monitoring
</Typography>
<Box className="current-monitors-counter">
{monitorState.monitors.length}
</Box>
@@ -0,0 +1,67 @@
<svg width="768" height="768" viewBox="0 0 768 768" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_14_2)">
<mask id="mask0_14_2" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="768" height="768">
<path d="M768 0H0V768H768V0Z" fill="url(#paint0_radial_14_2)"/>
</mask>
<g mask="url(#mask0_14_2)">
<mask id="mask1_14_2" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="768" height="768">
<path d="M768 0H0V768H768V0Z" fill="white"/>
</mask>
<g mask="url(#mask1_14_2)">
<mask id="mask2_14_2" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="768" height="768">
<path d="M768 0H0V768H768V0Z" fill="white"/>
</mask>
<g mask="url(#mask2_14_2)">
<path d="M0.5 0V768" stroke="#b8b9bd"/>
<path d="M48.5 0V768" stroke="#b8b9bd"/>
<path d="M96.5 0V768" stroke="#b8b9bd"/>
<path d="M144.5 0V768" stroke="#b8b9bd"/>
<path d="M192.5 0V768" stroke="#b8b9bd"/>
<path d="M240.5 0V768" stroke="#b8b9bd"/>
<path d="M288.5 0V768" stroke="#b8b9bd"/>
<path d="M336.5 0V768" stroke="#b8b9bd"/>
<path d="M384.5 0V768" stroke="#b8b9bd"/>
<path d="M432.5 0V768" stroke="#b8b9bd"/>
<path d="M480.5 0V768" stroke="#b8b9bd"/>
<path d="M528.5 0V768" stroke="#b8b9bd"/>
<path d="M576.5 0V768" stroke="#b8b9bd"/>
<path d="M624.5 0V768" stroke="#b8b9bd"/>
<path d="M672.5 0V768" stroke="#b8b9bd"/>
<path d="M720.5 0V768" stroke="#b8b9bd"/>
</g>
<path d="M767.5 0.5H0.5V767.5H767.5V0.5Z" stroke="#b8b9bd"/>
<mask id="mask3_14_2" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="768" height="768">
<path d="M768 0H0V768H768V0Z" fill="white"/>
</mask>
<g mask="url(#mask3_14_2)">
<path d="M0 47.5H768" stroke="#b8b9bd"/>
<path d="M0 95.5H768" stroke="#b8b9bd"/>
<path d="M0 143.5H768" stroke="#b8b9bd"/>
<path d="M0 191.5H768" stroke="#b8b9bd"/>
<path d="M0 239.5H768" stroke="#b8b9bd"/>
<path d="M0 287.5H768" stroke="#b8b9bd"/>
<path d="M0 335.5H768" stroke="#b8b9bd"/>
<path d="M0 383.5H768" stroke="#b8b9bd"/>
<path d="M0 431.5H768" stroke="#b8b9bd"/>
<path d="M0 479.5H768" stroke="#b8b9bd"/>
<path d="M0 527.5H768" stroke="#b8b9bd"/>
<path d="M0 575.5H768" stroke="#b8b9bd"/>
<path d="M0 623.5H768" stroke="#b8b9bd"/>
<path d="M0 671.5H768" stroke="#b8b9bd"/>
<path d="M0 719.5H768" stroke="#b8b9bd"/>
<path d="M0 767.5H768" stroke="#b8b9bd"/>
</g>
<path d="M767.5 0.5H0.5V767.5H767.5V0.5Z" stroke="#b8b9bd"/>
</g>
</g>
</g>
<defs>
<radialGradient id="paint0_radial_14_2" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(384 384) rotate(90) scale(384)">
<stop/>
<stop offset="1" stop-opacity="0"/>
</radialGradient>
<clipPath id="clip0_14_2">
<rect width="768" height="768" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.5 17H21.5L16.5 22H21.5M21.9506 13C21.9833 12.6711 22 12.3375 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C12.1677 22 12.3344 21.9959 12.5 21.9877C12.6678 21.9795 12.8345 21.9671 13 21.9506M12 6V12L15.7384 13.8692" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 454 B

@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 17L17 7M17 7H7M17 7V17" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 224 B

+1 -1
View File
@@ -85,7 +85,7 @@
--env-var-font-size-medium: 13px;
--env-var-font-size-medium-plus: 14px;
--env-var-font-size-large: 16px;
--env-var-font-size-large-plus: 24px;
--env-var-font-size-large-plus: 22px;
--env-var-font-size-xlarge: 30px;
--env-var-img-width-1: 20px;