mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-01-27 20:29:32 -06:00
Merge branch 'master' of https://github.com/veyselboybay/bluewave-uptime
This commit is contained in:
@@ -18,23 +18,47 @@ const levelConfig = {
|
||||
variant: "contained",
|
||||
color: "error",
|
||||
},
|
||||
imagePrimary: {
|
||||
color: "primary",
|
||||
variant: "text",
|
||||
},
|
||||
imageSecondary: {
|
||||
color: "secondary",
|
||||
variant: "text",
|
||||
},
|
||||
imageTertiary: {
|
||||
color: "tertiary",
|
||||
variant: "text",
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @component
|
||||
* @param {Object} props
|
||||
* @param {'primary' | 'secondary' | 'tertiary' | 'error'} props.level - The level of the button
|
||||
* @param {'primary' | 'secondary' | 'tertiary' | 'error' | 'imagePrimary' | 'imageSecondary' | 'imageTertiary'} props.level - The level of the button
|
||||
* @param {string} props.label - The label of the button
|
||||
* @param {React.ReactNode} props.img - Image for button
|
||||
* @param {boolean} [props.disabled] - Whether the button is disabled
|
||||
* @param {Object} prps.sx - Styles for the button
|
||||
* @returns {JSX.Element}
|
||||
* @example
|
||||
* // Render an error button
|
||||
* <Button level="error" label="Error" disabled />
|
||||
* <Button level="error" label="Error" disabled sx={{marginTop: "1rem"}}/>
|
||||
*/
|
||||
|
||||
const Button = ({ level, label, disabled }) => {
|
||||
const Button = ({ level, label, disabled, img, sx }) => {
|
||||
const { variant, color } = levelConfig[level];
|
||||
return (
|
||||
<MuiButton variant={variant} color={color} disabled={disabled}>
|
||||
<MuiButton
|
||||
variant={variant}
|
||||
color={color}
|
||||
disabled={disabled}
|
||||
sx={{
|
||||
textTransform: "none",
|
||||
...sx,
|
||||
}}
|
||||
>
|
||||
{img && img}
|
||||
{label}
|
||||
</MuiButton>
|
||||
);
|
||||
@@ -43,6 +67,8 @@ const Button = ({ level, label, disabled }) => {
|
||||
Button.propTypes = {
|
||||
level: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
img: PropTypes.node,
|
||||
sx: PropTypes.object,
|
||||
disabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import PropTypes from "prop-types";
|
||||
import "./BaseLabel.css";
|
||||
import { useTheme } from "@mui/material";
|
||||
|
||||
/**
|
||||
* @typedef {Object} Styles
|
||||
* @param {string} [color] - The text color
|
||||
* @param {string} [backgroundColor] - The background color
|
||||
* @param {string} [borderColor] - The border color
|
||||
*/
|
||||
|
||||
/**
|
||||
* @component
|
||||
* @param {Object} props
|
||||
* @param {string} props.label - The label of the label
|
||||
* @param {Styles} props.styles - CSS Styles passed from parent component
|
||||
* @param {React.ReactNode} children - Children passed from parent component
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
|
||||
const BaseLabel = ({ label, styles, children }) => {
|
||||
const theme = useTheme();
|
||||
// Grab the default borderRadius from the theme to match button style
|
||||
const { borderRadius } = theme.shape;
|
||||
// Calculate padding for the label to mimic button. Appears to scale correctly, not 100% sure though.
|
||||
const padding = theme.spacing(1 * 0.75, 2);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="label"
|
||||
style={{
|
||||
borderRadius: borderRadius,
|
||||
borderColor: theme.palette.tertiary.main,
|
||||
color: theme.palette.tertiary.main,
|
||||
padding: padding,
|
||||
...styles,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
BaseLabel.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
styles: PropTypes.shape({
|
||||
color: PropTypes.string,
|
||||
backgroundColor: PropTypes.string,
|
||||
}),
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
export default BaseLabel;
|
||||
@@ -1,67 +0,0 @@
|
||||
import { useTheme } from "@mui/material";
|
||||
import { PropTypes } from "prop-types";
|
||||
import BaseLabel from "./BaseLabel";
|
||||
|
||||
// Produces a lighter color based on a hex color and a percent
|
||||
// lightenColor("#067647", 20) will produce a color 20% lighter than #067647
|
||||
const lightenColor = (color, percent) => {
|
||||
let r = parseInt(color.substring(1, 3), 16);
|
||||
let g = parseInt(color.substring(3, 5), 16);
|
||||
let b = parseInt(color.substring(5, 7), 16);
|
||||
|
||||
const amt = Math.round((255 * percent) / 100);
|
||||
|
||||
r = r + amt <= 255 ? r + amt : 255;
|
||||
g = g + amt <= 255 ? g + amt : 255;
|
||||
b = b + amt <= 255 ? b + amt : 255;
|
||||
|
||||
r = r.toString(16).padStart(2, "0");
|
||||
g = g.toString(16).padStart(2, "0");
|
||||
b = b.toString(16).padStart(2, "0");
|
||||
|
||||
return `#${r}${g}${b}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* @component
|
||||
* @param {Object} props
|
||||
* @param {string} props.label - The label of the label
|
||||
* @param {string} props.color - The color of the label, specified in #RRGGBB format
|
||||
* @returns {JSX.Element}
|
||||
* @example
|
||||
* // Render a red label
|
||||
* <ColoredLabel label="Label" color="#FF0000" />
|
||||
*/
|
||||
|
||||
const ColoredLabel = ({ label, color }) => {
|
||||
const theme = useTheme();
|
||||
// If an invalid color is passed, default to the labelGray color
|
||||
if (
|
||||
typeof color !== "string" ||
|
||||
!/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(color)
|
||||
) {
|
||||
color = theme.palette.labelGray.color;
|
||||
}
|
||||
|
||||
// Calculate lighter shades for border and bg
|
||||
const borderColor = lightenColor(color, 20);
|
||||
const bgColor = lightenColor(color, 75);
|
||||
|
||||
return (
|
||||
<BaseLabel
|
||||
label={label}
|
||||
styles={{
|
||||
color: color,
|
||||
borderColor: borderColor,
|
||||
backgroundColor: bgColor,
|
||||
}}
|
||||
></BaseLabel>
|
||||
);
|
||||
};
|
||||
|
||||
ColoredLabel.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
color: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default ColoredLabel;
|
||||
@@ -1,46 +0,0 @@
|
||||
import PropTypes from "prop-types";
|
||||
import BaseLabel from "./BaseLabel";
|
||||
import { Box } from "@mui/material";
|
||||
import { useTheme } from "@mui/material";
|
||||
|
||||
/**
|
||||
* @component
|
||||
* @param {Object} props
|
||||
* @param {'Seen' | 'Waiting' | 'New' | 'Active'} props.status - The status for the label
|
||||
* @returns {JSX.Element}
|
||||
* @example
|
||||
* // Render an active label
|
||||
* <StatusLabel status="Active" />
|
||||
*/
|
||||
|
||||
const StatusLabel = ({ status }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const colorLookup = {
|
||||
Seen: theme.palette.labelGray.color,
|
||||
Waiting: theme.palette.labelRed.color,
|
||||
New: theme.palette.labelOrange.color,
|
||||
Active: theme.palette.labelGreen.color,
|
||||
};
|
||||
|
||||
// Look up the color for the status, default to labelGray if not found
|
||||
const color = colorLookup[status] || theme.palette.labelGray.color;
|
||||
|
||||
return (
|
||||
<BaseLabel label={status}>
|
||||
<Box
|
||||
width={12}
|
||||
height={12}
|
||||
bgcolor={color}
|
||||
borderRadius="50%"
|
||||
marginRight={1}
|
||||
/>
|
||||
</BaseLabel>
|
||||
);
|
||||
};
|
||||
|
||||
StatusLabel.propTypes = {
|
||||
status: PropTypes.oneOf(["Seen", "Waiting", "New", "Active"]),
|
||||
};
|
||||
|
||||
export default StatusLabel;
|
||||
157
Client/src/Components/Label/index.jsx
Normal file
157
Client/src/Components/Label/index.jsx
Normal file
@@ -0,0 +1,157 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { Box } from "@mui/material";
|
||||
import { useTheme } from "@mui/material";
|
||||
import "./index.css";
|
||||
|
||||
/**
|
||||
* @typedef {Object} Styles
|
||||
* @param {string} [color] - The text color
|
||||
* @param {string} [backgroundColor] - The background color
|
||||
* @param {string} [borderColor] - The border color
|
||||
*/
|
||||
|
||||
/**
|
||||
* @component
|
||||
* @param {Object} props
|
||||
* @param {string} props.label - The label of the label
|
||||
* @param {Styles} props.styles - CSS Styles passed from parent component
|
||||
* @param {React.ReactNode} children - Children passed from parent component
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
|
||||
const BaseLabel = ({ label, styles, children }) => {
|
||||
const theme = useTheme();
|
||||
// Grab the default borderRadius from the theme to match button style
|
||||
const { borderRadius } = theme.shape;
|
||||
// Calculate padding for the label to mimic button. Appears to scale correctly, not 100% sure though.
|
||||
const padding = theme.spacing(1 * 0.75, 2);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="label"
|
||||
style={{
|
||||
borderRadius: borderRadius,
|
||||
borderColor: theme.palette.tertiary.main,
|
||||
color: theme.palette.tertiary.main,
|
||||
padding: padding,
|
||||
...styles,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
BaseLabel.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
styles: PropTypes.shape({
|
||||
color: PropTypes.string,
|
||||
backgroundColor: PropTypes.string,
|
||||
}),
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
// Produces a lighter color based on a hex color and a percent
|
||||
// lightenColor("#067647", 20) will produce a color 20% lighter than #067647
|
||||
const lightenColor = (color, percent) => {
|
||||
let r = parseInt(color.substring(1, 3), 16);
|
||||
let g = parseInt(color.substring(3, 5), 16);
|
||||
let b = parseInt(color.substring(5, 7), 16);
|
||||
|
||||
const amt = Math.round((255 * percent) / 100);
|
||||
|
||||
r = r + amt <= 255 ? r + amt : 255;
|
||||
g = g + amt <= 255 ? g + amt : 255;
|
||||
b = b + amt <= 255 ? b + amt : 255;
|
||||
|
||||
r = r.toString(16).padStart(2, "0");
|
||||
g = g.toString(16).padStart(2, "0");
|
||||
b = b.toString(16).padStart(2, "0");
|
||||
|
||||
return `#${r}${g}${b}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* @component
|
||||
* @param {Object} props
|
||||
* @param {string} props.label - The label of the label
|
||||
* @param {string} props.color - The color of the label, specified in #RRGGBB format
|
||||
* @returns {JSX.Element}
|
||||
* @example
|
||||
* // Render a red label
|
||||
* <ColoredLabel label="Label" color="#FF0000" />
|
||||
*/
|
||||
|
||||
const ColoredLabel = ({ label, color }) => {
|
||||
const theme = useTheme();
|
||||
// If an invalid color is passed, default to the labelGray color
|
||||
if (
|
||||
typeof color !== "string" ||
|
||||
!/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(color)
|
||||
) {
|
||||
color = theme.palette.labelGray.color;
|
||||
}
|
||||
|
||||
// Calculate lighter shades for border and bg
|
||||
const borderColor = lightenColor(color, 20);
|
||||
const bgColor = lightenColor(color, 75);
|
||||
|
||||
return (
|
||||
<BaseLabel
|
||||
label={label}
|
||||
styles={{
|
||||
color: color,
|
||||
borderColor: borderColor,
|
||||
backgroundColor: bgColor,
|
||||
}}
|
||||
></BaseLabel>
|
||||
);
|
||||
};
|
||||
|
||||
ColoredLabel.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
color: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
/**
|
||||
* @component
|
||||
* @param {Object} props
|
||||
* @param {'Seen' | 'Waiting' | 'New' | 'Active'} props.status - The status for the label
|
||||
* @returns {JSX.Element}
|
||||
* @example
|
||||
* // Render an active label
|
||||
* <StatusLabel status="Active" />
|
||||
*/
|
||||
|
||||
const StatusLabel = ({ status }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const colorLookup = {
|
||||
Seen: theme.palette.labelGray.color,
|
||||
Waiting: theme.palette.labelRed.color,
|
||||
New: theme.palette.labelOrange.color,
|
||||
Active: theme.palette.labelGreen.color,
|
||||
};
|
||||
|
||||
// Look up the color for the status, default to labelGray if not found
|
||||
const color = colorLookup[status] || theme.palette.labelGray.color;
|
||||
|
||||
return (
|
||||
<BaseLabel label={status}>
|
||||
<Box
|
||||
width={12}
|
||||
height={12}
|
||||
bgcolor={color}
|
||||
borderRadius="50%"
|
||||
marginRight={1}
|
||||
/>
|
||||
</BaseLabel>
|
||||
);
|
||||
};
|
||||
|
||||
StatusLabel.propTypes = {
|
||||
status: PropTypes.oneOf(["Seen", "Waiting", "New", "Active"]),
|
||||
};
|
||||
|
||||
export { ColoredLabel, StatusLabel };
|
||||
@@ -1,6 +1,15 @@
|
||||
import { Link as MuiLink, useTheme } from "@mui/material";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
/**
|
||||
* @component
|
||||
* @param {Object} props
|
||||
* @param {'primary' | 'secondary' | 'tertiary' | 'error'} props.level - The level of the link
|
||||
* @param {string} props.label - The label of the link
|
||||
* @param {string} props.url - The URL of the link
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
|
||||
const Link = ({ level, label, url }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -22,14 +31,6 @@ const Link = ({ level, label, url }) => {
|
||||
},
|
||||
error: {},
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {'primary' | 'secondary' | 'tertiary' | 'error'} level - The level of the link
|
||||
* @property {string} label - The label of the link
|
||||
* @property {string} url - The URL of the link
|
||||
*/
|
||||
|
||||
const { sx, color } = levelConfig[level];
|
||||
return (
|
||||
<MuiLink href={url} sx={sx} color={color}>
|
||||
|
||||
0
Client/src/Components/Search/index.css
Normal file
0
Client/src/Components/Search/index.css
Normal file
49
Client/src/Components/Search/index.jsx
Normal file
49
Client/src/Components/Search/index.jsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import Autocomplete from '@mui/material/Autocomplete';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import Box from '@mui/material/Box';
|
||||
import { useTheme } from "@mui/material";
|
||||
|
||||
const teamMembers = [
|
||||
{ title: 'John Doe'},
|
||||
{ title: 'Jane Smith'},
|
||||
{ title: 'Alex Johnson'},
|
||||
];
|
||||
|
||||
/**
|
||||
* @component
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
|
||||
export default function Search()
|
||||
{
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Box padding={theme.spacing(2)}> {/* Add padding to the container */}
|
||||
<Autocomplete
|
||||
multiple
|
||||
id="tags-outlined"
|
||||
options={teamMembers}
|
||||
getOptionLabel={(option) => option.title}
|
||||
filterSelectedOptions
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Team Members"
|
||||
placeholder="Favorites"
|
||||
/>
|
||||
)}
|
||||
renderTags={(value, getTagProps) =>
|
||||
value.map((option) => (
|
||||
<Chip
|
||||
key={option.title}
|
||||
variant="outlined"
|
||||
label={option.title}
|
||||
{...getTagProps({ option })}
|
||||
/>
|
||||
))
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
133
Client/src/Components/Section/index.jsx
Normal file
133
Client/src/Components/Section/index.jsx
Normal file
@@ -0,0 +1,133 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
Box,
|
||||
Container,
|
||||
useTheme,
|
||||
Typography,
|
||||
TextField,
|
||||
Switch,
|
||||
} from "@mui/material";
|
||||
import MenuIcon from "@mui/icons-material/Menu";
|
||||
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
|
||||
import Button from "../Button/";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
|
||||
/**
|
||||
* @component
|
||||
* @param {Object} props
|
||||
* @param { Array} props.montitors - Array of monitors associated with the section
|
||||
* @returns {JSX.Element}
|
||||
* @example
|
||||
* // Renders a section component with a list of monitors
|
||||
* <Section monitors={monitors} />
|
||||
*/
|
||||
|
||||
const Section = ({ monitors }) => {
|
||||
const [monitorStates, setMonitorStates] = useState(
|
||||
monitors.map((monitor) => monitor.isActive)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Monitor states updated", monitorStates);
|
||||
// Update DB here
|
||||
}, [monitorStates]);
|
||||
|
||||
const handleMonitor = (monitorIndex) => {
|
||||
setMonitorStates((prevStates) => {
|
||||
const newStates = [...prevStates];
|
||||
newStates[monitorIndex] = !newStates[monitorIndex];
|
||||
|
||||
return newStates;
|
||||
// Need to update DB with new monitor state
|
||||
});
|
||||
};
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<>
|
||||
<Container
|
||||
disableGutters
|
||||
sx={{
|
||||
border: `1px solid ${theme.palette.section.borderColor}`,
|
||||
borderRadius: `${theme.shape.borderRadius}px`,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
textAlign: "left",
|
||||
padding: `${theme.spacing(2)}`,
|
||||
bgcolor: `${theme.palette.section.bgColor}`,
|
||||
borderBottom: `1px solid ${theme.palette.section.borderColor}`,
|
||||
}}
|
||||
>
|
||||
<Typography>Section Name</Typography>
|
||||
<TextField
|
||||
placeholder="Service Name"
|
||||
sx={{
|
||||
"& input": {
|
||||
width: "320px",
|
||||
height: "34px",
|
||||
padding: "10px 14px 10px 14px",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "start",
|
||||
textAlign: "left",
|
||||
padding: `${theme.spacing(2)}`,
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<Typography>Servers List</Typography>
|
||||
<Button level="primary" label="Add new" />
|
||||
{monitors.map((monitor, index) => {
|
||||
return (
|
||||
<Box
|
||||
key={monitor.id}
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
width: "100%",
|
||||
padding: "10px 14px 10px 14px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
border: `1px solid ${theme.palette.section.borderColor}`,
|
||||
borderRadius: `${theme.shape.borderRadius}px`,
|
||||
bgcolor: `${theme.palette.section.bgColor}`,
|
||||
}}
|
||||
>
|
||||
<MenuIcon
|
||||
sx={{ color: `${theme.palette.section.borderColor}` }}
|
||||
/>
|
||||
<Switch
|
||||
checked={monitorStates[index]}
|
||||
onChange={() => handleMonitor(index)}
|
||||
/>
|
||||
<Typography sx={{ flexGrow: 1 }}>{monitor.name}</Typography>
|
||||
<DeleteOutlineIcon
|
||||
sx={{ color: `${theme.palette.section.borderColor}` }}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</Container>
|
||||
<Button
|
||||
sx={{ marginTop: theme.spacing(2) }}
|
||||
level="imageSecondary"
|
||||
label="Add new section"
|
||||
img={<AddIcon />}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Section.propTypes = {
|
||||
monitors: PropTypes.array,
|
||||
};
|
||||
|
||||
export default Section;
|
||||
@@ -1,9 +1,7 @@
|
||||
import React from "react";
|
||||
import Button from "../../Components/Button";
|
||||
import Button from "../../Components/Button/";
|
||||
import Link from "../../Components/Link";
|
||||
import ColoredLabel from "../../Components/Label/ColoredLabel";
|
||||
import {
|
||||
Box,
|
||||
useTheme,
|
||||
Switch,
|
||||
Checkbox,
|
||||
@@ -15,15 +13,17 @@ import {
|
||||
Tab,
|
||||
Tabs,
|
||||
} from "@mui/material";
|
||||
import StatusLabel from "../../Components/Label/StautsLabel";
|
||||
import Avatar from "../../Components/Avatar/Avatar";
|
||||
import ProgressStepper from "../../Components/ProgressStepper/ProgressStepper";
|
||||
import { ColoredLabel, StatusLabel } from "../../Components/Label/";
|
||||
import Avatar from "../../Components/Avatar/";
|
||||
import ProgressStepper from "../../Components/ProgressStepper";
|
||||
import avatarImage from "../../assets/Images/avatar_placeholder.png";
|
||||
import Section from "../../Components/Section";
|
||||
import { DataGrid } from "@mui/x-data-grid";
|
||||
|
||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
|
||||
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import Divider from "@mui/material/Divider";
|
||||
|
||||
const cols = [
|
||||
{
|
||||
@@ -84,6 +84,12 @@ const steps = [
|
||||
{ label: "Invite your team", content: "Start collaborating with your team" },
|
||||
];
|
||||
|
||||
const monitors = [
|
||||
{ id: 0, name: "Google", isActive: true },
|
||||
{ id: 1, name: "Yahoo", isActive: false },
|
||||
{ id: 2, name: "Reddit", isActive: true },
|
||||
];
|
||||
|
||||
const Demo = () => {
|
||||
const [radio, setRadio] = React.useState(1);
|
||||
const [tab, setTab] = React.useState("departments");
|
||||
@@ -105,17 +111,82 @@ const Demo = () => {
|
||||
alert(event.target.checked ? `${type} checked` : `${type} unchecked`);
|
||||
};
|
||||
|
||||
const links = [
|
||||
{
|
||||
name: "Buttons",
|
||||
url: "#buttons",
|
||||
},
|
||||
{
|
||||
name: "Disabled Buttons",
|
||||
url: "#disabled-buttons",
|
||||
},
|
||||
{
|
||||
name: "Labels",
|
||||
url: "#labels",
|
||||
},
|
||||
{
|
||||
name: "Status Labels",
|
||||
url: "#status-labels",
|
||||
},
|
||||
{
|
||||
name: "Avatar",
|
||||
url: "#avatar",
|
||||
},
|
||||
{
|
||||
name: "Switches",
|
||||
url: "#switches",
|
||||
},
|
||||
{
|
||||
name: "Checkboxes",
|
||||
url: "#checkboxes",
|
||||
},
|
||||
{
|
||||
name: "Radio",
|
||||
url: "#radio",
|
||||
},
|
||||
{
|
||||
name: "Table",
|
||||
url: "#table",
|
||||
},
|
||||
{
|
||||
name: "Tabs",
|
||||
url: "#tabs",
|
||||
},
|
||||
{
|
||||
name: "Date Picker",
|
||||
url: "#date-picker",
|
||||
},
|
||||
{
|
||||
name: "Stepper",
|
||||
url: "#stepper",
|
||||
},
|
||||
{
|
||||
name: "Section",
|
||||
url: "#section",
|
||||
},
|
||||
];
|
||||
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<div>
|
||||
<h4>Buttons</h4>
|
||||
<ul style={{ listStyle: "none" }}>
|
||||
{links.map((link) => (
|
||||
<li key={link.url}>
|
||||
<Link level="primary" label={link.name} url={link.url} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
|
||||
<h4 id="buttons">Buttons</h4>
|
||||
<div>
|
||||
<Button level="primary" label="Primary" />
|
||||
<Button level="secondary" label="Secondary" />
|
||||
<Button level="tertiary" label="Tertiary" />
|
||||
<Button level="error" label="Error" />
|
||||
<Button level="imageTertiary" label="Image Button" img={<AddIcon />} />
|
||||
</div>
|
||||
<h4>Disabled Buttons</h4>
|
||||
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
|
||||
<h4 id="disabled-buttons">Disabled Buttons</h4>
|
||||
<div>
|
||||
<Button level="primary" label="Primary" disabled />
|
||||
<Button level="secondary" label="Secondary" disabled />
|
||||
@@ -129,43 +200,48 @@ const Demo = () => {
|
||||
url={"https://www.google.com"}
|
||||
/>
|
||||
</div>
|
||||
<h4>Labels</h4>
|
||||
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
|
||||
<h4 id="labels">Labels</h4>
|
||||
<div>
|
||||
<ColoredLabel label="Label" color={theme.palette.labelGray.color} />
|
||||
<ColoredLabel label="Label" color={theme.palette.labelPurple.color} />
|
||||
<ColoredLabel label="Label" color={theme.palette.labelGreen.color} />
|
||||
<ColoredLabel label="Label" color={theme.palette.labelOrange.color} />
|
||||
</div>
|
||||
|
||||
<h4>Status Lables</h4>
|
||||
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
|
||||
<h4 id="status-labels">Status Lables</h4>
|
||||
<div>
|
||||
<StatusLabel status="Seen" />
|
||||
<StatusLabel status="Waiting" />
|
||||
<StatusLabel status="New" />
|
||||
<StatusLabel status="Active" />
|
||||
</div>
|
||||
<h4>Avatar</h4>
|
||||
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
|
||||
<h4 id="avatar">Avatar</h4>
|
||||
<div style={{ display: "flex" }}>
|
||||
<Avatar src={avatarImage} firstName="Alex" lastName="Holliday" />
|
||||
<Avatar firstName="Alex" lastName="Holliday" />
|
||||
<Avatar src={avatarImage} firstName="Alex" lastName="Holliday" small />
|
||||
<Avatar firstName="Alex" lastName="Holliday" small />
|
||||
</div>
|
||||
<h4>Switches</h4>
|
||||
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
|
||||
<h4 id="switches">Switches</h4>
|
||||
<div>
|
||||
<Switch onChange={(event) => change(event, "Switch")} />
|
||||
<Switch size="small" />
|
||||
<Switch disabled />
|
||||
<Switch checked />
|
||||
</div>
|
||||
<h4>Checkboxes</h4>
|
||||
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
|
||||
<h4 id="checkboxes">Checkboxes</h4>
|
||||
<div>
|
||||
<Checkbox onChange={(event) => change(event, "Checkbox")} />
|
||||
<Checkbox size="small" />
|
||||
<Checkbox disabled />
|
||||
<Checkbox checked />
|
||||
</div>
|
||||
<h4>Radio</h4>
|
||||
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
|
||||
<h4 id="radio">Radio</h4>
|
||||
<div>
|
||||
<FormControl>
|
||||
<FormLabel>Demo Radio</FormLabel>
|
||||
@@ -187,7 +263,8 @@ const Demo = () => {
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
</div>
|
||||
<h4>Table</h4>
|
||||
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
|
||||
<h4 id="table">Table</h4>
|
||||
<div style={{ width: "75vw" }}>
|
||||
<DataGrid
|
||||
autoHeight
|
||||
@@ -201,7 +278,8 @@ const Demo = () => {
|
||||
pageSizeOptions={[5, 10]}
|
||||
/>
|
||||
</div>
|
||||
<h4>Tabs</h4>
|
||||
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
|
||||
<h4 id="tabs">Tabs</h4>
|
||||
<div style={{ display: "flex", justifyContent: "center" }}>
|
||||
{" "}
|
||||
<Tabs value={tab} onChange={handleTab}>
|
||||
@@ -211,19 +289,22 @@ const Demo = () => {
|
||||
<Tab label="Approvals" value="approvals" />
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
<h4>Date Picker</h4>
|
||||
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
|
||||
<h4 id="date-picker">Date Picker</h4>
|
||||
<div>
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||
<DatePicker onChange={handleDate} />
|
||||
</LocalizationProvider>
|
||||
<h4>{date}</h4>
|
||||
</div>
|
||||
|
||||
<h4>Stepper</h4>
|
||||
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
|
||||
<h4 id="stepper">Stepper</h4>
|
||||
<div>
|
||||
<ProgressStepper steps={steps}></ProgressStepper>
|
||||
</div>
|
||||
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
|
||||
<h4 id="section">Section</h4>
|
||||
<Section monitors={monitors} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import DropdownTeamMember from "../../Components/DropdownTeamMember";
|
||||
import Search from "../../Components/Search";
|
||||
import "./index.css";
|
||||
|
||||
const Home = () => {
|
||||
@@ -6,6 +7,7 @@ const Home = () => {
|
||||
<>
|
||||
<div>Home</div>
|
||||
<DropdownTeamMember />
|
||||
<Search />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,6 +12,10 @@ const labelGray = "#475467";
|
||||
const labelPurple = "#6941C6";
|
||||
const labelGreen = "#067647";
|
||||
const labelRed = "#F04438";
|
||||
|
||||
//Section colros
|
||||
const sectionBorder = "#D0D5DD";
|
||||
const sectionBg = "#F8F9F8";
|
||||
const theme = createTheme({
|
||||
palette: {
|
||||
primary: {
|
||||
@@ -39,6 +43,13 @@ const theme = createTheme({
|
||||
labelRed: {
|
||||
color: labelRed,
|
||||
},
|
||||
section: {
|
||||
borderColor: sectionBorder,
|
||||
bgColor: sectionBg,
|
||||
},
|
||||
},
|
||||
shape: {
|
||||
borderRadius: 4,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
92
README.md
92
README.md
@@ -28,11 +28,103 @@ BlueWave uptime monitoring application
|
||||
1. Change directory to the `Server` directory
|
||||
2. Install all depencies by running `npm install`
|
||||
|
||||
---
|
||||
|
||||
#### Configuration
|
||||
|
||||
Configure the server with the following environmental variables
|
||||
|
||||
| ENV Varialbe Name | Required/Optional | Type | Description |
|
||||
| -------------------- | ----------------- | ------ | --------------------------------- |
|
||||
| DB_CONNECTION_STRING | Required | string | Specfies URL for MongoDB Database |
|
||||
|
||||
---
|
||||
|
||||
#### Starting the Development Server
|
||||
|
||||
1. run `npm run dev` to start the development server
|
||||
|
||||
---
|
||||
|
||||
#### Endpoints
|
||||
|
||||
All endpoints return a response in this format:
|
||||
|
||||
| Name | Type | Notes |
|
||||
| ------- | --------- | ----------------------------- |
|
||||
| success | `boolean` | Success or failure of request |
|
||||
| msg | `string` | Message describing response |
|
||||
| data | `Object` | Arbitrary Payload |
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
{success: true, msg: "Successful Request", data: {test: testData}}
|
||||
```
|
||||
|
||||
##### Data Types
|
||||
|
||||
###### Monitor
|
||||
|
||||
| Name | Type | Notes |
|
||||
| ----------- | --------- | ---------------------------------------- |
|
||||
| userId | `string` | Unique ID identifying monitor creator |
|
||||
| name | `string` | Name of the monitor |
|
||||
| description | `string` | Description of the monitor |
|
||||
| url | `string` | Url the monitor will ping |
|
||||
| isActive | `boolean` | Whether or not the monitor is active |
|
||||
| interval | `integer` | Interval with which to ping monitor (ms) |
|
||||
| updatedAt | `Date` | Last time the monitor was updated |
|
||||
| CreatedAt | `Date` | When the monitor was updated |
|
||||
|
||||
---
|
||||
|
||||
##### GET /api/v1/monitors
|
||||
|
||||
###### Response
|
||||
|
||||
| Status Code | Type | Description |
|
||||
| ----------- | --------------------- | -------------------------------- |
|
||||
| 200 | Response with Payload | Response with a list of monitors |
|
||||
|
||||
###### Payload
|
||||
|
||||
| Type | Notes |
|
||||
| ---------------- | ----------------- |
|
||||
| `Array[Monitor]` | Array of monitors |
|
||||
|
||||
---
|
||||
|
||||
##### GET /api/v1/monitor/:id
|
||||
|
||||
###### Response
|
||||
|
||||
| Status Code | Type | Description |
|
||||
| ----------- | --------------------- | ------------------------------ |
|
||||
| 200 | Response with Payload | Response with a single monitor |
|
||||
|
||||
###### Payload
|
||||
|
||||
| Type | Notes |
|
||||
| --------- | --------------------------------------------------- |
|
||||
| `Monitor` | Single monitor with the id in the request parameter |
|
||||
|
||||
---
|
||||
|
||||
##### GET /api/v1/monitors/user/:userId
|
||||
|
||||
| Status Code | Type | Description |
|
||||
| ----------- | --------------------- | -------------------------------- |
|
||||
| 200 | Response with Payload | Response with a list of monitors |
|
||||
|
||||
###### Payload
|
||||
|
||||
| Type | Notes |
|
||||
| ---------------- | ------------------------------------------------------------------ |
|
||||
| `Array[Monitor]` | Array of monitors created by user with userId specified in request |
|
||||
|
||||
## Contributors
|
||||
|
||||
<a href="https://github.com/bluewave-labs/bluewave-uptime/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=bluewave-labs/bluewave-uptime" />
|
||||
</a>
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
const mongoose = require('mongoose')
|
||||
const PORT = process.env.PORT || 5000;
|
||||
|
||||
const PORT = process.env.PORT || 5000
|
||||
const connectDbAndRunServer = async (app, db) => {
|
||||
try {
|
||||
await db.connect();
|
||||
app.listen(PORT, () => {
|
||||
console.log(`server started on port:${PORT}`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Failed to connect to DB");
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const connectDbAndRunServer = async (app) => {
|
||||
await mongoose.connect(process.env.DB_CONNECTION_STRING).then(() => {
|
||||
console.log("DB connected")
|
||||
// run server
|
||||
app.listen(PORT, () => {
|
||||
console.log(`server started on port:${PORT}`)
|
||||
})
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
module.exports = { connectDbAndRunServer }
|
||||
module.exports = { connectDbAndRunServer };
|
||||
|
||||
56
Server/controllers/monitorController.js
Normal file
56
Server/controllers/monitorController.js
Normal file
@@ -0,0 +1,56 @@
|
||||
const {
|
||||
getMonitorsByIdValidation,
|
||||
getMonitorsByUserIdValidation,
|
||||
} = require("../validation/joi");
|
||||
|
||||
// Gets all monitors
|
||||
const getAllMonitors = async (req, res) => {
|
||||
try {
|
||||
const monitors = await req.db.getAllMonitors();
|
||||
return res.json({ success: true, msg: "Monitors found", data: monitors });
|
||||
} catch (error) {
|
||||
return res.status(500).json({ success: false, msg: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// Get a monitor by ID
|
||||
const getMonitorById = async (req, res) => {
|
||||
const { error } = getMonitorsByIdValidation.validate(req.params);
|
||||
if (error) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ success: false, msg: error.details[0].message });
|
||||
}
|
||||
|
||||
try {
|
||||
const monitorId = req.params.monitorId;
|
||||
const monitor = await req.db.getMonitorById(monitorId);
|
||||
return res.json({ success: true, msg: "Monitor found", data: monitor });
|
||||
} catch (error) {
|
||||
return res.status(500).json({ success: false, msg: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// Gets a monitor by user ID
|
||||
const getMonitorsByUserId = async (req, res) => {
|
||||
const { error } = getMonitorsByUserIdValidation.validate(req.params);
|
||||
if (error) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ success: false, msg: error.details[0].message });
|
||||
}
|
||||
|
||||
try {
|
||||
const userId = req.params.userId;
|
||||
const monitors = await req.db.getMonitorsByUserId(userId);
|
||||
return res.json({
|
||||
success: true,
|
||||
msg: `Monitors for user ${userId} found`,
|
||||
data: monitors,
|
||||
});
|
||||
} catch (error) {
|
||||
return res.status(500).json({ success: false, msg: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { getAllMonitors, getMonitorById, getMonitorsByUserId };
|
||||
81
Server/db/FakeDb.js
Normal file
81
Server/db/FakeDb.js
Normal file
@@ -0,0 +1,81 @@
|
||||
// **************************
|
||||
// The idea here is to provide a layer of abstraction between the database and whoever is using it.
|
||||
// Instead of directly calling mongoose methods, we can call the methods on the DB object.
|
||||
// If this were Typescript or Java or Golang an interface would be implemented to ensure the methods are available.
|
||||
// But we do the best we can with Javascript.
|
||||
//
|
||||
// If the methods are consistent all we have to do to swap out one DB for another is simply change the import.
|
||||
//
|
||||
// Example:
|
||||
// We start with the fake DB:
|
||||
//
|
||||
// const db = require("../db/FakeDb");
|
||||
// const monitors = await db.getAllMonitors();
|
||||
//
|
||||
// And when we want to swtich to a real DB, all we have to do is swap the import
|
||||
//
|
||||
// const db = require("../db/MongoDb");
|
||||
// const monitors = await db.getAllMonitors();
|
||||
//
|
||||
// The rest of the code is the same, as all the `db` methods are standardized.
|
||||
// **************************
|
||||
|
||||
const Monitor = require("../models/Monitor");
|
||||
|
||||
const FAKE_MONITOR_DATA = [];
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
FAKE_MONITOR_DATA.push(
|
||||
new Monitor({
|
||||
userId: i % 2 === 0 ? 1 : 2,
|
||||
name: `Monitor ${i}`,
|
||||
description: `Description for Monitor ${i}`,
|
||||
url: `https://monitor${i}.com`,
|
||||
isActive: true,
|
||||
interval: 60000,
|
||||
updated_at: new Date(),
|
||||
created_at: new Date(),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const connect = async () => {
|
||||
try {
|
||||
await console.log("Connected to FakeDB");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const getAllMonitors = async () => {
|
||||
return FAKE_MONITOR_DATA;
|
||||
};
|
||||
|
||||
const getMonitorById = async (monitorId) => {
|
||||
const idx = FAKE_MONITOR_DATA.findIndex((monitor) => {
|
||||
return monitor.id === monitorId;
|
||||
});
|
||||
if (idx === -1) {
|
||||
throw new Error(`Monitor with id ${monitorId} not found`);
|
||||
}
|
||||
return FAKE_MONITOR_DATA[idx];
|
||||
};
|
||||
|
||||
const getMonitorsByUserId = async (userId) => {
|
||||
const userMonitors = FAKE_MONITOR_DATA.filter((monitor) => {
|
||||
return monitor.userId === userId;
|
||||
});
|
||||
|
||||
if (userMonitors.length === 0) {
|
||||
throw new Error(`Monitors for user ${userId} not found`);
|
||||
}
|
||||
|
||||
return userMonitors;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
connect,
|
||||
getAllMonitors,
|
||||
getMonitorById,
|
||||
getMonitorsByUserId,
|
||||
};
|
||||
49
Server/db/MongoDB.js
Normal file
49
Server/db/MongoDB.js
Normal file
@@ -0,0 +1,49 @@
|
||||
const Monitor = require("../models/Monitor");
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const connect = async () => {
|
||||
try {
|
||||
await mongoose.connect(process.env.DB_CONNECTION_STRING);
|
||||
console.log("Connected to MongoDB");
|
||||
} catch (error) {
|
||||
console.error("Failed to connect to MongoDB");
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Gets all monitors
|
||||
const getAllMonitors = async (req, res) => {
|
||||
try {
|
||||
const monitors = await Monitor.find();
|
||||
return monitors;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Get a monitor by ID
|
||||
const getMonitorById = async (req, res) => {
|
||||
try {
|
||||
const monitor = await Monitor.findById(req.params.monitorId);
|
||||
return monitor;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Gets a monitor by user ID
|
||||
const getMonitorsByUserId = async (req, res) => {
|
||||
try {
|
||||
const monitors = await Monitor.find({ userId: req.params.userId });
|
||||
return monitors;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
connect,
|
||||
getAllMonitors,
|
||||
getMonitorById,
|
||||
getMonitorsByUserId,
|
||||
};
|
||||
@@ -1,23 +1,55 @@
|
||||
const express = require('express')
|
||||
const helmet = require('helmet')
|
||||
const cors = require('cors')
|
||||
const authRouter = require('./routes/authRoute')
|
||||
const { connectDbAndRunServer } = require('./configs/db')
|
||||
require('dotenv').config()
|
||||
const express = require("express");
|
||||
const helmet = require("helmet");
|
||||
const cors = require("cors");
|
||||
const authRouter = require("./routes/authRoute");
|
||||
const monitorRouter = require("./routes/monitorRoute");
|
||||
const { connectDbAndRunServer } = require("./configs/db");
|
||||
require("dotenv").config();
|
||||
// const { sendEmail } = require('./utils/sendEmail')
|
||||
|
||||
// **************************
|
||||
// Here is where we can swap out DBs easily. Spin up a mongoDB instance and try it out.
|
||||
// Simply comment out the FakeDB and uncomment the MongoDB or vice versa.
|
||||
// We can easily swap between any type of data source as long as the methods are implemented
|
||||
//
|
||||
// FakeDB
|
||||
// const db = require("./db/FakeDb");
|
||||
//
|
||||
// MongoDB
|
||||
const db = require("./db/MongoDB");
|
||||
//
|
||||
// **************************
|
||||
|
||||
const app = express()
|
||||
/**
|
||||
* NOTES
|
||||
* Email Service will be added
|
||||
* Logger Service will be added (Winston or similar)
|
||||
*/
|
||||
|
||||
const app = express();
|
||||
|
||||
// middlewares
|
||||
app.use(cors(
|
||||
//We will add configuration later
|
||||
))
|
||||
app.use(express.json())
|
||||
app.use(helmet())
|
||||
app.use(
|
||||
cors()
|
||||
//We will add configuration later
|
||||
);
|
||||
app.use(express.json());
|
||||
app.use(helmet());
|
||||
|
||||
// **************************
|
||||
// Make DB accessible anywhere we have a Request object
|
||||
// By adding the DB to the request object, we can access it in any route
|
||||
// Thus we do not need to import it in every route file, and we can easily swap out DBs as there is only one place to change it
|
||||
// **************************
|
||||
app.use((req, res, next) => {
|
||||
req.db = db;
|
||||
next();
|
||||
});
|
||||
|
||||
//routes
|
||||
app.use('/api/v1/auth', authRouter);
|
||||
app.use("/api/v1/auth", authRouter);
|
||||
|
||||
app.use("/api/v1/monitors", monitorRouter);
|
||||
|
||||
// Testing email service
|
||||
// app.use('/sendEmail', async (req, res) => {
|
||||
@@ -26,12 +58,12 @@ app.use('/api/v1/auth', authRouter);
|
||||
// })
|
||||
|
||||
//health check
|
||||
app.use('/api/v1/healthy', (req, res) => {
|
||||
try {
|
||||
return res.status(200).json({message:"Healthy"})
|
||||
} catch (error) {
|
||||
return res.status(500).json({message:error.message})
|
||||
}
|
||||
})
|
||||
app.use("/api/v1/healthy", (req, res) => {
|
||||
try {
|
||||
return res.status(200).json({ message: "Healthy" });
|
||||
} catch (error) {
|
||||
return res.status(500).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
connectDbAndRunServer(app);
|
||||
connectDbAndRunServer(app, db);
|
||||
|
||||
37
Server/models/Monitor.js
Normal file
37
Server/models/Monitor.js
Normal file
@@ -0,0 +1,37 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const MonitorSchema = mongoose.Schema({
|
||||
userId: {
|
||||
type: String,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
interval: {
|
||||
// in milliseconds
|
||||
type: Number,
|
||||
default: 60000,
|
||||
},
|
||||
updated_at: {
|
||||
type: Date,
|
||||
default: Date.now,
|
||||
},
|
||||
created_at: {
|
||||
type: Date,
|
||||
default: Date.now,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = mongoose.model("Monitor", MonitorSchema);
|
||||
7
Server/routes/monitorRoute.js
Normal file
7
Server/routes/monitorRoute.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const router = require("express").Router();
|
||||
const monitorController = require("../controllers/monitorController");
|
||||
router.get("/", monitorController.getAllMonitors);
|
||||
router.get("/:monitorId", monitorController.getMonitorById);
|
||||
router.get("/user/:userId", monitorController.getMonitorsByUserId);
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,10 +1,21 @@
|
||||
const joi = require('joi')
|
||||
|
||||
const joi = require("joi");
|
||||
const user = require("../models/user");
|
||||
|
||||
const authValidation = joi.object({
|
||||
email: joi.string().email().required(),
|
||||
password: joi.string().min(8).required(),
|
||||
email: joi.string().email().required(),
|
||||
password: joi.string().min(8).required(),
|
||||
});
|
||||
|
||||
const getMonitorsByIdValidation = joi.object({
|
||||
monitorId: joi.string().required(),
|
||||
});
|
||||
|
||||
module.exports = {authValidation}
|
||||
const getMonitorsByUserIdValidation = joi.object({
|
||||
userId: joi.string().required(),
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
authValidation,
|
||||
getMonitorsByIdValidation,
|
||||
getMonitorsByUserIdValidation,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user