Merge pull request #716 from bluewave-labs/feat/monitors-bar-chart
Monitor dashboard bar chart
This commit is contained in:
8 files changed
+178
-51
No files matched your search
@@ -23,8 +23,8 @@ const PulseDot = ({ color }) => {
|
||||
justifyContent="center"
|
||||
>
|
||||
<Box
|
||||
minWidth="16px"
|
||||
minHeight="16px"
|
||||
minWidth="18px"
|
||||
minHeight="18px"
|
||||
sx={{
|
||||
position: "relative",
|
||||
backgroundColor: color,
|
||||
|
||||
@@ -1,22 +1,12 @@
|
||||
.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;
|
||||
}
|
||||
.MuiTable-root .host a svg {
|
||||
width: var(--env-var-font-size-large);
|
||||
height: var(--env-var-font-size-large);
|
||||
}
|
||||
.MuiTable-root .host div:nth-child(2) {
|
||||
.MuiTable-root .host {
|
||||
width: fit-content;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.MuiTable-root .host div:nth-child(2) span {
|
||||
.MuiTable-root .host span {
|
||||
font-size: var(--env-var-font-size-small);
|
||||
margin-left: 10px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.MuiTable-root .label {
|
||||
@@ -128,6 +118,7 @@
|
||||
.MuiTablePagination-root p {
|
||||
color: var(--env-var-color-2);
|
||||
font-weight: 500;
|
||||
font-size: var(--env-var-font-size-small-plus);
|
||||
}
|
||||
.MuiTablePagination-root button,
|
||||
.MuiTablePagination-root .MuiSelect-select {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
.bar-tooltip .MuiTooltip-tooltip {
|
||||
background-color: white;
|
||||
border: solid 1px black;
|
||||
box-shadow: var(--env-var-shadow-1);
|
||||
border: solid 1px var(--env-var-color-6);
|
||||
border-radius: var(--env-var-radius-1);
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.bar-tooltip .MuiTooltip-tooltip p {
|
||||
font-size: var(--env-var-font-size-small-plus);
|
||||
color: var(--env-var-color-2);
|
||||
font-weight: 500;
|
||||
}
|
||||
.bar-tooltip .MuiTooltip-tooltip span {
|
||||
font-size: var(--env-var-font-size-small);
|
||||
color: var(--env-var-color-2);
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Box, Stack, Tooltip, Typography } from "@mui/material";
|
||||
import { formatDate } from "../../../Utils/timeUtils";
|
||||
import { useEffect, useState } from "react";
|
||||
import "./index.css";
|
||||
|
||||
const BarChart = ({ checks = [] }) => {
|
||||
const theme = useTheme();
|
||||
const [animate, setAnimate] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setAnimate(true);
|
||||
});
|
||||
|
||||
// set responseTime to average if there's only one check
|
||||
if (checks.length === 1) {
|
||||
checks[0] = { ...checks[0], responseTime: 50 };
|
||||
}
|
||||
|
||||
if (checks.length !== 25) {
|
||||
const placeholders = Array(25 - checks.length).fill("placeholder");
|
||||
checks = [...checks, ...placeholders];
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
flexWrap="nowrap"
|
||||
gap="3px"
|
||||
height="50px"
|
||||
width="fit-content"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
sx={{
|
||||
cursor: "default",
|
||||
}}
|
||||
>
|
||||
{checks.map((check, index) =>
|
||||
check === "placeholder" ? (
|
||||
<Box
|
||||
key={`${check}-${index}`}
|
||||
position="relative"
|
||||
width="9px"
|
||||
height="100%"
|
||||
backgroundColor={theme.palette.otherColors.fillGray}
|
||||
sx={{
|
||||
borderRadius: "3px",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Tooltip
|
||||
title={
|
||||
<>
|
||||
<Typography>
|
||||
{formatDate(new Date(check.createdAt), { year: undefined })}
|
||||
</Typography>
|
||||
<Box mt={theme.gap.xs}>
|
||||
<Box
|
||||
display="inline-block"
|
||||
width={theme.gap.small}
|
||||
height={theme.gap.small}
|
||||
backgroundColor={
|
||||
check.status
|
||||
? theme.label.up.dotColor
|
||||
: theme.label.down.dotColor
|
||||
}
|
||||
sx={{ borderRadius: "50%" }}
|
||||
/>
|
||||
<Stack
|
||||
display="inline-flex"
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
ml={theme.gap.xs}
|
||||
gap={theme.gap.large}
|
||||
>
|
||||
<Typography component="span" sx={{ opacity: 0.8 }}>
|
||||
Response Time
|
||||
</Typography>
|
||||
<Typography component="span">
|
||||
{check.originalResponseTime}
|
||||
<Typography component="span" sx={{ opacity: 0.8 }}>
|
||||
{" "}
|
||||
ms
|
||||
</Typography>
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
</>
|
||||
}
|
||||
placement="top"
|
||||
key={`check-${check?._id}`}
|
||||
slotProps={{
|
||||
popper: {
|
||||
className: "bar-tooltip",
|
||||
modifiers: [
|
||||
{
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: [0, -10],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
position="relative"
|
||||
width="9px"
|
||||
height="100%"
|
||||
backgroundColor={
|
||||
check.status ? theme.label.up.bgColor : theme.label.down.bgColor
|
||||
}
|
||||
sx={{
|
||||
borderRadius: "3px",
|
||||
"&:hover > .MuiBox-root": {
|
||||
filter: "brightness(0.8)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
position="absolute"
|
||||
bottom={0}
|
||||
width="100%"
|
||||
height={`${animate ? check.responseTime : 0}%`}
|
||||
backgroundColor={
|
||||
check.status
|
||||
? theme.label.up.dotColor
|
||||
: theme.label.down.dotColor
|
||||
}
|
||||
sx={{
|
||||
borderRadius: "3px",
|
||||
transition: "height 600ms cubic-bezier(0.4, 0, 0.2, 1)",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default BarChart;
|
||||
@@ -427,7 +427,7 @@ function Sidebar() {
|
||||
onClick={() =>
|
||||
item.path === "support"
|
||||
? window.open(
|
||||
"https://github.com/bluewave-labs/bluewave-uptime",
|
||||
"https://github.com/bluewave-labs/bluewave-uptime/issues",
|
||||
"_blank",
|
||||
"noreferrer"
|
||||
)
|
||||
|
||||
@@ -10,11 +10,9 @@ 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 BasicTable from "../../Components/BasicTable";
|
||||
import { StatusLabel } from "../../Components/Label";
|
||||
import { createToast } from "../../Utils/toastUtils";
|
||||
import ResponseTimeChart from "../../Components/Charts/ResponseTimeChart";
|
||||
import {
|
||||
Box,
|
||||
IconButton,
|
||||
@@ -28,6 +26,7 @@ import {
|
||||
|
||||
import Settings from "../../assets/icons/settings-bold.svg?react";
|
||||
import PropTypes from "prop-types";
|
||||
import BarChart from "../../Components/Charts/BarChart";
|
||||
|
||||
const ActionsMenu = ({ monitor }) => {
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
@@ -206,7 +205,6 @@ ActionsMenu.propTypes = {
|
||||
*
|
||||
* @component
|
||||
* @param {Object} params - An object containing the following properties:
|
||||
* @param {string} params.url - The URL of the host.
|
||||
* @param {string} params.title - The name of the host.
|
||||
* @param {string} params.percentageColor - The color of the percentage text.
|
||||
* @param {number} params.percentage - The percentage to display.
|
||||
@@ -214,40 +212,17 @@ ActionsMenu.propTypes = {
|
||||
*/
|
||||
const Host = ({ params }) => {
|
||||
return (
|
||||
<Stack direction="row" alignItems="center" className="host">
|
||||
<IconButton
|
||||
aria-label="monitor link"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
window.open(params.url, "_blank", "noreferrer");
|
||||
}}
|
||||
sx={{
|
||||
"&:focus": {
|
||||
outline: "none",
|
||||
},
|
||||
mr: "5px",
|
||||
}}
|
||||
>
|
||||
<OpenInNewPage
|
||||
style={{
|
||||
marginTop: "-1px",
|
||||
marginRight: "-1px",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
<Box>
|
||||
{params.title}
|
||||
<Typography component="span" sx={{ color: params.percentageColor }}>
|
||||
{params.percentage}%
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack direction="row" alignItems="baseline" className="host">
|
||||
{params.title}
|
||||
<Typography component="span" sx={{ color: params.percentageColor }}>
|
||||
{params.percentage}%
|
||||
</Typography>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
Host.propTypes = {
|
||||
params: PropTypes.shape({
|
||||
url: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
percentageColor: PropTypes.string,
|
||||
percentage: PropTypes.number,
|
||||
@@ -326,7 +301,6 @@ const Monitors = () => {
|
||||
|
||||
data.rows = monitorState.monitors.map((monitor, idx) => {
|
||||
const params = {
|
||||
url: monitor.url,
|
||||
title: monitor.name,
|
||||
percentage: 100,
|
||||
percentageColor:
|
||||
@@ -357,7 +331,7 @@ const Monitors = () => {
|
||||
/>
|
||||
),
|
||||
},
|
||||
{ id: idx + 2, data: <ResponseTimeChart checks={reversedChecks} /> },
|
||||
{ id: idx + 2, data: <BarChart checks={reversedChecks} /> },
|
||||
{
|
||||
id: idx + 3,
|
||||
data: (
|
||||
|
||||
@@ -43,7 +43,7 @@ export const formatDurationRounded = (ms) => {
|
||||
return time;
|
||||
};
|
||||
|
||||
export const formatDate = (date) => {
|
||||
export const formatDate = (date, customOptions) => {
|
||||
const options = {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
@@ -51,6 +51,7 @@ export const formatDate = (date) => {
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
hour12: true,
|
||||
...customOptions
|
||||
};
|
||||
|
||||
// Return the date using the specified options
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
--env-var-width-2: 360px;
|
||||
|
||||
--env-var-height-1: 100vh;
|
||||
--env-var-height-2: 36px;
|
||||
--env-var-height-2: 34px;
|
||||
|
||||
--env-var-nav-bar-height: 70px;
|
||||
--env-var-side-bar-width: 250px;
|
||||
|
||||
Reference in new issue
Block a user