No files matched your search
@@ -32,6 +32,7 @@ import { ThemeProvider } from "@emotion/react";
|
||||
import lightTheme from "./Utils/Theme/lightTheme";
|
||||
import darkTheme from "./Utils/Theme/darkTheme";
|
||||
import { useSelector } from "react-redux";
|
||||
import { CssBaseline } from "@mui/material";
|
||||
|
||||
function App() {
|
||||
const AdminCheckedRegister = withAdminCheck(Register);
|
||||
@@ -45,6 +46,7 @@ function App() {
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={mode === "light" ? lightTheme : darkTheme}>
|
||||
<CssBaseline />
|
||||
<Routes>
|
||||
<Route exact path="/" element={<HomeLayout />}>
|
||||
<Route
|
||||
@@ -57,7 +59,7 @@ function App() {
|
||||
element={<ProtectedRoute Component={MonitorsWithAdminProp} />}
|
||||
/>
|
||||
<Route
|
||||
path="/monitors/create"
|
||||
path="/monitors/create/:monitorId?"
|
||||
element={<ProtectedRoute Component={CreateMonitor} />}
|
||||
/>
|
||||
<Route
|
||||
|
||||
@@ -64,10 +64,10 @@ const Avatar = ({ src, small, sx }) => {
|
||||
"&::before": {
|
||||
content: `""`,
|
||||
position: "absolute",
|
||||
top: "0",
|
||||
left: "0",
|
||||
width: `calc(100% - ${border * 2}px)`,
|
||||
height: `calc(100% - ${border * 2}px)`,
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: `100%`,
|
||||
height: `100%`,
|
||||
border: `${border}px solid rgba(255,255,255,0.2)`,
|
||||
borderRadius: "50%",
|
||||
},
|
||||
|
||||
@@ -9,8 +9,8 @@ import {
|
||||
} from "recharts";
|
||||
import { Box, Stack, Typography } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useMemo } from "react";
|
||||
import "./index.css";
|
||||
import React, { useMemo } from "react";
|
||||
|
||||
const CustomToolTip = ({ active, payload, label }) => {
|
||||
const theme = useTheme();
|
||||
@@ -97,7 +97,7 @@ const MonitorDetailsAreaChart = ({ checks }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const memoizedChecks = useMemo(() => checks, []);
|
||||
const memoizedChecks = useMemo(() => checks, [checks[0]]);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -143,12 +143,17 @@ const MonitorDetailsAreaChart = ({ checks }) => {
|
||||
tickLine={false}
|
||||
height={18}
|
||||
/>
|
||||
<Tooltip content={<CustomToolTip />} />
|
||||
<Tooltip
|
||||
cursor={{ stroke: theme.palette.border.light }}
|
||||
content={<CustomToolTip />}
|
||||
/>
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="responseTime"
|
||||
stroke={theme.palette.primary.main}
|
||||
fill="url(#colorUv)"
|
||||
strokeWidth={1.5}
|
||||
activeDot={{ stroke: theme.palette.background.main, r: 5 }}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
|
||||
@@ -79,6 +79,7 @@ const Select = ({
|
||||
displayEmpty
|
||||
inputProps={{ id: id }}
|
||||
IconComponent={KeyboardArrowDownIcon}
|
||||
MenuProps={{ disableScrollLock: true }}
|
||||
sx={{
|
||||
fontSize: 13,
|
||||
minWidth: "125px",
|
||||
|
||||
@@ -138,6 +138,11 @@ const StatusLabel = ({ status, text, customStyles }) => {
|
||||
bgColor: theme.palette.error.bg,
|
||||
borderColor: theme.palette.error.light,
|
||||
},
|
||||
paused: {
|
||||
dotColor: theme.palette.warning.main,
|
||||
bgColor: theme.palette.warning.bg,
|
||||
borderColor: theme.palette.warning.light,
|
||||
},
|
||||
pending: {
|
||||
dotColor: theme.palette.warning.main,
|
||||
bgColor: theme.palette.warning.bg,
|
||||
@@ -175,7 +180,13 @@ const StatusLabel = ({ status, text, customStyles }) => {
|
||||
};
|
||||
|
||||
StatusLabel.propTypes = {
|
||||
status: PropTypes.oneOf(["up", "down", "pending", "cannot resolve"]),
|
||||
status: PropTypes.oneOf([
|
||||
"up",
|
||||
"down",
|
||||
"paused",
|
||||
"pending",
|
||||
"cannot resolve",
|
||||
]),
|
||||
text: PropTypes.string,
|
||||
customStyles: PropTypes.object,
|
||||
};
|
||||
|
||||
@@ -44,7 +44,7 @@ const ProgressUpload = ({
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
border: 1,
|
||||
borderColor: theme.palette.border.light,
|
||||
backgroundColor: "#fafafa",
|
||||
backgroundColor: theme.palette.background.fill,
|
||||
"&:has(.input-error)": {
|
||||
borderColor: theme.palette.error.main,
|
||||
backgroundColor: theme.palette.error.bg,
|
||||
@@ -62,23 +62,35 @@ const ProgressUpload = ({
|
||||
direction="row"
|
||||
mb={error ? 0 : theme.spacing(5)}
|
||||
gap={theme.spacing(5)}
|
||||
alignItems={error ? "center" : "flex-end"}
|
||||
alignItems={error ? "center" : "flex-start"}
|
||||
>
|
||||
{error ? (
|
||||
<ErrorOutlineOutlinedIcon />
|
||||
) : icon ? (
|
||||
<IconButton
|
||||
<Box
|
||||
sx={{
|
||||
position: "relative",
|
||||
height: 30,
|
||||
minWidth: 30,
|
||||
border: 1,
|
||||
borderColor: theme.palette.border.dark,
|
||||
borderRadius: 2,
|
||||
backgroundColor: theme.palette.background.main,
|
||||
pointerEvents: "none",
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
border: 2,
|
||||
borderColor: theme.palette.border.light,
|
||||
boxShadow: theme.shape.boxShadow,
|
||||
"& svg": {
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
width: 23,
|
||||
height: 23,
|
||||
"& path": {
|
||||
fill: theme.palette.other.icon,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
</IconButton>
|
||||
</Box>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
@@ -90,6 +90,14 @@ function Sidebar() {
|
||||
const [open, setOpen] = useState({ Dashboard: false, Account: false });
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const [popup, setPopup] = useState();
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
|
||||
// Remove demo password if demo
|
||||
if (user.role?.includes("demo")) {
|
||||
menu[3].nested = menu[3].nested.filter((item) => {
|
||||
return item.name !== "Password";
|
||||
});
|
||||
}
|
||||
|
||||
const openPopup = (event, id) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
@@ -298,6 +306,7 @@ function Sidebar() {
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl) && popup === item.name}
|
||||
onClose={closePopup}
|
||||
disableScrollLock
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
@@ -583,6 +592,7 @@ function Sidebar() {
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl) && popup === "logout"}
|
||||
onClose={closePopup}
|
||||
disableScrollLock
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
@@ -644,6 +654,9 @@ function Sidebar() {
|
||||
gap: theme.spacing(4),
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
pl: theme.spacing(4),
|
||||
"& svg path": {
|
||||
stroke: theme.palette.other.icon,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<LogoutSvg />
|
||||
|
||||
@@ -331,22 +331,24 @@ const ProfilePanel = () => {
|
||||
borderColor: theme.palette.border.light,
|
||||
}}
|
||||
/>
|
||||
<Box component="form" noValidate spellCheck="false">
|
||||
<Box mb={theme.spacing(6)}>
|
||||
<Typography component="h1">Delete account</Typography>
|
||||
<Typography component="p" sx={{ opacity: 0.6 }}>
|
||||
Note that deleting your account will remove all data from our
|
||||
system. This is permanent and non-recoverable.
|
||||
</Typography>
|
||||
{!user.role.includes("demo") && (
|
||||
<Box component="form" noValidate spellCheck="false">
|
||||
<Box mb={theme.spacing(6)}>
|
||||
<Typography component="h1">Delete account</Typography>
|
||||
<Typography component="p" sx={{ opacity: 0.6 }}>
|
||||
Note that deleting your account will remove all data from our
|
||||
system. This is permanent and non-recoverable.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={() => setIsOpen("delete")}
|
||||
>
|
||||
Delete account
|
||||
</Button>
|
||||
</Box>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={() => setIsOpen("delete")}
|
||||
>
|
||||
Delete account
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
{/* TODO - Update ModalPopup Component with @mui for reusability */}
|
||||
<Modal
|
||||
aria-labelledby="modal-delete-account"
|
||||
@@ -414,12 +416,12 @@ const ProfilePanel = () => {
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
width: 450,
|
||||
width: 475,
|
||||
bgcolor: theme.palette.background.main,
|
||||
border: 1,
|
||||
borderColor: theme.palette.border.light,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
boxShadow: 24,
|
||||
boxShadow: theme.shape.boxShadow,
|
||||
p: theme.spacing(15),
|
||||
"&:focus": {
|
||||
outline: "none",
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import TabPanel from "@mui/lab/TabPanel";
|
||||
import { Button, ButtonGroup, Modal, Stack, Typography } from "@mui/material";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Modal,
|
||||
Stack,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { useEffect, useState } from "react";
|
||||
import Field from "../../Inputs/Field";
|
||||
import { credentials } from "../../../Validation/validation";
|
||||
@@ -24,6 +31,7 @@ const TeamPanel = () => {
|
||||
superadmin: "Super admin",
|
||||
admin: "Admin",
|
||||
user: "Team member",
|
||||
demo: "Demo User",
|
||||
};
|
||||
|
||||
const theme = useTheme();
|
||||
@@ -88,7 +96,7 @@ const TeamPanel = () => {
|
||||
id: idx,
|
||||
data: (
|
||||
<Stack>
|
||||
<Typography style={{ color: theme.palette.text.secondary }}>
|
||||
<Typography color={theme.palette.text.secondary}>
|
||||
{member.firstName + " " + member.lastName}
|
||||
</Typography>
|
||||
<Typography>
|
||||
@@ -322,7 +330,6 @@ const TeamPanel = () => {
|
||||
aria-describedby="invite-member-to-team"
|
||||
open={isOpen}
|
||||
onClose={closeInviteModal}
|
||||
disablePortal
|
||||
>
|
||||
<Stack
|
||||
gap={theme.spacing(5)}
|
||||
@@ -331,29 +338,38 @@ const TeamPanel = () => {
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
width: 400,
|
||||
width: 450,
|
||||
bgcolor: theme.palette.background.main,
|
||||
border: 1,
|
||||
borderColor: theme.palette.border.light,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
boxShadow: 24,
|
||||
boxShadow: theme.shape.boxShadow,
|
||||
p: theme.spacing(15),
|
||||
"&:focus": {
|
||||
outline: "none",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Typography id="modal-invite-member" component="h1">
|
||||
Invite new team member
|
||||
</Typography>
|
||||
<Typography
|
||||
id="invite-member-to-team"
|
||||
component="p"
|
||||
sx={{ mb: theme.spacing(6) }}
|
||||
>
|
||||
When you add a new team member, they will get access to all
|
||||
monitors.
|
||||
</Typography>
|
||||
<Box>
|
||||
<Typography
|
||||
id="modal-invite-member"
|
||||
component="h1"
|
||||
fontWeight={600}
|
||||
fontColor={theme.palette.text.secondary}
|
||||
>
|
||||
Invite new team member
|
||||
</Typography>
|
||||
<Typography
|
||||
id="invite-member-to-team"
|
||||
component="p"
|
||||
fontSize={13}
|
||||
color={theme.palette.text.accent}
|
||||
sx={{ mt: theme.spacing(1), mb: theme.spacing(4) }}
|
||||
>
|
||||
When you add a new team member, they will get access to all
|
||||
monitors.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Field
|
||||
type="email"
|
||||
id="input-team-member"
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
#root:has(.home-layout) {
|
||||
background-color: var(--secondary-bg);
|
||||
}
|
||||
|
||||
.home-layout {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
|
||||
@@ -1,24 +1,15 @@
|
||||
import Sidebar from "../../Components/Sidebar";
|
||||
import { Outlet } from "react-router";
|
||||
import { Box, Stack } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Stack } from "@mui/material";
|
||||
|
||||
import "./index.css";
|
||||
|
||||
const HomeLayout = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box backgroundColor={theme.palette.background.alt}>
|
||||
<Stack
|
||||
className="home-layout"
|
||||
flexDirection="row"
|
||||
gap={theme.spacing(14)}
|
||||
>
|
||||
<Sidebar />
|
||||
<Outlet />
|
||||
</Stack>
|
||||
</Box>
|
||||
<Stack className="home-layout" flexDirection="row" gap={14}>
|
||||
<Sidebar />
|
||||
<Outlet />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -22,15 +22,20 @@ const Account = ({ open = "profile" }) => {
|
||||
const handleTabChange = (event, newTab) => {
|
||||
navigate(`/account/${newTab}`);
|
||||
};
|
||||
|
||||
let tabList = ["Profile", "Password", "Team"];
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
|
||||
const requiredRoles = ["superadmin", "admin"];
|
||||
let tabList = ["Profile", "Password", "Team"];
|
||||
const hideTeams = !requiredRoles.some((role) => user.role.includes(role));
|
||||
if (hideTeams) {
|
||||
tabList = ["Profile", "Password"];
|
||||
}
|
||||
|
||||
// Remove password for demo
|
||||
if (user.role.includes("demo")) {
|
||||
tabList = ["Profile"];
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="account"
|
||||
@@ -74,7 +79,7 @@ const Account = ({ open = "profile" }) => {
|
||||
</TabList>
|
||||
</Box>
|
||||
<ProfilePanel />
|
||||
<PasswordPanel />
|
||||
{user.role.includes("superadmin") && <PasswordPanel />}
|
||||
{!hideTeams && <TeamPanel />}
|
||||
</TabContext>
|
||||
</Box>
|
||||
|
||||
@@ -5,7 +5,8 @@ import { useDispatch } from "react-redux";
|
||||
import { useNavigate } from "react-router";
|
||||
import { createToast } from "../../Utils/toastUtils";
|
||||
import { forgotPassword } from "../../Features/Auth/authSlice";
|
||||
import background from "../../assets/Images/background_pattern_decorative.png";
|
||||
import { IconBox } from "./styled";
|
||||
import Background from "../../assets/Images/background-grid.svg?react";
|
||||
import EmailIcon from "../../assets/icons/email.svg?react";
|
||||
import Logo from "../../assets/icons/bwu-icon.svg?react";
|
||||
import "./index.css";
|
||||
@@ -93,16 +94,20 @@ const CheckEmail = () => {
|
||||
fontWeight: 600,
|
||||
fontSize: 22,
|
||||
},
|
||||
"& p": {
|
||||
fontSize: 14,
|
||||
color: theme.palette.text.accent,
|
||||
},
|
||||
"& p": { color: theme.palette.text.accent, fontSize: 13.5 },
|
||||
"& span": { fontSize: "inherit" },
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
className="background-pattern-svg"
|
||||
sx={{ backgroundImage: `url(${background})` }}
|
||||
/>
|
||||
sx={{
|
||||
"& svg g g:last-of-type path": {
|
||||
stroke: theme.palette.border.light,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Background style={{ width: "100%" }} />
|
||||
</Box>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
@@ -134,14 +139,16 @@ const CheckEmail = () => {
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
gap={{ xs: theme.spacing(8), sm: theme.spacing(12) }}
|
||||
gap={{ xs: theme.spacing(8), sm: theme.spacing(10) }}
|
||||
alignItems="center"
|
||||
textAlign="center"
|
||||
>
|
||||
<Box>
|
||||
<EmailIcon alt="email icon" />
|
||||
<IconBox>
|
||||
<EmailIcon alt="email icon" />
|
||||
</IconBox>
|
||||
<Typography component="h1">Check your email</Typography>
|
||||
<Typography mt={theme.spacing(2)}>
|
||||
<Typography>
|
||||
We sent a password reset link to{" "}
|
||||
<Typography className="email-sent-to" component="span">
|
||||
{email || "username@email.com"}
|
||||
@@ -159,7 +166,7 @@ const CheckEmail = () => {
|
||||
>
|
||||
Open email app
|
||||
</Button>
|
||||
<Typography sx={{ alignSelf: "center", mb: theme.spacing(6) }}>
|
||||
<Typography sx={{ alignSelf: "center", mt: theme.spacing(6) }}>
|
||||
Didn't receive the email?{" "}
|
||||
<Typography
|
||||
component="span"
|
||||
|
||||
@@ -6,10 +6,11 @@ import { forgotPassword } from "../../Features/Auth/authSlice";
|
||||
import { useEffect, useState } from "react";
|
||||
import { credentials } from "../../Validation/validation";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { IconBox } from "./styled";
|
||||
import Field from "../../Components/Inputs/Field";
|
||||
import Logo from "../../assets/icons/bwu-icon.svg?react";
|
||||
import Key from "../../assets/icons/key.svg?react";
|
||||
import background from "../../assets/Images/background_pattern_decorative.png";
|
||||
import Background from "../../assets/Images/background-grid.svg?react";
|
||||
import LoadingButton from "@mui/lab/LoadingButton";
|
||||
import "./index.css";
|
||||
|
||||
@@ -94,7 +95,7 @@ const ForgotPassword = () => {
|
||||
"& h1": {
|
||||
color: theme.palette.primary.main,
|
||||
fontWeight: 600,
|
||||
fontSize: 24,
|
||||
fontSize: 21,
|
||||
},
|
||||
"& p": {
|
||||
fontSize: 14,
|
||||
@@ -104,8 +105,14 @@ const ForgotPassword = () => {
|
||||
>
|
||||
<Box
|
||||
className="background-pattern-svg"
|
||||
sx={{ backgroundImage: `url(${background})` }}
|
||||
/>
|
||||
sx={{
|
||||
"& svg g g:last-of-type path": {
|
||||
stroke: theme.palette.border.light,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Background style={{ width: "100%" }} />
|
||||
</Box>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
@@ -142,7 +149,9 @@ const ForgotPassword = () => {
|
||||
textAlign="center"
|
||||
>
|
||||
<Box>
|
||||
<Key alt="password key icon" />
|
||||
<IconBox>
|
||||
<Key alt="password key icon" />
|
||||
</IconBox>
|
||||
<Typography component="h1">Forgot password?</Typography>
|
||||
<Typography>
|
||||
No worries, we'll send you reset instructions.
|
||||
|
||||
@@ -8,13 +8,14 @@ import { useDispatch, useSelector } from "react-redux";
|
||||
import { createToast } from "../../Utils/toastUtils";
|
||||
import { networkService } from "../../main";
|
||||
import Field from "../../Components/Inputs/Field";
|
||||
import background from "../../assets/Images/background_pattern_decorative.png";
|
||||
import Background from "../../assets/Images/background-grid.svg?react";
|
||||
import Logo from "../../assets/icons/bwu-icon.svg?react";
|
||||
import Mail from "../../assets/icons/mail.svg?react";
|
||||
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
|
||||
import PropTypes from "prop-types";
|
||||
import { logger } from "../../Utils/Logger";
|
||||
import "./index.css";
|
||||
const DEMO = import.meta.env.VITE_APP_DEMO;
|
||||
|
||||
/**
|
||||
* Displays the initial landing page.
|
||||
@@ -278,9 +279,9 @@ const StepTwo = ({ form, errors, onSubmit, onChange, onBack }) => {
|
||||
textAlign="center"
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "103%",
|
||||
top: "104%",
|
||||
left: "50%",
|
||||
transform: "translateX(-50%)"
|
||||
transform: "translateX(-50%)",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
@@ -327,8 +328,8 @@ const Login = () => {
|
||||
};
|
||||
|
||||
const [form, setForm] = useState({
|
||||
email: "",
|
||||
password: "",
|
||||
email: DEMO !== undefined ? "uptimedemo@demo.com" : "",
|
||||
password: DEMO !== undefined ? "Demouser1!" : "",
|
||||
});
|
||||
const [errors, setErrors] = useState({});
|
||||
const [step, setStep] = useState(0);
|
||||
@@ -438,18 +439,22 @@ const Login = () => {
|
||||
"& h1": {
|
||||
color: theme.palette.primary.main,
|
||||
fontWeight: 600,
|
||||
fontSize: 30,
|
||||
},
|
||||
"& p": {
|
||||
fontSize: 14,
|
||||
color: theme.palette.text.accent,
|
||||
fontSize: 28,
|
||||
},
|
||||
"& p": { fontSize: 14, color: theme.palette.text.accent },
|
||||
"& span": { fontSize: "inherit" },
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
className="background-pattern-svg"
|
||||
sx={{ backgroundImage: `url(${background})` }}
|
||||
/>
|
||||
sx={{
|
||||
"& svg g g:last-of-type path": {
|
||||
stroke: theme.palette.border.light,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Background style={{ width: "100%" }} />
|
||||
</Box>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
|
||||
@@ -4,8 +4,9 @@ import { useNavigate } from "react-router";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { clearAuthState } from "../../Features/Auth/authSlice";
|
||||
import { clearUptimeMonitorState } from "../../Features/UptimeMonitors/uptimeMonitorsSlice";
|
||||
import background from "../../assets/Images/background_pattern_decorative.png";
|
||||
import ConfirmIcon from "../../assets/icons/confirm-icon.svg?react";
|
||||
import { IconBox } from "./styled";
|
||||
import Background from "../../assets/Images/background-grid.svg?react";
|
||||
import ConfirmIcon from "../../assets/icons/check-outlined.svg?react";
|
||||
import Logo from "../../assets/icons/bwu-icon.svg?react";
|
||||
import "./index.css";
|
||||
|
||||
@@ -28,18 +29,21 @@ const NewPasswordConfirmed = () => {
|
||||
"& h1": {
|
||||
color: theme.palette.primary.main,
|
||||
fontWeight: 600,
|
||||
fontSize: 22,
|
||||
},
|
||||
"& p": {
|
||||
fontSize: 14,
|
||||
color: theme.palette.text.accent,
|
||||
fontSize: 21,
|
||||
},
|
||||
"& p": { fontSize: 13.5, color: theme.palette.text.accent },
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
className="background-pattern-svg"
|
||||
sx={{ backgroundImage: `url(${background})` }}
|
||||
/>
|
||||
sx={{
|
||||
"& svg g g:last-of-type path": {
|
||||
stroke: theme.palette.border.light,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Background style={{ width: "100%" }} />
|
||||
</Box>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
@@ -76,9 +80,11 @@ const NewPasswordConfirmed = () => {
|
||||
textAlign="center"
|
||||
>
|
||||
<Box>
|
||||
<ConfirmIcon alt="password confirm icon" />
|
||||
<IconBox>
|
||||
<ConfirmIcon alt="password confirm icon" />
|
||||
</IconBox>
|
||||
<Typography component="h1">Password reset</Typography>
|
||||
<Typography mt={theme.spacing(2)}>
|
||||
<Typography mb={theme.spacing(2)}>
|
||||
Your password has been successfully reset. Click below to log in
|
||||
magically.
|
||||
</Typography>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { credentials } from "../../../Validation/validation";
|
||||
import { createToast } from "../../../Utils/toastUtils";
|
||||
import { register } from "../../../Features/Auth/authSlice";
|
||||
import { useParams } from "react-router-dom";
|
||||
import background from "../../../assets/Images/background_pattern_decorative.png";
|
||||
import Background from "../../../assets/Images/background-grid.svg?react";
|
||||
import Logo from "../../../assets/icons/bwu-icon.svg?react";
|
||||
import Mail from "../../../assets/icons/mail.svg?react";
|
||||
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
|
||||
@@ -658,18 +658,22 @@ const Register = ({ isSuperAdmin }) => {
|
||||
"& h1": {
|
||||
color: theme.palette.primary.main,
|
||||
fontWeight: 600,
|
||||
fontSize: 30,
|
||||
},
|
||||
"& p": {
|
||||
fontSize: 14,
|
||||
color: theme.palette.text.accent,
|
||||
fontSize: 28,
|
||||
},
|
||||
"& p": { fontSize: 14, color: theme.palette.text.accent },
|
||||
"& span": { fontSize: "inherit" },
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
className="background-pattern-svg"
|
||||
sx={{ backgroundImage: `url(${background})` }}
|
||||
/>
|
||||
sx={{
|
||||
"& svg g g:last-of-type path": {
|
||||
stroke: theme.palette.border.light,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Background style={{ width: "100%" }} />
|
||||
</Box>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
|
||||
@@ -9,11 +9,12 @@ import { credentials } from "../../Validation/validation";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Check from "../../Components/Check/Check";
|
||||
import Field from "../../Components/Inputs/Field";
|
||||
import LockIcon from "../../assets/icons/lock-button-icon.svg?react";
|
||||
import background from "../../assets/Images/background_pattern_decorative.png";
|
||||
import LockIcon from "../../assets/icons/lock.svg?react";
|
||||
import Background from "../../assets/Images/background-grid.svg?react";
|
||||
import Logo from "../../assets/icons/bwu-icon.svg?react";
|
||||
import LoadingButton from "@mui/lab/LoadingButton";
|
||||
import "./index.css";
|
||||
import { IconBox } from "./styled";
|
||||
|
||||
const SetNewPassword = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -121,8 +122,14 @@ const SetNewPassword = () => {
|
||||
>
|
||||
<Box
|
||||
className="background-pattern-svg"
|
||||
sx={{ backgroundImage: `url(${background})` }}
|
||||
/>
|
||||
sx={{
|
||||
"& svg g g:last-of-type path": {
|
||||
stroke: theme.palette.border.light,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Background style={{ width: "100%" }} />
|
||||
</Box>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
@@ -159,7 +166,9 @@ const SetNewPassword = () => {
|
||||
textAlign="center"
|
||||
>
|
||||
<Box>
|
||||
<LockIcon alt="lock icon" />
|
||||
<IconBox>
|
||||
<LockIcon alt="lock icon" />
|
||||
</IconBox>
|
||||
<Typography component="h1">Set new password</Typography>
|
||||
<Typography>
|
||||
Your new password must be different to previously used passwords.
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
font-size: var(--env-var-font-size-xlarge);
|
||||
font-weight: 600;
|
||||
}
|
||||
.auth button:not(.MuiIconButton-root),
|
||||
.auth p,
|
||||
.auth span {
|
||||
.auth button:not(.MuiIconButton-root) {
|
||||
font-size: var(--env-var-font-size-medium-plus);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Box, styled } from "@mui/material";
|
||||
|
||||
export const IconBox = styled(Box)(({ theme }) => ({
|
||||
height: 48,
|
||||
minWidth: 48,
|
||||
width: 48,
|
||||
position: "relative",
|
||||
border: 1,
|
||||
borderStyle: "solid",
|
||||
borderColor: theme.palette.border.dark,
|
||||
borderRadius: 12,
|
||||
backgroundColor: theme.palette.background.accent,
|
||||
margin: "auto",
|
||||
marginBottom: 8,
|
||||
"& svg": {
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
width: 24,
|
||||
height: 24,
|
||||
"& path": {
|
||||
stroke: theme.palette.text.tertiary,
|
||||
},
|
||||
},
|
||||
}));
|
||||
@@ -1,10 +1,3 @@
|
||||
.configure-monitor h1.MuiTypography-root {
|
||||
font-size: var(--env-var-font-size-large-plus);
|
||||
font-weight: 600;
|
||||
}
|
||||
.configure-monitor p.MuiTypography-root {
|
||||
font-size: var(--env-var-font-size-medium);
|
||||
}
|
||||
.configure-monitor h1.MuiTypography-root + span.MuiTypography-root {
|
||||
font-size: var(--env-var-font-size-small-plus);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useNavigate, useParams } from "react-router";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Box, Button, Modal, Stack, Typography } from "@mui/material";
|
||||
import { Box, Button, Modal, Stack, Tooltip, Typography } from "@mui/material";
|
||||
import { monitorValidation } from "../../../Validation/validation";
|
||||
import { createToast } from "../../../Utils/toastUtils";
|
||||
import { logger } from "../../../Utils/Logger";
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
deleteUptimeMonitor,
|
||||
} from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
|
||||
import Field from "../../../Components/Inputs/Field";
|
||||
import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline";
|
||||
import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded";
|
||||
import PauseIcon from "../../../assets/icons/pause-icon.svg?react";
|
||||
import ResumeIcon from "../../../assets/icons/resume-icon.svg?react";
|
||||
import Select from "../../../Components/Inputs/Select";
|
||||
import Checkbox from "../../../Components/Inputs/Checkbox";
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
@@ -202,7 +202,7 @@ const Configure = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack className="configure-monitor" gap={theme.spacing(12)}>
|
||||
<Stack className="configure-monitor" gap={theme.spacing(10)}>
|
||||
{Object.keys(monitor).length === 0 ? (
|
||||
<SkeletonLayout />
|
||||
) : (
|
||||
@@ -222,27 +222,74 @@ const Configure = () => {
|
||||
flex={1}
|
||||
>
|
||||
<Stack direction="row" gap={theme.spacing(2)}>
|
||||
<PulseDot color={statusColor[monitor?.status ?? undefined]} />
|
||||
<Box>
|
||||
{parsedUrl?.host ? (
|
||||
<Typography
|
||||
component="h1"
|
||||
mb={theme.spacing(2)}
|
||||
lineHeight={1}
|
||||
color={theme.palette.text.primary}
|
||||
>
|
||||
{parsedUrl.host || "..."}
|
||||
</Typography>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
<Typography
|
||||
component="span"
|
||||
lineHeight={theme.spacing(12)}
|
||||
sx={{ color: statusColor[monitor?.status ?? undefined] }}
|
||||
component="h1"
|
||||
fontSize={22}
|
||||
fontWeight={500}
|
||||
color={theme.palette.text.primary}
|
||||
>
|
||||
{statusMsg[monitor?.status ?? undefined]}
|
||||
{monitor.name}
|
||||
</Typography>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
height="fit-content"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Tooltip
|
||||
title={statusMsg[monitor?.status ?? undefined]}
|
||||
disableInteractive
|
||||
slotProps={{
|
||||
popper: {
|
||||
modifiers: [
|
||||
{
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: [0, -8],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<PulseDot
|
||||
color={statusColor[monitor?.status ?? undefined]}
|
||||
/>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
<Typography
|
||||
component="h2"
|
||||
fontSize={14.5}
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
{monitor.url?.replace(/^https?:\/\//, "") || "..."}
|
||||
</Typography>
|
||||
<Typography
|
||||
position="relative"
|
||||
fontSize={12}
|
||||
color={theme.palette.text.tertiary}
|
||||
ml={theme.spacing(6)}
|
||||
mt={theme.spacing(1)}
|
||||
sx={{
|
||||
"&:before": {
|
||||
position: "absolute",
|
||||
content: `""`,
|
||||
width: 4,
|
||||
height: 4,
|
||||
borderRadius: "50%",
|
||||
backgroundColor: theme.palette.text.tertiary,
|
||||
opacity: 0.8,
|
||||
left: -10,
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
Editting...
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
@@ -255,24 +302,29 @@ const Configure = () => {
|
||||
color="secondary"
|
||||
loading={isLoading}
|
||||
sx={{
|
||||
border: "none",
|
||||
backgroundColor: theme.palette.background.main,
|
||||
px: theme.spacing(5),
|
||||
pl: theme.spacing(4),
|
||||
pr: theme.spacing(6),
|
||||
mr: theme.spacing(6),
|
||||
"& svg": {
|
||||
mr: theme.spacing(2),
|
||||
width: 22,
|
||||
height: 22,
|
||||
"& path": {
|
||||
stroke: theme.palette.text.tertiary,
|
||||
strokeWidth: 1.7,
|
||||
},
|
||||
},
|
||||
}}
|
||||
onClick={handlePause}
|
||||
>
|
||||
{monitor?.isActive ? (
|
||||
<>
|
||||
<PauseCircleOutlineIcon />
|
||||
<PauseIcon />
|
||||
Pause
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<PlayCircleOutlineRoundedIcon />
|
||||
<ResumeIcon />
|
||||
Resume
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { Box, Button, ButtonGroup, Stack, Typography } from "@mui/material";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { monitorValidation } from "../../../Validation/validation";
|
||||
import { createUptimeMonitor } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { createToast } from "../../../Utils/toastUtils";
|
||||
import { logger } from "../../../Utils/Logger";
|
||||
@@ -13,11 +13,13 @@ import Field from "../../../Components/Inputs/Field";
|
||||
import Select from "../../../Components/Inputs/Select";
|
||||
import Checkbox from "../../../Components/Inputs/Checkbox";
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
import { getUptimeMonitorById } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
|
||||
import "./index.css";
|
||||
|
||||
const CreateMonitor = () => {
|
||||
const MS_PER_MINUTE = 60000;
|
||||
const { user, authToken } = useSelector((state) => state.auth);
|
||||
const { monitors } = useSelector((state) => state.uptimeMonitors);
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
@@ -30,6 +32,7 @@ const CreateMonitor = () => {
|
||||
"notify-email-default": "notification-email",
|
||||
};
|
||||
|
||||
const { monitorId } = useParams();
|
||||
const [monitor, setMonitor] = useState({
|
||||
url: "",
|
||||
name: "",
|
||||
@@ -40,6 +43,36 @@ const CreateMonitor = () => {
|
||||
const [https, setHttps] = useState(true);
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMonitor = async () => {
|
||||
if (monitorId) {
|
||||
const action = await dispatch(
|
||||
getUptimeMonitorById({ authToken, monitorId })
|
||||
);
|
||||
|
||||
if (action.payload.success) {
|
||||
const data = action.payload.data;
|
||||
const { name, ...rest } = data; //data.name is read-only
|
||||
if (rest.type === "http") {
|
||||
const url = new URL(rest.url);
|
||||
rest.url = url.host;
|
||||
}
|
||||
rest.name = `${name} (Clone)`;
|
||||
rest.interval /= MS_PER_MINUTE;
|
||||
setMonitor({
|
||||
...rest,
|
||||
});
|
||||
} else {
|
||||
navigate("/not-found", { replace: true });
|
||||
createToast({
|
||||
body: "There was an error cloning the monitor.",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
fetchMonitor();
|
||||
}, [monitorId, authToken, monitors]);
|
||||
|
||||
const handleChange = (event, name) => {
|
||||
const { value, id } = event.target;
|
||||
if (!name) name = idMap[id];
|
||||
|
||||
@@ -15,7 +15,6 @@ import { networkService } from "../../../main";
|
||||
import { logger } from "../../../Utils/Logger";
|
||||
import {
|
||||
formatDate,
|
||||
formatDuration,
|
||||
formatDurationRounded,
|
||||
formatDurationSplit,
|
||||
} from "../../../Utils/timeUtils";
|
||||
@@ -35,13 +34,14 @@ import { StatBox, ChartBox, IconBox } from "./styled";
|
||||
import { DownBarChart, ResponseGaugeChart, UpBarChart } from "./Charts";
|
||||
import SkeletonLayout from "./skeleton";
|
||||
import "./index.css";
|
||||
|
||||
import useUtils from "../utils";
|
||||
/**
|
||||
* Details page component displaying monitor details and related information.
|
||||
* @component
|
||||
*/
|
||||
const DetailsPage = ({ isAdmin }) => {
|
||||
const theme = useTheme();
|
||||
const { statusColor, statusStyles, statusMsg, determineState } = useUtils();
|
||||
const [monitor, setMonitor] = useState({});
|
||||
const { monitorId } = useParams();
|
||||
const { authToken } = useSelector((state) => state.auth);
|
||||
@@ -90,15 +90,17 @@ const DetailsPage = ({ isAdmin }) => {
|
||||
monitorId
|
||||
);
|
||||
|
||||
let [month, day, year] = res?.data?.data?.certificateDate.split("/");
|
||||
const date = new Date(year, month - 1, day);
|
||||
if (res?.data?.data?.certificateDate) {
|
||||
let [month, day, year] = res.data.data.certificateDate.split("/");
|
||||
const date = new Date(year, month - 1, day);
|
||||
|
||||
setCertificateExpiry(
|
||||
formatDate(date, {
|
||||
hour: undefined,
|
||||
minute: undefined,
|
||||
}) ?? "N/A"
|
||||
);
|
||||
setCertificateExpiry(
|
||||
formatDate(date, {
|
||||
hour: undefined,
|
||||
minute: undefined,
|
||||
}) ?? "N/A"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
@@ -121,18 +123,6 @@ const DetailsPage = ({ isAdmin }) => {
|
||||
const [hoveredUptimeData, setHoveredUptimeData] = useState(null);
|
||||
const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null);
|
||||
|
||||
const statusColor = {
|
||||
true: theme.palette.success.main,
|
||||
false: theme.palette.error.main,
|
||||
undefined: theme.palette.warning.main,
|
||||
};
|
||||
|
||||
const statusMsg = {
|
||||
true: "Your site is up.",
|
||||
false: "Your site is down.",
|
||||
undefined: "Pending...",
|
||||
};
|
||||
|
||||
return (
|
||||
<Box className="monitor-details">
|
||||
{loading ? (
|
||||
@@ -158,11 +148,12 @@ const DetailsPage = ({ isAdmin }) => {
|
||||
</Typography>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="flex-end"
|
||||
alignItems="center"
|
||||
height="fit-content"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Tooltip
|
||||
title={statusMsg[monitor?.status ?? undefined]}
|
||||
title={statusMsg[determineState(monitor)]}
|
||||
disableInteractive
|
||||
slotProps={{
|
||||
popper: {
|
||||
@@ -178,20 +169,19 @@ const DetailsPage = ({ isAdmin }) => {
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<PulseDot
|
||||
color={statusColor[monitor?.status ?? undefined]}
|
||||
/>
|
||||
<PulseDot color={statusColor[determineState(monitor)]} />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
<Typography
|
||||
component="h2"
|
||||
fontSize={14.5}
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
{monitor.url?.replace(/^https?:\/\//, "") || "..."}
|
||||
</Typography>
|
||||
<Typography
|
||||
mt={theme.spacing(1)}
|
||||
ml={theme.spacing(6)}
|
||||
lineHeight="20px"
|
||||
fontSize={12}
|
||||
position="relative"
|
||||
color={theme.palette.text.tertiary}
|
||||
@@ -205,7 +195,8 @@ const DetailsPage = ({ isAdmin }) => {
|
||||
backgroundColor: theme.palette.text.tertiary,
|
||||
opacity: 0.8,
|
||||
left: -9,
|
||||
top: "42%",
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
@@ -289,27 +280,7 @@ const DetailsPage = ({ isAdmin }) => {
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack direction="row" gap={theme.spacing(8)}>
|
||||
<StatBox
|
||||
sx={
|
||||
monitor?.status === undefined
|
||||
? {
|
||||
backgroundColor: theme.palette.warning.light,
|
||||
borderColor: theme.palette.warning.border,
|
||||
"& h2": { color: theme.palette.warning.main },
|
||||
}
|
||||
: monitor?.status
|
||||
? {
|
||||
backgroundColor: theme.palette.success.bg,
|
||||
borderColor: theme.palette.success.light,
|
||||
"& h2": { color: theme.palette.success.main },
|
||||
}
|
||||
: {
|
||||
backgroundColor: theme.palette.error.bg,
|
||||
borderColor: theme.palette.error.light,
|
||||
"& h2": { color: theme.palette.error.main },
|
||||
}
|
||||
}
|
||||
>
|
||||
<StatBox sx={statusStyles[determineState(monitor)]}>
|
||||
<Typography component="h2">active for</Typography>
|
||||
<Typography>
|
||||
{splitDuration(monitor?.uptimeDuration)}
|
||||
|
||||
@@ -32,6 +32,7 @@ import LeftArrow from "../../../../assets/icons/left-arrow.svg?react";
|
||||
import RightArrow from "../../../../assets/icons/right-arrow.svg?react";
|
||||
import SelectorVertical from "../../../../assets/icons/selector-vertical.svg?react";
|
||||
import ActionsMenu from "../actionsMenu";
|
||||
import useUtils from "../../utils";
|
||||
|
||||
/**
|
||||
* Component for pagination actions (first, previous, next, last).
|
||||
@@ -47,7 +48,6 @@ import ActionsMenu from "../actionsMenu";
|
||||
*/
|
||||
const TablePaginationActions = (props) => {
|
||||
const { count, page, rowsPerPage, onPageChange } = props;
|
||||
|
||||
const handleFirstPageButtonClick = (event) => {
|
||||
onPageChange(event, 0);
|
||||
};
|
||||
@@ -110,6 +110,7 @@ const MonitorTable = ({ isAdmin }) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const { determineState } = useUtils();
|
||||
|
||||
const { rowsPerPage } = useSelector((state) => state.ui.monitors);
|
||||
const [page, setPage] = useState(0);
|
||||
@@ -219,14 +220,11 @@ const MonitorTable = ({ isAdmin }) => {
|
||||
title: monitor.name,
|
||||
percentage: uptimePercentage,
|
||||
percentageColor,
|
||||
status:
|
||||
monitor.status === undefined
|
||||
? "pending"
|
||||
: monitor.status === true
|
||||
? "up"
|
||||
: "down",
|
||||
status: determineState(monitor),
|
||||
};
|
||||
|
||||
console.log(params);
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
key={monitor._id}
|
||||
@@ -302,6 +300,7 @@ const MonitorTable = ({ isAdmin }) => {
|
||||
select: {
|
||||
MenuProps: {
|
||||
keepMounted: true,
|
||||
disableScrollLock: true,
|
||||
PaperProps: {
|
||||
className: "pagination-dropdown",
|
||||
sx: {
|
||||
|
||||
@@ -137,6 +137,14 @@ const ActionsMenu = ({ monitor, isAdmin, updateCallback }) => {
|
||||
Configure
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(`/monitors/create/${actions.id}`);
|
||||
}}
|
||||
>
|
||||
Clone
|
||||
</MenuItem>
|
||||
{isAdmin && (
|
||||
<MenuItem
|
||||
onClick={(e) => {
|
||||
|
||||
@@ -110,8 +110,7 @@ const Monitors = ({ isAdmin }) => {
|
||||
borderColor={theme.palette.border.light}
|
||||
backgroundColor={theme.palette.background.accent}
|
||||
>
|
||||
{monitorState?.monitorsSummary?.monitors?.monitors
|
||||
?.length || 0}
|
||||
{monitorState?.monitorsSummary?.monitorCounts?.total || 0}
|
||||
</Box>
|
||||
{/* TODO - add search bar */}
|
||||
</Stack>
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
import Host from "./host";
|
||||
import ActionsMenu from "./actionsMenu";
|
||||
import BarChart from "../../../Components/Charts/BarChart";
|
||||
import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded";
|
||||
import { StatusLabel } from "../../../Components/Label";
|
||||
import { Box } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
|
||||
const data = {
|
||||
cols: [
|
||||
{ id: 1, name: "Host" },
|
||||
{
|
||||
id: 2,
|
||||
name: (
|
||||
<Box width="max-content">
|
||||
Status
|
||||
<span>
|
||||
<ArrowDownwardRoundedIcon />
|
||||
</span>
|
||||
</Box>
|
||||
),
|
||||
},
|
||||
{ id: 3, name: "Response Time" },
|
||||
{ id: 4, name: "Type" },
|
||||
{ id: 5, name: "Actions" },
|
||||
],
|
||||
rows: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds table data for a list of monitors.
|
||||
*
|
||||
* @param {Array} monitors - An array of monitor objects containing information about each monitor.
|
||||
* @param {boolean} isAdmin - Flag indicating if the current user is an admin.
|
||||
* @param {Function} navigate - A function to navigate to the monitor detail page.
|
||||
* @returns {Object} The data structure containing columns and rows for the table.
|
||||
*/
|
||||
export const buildData = (monitors, isAdmin, navigate) => {
|
||||
const theme = useTheme();
|
||||
|
||||
data.rows = monitors.map((monitor, idx) => {
|
||||
let uptimePercentage = "";
|
||||
let percentageColor = theme.palette.percentage.uptimeExcellent;
|
||||
|
||||
// Determine uptime percentage and color based on the monitor's uptimePercentage value
|
||||
if (monitor.uptimePercentage !== undefined) {
|
||||
uptimePercentage =
|
||||
monitor.uptimePercentage === 0
|
||||
? "0"
|
||||
: (monitor.uptimePercentage * 100).toFixed(2);
|
||||
|
||||
percentageColor =
|
||||
monitor.uptimePercentage < 0.25
|
||||
? theme.palette.percentage.uptimePoor
|
||||
: monitor.uptimePercentage < 0.5
|
||||
? theme.palette.percentage.uptimeFair
|
||||
: monitor.uptimePercentage < 0.75
|
||||
? theme.palette.percentage.uptimeGood
|
||||
: theme.palette.percentage.uptimeExcellent;
|
||||
}
|
||||
|
||||
const params = {
|
||||
url: monitor.url,
|
||||
title: monitor.name,
|
||||
percentage: uptimePercentage,
|
||||
percentageColor,
|
||||
status:
|
||||
monitor.status === undefined
|
||||
? "pending"
|
||||
: monitor.status === true
|
||||
? "up"
|
||||
: "down",
|
||||
};
|
||||
|
||||
// Reverse checks so the latest check is on the right
|
||||
const reversedChecks = monitor.checks.slice().reverse();
|
||||
|
||||
return {
|
||||
id: monitor._id,
|
||||
handleClick: () => {
|
||||
navigate(`/monitors/${monitor._id}`);
|
||||
},
|
||||
data: [
|
||||
{ id: idx, data: <Host params={params} /> },
|
||||
{
|
||||
id: idx + 1,
|
||||
data: (
|
||||
<StatusLabel
|
||||
status={params.status}
|
||||
text={params.status}
|
||||
customStyles={{ textTransform: "capitalize" }}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{ id: idx + 2, data: <BarChart checks={reversedChecks} /> },
|
||||
{
|
||||
id: idx + 3,
|
||||
data: (
|
||||
<span style={{ textTransform: "uppercase" }}>{monitor.type}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: idx + 4,
|
||||
data: <ActionsMenu monitor={monitor} isAdmin={isAdmin} />,
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
return data;
|
||||
};
|
||||
@@ -32,4 +32,7 @@ export const ConfigBox = styled(Stack)(({ theme }) => ({
|
||||
"& h3, & p": {
|
||||
color: theme.palette.text.tertiary,
|
||||
},
|
||||
"& p": {
|
||||
fontSize: 13,
|
||||
},
|
||||
}));
|
||||
@@ -0,0 +1,54 @@
|
||||
import { useTheme } from "@mui/material";
|
||||
|
||||
const useUtils = () => {
|
||||
const theme = useTheme();
|
||||
const determineState = (monitor) => {
|
||||
if (monitor.isActive === false) return "paused";
|
||||
if (monitor?.status === undefined) return "pending";
|
||||
return monitor?.status == true ? "up" : "down";
|
||||
};
|
||||
|
||||
const statusColor = {
|
||||
up: theme.palette.success.main,
|
||||
down: theme.palette.error.main,
|
||||
paused: theme.palette.warning.main,
|
||||
pending: theme.palette.warning.main,
|
||||
};
|
||||
|
||||
const statusMsg = {
|
||||
up: "Your site is up.",
|
||||
down: "Your site is down.",
|
||||
paused: "Pending...",
|
||||
};
|
||||
const statusStyles = {
|
||||
up: {
|
||||
backgroundColor: theme.palette.success.bg,
|
||||
borderColor: theme.palette.success.light,
|
||||
"& h2": { color: theme.palette.success.main },
|
||||
},
|
||||
down: {
|
||||
backgroundColor: theme.palette.error.bg,
|
||||
borderColor: theme.palette.error.light,
|
||||
"& h2": { color: theme.palette.error.main },
|
||||
},
|
||||
paused: {
|
||||
backgroundColor: theme.palette.warning.light,
|
||||
borderColor: theme.palette.warning.border,
|
||||
"& h2": { color: theme.palette.warning.main },
|
||||
},
|
||||
pending: {
|
||||
backgroundColor: theme.palette.warning.light,
|
||||
borderColor: theme.palette.warning.border,
|
||||
"& h2": { color: theme.palette.warning.main },
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
determineState,
|
||||
statusColor,
|
||||
statusMsg,
|
||||
statusStyles,
|
||||
};
|
||||
};
|
||||
|
||||
export default useUtils;
|
||||
@@ -18,7 +18,7 @@ const border = { light: "#27272a", dark: "#36363e" };
|
||||
const fontFamilyDefault =
|
||||
'"Inter","system-ui", "Avenir", "Helvetica", "Arial", sans-serif';
|
||||
const shadow =
|
||||
"0px 4px 24px -4px rgba(16, 24, 40, 0.08), 0px 3px 3px -3px rgba(16, 24, 40, 0.03)";
|
||||
"0px 4px 24px -4px rgba(255, 255, 255, 0.03), 0px 3px 3px -3px rgba(255, 255, 255, 0.01)";
|
||||
|
||||
const darkTheme = createTheme({
|
||||
typography: { fontFamily: fontFamilyDefault, fontSize: 13 },
|
||||
@@ -53,8 +53,8 @@ const darkTheme = createTheme({
|
||||
warning: {
|
||||
text: "#e88c30",
|
||||
main: "#FF9F00",
|
||||
light: "#272115",
|
||||
bg: "#624711",
|
||||
light: "#624711",
|
||||
bg: "#262115",
|
||||
border: "#e88c30",
|
||||
},
|
||||
percentage: {
|
||||
@@ -74,6 +74,16 @@ const darkTheme = createTheme({
|
||||
},
|
||||
spacing: 2,
|
||||
components: {
|
||||
MuiCssBaseline: {
|
||||
styleOverrides: {
|
||||
body: {
|
||||
backgroundImage:
|
||||
"radial-gradient(circle, #09090b, #0c0c0e, #0f0f11, #111113, #131315, #131315, #131315, #131315, #111113, #0f0f11, #0c0c0e, #09090b)",
|
||||
lineHeight: "inherit",
|
||||
paddingLeft: "calc(100vw - 100%)",
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiButton: {
|
||||
defaultProps: {
|
||||
disableRipple: true,
|
||||
@@ -127,7 +137,7 @@ const darkTheme = createTheme({
|
||||
padding: 4,
|
||||
transition: "none",
|
||||
"&:hover": {
|
||||
backgroundColor: background.fill,
|
||||
backgroundColor: border.light,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -50,8 +50,8 @@ const lightTheme = createTheme({
|
||||
warning: {
|
||||
text: "#DC6803",
|
||||
main: "#fdb022",
|
||||
light: "#fffcf5",
|
||||
bg: "#ffecbc",
|
||||
light: "#ffecbc",
|
||||
bg: "#fffcf5",
|
||||
border: "#fec84b",
|
||||
},
|
||||
percentage: {
|
||||
@@ -71,6 +71,16 @@ const lightTheme = createTheme({
|
||||
},
|
||||
spacing: 2,
|
||||
components: {
|
||||
MuiCssBaseline: {
|
||||
styleOverrides: {
|
||||
body: {
|
||||
backgroundImage:
|
||||
"radial-gradient(circle, #fcfcfd, #fdfcfd, #fdfdfd, #fefdfe, #fefefe, #fefefe, #fefefe, #fefefe, #fefdfe, #fdfdfd, #fdfcfd, #fcfcfd)",
|
||||
lineHeight: "inherit",
|
||||
paddingLeft: "calc(100vw - 100%)",
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiButton: {
|
||||
defaultProps: {
|
||||
disableRipple: true,
|
||||
|
||||
|
Before Width: | Height: | Size: 30 KiB |
@@ -1,3 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.24935 10.0001L8.74935 12.5001L13.7493 7.50008M18.3327 10.0001C18.3327 14.6025 14.6017 18.3334 9.99935 18.3334C5.39698 18.3334 1.66602 14.6025 1.66602 10.0001C1.66602 5.39771 5.39698 1.66675 9.99935 1.66675C14.6017 1.66675 18.3327 5.39771 18.3327 10.0001Z" stroke="#1570EF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.24935 10.0001L8.74935 12.5001L13.7493 7.50008M18.3327 10.0001C18.3327 14.6025 14.6017 18.3334 9.99935 18.3334C5.39698 18.3334 1.66602 14.6025 1.66602 10.0001C1.66602 5.39771 5.39698 1.66675 9.99935 1.66675C14.6017 1.66675 18.3327 5.39771 18.3327 10.0001Z" stroke="#1570EF" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 454 B After Width: | Height: | Size: 456 B |
@@ -1,18 +0,0 @@
|
||||
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d_137_19027)">
|
||||
<path d="M2.5 13C2.5 6.64873 7.64873 1.5 14 1.5H46C52.3513 1.5 57.5 6.64873 57.5 13V45C57.5 51.3513 52.3513 56.5 46 56.5H14C7.64873 56.5 2.5 51.3513 2.5 45V13Z" stroke="#EAECF0" shape-rendering="crispEdges"/>
|
||||
<path d="M24.7499 28.9999L28.2499 32.4999L35.2499 25.4999M41.6666 28.9999C41.6666 35.4432 36.4432 40.6666 29.9999 40.6666C23.5566 40.6666 18.3333 35.4432 18.3333 28.9999C18.3333 22.5566 23.5566 17.3333 29.9999 17.3333C36.4432 17.3333 41.6666 22.5566 41.6666 28.9999Z" stroke="#344054" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_137_19027" x="0" y="0" width="60" height="60" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.0627451 0 0 0 0 0.0941176 0 0 0 0 0.156863 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_137_19027"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_137_19027" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -1,18 +1,3 @@
|
||||
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d_137_18989)">
|
||||
<path d="M2.5 13C2.5 6.64873 7.64873 1.5 14 1.5H46C52.3513 1.5 57.5 6.64873 57.5 13V45C57.5 51.3513 52.3513 56.5 46 56.5H14C7.64873 56.5 2.5 51.3513 2.5 45V13Z" stroke="#EAECF0" shape-rendering="crispEdges"/>
|
||||
<path d="M18.3333 23.1667L27.859 29.8347C28.6304 30.3746 29.016 30.6446 29.4356 30.7492C29.8061 30.8416 30.1937 30.8416 30.5643 30.7492C30.9838 30.6446 31.3695 30.3746 32.1408 29.8347L41.6666 23.1667M23.9333 38.3333H36.0666C38.0268 38.3333 39.0069 38.3333 39.7556 37.9519C40.4141 37.6163 40.9496 37.0809 41.2851 36.4223C41.6666 35.6736 41.6666 34.6935 41.6666 32.7333V25.2667C41.6666 23.3065 41.6666 22.3264 41.2851 21.5777C40.9496 20.9191 40.4141 20.3837 39.7556 20.0481C39.0069 19.6667 38.0268 19.6667 36.0666 19.6667H23.9333C21.9731 19.6667 20.993 19.6667 20.2443 20.0481C19.5857 20.3837 19.0503 20.9191 18.7147 21.5777C18.3333 22.3264 18.3333 23.3065 18.3333 25.2667V32.7333C18.3333 34.6935 18.3333 35.6736 18.7147 36.4223C19.0503 37.0809 19.5857 37.6163 20.2443 37.9519C20.993 38.3333 21.9731 38.3333 23.9333 38.3333Z" stroke="#344054" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_137_18989" x="0" y="0" width="60" height="60" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.0627451 0 0 0 0 0.0941176 0 0 0 0 0.156863 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_137_18989"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_137_18989" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 7L10.1649 12.7154C10.8261 13.1783 11.1567 13.4097 11.5163 13.4993C11.8339 13.5785 12.1661 13.5785 12.4837 13.4993C12.8433 13.4097 13.1739 13.1783 13.8351 12.7154L22 7M6.8 20H17.2C18.8802 20 19.7202 20 20.362 19.673C20.9265 19.3854 21.3854 18.9265 21.673 18.362C22 17.7202 22 16.8802 22 15.2V8.8C22 7.11984 22 6.27976 21.673 5.63803C21.3854 5.07354 20.9265 4.6146 20.362 4.32698C19.7202 4 18.8802 4 17.2 4H6.8C5.11984 4 4.27976 4 3.63803 4.32698C3.07354 4.6146 2.6146 5.07354 2.32698 5.63803C2 6.27976 2 7.11984 2 8.8V15.2C2 16.8802 2 17.7202 2.32698 18.362C2.6146 18.9265 3.07354 19.3854 3.63803 19.673C4.27976 20 5.11984 20 6.8 20Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 832 B |
@@ -1,18 +1,3 @@
|
||||
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d_137_18974)">
|
||||
<path d="M2.5 13C2.5 6.64873 7.64873 1.5 14 1.5H46C52.3513 1.5 57.5 6.64873 57.5 13V45C57.5 51.3513 52.3513 56.5 46 56.5H14C7.64873 56.5 2.5 51.3513 2.5 45V13Z" stroke="#EAECF0" shape-rendering="crispEdges"/>
|
||||
<path d="M35.8333 25.4999C35.8333 24.9028 35.6055 24.3057 35.1499 23.8501C34.6943 23.3945 34.0972 23.1667 33.5 23.1667M33.5 32.5C37.366 32.5 40.5 29.366 40.5 25.5C40.5 21.634 37.366 18.5 33.5 18.5C29.634 18.5 26.5 21.634 26.5 25.5C26.5 25.8193 26.5214 26.1336 26.5628 26.4415C26.6309 26.948 26.6649 27.2013 26.642 27.3615C26.6181 27.5284 26.5877 27.6184 26.5055 27.7655C26.4265 27.9068 26.2873 28.046 26.009 28.3243L20.0467 34.2866C19.845 34.4884 19.7441 34.5893 19.6719 34.707C19.608 34.8114 19.5608 34.9252 19.5322 35.0442C19.5 35.1785 19.5 35.3212 19.5 35.6065V37.6333C19.5 38.2867 19.5 38.6134 19.6272 38.863C19.739 39.0825 19.9175 39.261 20.137 39.3728C20.3866 39.5 20.7133 39.5 21.3667 39.5H24.1667V37.1667H26.5V34.8333H28.8333L30.6757 32.991C30.954 32.7127 31.0932 32.5735 31.2345 32.4945C31.3816 32.4123 31.4716 32.3819 31.6385 32.358C31.7987 32.3351 32.052 32.3691 32.5585 32.4372C32.8664 32.4786 33.1807 32.5 33.5 32.5Z" stroke="#344054" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_137_18974" x="0" y="0" width="60" height="60" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.0627451 0 0 0 0 0.0941176 0 0 0 0 0.156863 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_137_18974"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_137_18974" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17 8.99994C17 8.48812 16.8047 7.9763 16.4142 7.58579C16.0237 7.19526 15.5118 7 15 7M15 15C18.3137 15 21 12.3137 21 9C21 5.68629 18.3137 3 15 3C11.6863 3 9 5.68629 9 9C9 9.27368 9.01832 9.54308 9.05381 9.80704C9.11218 10.2412 9.14136 10.4583 9.12172 10.5956C9.10125 10.7387 9.0752 10.8157 9.00469 10.9419C8.937 11.063 8.81771 11.1823 8.57913 11.4209L3.46863 16.5314C3.29568 16.7043 3.2092 16.7908 3.14736 16.8917C3.09253 16.9812 3.05213 17.0787 3.02763 17.1808C3 17.2959 3 17.4182 3 17.6627V19.4C3 19.9601 3 20.2401 3.10899 20.454C3.20487 20.6422 3.35785 20.7951 3.54601 20.891C3.75992 21 4.03995 21 4.6 21H7V19H9V17H11L12.5791 15.4209C12.8177 15.1823 12.937 15.063 13.0581 14.9953C13.1843 14.9248 13.2613 14.8987 13.4044 14.8783C13.5417 14.8586 13.7588 14.8878 14.193 14.9462C14.4569 14.9817 14.7263 15 15 15Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 1005 B |
@@ -1,18 +0,0 @@
|
||||
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d_137_19004)">
|
||||
<path d="M2.5 13C2.5 6.64873 7.64873 1.5 14 1.5H46C52.3513 1.5 57.5 6.64873 57.5 13V45C57.5 51.3513 52.3513 56.5 46 56.5H14C7.64873 56.5 2.5 51.3513 2.5 45V13Z" stroke="#EAECF0" shape-rendering="crispEdges"/>
|
||||
<path d="M35.8334 26.6667V24.3333C35.8334 21.1117 33.2217 18.5 30.0001 18.5C26.7784 18.5 24.1667 21.1117 24.1667 24.3333V26.6667M30.0001 31.9167V34.25M26.2667 39.5H33.7334C35.6936 39.5 36.6737 39.5 37.4224 39.1185C38.081 38.783 38.6164 38.2475 38.9519 37.589C39.3334 36.8403 39.3334 35.8602 39.3334 33.9V32.2667C39.3334 30.3065 39.3334 29.3264 38.9519 28.5777C38.6164 27.9191 38.081 27.3837 37.4224 27.0481C36.6737 26.6667 35.6936 26.6667 33.7334 26.6667H26.2667C24.3066 26.6667 23.3265 26.6667 22.5778 27.0481C21.9192 27.3837 21.3838 27.9191 21.0482 28.5777C20.6667 29.3264 20.6667 30.3065 20.6667 32.2667V33.9C20.6667 35.8602 20.6667 36.8403 21.0482 37.589C21.3838 38.2475 21.9192 38.783 22.5778 39.1185C23.3265 39.5 24.3066 39.5 26.2667 39.5Z" stroke="#344054" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_137_19004" x="0" y="0" width="60" height="60" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.0627451 0 0 0 0 0.0941176 0 0 0 0 0.156863 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_137_19004"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_137_19004" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB |
@@ -1,3 +1,3 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.3337 5.66667V4.33333C10.3337 2.49238 8.84127 1 7.00033 1C5.15938 1 3.66699 2.49238 3.66699 4.33333V5.66667M7.00033 8.66667V10M4.86699 13H9.13366C10.2538 13 10.8138 13 11.2416 12.782C11.618 12.5903 11.9239 12.2843 12.1157 11.908C12.3337 11.4802 12.3337 10.9201 12.3337 9.8V8.86667C12.3337 7.74656 12.3337 7.18651 12.1157 6.75869C11.9239 6.38236 11.618 6.0764 11.2416 5.88465C10.8138 5.66667 10.2538 5.66667 9.13366 5.66667H4.86699C3.74689 5.66667 3.18683 5.66667 2.75901 5.88465C2.38269 6.0764 2.07673 6.38236 1.88498 6.75869C1.66699 7.18651 1.66699 7.74656 1.66699 8.86667V9.8C1.66699 10.9201 1.66699 11.4802 1.88498 11.908C2.07673 12.2843 2.38269 12.5903 2.75901 12.782C3.18683 13 3.74689 13 4.86699 13Z" stroke="#667085" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.3337 5.66667V4.33333C10.3337 2.49238 8.84127 1 7.00033 1C5.15938 1 3.66699 2.49238 3.66699 4.33333V5.66667M7.00033 8.66667V10M4.86699 13H9.13366C10.2538 13 10.8138 13 11.2416 12.782C11.618 12.5903 11.9239 12.2843 12.1157 11.908C12.3337 11.4802 12.3337 10.9201 12.3337 9.8V8.86667C12.3337 7.74656 12.3337 7.18651 12.1157 6.75869C11.9239 6.38236 11.618 6.0764 11.2416 5.88465C10.8138 5.66667 10.2538 5.66667 9.13366 5.66667H4.86699C3.74689 5.66667 3.18683 5.66667 2.75901 5.88465C2.38269 6.0764 2.07673 6.38236 1.88498 6.75869C1.66699 7.18651 1.66699 7.74656 1.66699 8.86667V9.8C1.66699 10.9201 1.66699 11.4802 1.88498 11.908C2.07673 12.2843 2.38269 12.5903 2.75901 12.782C3.18683 13 3.74689 13 4.86699 13Z" stroke="#667085" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 907 B After Width: | Height: | Size: 905 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="M9.5 15V9M14.5 15V9M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 327 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.5 8.96533C9.5 8.48805 9.5 8.24941 9.59974 8.11618C9.68666 8.00007 9.81971 7.92744 9.96438 7.9171C10.1304 7.90525 10.3311 8.03429 10.7326 8.29239L15.4532 11.3271C15.8016 11.551 15.9758 11.663 16.0359 11.8054C16.0885 11.9298 16.0885 12.0702 16.0359 12.1946C15.9758 12.337 15.8016 12.449 15.4532 12.6729L10.7326 15.7076C10.3311 15.9657 10.1304 16.0948 9.96438 16.0829C9.81971 16.0726 9.68666 15.9999 9.59974 15.8838C9.5 15.7506 9.5 15.512 9.5 15.0347V8.96533Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 859 B |
@@ -56,12 +56,6 @@ html {
|
||||
0px 3px 3px -3px rgba(16, 24, 40, 0.03);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-width: 320px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.MuiInputBase-root.Mui-disabled input {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@@ -33,8 +33,11 @@ services:
|
||||
- ./redis/data:/data
|
||||
mongodb:
|
||||
image: uptime_database_mongo:latest
|
||||
command: ["mongod", "--quiet"]
|
||||
command: ["mongod", "--quiet", "--auth"]
|
||||
ports:
|
||||
- "27017:27017"
|
||||
volumes:
|
||||
- ./mongo/data:/data/db
|
||||
- ./mongo/init/create_users.js:/docker-entrypoint-initdb.d/create_users.js
|
||||
env_file:
|
||||
- mongo.env
|
||||
@@ -0,0 +1,16 @@
|
||||
var username = process.env.USERNAME_ENV_VAR;
|
||||
var password = process.env.PASSWORD_ENV_VAR;
|
||||
|
||||
db = db.getSiblingDB("uptime_db");
|
||||
|
||||
db.createUser({
|
||||
user: username,
|
||||
pwd: password,
|
||||
roles: [
|
||||
{
|
||||
role: "readWrite",
|
||||
db: "uptime_db",
|
||||
},
|
||||
],
|
||||
});
|
||||
print("User uptime_user created successfully");
|
||||
@@ -1,2 +1,3 @@
|
||||
FROM mongo
|
||||
EXPOSE 27017
|
||||
EXPOSE 27017
|
||||
CMD ["mongod"]
|
||||
@@ -10,11 +10,9 @@
|
||||
|
||||
<p align="center"><strong>An open source server monitoring application</strong></p>
|
||||
|
||||
|
||||

|
||||
(yes, we have a light theme as well, but this looks better on readme.md)
|
||||
|
||||
|
||||
BlueWave Uptime is an open source server monitoring application used to track the operational status and performance of servers and websites. It regularly checks whether a server/website is accessible and performs optimally, providing real-time alerts and reports on the monitored services' availability, downtime, and response time.
|
||||
|
||||
## Features
|
||||
@@ -172,13 +170,21 @@ The fastest way to start the application is to use our Dockerfiles and [Docker C
|
||||
To get the application up and running you need to:
|
||||
|
||||
1. In the `Docker` directory run the build script `build_images.sh` to build docker images for the client, server, Redis database, and MongoDB database.
|
||||
2. In the `Docker` directory, create a `server.env` file with the [requried environtmental variables](#env-vars-server) for the server. Sample file:
|
||||
|
||||
2. In the `Dokcer` directory create a `mongo.env` file with a username and password:
|
||||
|
||||
```
|
||||
USERNAME_ENV_VAR=user
|
||||
PASSWORD_ENV_VAR=password
|
||||
```
|
||||
|
||||
3. In the `Docker` directory, create a `server.env` file with the [requried environtmental variables](#env-vars-server) for the server. Sample file:
|
||||
|
||||
```
|
||||
CLIENT_HOST="http://localhost:5173"
|
||||
JWT_SECRET=<jwt_secret>
|
||||
DB_TYPE="MongoDB"
|
||||
DB_CONNECTION_STRING="mongodb://mongodb:27017/uptime_db"
|
||||
DB_CONNECTION_STRING="mongodb://<username>:<password>@mongodb:27017/uptime_db"
|
||||
REDIS_HOST="redis"
|
||||
REDIS_PORT=6379
|
||||
TOKEN_TTL="99d"
|
||||
@@ -189,14 +195,14 @@ SYSTEM_EMAIL_ADDRESS=<system_email>
|
||||
SYSTEM_EMAIL_PASSWORD=<system_email_password>
|
||||
```
|
||||
|
||||
3. In the `Client` directory, create a `client.env` file with the [required environtmental variables](#env-vars-client) for the client. Sample file:
|
||||
4. In the `Client` directory, create a `client.env` file with the [required environtmental variables](#env-vars-client) for the client. Sample file:
|
||||
|
||||
```
|
||||
VITE_APP_API_BASE_URL="http://localhost:5000/api/v1"
|
||||
VITE_APP_API_LOG_LEVEL="debug"
|
||||
```
|
||||
|
||||
4. In the `Docker` directory run `docker compose up` to run the `docker-compose.yaml` file and start all four images.
|
||||
5. In the `Docker` directory run `docker compose up` to run the `docker-compose.yaml` file and start all four images.
|
||||
|
||||
That's it, the application is ready to use on port 80.
|
||||
<br/>
|
||||
@@ -214,10 +220,11 @@ That's it, the application is ready to use on port 80.
|
||||
|
||||
##### Environmental Variables <a id="env-vars-client"></a>
|
||||
|
||||
| ENV Variable Name | Required/Optional | Type | Description | Accepted Values |
|
||||
| --------------------- | ----------------- | -------- | ------------------ | ---------------------------------- |
|
||||
| VITE_APP_API_BASE_URL | Required | `string` | Base URL of server | {host}/api/v1 |
|
||||
| VITE_APP_LOG_LEVEL | Optional | `string` | Log level | `"none"`\|`"error"` \| `"warn"` \| |
|
||||
| ENV Variable Name | Required/Optional | Type | Description | Accepted Values |
|
||||
| --------------------- | ----------------- | --------- | ------------------ | ---------------------------------- |
|
||||
| VITE_APP_API_BASE_URL | Required | `string` | Base URL of server | {host}/api/v1 |
|
||||
| VITE_APP_LOG_LEVEL | Optional | `string` | Log level | `"none"`\|`"error"` \| `"warn"` \| |
|
||||
| VITE_APP_DEMO | Optional | `boolean` | Demo server or not | `true`\|`false` \| |
|
||||
|
||||
<br/>
|
||||
|
||||
|
||||
@@ -463,13 +463,12 @@ const deleteUserController = async (req, res, next) => {
|
||||
throw new Error(errorMessages.DB_USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 1. Find all the monitors associated with the user id
|
||||
// 1. Find all the monitors associated with the team ID if superadmin
|
||||
|
||||
const monitors = await req.db.getMonitorsByUserId({
|
||||
params: { userId: _id },
|
||||
const result = await req.db.getMonitorsByTeamId({
|
||||
params: { teamId: user.teamId },
|
||||
});
|
||||
|
||||
if (monitors) {
|
||||
if (user.role.includes("superadmin") && result?.monitors.length > 0) {
|
||||
//2. Remove all jobs, delete checks and alerts
|
||||
await Promise.all(
|
||||
monitors.map(async (monitor) => {
|
||||
@@ -484,22 +483,17 @@ const deleteUserController = async (req, res, next) => {
|
||||
|
||||
// 3. Delete each monitor
|
||||
await req.db.deleteMonitorsByUserId(user._id);
|
||||
|
||||
// 4. Delete the user by id
|
||||
await req.db.deleteUser(user._id);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_DELETE_USER,
|
||||
});
|
||||
} else {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
msg: errorMessages.MONITOR_GET_BY_USER_ID,
|
||||
});
|
||||
}
|
||||
// 4. Delete the user by id
|
||||
await req.db.deleteUser(user._id);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_DELETE_USER,
|
||||
});
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method === undefined && error.method === "deleteUserController";
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -38,7 +38,7 @@ const UserSchema = mongoose.Schema(
|
||||
role: {
|
||||
type: [String],
|
||||
default: "user",
|
||||
enum: ["user", "admin", "superadmin"],
|
||||
enum: ["user", "admin", "superadmin", "demo"],
|
||||
},
|
||||
teamId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
|
||||
@@ -39,7 +39,7 @@ class JobQueue {
|
||||
queue.networkService = networkService;
|
||||
const monitors = await db.getAllMonitors();
|
||||
for (const monitor of monitors) {
|
||||
if (monitor.active) {
|
||||
if (monitor.isActive) {
|
||||
await queue.addJob(monitor.id, monitor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ const registrationBodyValidation = joi.object({
|
||||
profileImage: joi.any(),
|
||||
role: joi
|
||||
.array()
|
||||
.items(joi.string().valid("superadmin", "admin", "user"))
|
||||
.items(joi.string().valid("superadmin", "admin", "user", "demo"))
|
||||
.min(1)
|
||||
.required(),
|
||||
teamId: joi.string().allow("").required(),
|
||||
|
||||