Merge pull request #1607 from bluewave-labs/fix/be/incidents

fix: be/incidents, resolves #1567
This commit is contained in:
Alexander Holliday authored and GitHub committed 2025-01-22 12:44:14 -08:00
commit 027b331220
19 files changed
+347 -243

No files matched your search

@@ -16,7 +16,7 @@ import { BaseLabel } from "../Label";
const DEFAULT_CODE = 9999; // Default code for unknown status
const handleStatusCode = (status) => {
if (status >= 100 && status < 600) {
if (status) {
return status;
}
return DEFAULT_CODE;
@@ -5,11 +5,11 @@ import { TablePaginationActions } from "./Actions";
import SelectorVertical from "../../../assets/icons/selector-vertical.svg?react";
Pagination.propTypes = {
monitorCount: PropTypes.number.isRequired, // Total number of items for pagination.
itemCount: PropTypes.number.isRequired, // Total number of items for pagination.
page: PropTypes.number.isRequired, // Current page index.
rowsPerPage: PropTypes.number.isRequired, // Number of rows displayed per page.
handleChangePage: PropTypes.func.isRequired, // Function to handle page changes.
handleChangeRowsPerPage: PropTypes.func.isRequired, // Function to handle changes in rows per page.
handleChangeRowsPerPage: PropTypes.func, // Function to handle changes in rows per page.
};
const ROWS_PER_PAGE_OPTIONS = [5, 10, 15, 25];
@@ -18,6 +18,7 @@ const ROWS_PER_PAGE_OPTIONS = [5, 10, 15, 25];
* Pagination component for table navigation with customized styling and behavior.
*
* @param {object} props - Component properties.
* @param {string} props.paginationLabel - Label for the pagination.
* @param {number} props.monitorCount - Total number of monitors to paginate.
* @param {number} props.page - Current page index (0-based).
* @param {number} props.rowsPerPage - Number of rows to display per page.
@@ -26,7 +27,8 @@ const ROWS_PER_PAGE_OPTIONS = [5, 10, 15, 25];
* @returns {JSX.Element} The Pagination component.
*/
function Pagination({
monitorCount,
paginationLabel,
itemCount,
page,
rowsPerPage,
handleChangePage,
@@ -35,7 +37,7 @@ function Pagination({
const theme = useTheme();
const start = page * rowsPerPage + 1;
const end = Math.min(page * rowsPerPage + rowsPerPage, monitorCount);
const end = Math.min(page * rowsPerPage + rowsPerPage, itemCount);
const range = `${start} - ${end}`;
return (
@@ -51,11 +53,11 @@ function Pagination({
variant="body2"
sx={{ opacity: 0.7 }}
>
Showing {range} of {monitorCount} monitor(s)
Showing {range} of {itemCount} {paginationLabel}
</Typography>
<TablePagination
component="div"
count={monitorCount}
count={itemCount}
page={page}
onPageChange={handleChangePage}
rowsPerPage={rowsPerPage}
@@ -121,4 +123,4 @@ function Pagination({
);
}
export { Pagination };
export default Pagination;
+2 -1
View File
@@ -82,9 +82,10 @@ const DataTable = ({ headers, data, config = { emptyView: "No data" } }) => {
</TableRow>
) : (
data.map((row) => {
const key = row.id || row._id || Math.random();
return (
<TableRow
key={row.id}
key={key}
sx={config?.rowSX ?? {}}
onClick={() => config?.onRowClick(row)}
>
@@ -1,8 +1,6 @@
import PropTypes from "prop-types";
import { Pagination, PaginationItem, Typography, Box } from "@mui/material";
import { Typography, Box } from "@mui/material";
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
import ArrowForwardRoundedIcon from "@mui/icons-material/ArrowForwardRounded";
import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import { networkService } from "../../../main";
@@ -16,7 +14,9 @@ import { HttpStatusLabel } from "../../../Components/HttpStatusLabel";
import { Empty } from "./Empty/Empty";
import { IncidentSkeleton } from "./Skeleton/Skeleton";
import DataTable from "../../../Components/Table";
const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
import Pagination from "../../../Components/Table/TablePagination";
const IncidentTable = ({ monitors, selectedMonitor, filter, dateRange }) => {
const uiTimezone = useSelector((state) => state.ui.timezone);
const theme = useTheme();
@@ -24,19 +24,10 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
const mode = useSelector((state) => state.ui.mode);
const [checks, setChecks] = useState([]);
const [checksCount, setChecksCount] = useState(0);
const [paginationController, setPaginationController] = useState({
page: 0,
rowsPerPage: 14,
});
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setPaginationController((prevPaginationController) => ({
...prevPaginationController,
page: 0,
}));
}, [filter, selectedMonitor]);
useEffect(() => {
const fetchPage = async () => {
if (!monitors || Object.keys(monitors).length === 0) {
@@ -51,10 +42,10 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
teamId: user.teamId,
sortOrder: "desc",
limit: null,
dateRange: null,
dateRange,
filter: filter,
page: paginationController.page,
rowsPerPage: paginationController.rowsPerPage,
page: page,
rowsPerPage: rowsPerPage,
});
} else {
res = await networkService.getChecksByMonitor({
@@ -62,12 +53,13 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
monitorId: selectedMonitor,
sortOrder: "desc",
limit: null,
dateRange: null,
dateRange,
filter: filter,
page: paginationController.page,
rowsPerPage: paginationController.rowsPerPage,
page,
rowsPerPage,
});
}
setChecks(res.data.data.checks);
setChecksCount(res.data.data.checksCount);
} catch (error) {
@@ -77,21 +69,14 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
}
};
fetchPage();
}, [
authToken,
user,
monitors,
selectedMonitor,
filter,
paginationController.page,
paginationController.rowsPerPage,
]);
}, [authToken, user, monitors, selectedMonitor, filter, page, rowsPerPage, dateRange]);
const handlePageChange = (_, newPage) => {
setPaginationController({
...paginationController,
page: newPage - 1, // 0-indexed
});
setPage(newPage);
};
const handleChangeRowsPerPage = (event) => {
setRowsPerPage(event.target.value);
};
const headers = [
@@ -134,28 +119,6 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
{ id: "message", content: "Message", render: (row) => row.message },
];
let paginationComponent = <></>;
if (checksCount > paginationController.rowsPerPage) {
paginationComponent = (
<Pagination
count={Math.ceil(checksCount / paginationController.rowsPerPage)}
page={paginationController.page + 1} //0-indexed
onChange={handlePageChange}
shape="rounded"
renderItem={(item) => (
<PaginationItem
slots={{
previous: ArrowBackRoundedIcon,
next: ArrowForwardRoundedIcon,
}}
{...item}
/>
)}
sx={{ mt: "auto" }}
/>
);
}
let sharedStyles = {
border: 1,
borderColor: theme.palette.primary.lowContrast,
@@ -198,8 +161,14 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
headers={headers}
data={checks}
/>
{paginationComponent}
<Pagination
paginationLabel="incidents"
itemCount={checksCount}
page={page}
rowsPerPage={rowsPerPage}
handleChangePage={handlePageChange}
handleChangeRowsPerPage={handleChangeRowsPerPage}
/>
</>
)}
</>
@@ -210,6 +179,7 @@ IncidentTable.propTypes = {
monitors: PropTypes.object.isRequired,
selectedMonitor: PropTypes.string.isRequired,
filter: PropTypes.string.isRequired,
dateRange: PropTypes.string.isRequired,
};
export default IncidentTable;
-11
View File
@@ -1,11 +0,0 @@
.incidents h1.MuiTypography-root {
font-size: var(--env-var-font-size-large);
font-weight: 600;
}
.incidents button.MuiButtonBase-root,
.incidents p.MuiTypography-root {
font-size: var(--env-var-font-size-medium);
}
.incidents button.MuiButtonBase-root {
min-height: 34px;
}
+115 -45
View File
@@ -8,7 +8,6 @@ import { useTheme } from "@emotion/react";
import Select from "../../Components/Inputs/Select";
import IncidentTable from "./IncidentTable";
import SkeletonLayout from "./skeleton";
import "./index.css";
const Incidents = () => {
const theme = useTheme();
@@ -21,6 +20,7 @@ const Incidents = () => {
// TODO do something with these filters
const [filter, setFilter] = useState("all");
const [dateRange, setDateRange] = useState("hour");
useEffect(() => {
const fetchMonitors = async () => {
@@ -77,62 +77,132 @@ const Incidents = () => {
<>
<Stack
direction="row"
alignItems="center"
justifyContent="space-between"
gap={theme.spacing(6)}
>
<Typography
display="inline-block"
component="h1"
color={theme.palette.primary.contrastTextSecondary}
<Stack
direction="row"
alignItems="center"
gap={theme.spacing(6)}
>
Incidents for
</Typography>
<Select
id="incidents-select-monitor"
placeholder="All servers"
value={selectedMonitor}
onChange={handleSelect}
items={Object.values(monitors)}
sx={{
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastTextSecondary,
}}
/>
<ButtonGroup
sx={{
ml: "auto",
"& .MuiButtonBase-root, & .MuiButtonBase-root:hover": {
borderColor: theme.palette.primary.lowContrast,
},
}}
<Typography
display="inline-block"
component="h1"
color={theme.palette.primary.contrastTextSecondary}
>
Incidents for
</Typography>
<Select
id="incidents-select-monitor"
placeholder="All servers"
value={selectedMonitor}
onChange={handleSelect}
items={Object.values(monitors)}
sx={{
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastTextSecondary,
}}
/>
</Stack>
<Stack
direction="row"
alignItems="center"
gap={theme.spacing(6)}
>
<Button
variant="group"
filled={(filter === "all").toString()}
onClick={() => setFilter("all")}
<Typography
display="inline-block"
component="h1"
color={theme.palette.primary.contrastTextSecondary}
>
All
</Button>
<Button
variant="group"
filled={(filter === "down").toString()}
onClick={() => setFilter("down")}
Filter by:
</Typography>
<ButtonGroup
sx={{
ml: "auto",
"& .MuiButtonBase-root, & .MuiButtonBase-root:hover": {
borderColor: theme.palette.primary.lowContrast,
},
}}
>
Down
</Button>
<Button
variant="group"
filled={(filter === "resolve").toString()}
onClick={() => setFilter("resolve")}
<Button
variant="group"
filled={(filter === "all").toString()}
onClick={() => setFilter("all")}
>
All
</Button>
<Button
variant="group"
filled={(filter === "down").toString()}
onClick={() => setFilter("down")}
>
Down
</Button>
<Button
variant="group"
filled={(filter === "resolve").toString()}
onClick={() => setFilter("resolve")}
>
Cannot resolve
</Button>
</ButtonGroup>
</Stack>
<Stack
direction="row"
alignItems="center"
gap={theme.spacing(6)}
>
<Typography
display="inline-block"
component="h1"
color={theme.palette.primary.contrastTextSecondary}
>
Cannot Resolve
</Button>
</ButtonGroup>
Show:
</Typography>
<ButtonGroup
sx={{
ml: "auto",
"& .MuiButtonBase-root, & .MuiButtonBase-root:hover": {
borderColor: theme.palette.primary.lowContrast,
},
}}
>
<Button
variant="group"
filled={(dateRange === "hour").toString()}
onClick={() => setDateRange("hour")}
>
Last hour
</Button>
<Button
variant="group"
filled={(dateRange === "day").toString()}
onClick={() => setDateRange("day")}
>
Last day
</Button>
<Button
variant="group"
filled={(dateRange === "week").toString()}
onClick={() => setDateRange("week")}
>
Last week
</Button>
<Button
variant="group"
filled={(dateRange === "all").toString()}
onClick={() => setDateRange("all")}
>
All
</Button>
</ButtonGroup>
</Stack>
</Stack>
<IncidentTable
monitors={monitors}
selectedMonitor={selectedMonitor}
filter={filter}
dateRange={dateRange}
/>
</>
)}
+3 -2
View File
@@ -13,7 +13,7 @@ import { Box, Button, IconButton, Stack } from "@mui/material";
import Breadcrumbs from "../../Components/Breadcrumbs";
import { StatusLabel } from "../../Components/Label";
import { Heading } from "../../Components/Heading";
import { Pagination } from "../../Components/Table/TablePagination/index.jsx";
import Pagination from "../../Components/Table/TablePagination/index.jsx";
// import { getInfrastructureMonitorsByTeamId } from "../../Features/InfrastructureMonitors/infrastructureMonitorsSlice";
import { networkService } from "../../Utils/NetworkService.js";
import CustomGauge from "../../Components/Charts/CustomGauge/index.jsx";
@@ -269,7 +269,8 @@ function Infrastructure() {
/>
<Pagination
monitorCount={summary?.totalMonitors ?? 0}
itemCount={summary?.totalMonitors ?? 0}
paginationLabel="monitors"
page={page}
rowsPerPage={rowsPerPage}
handleChangePage={handleChangePage}
+10 -10
View File
@@ -108,16 +108,16 @@ const CreateMonitor = () => {
}
if (monitor.type === "http") {
const checkEndpointAction = await dispatch(
checkEndpointResolution({ authToken, monitorURL: form.url })
);
if (checkEndpointAction.meta.requestStatus === "rejected") {
createToast({
body: "The endpoint you entered doesn't resolve. Check the URL again.",
});
setErrors({ url: "The entered URL is not reachable." });
return;
}
// const checkEndpointAction = await dispatch(
// checkEndpointResolution({ authToken, monitorURL: form.url })
// );
// if (checkEndpointAction.meta.requestStatus === "rejected") {
// createToast({
// body: "The endpoint you entered doesn't resolve. Check the URL again.",
// });
// setErrors({ url: "The entered URL is not reachable." });
// return;
// }
}
form = {
+3 -2
View File
@@ -5,7 +5,7 @@ import SkeletonLayout from "./skeleton";
import Fallback from "./fallback";
import StatusBox from "./StatusBox";
import UptimeDataTable from "./UptimeDataTable";
import { Pagination } from "../../../Components/Table/TablePagination";
import Pagination from "../../../Components/Table/TablePagination";
// Utils
import { useTheme } from "@emotion/react";
@@ -225,7 +225,8 @@ const UptimeMonitors = () => {
triggerUpdate={triggerUpdate}
/>
<Pagination
monitorCount={totalMonitors}
itemCount={totalMonitors}
paginationLabel="monitors"
page={page}
rowsPerPage={rowsPerPage}
handleChangePage={handleChangePage}
+5 -6
View File
@@ -44,7 +44,7 @@ class CheckController {
}
};
getChecks = async (req, res, next) => {
getChecksByMonitor = async (req, res, next) => {
try {
await getChecksParamValidation.validateAsync(req.params);
await getChecksQueryValidation.validateAsync(req.query);
@@ -54,19 +54,18 @@ class CheckController {
}
try {
const checks = await this.db.getChecks(req);
const checksCount = await this.db.getChecksCount(req);
const result = await this.db.getChecksByMonitor(req);
return res.success({
msg: successMessages.CHECK_GET,
data: { checksCount, checks },
data: result,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getChecks"));
}
};
getTeamChecks = async (req, res, next) => {
getChecksByTeam = async (req, res, next) => {
try {
await getTeamChecksParamValidation.validateAsync(req.params);
await getTeamChecksQueryValidation.validateAsync(req.query);
@@ -75,7 +74,7 @@ class CheckController {
return;
}
try {
const checkData = await this.db.getTeamChecks(req);
const checkData = await this.db.getChecksByTeam(req);
return res.success({
msg: successMessages.CHECK_GET,
+12 -1
View File
@@ -20,7 +20,7 @@ import { getTokenFromHeaders } from "../utils/utils.js";
import logger from "../utils/logger.js";
import { handleError, handleValidationError } from "./controllerUtils.js";
import axios from "axios";
import seedDb from "../db/mongo/utils/seedDb.js";
const SERVICE_NAME = "monitorController";
class MonitorController {
@@ -495,6 +495,17 @@ class MonitorController {
next(handleError(error, SERVICE_NAME, "getMonitorsByTeamId"));
}
};
seedDb = async (req, res, next) => {
try {
const token = getTokenFromHeaders(req.headers);
const { jwtSecret } = this.settingsService.getSettings();
const { _id, teamId } = jwt.verify(token, jwtSecret);
await seedDb(_id, teamId);
} catch (error) {
next(handleError(error, SERVICE_NAME, "seedDb"));
}
};
}
export default MonitorController;
+9
View File
@@ -12,6 +12,13 @@ const BaseCheckSchema = mongoose.Schema({
immutable: true,
index: true,
},
teamId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Team",
immutable: true,
index: true,
},
/**
* Status of the check (true for up, false for down).
*
@@ -69,6 +76,8 @@ const CheckSchema = mongoose.Schema({ ...BaseCheckSchema.obj }, { timestamps: tr
CheckSchema.index({ createdAt: 1 });
CheckSchema.index({ monitorId: 1, createdAt: 1 });
CheckSchema.index({ monitorId: 1, createdAt: -1 });
CheckSchema.index({ teamId: 1, createdAt: -1 });
CheckSchema.index({ teamId: 1 });
export default mongoose.model("Check", CheckSchema);
export { BaseCheckSchema };
+81 -91
View File
@@ -2,11 +2,15 @@ import Check from "../../models/Check.js";
import Monitor from "../../models/Monitor.js";
import User from "../../models/User.js";
import logger from "../../../utils/logger.js";
import { ObjectId } from "mongodb";
const SERVICE_NAME = "checkModule";
const dateRangeLookup = {
hour: new Date(new Date().setHours(new Date().getHours() - 1)),
day: new Date(new Date().setDate(new Date().getDate() - 1)),
week: new Date(new Date().setDate(new Date().getDate() - 7)),
month: new Date(new Date().setMonth(new Date().getMonth() - 1)),
all: undefined,
};
/**
@@ -56,41 +60,6 @@ const createCheck = async (checkData) => {
}
};
const getChecksCount = async (req) => {
const monitorId = req.params.monitorId;
const dateRange = req.query.dateRange;
const filter = req.query.filter;
// Build query
const checksQuery = { monitorId: monitorId };
// Filter checks by "day", "week", or "month"
if (dateRange !== undefined) {
checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] };
}
if (filter !== undefined) {
checksQuery.status = false;
switch (filter) {
case "all":
break;
case "down":
break;
case "resolve":
checksQuery.statusCode = 5000;
break;
default:
logger.warn({
message: "invalid filter",
service: SERVICE_NAME,
method: "getChecksCount",
});
break;
}
}
const count = await Check.countDocuments(checksQuery);
return count;
};
/**
* Get all checks for a monitor
* @async
@@ -98,33 +67,31 @@ const getChecksCount = async (req) => {
* @returns {Promise<Array<Check>>}
* @throws {Error}
*/
const getChecks = async (req) => {
const getChecksByMonitor = async (req) => {
try {
const { monitorId } = req.params;
let { sortOrder, limit, dateRange, filter, page, rowsPerPage } = req.query;
// Default limit to 0 if not provided
limit = limit === undefined ? 0 : limit;
let { sortOrder, dateRange, filter, page, rowsPerPage } = req.query;
page = parseInt(page);
rowsPerPage = parseInt(rowsPerPage);
// Match
const matchStage = {
monitorId: ObjectId.createFromHexString(monitorId),
status: false,
...(dateRangeLookup[dateRange] && {
createdAt: {
$gte: dateRangeLookup[dateRange],
},
}),
};
// Default sort order is newest -> oldest
sortOrder = sortOrder === "asc" ? 1 : -1;
// Build query
const checksQuery = { monitorId: monitorId };
// Filter checks by "day", "week", or "month"
if (dateRange !== undefined) {
checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] };
}
// Filter checks by status
if (filter !== undefined) {
checksQuery.status = false;
switch (filter) {
case "all":
break;
case "down":
break;
case "resolve":
checksQuery.statusCode = 5000;
matchStage.statusCode = 5000;
break;
default:
logger.warn({
@@ -136,16 +103,33 @@ const getChecks = async (req) => {
}
}
// Need to skip and limit here
//Sort
sortOrder = sortOrder === "asc" ? 1 : -1;
// Pagination
let skip = 0;
if (page && rowsPerPage) {
skip = page * rowsPerPage;
}
const checks = await Check.find(checksQuery)
.skip(skip)
.limit(rowsPerPage)
.sort({ createdAt: sortOrder });
return checks;
const checks = await Check.aggregate([
{ $match: matchStage },
{ $sort: { createdAt: sortOrder } },
{
$facet: {
summary: [{ $count: "checksCount" }],
checks: [{ $skip: skip }, { $limit: rowsPerPage }],
},
},
{
$project: {
checksCount: { $arrayElemAt: ["$summary.checksCount", 0] },
checks: "$checks",
},
},
]);
return checks[0];
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getChecks";
@@ -153,64 +137,71 @@ const getChecks = async (req) => {
}
};
const getTeamChecks = async (req) => {
const getChecksByTeam = async (req) => {
try {
let { sortOrder, dateRange, filter, page, rowsPerPage } = req.query;
page = parseInt(page);
rowsPerPage = parseInt(rowsPerPage);
const { teamId } = req.params;
let { sortOrder, limit, dateRange, filter, page, rowsPerPage } = req.query;
// Get monitorIDs
const userMonitors = await Monitor.find({ teamId: teamId }).select("_id");
//Build check query
// Default limit to 0 if not provided
limit = limit === undefined ? 0 : limit;
// Default sort order is newest -> oldest
sortOrder = sortOrder === "asc" ? 1 : -1;
const checksQuery = { monitorId: { $in: userMonitors } };
const matchStage = {
teamId: ObjectId.createFromHexString(teamId),
status: false,
...(dateRangeLookup[dateRange] && {
createdAt: {
$gte: dateRangeLookup[dateRange],
},
}),
};
// Add filter to match stage
if (filter !== undefined) {
checksQuery.status = false;
switch (filter) {
case "all":
break;
case "down":
checksQuery.status = false;
break;
case "resolve":
checksQuery.statusCode = 5000;
matchStage.statusCode = 5000;
break;
default:
logger.warn({
message: "invalid filter",
service: SERVICE_NAME,
method: "getTeamChecks",
method: "getChecksByTeam",
});
break;
}
}
if (dateRange !== undefined) {
checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] };
}
sortOrder = sortOrder === "asc" ? 1 : -1;
// Skip and limit for pagination
// pagination
let skip = 0;
if (page && rowsPerPage) {
skip = page * rowsPerPage;
}
const checksCount = await Check.countDocuments(checksQuery);
const checks = await Check.aggregate([
{ $match: matchStage },
{ $sort: { createdAt: sortOrder } },
const checks = await Check.find(checksQuery)
.skip(skip)
.limit(rowsPerPage)
.sort({ createdAt: sortOrder })
.select(["monitorId", "status", "responseTime", "statusCode", "message"]);
return { checksCount, checks };
{
$facet: {
summary: [{ $count: "checksCount" }],
checks: [{ $skip: skip }, { $limit: rowsPerPage }],
},
},
{
$project: {
checksCount: { $arrayElemAt: ["$summary.checksCount", 0] },
checks: "$checks",
},
},
]);
return checks[0];
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getTeamChecks";
error.method = "getChecksByTeam";
throw error;
}
};
@@ -298,9 +289,8 @@ const updateChecksTTL = async (teamId, ttl) => {
export {
createCheck,
getChecksCount,
getChecks,
getTeamChecks,
getChecksByMonitor,
getChecksByTeam,
deleteChecks,
deleteChecksByTeamId,
updateChecksTTL,
+57
View File
@@ -0,0 +1,57 @@
import Monitor from "../../models/Monitor.js";
import Check from "../../models/Check.js";
const generateRandomUrl = () => {
const domains = ["example.com", "test.org", "demo.net", "sample.io", "mock.dev"];
const paths = ["api", "status", "health", "ping", "check"];
return `https://${domains[Math.floor(Math.random() * domains.length)]}/${paths[Math.floor(Math.random() * paths.length)]}`;
};
const generateChecks = (monitorId, teamId, count) => {
const checks = [];
const endTime = new Date(Date.now() - 10 * 60 * 1000); // 10 minutes ago
const startTime = new Date(endTime - count * 60 * 1000); // count minutes before endTime
for (let i = 0; i < count; i++) {
const timestamp = new Date(startTime.getTime() + i * 60 * 1000);
const status = Math.random() > 0.05; // 95% chance of being up
checks.push({
monitorId,
teamId,
status,
responseTime: Math.floor(Math.random() * 1000), // Random response time between 0-1000ms
createdAt: timestamp,
updatedAt: timestamp,
});
}
return checks;
};
const seedDb = async (userId, teamId) => {
try {
console.log("Deleting all monitors and checks");
await Monitor.deleteMany({});
await Check.deleteMany({});
console.log("Adding monitors");
for (let i = 0; i < 300; i++) {
const monitor = await Monitor.create({
name: `Monitor ${i}`,
url: generateRandomUrl(),
type: "http",
userId,
teamId,
interval: 60000,
active: false,
});
console.log(`Adding monitor and checks for monitor ${i}`);
const checks = generateChecks(monitor._id, teamId, 10000);
await Check.insertMany(checks);
}
} catch (error) {
console.error(error);
}
};
export default seedDb;
+2 -2
View File
@@ -11,7 +11,7 @@ class CheckRoutes {
}
initRoutes() {
this.router.get("/:monitorId", this.checkController.getChecks);
this.router.get("/:monitorId", this.checkController.getChecksByMonitor);
this.router.post(
"/:monitorId",
verifyOwnership(Monitor, "monitorId"),
@@ -23,7 +23,7 @@ class CheckRoutes {
this.checkController.deleteChecks
);
this.router.get("/team/:teamId", this.checkController.getTeamChecks);
this.router.get("/team/:teamId", this.checkController.getChecksByTeam);
this.router.delete(
"/team/:teamId",
+2
View File
@@ -75,6 +75,8 @@ class MonitorRoutes {
isAllowed(["admin", "superadmin"]),
this.monitorController.addDemoMonitors
);
this.router.post("/seed", isAllowed(["superadmin"]), this.monitorController.seedDb);
}
getRouter() {
+1
View File
@@ -131,6 +131,7 @@ class NetworkService {
const httpResponse = {
monitorId: job.data._id,
teamId: job.data.teamId,
type: job.data.type,
responseTime,
payload: response?.data,
+2 -1
View File
@@ -83,10 +83,11 @@ class StatusService {
* @returns {Object} The check object.
*/
buildCheck = (networkResponse) => {
const { monitorId, type, status, responseTime, code, message, payload } =
const { monitorId, teamId, type, status, responseTime, code, message, payload } =
networkResponse;
const check = {
monitorId,
teamId,
status,
statusCode: code,
responseTime,
+5 -5
View File
@@ -131,7 +131,7 @@ const getMonitorByIdQueryValidation = joi.object({
status: joi.boolean(),
sortOrder: joi.string().valid("asc", "desc"),
limit: joi.number(),
dateRange: joi.string().valid("day", "week", "month"),
dateRange: joi.string().valid("hour", "day", "week", "month", "all"),
numToDisplay: joi.number(),
normalize: joi.boolean(),
});
@@ -166,7 +166,7 @@ const getMonitorStatsByIdQueryValidation = joi.object({
status: joi.string(),
limit: joi.number(),
sortOrder: joi.string().valid("asc", "desc"),
dateRange: joi.string().valid("day", "week", "month", "all"),
dateRange: joi.string().valid("hour", "day", "week", "month", "all"),
numToDisplay: joi.number(),
normalize: joi.boolean(),
});
@@ -217,7 +217,7 @@ const getHardwareDetailsByIdParamValidation = joi.object({
});
const getHardwareDetailsByIdQueryValidation = joi.object({
dateRange: joi.string().valid("day", "week", "month"),
dateRange: joi.string().valid("hour", "day", "week", "month", "all"),
});
//****************************************
@@ -288,7 +288,7 @@ const getChecksParamValidation = joi.object({
const getChecksQueryValidation = joi.object({
sortOrder: joi.string().valid("asc", "desc"),
limit: joi.number(),
dateRange: joi.string().valid("day", "week", "month"),
dateRange: joi.string().valid("hour", "day", "week", "month", "all"),
filter: joi.string().valid("all", "down", "resolve"),
page: joi.number(),
rowsPerPage: joi.number(),
@@ -301,7 +301,7 @@ const getTeamChecksParamValidation = joi.object({
const getTeamChecksQueryValidation = joi.object({
sortOrder: joi.string().valid("asc", "desc"),
limit: joi.number(),
dateRange: joi.string().valid("day", "week", "month"),
dateRange: joi.string().valid("hour", "day", "week", "month", "all"),
filter: joi.string().valid("all", "down", "resolve"),
page: joi.number(),
rowsPerPage: joi.number(),