diff --git a/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js b/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js index 6cd91ac28..b2da2cbe6 100644 --- a/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js +++ b/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js @@ -40,7 +40,7 @@ export const checkEndpointResolution = createAsyncThunk( const res = await networkService.checkEndpointResolution({ authToken: authToken, monitorURL: monitorURL, - }) + }); return res.data; } catch (error) { if (error.response && error.response.data) { @@ -53,7 +53,7 @@ export const checkEndpointResolution = createAsyncThunk( return thunkApi.rejectWithValue(payload); } } -) +); export const getPagespeedMonitorById = createAsyncThunk( "monitors/getMonitorById", @@ -88,7 +88,6 @@ export const getPageSpeedByTeamId = createAsyncThunk( teamId: user.teamId, types: ["pagespeed"], }); - return res.data; } catch (error) { if (error.response && error.response.data) { diff --git a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js index 8782948ae..7ea34b9ea 100644 --- a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js +++ b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js @@ -78,62 +78,6 @@ export const getUptimeMonitorById = createAsyncThunk( } ); -export const getUptimeSummaryByTeamId = createAsyncThunk( - "monitors/getSummaryByTeamId", - async (token, thunkApi) => { - const user = jwtDecode(token); - try { - const res = await networkService.getMonitorsSummaryByTeamId({ - authToken: token, - teamId: user.teamId, - types: ["http", "ping", "docker", "port"], - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } -); - -export const getUptimeMonitorsByTeamId = createAsyncThunk( - "monitors/getMonitorsByTeamId", - async (config, thunkApi) => { - try { - const res = await networkService.getMonitorsByTeamId({ - authToken: config.authToken, - teamId: config.teamId, - limit: 25, - types: ["http", "ping", "docker", "port"], - status: null, - checkOrder: "desc", - normalize: true, - page: config.page, - rowsPerPage: config.rowsPerPage, - filter: config.filter, - field: config.sort.field, - order: config.sort.order, - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } -); - export const updateUptimeMonitor = createAsyncThunk( "monitors/updateMonitor", async (data, thunkApi) => { @@ -289,42 +233,6 @@ const uptimeMonitorsSlice = createSlice({ }, extraReducers: (builder) => { builder - // ***************************************************** - // Summary by teamId - // ***************************************************** - - .addCase(getUptimeSummaryByTeamId.pending, (state) => { - state.isLoading = true; - }) - .addCase(getUptimeSummaryByTeamId.fulfilled, (state, action) => { - state.isLoading = false; - state.success = action.payload.msg; - state.monitorsSummary = action.payload.data; - }) - .addCase(getUptimeSummaryByTeamId.rejected, (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload ? action.payload.msg : "Getting uptime summary failed"; - }) - - // ***************************************************** - // Monitors by teamId - // ***************************************************** - .addCase(getUptimeMonitorsByTeamId.pending, (state) => { - state.isLoading = true; - }) - .addCase(getUptimeMonitorsByTeamId.fulfilled, (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - }) - .addCase(getUptimeMonitorsByTeamId.rejected, (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Getting uptime monitors failed"; - }) // ***************************************************** // Create Monitor // ***************************************************** diff --git a/Client/src/Pages/Infrastructure/index.jsx b/Client/src/Pages/Infrastructure/index.jsx index be7d7e9ed..d9ef7528e 100644 --- a/Client/src/Pages/Infrastructure/index.jsx +++ b/Client/src/Pages/Infrastructure/index.jsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useState, useCallback } from "react"; import { useNavigate } from "react-router-dom"; import { /* useDispatch, */ useSelector } from "react-redux"; import { useTheme } from "@emotion/react"; @@ -50,12 +50,13 @@ function Infrastructure() { setRowsPerPage(parseInt(event.target.value)); setPage(0); }; - const [monitorState, setMonitorState] = useState({ monitors: [], total: 0 }); + const [monitors, setMonitors] = useState([]); + const [summary, setSummary] = useState({}); const { authToken } = useSelector((state) => state.auth); const user = jwtDecode(authToken); - const fetchMonitors = async () => { + const fetchMonitors = useCallback(async () => { try { setIsLoading(true); const response = await networkService.getMonitorsByTeamId({ @@ -63,29 +64,23 @@ function Infrastructure() { teamId: user.teamId, limit: 1, types: ["hardware"], - status: null, - checkOrder: "desc", - normalize: true, page: page, rowsPerPage: rowsPerPage, }); - setMonitorState({ - monitors: response?.data?.data?.monitors ?? [], - total: response?.data?.data?.monitorCount ?? 0, - }); + setMonitors(response?.data?.data?.monitors ?? []); + setSummary(response?.data?.data?.summary ?? {}); } catch (error) { console.error(error); } finally { setIsLoading(false); } - }; + }, [page, rowsPerPage, authToken, user.teamId]); useEffect(() => { fetchMonitors(); - }, [page, rowsPerPage]); + }, [fetchMonitors]); const { determineState } = useUtils(); - const { monitors, total: totalMonitors } = monitorState; // do it here function openDetails(id) { navigate(`/infrastructure/${id}`); @@ -191,7 +186,7 @@ function Infrastructure() { }; }); - let isActuallyLoading = isLoading && monitorState.monitors?.length === 0; + let isActuallyLoading = isLoading && monitors?.length === 0; return ( {isActuallyLoading ? ( - ) : monitorState.monitors?.length !== 0 ? ( + ) : monitors?.length !== 0 ? ( @@ -254,7 +249,7 @@ function Infrastructure() { borderColor={theme.palette.border.light} backgroundColor={theme.palette.background.accent} > - {totalMonitors} + {summary?.totalMonitors ?? 0} @@ -272,7 +267,7 @@ function Infrastructure() { data={monitorsAsRows} /> { sx={{ gridColumnStart: 1, gridColumnEnd: 4 }} > diff --git a/Client/src/Pages/PageSpeed/index.jsx b/Client/src/Pages/PageSpeed/index.jsx index b18148aa8..98761be6f 100644 --- a/Client/src/Pages/PageSpeed/index.jsx +++ b/Client/src/Pages/PageSpeed/index.jsx @@ -1,8 +1,7 @@ import { Box, Button, Grid, Stack } from "@mui/material"; import { useEffect, useState } from "react"; import { useTheme } from "@emotion/react"; -import { useDispatch, useSelector } from "react-redux"; -import { getPageSpeedByTeamId } from "../../Features/PageSpeedMonitor/pageSpeedMonitorSlice"; +import { useSelector } from "react-redux"; import Fallback from "../../Components/Fallback"; import "./index.css"; import { useNavigate } from "react-router"; @@ -15,16 +14,12 @@ import { Heading } from "../../Components/Heading"; import { useIsAdmin } from "../../Hooks/useIsAdmin"; const PageSpeed = () => { const theme = useTheme(); - const dispatch = useDispatch(); const navigate = useNavigate(); const isAdmin = useIsAdmin(); const { user, authToken } = useSelector((state) => state.auth); const [isLoading, setIsLoading] = useState(true); const [monitors, setMonitors] = useState([]); - const [monitorCount, setMonitorCount] = useState(0); - useEffect(() => { - dispatch(getPageSpeedByTeamId(authToken)); - }, [authToken, dispatch]); + const [summary, setSummary] = useState({}); useEffect(() => { const fetchMonitors = async () => { @@ -35,9 +30,6 @@ const PageSpeed = () => { teamId: user.teamId, limit: 10, types: ["pagespeed"], - status: null, - checkOrder: "desc", - normalize: true, page: null, rowsPerPage: null, filter: null, @@ -46,7 +38,7 @@ const PageSpeed = () => { }); if (res?.data?.data?.monitors) { setMonitors(res.data.data.monitors); - setMonitorCount(res.data.data.monitorCount); + setSummary(res.data.data.summary); } } catch (error) { console.log(error); @@ -118,7 +110,7 @@ const PageSpeed = () => { borderColor={theme.palette.border.light} backgroundColor={theme.palette.background.accent} > - {monitorCount} + {summary?.totalMonitors ?? 0} } props.monitors - Array of monitor objects to display + * @param {number} props.monitorCount - Total count of monitors + * @param {Object} props.sort - Current sort configuration + * @param {string} props.sort.field - Field to sort by + * @param {'asc'|'desc'} props.sort.order - Sort direction + * @param {Function} props.setSort - Callback to update sort configuration + * @param {string} props.search - Current search query + * @param {Function} props.setSearch - Callback to update search query + * @param {boolean} props.isSearching - Whether a search is in progress + * @param {Function} props.setIsSearching - Callback to update search state + * @param {Function} props.setIsLoading - Callback to update loading state + * @param {Function} props.triggerUpdate - Callback to trigger a data refresh + * @returns {JSX.Element} Rendered component + */ const UptimeDataTable = ({ isAdmin, isLoading, @@ -30,6 +59,7 @@ const UptimeDataTable = ({ setSearch, isSearching, setIsSearching, + setIsLoading, triggerUpdate, }) => { const { determineState } = useUtils(); @@ -37,8 +67,6 @@ const UptimeDataTable = ({ const theme = useTheme(); const navigate = useNavigate(); - //Utils - const debouncedFilter = useDebounce(search, 500); const handleSearch = (value) => { setIsSearching(true); setSearch(value); @@ -146,6 +174,7 @@ const UptimeDataTable = ({ monitor={row.monitor} isAdmin={isAdmin} updateRowCallback={triggerUpdate} + setIsLoading={setIsLoading} pauseCallback={triggerUpdate} /> ), @@ -187,7 +216,7 @@ const UptimeDataTable = ({ - {isSearching && ( + {(isSearching || isLoading) && ( <> { +const ActionsMenu = ({ + monitor, + isAdmin, + updateRowCallback, + pauseCallback, + setIsLoading, +}) => { const [anchorEl, setAnchorEl] = useState(null); const [actions, setActions] = useState({}); const [isOpen, setIsOpen] = useState(false); @@ -33,7 +38,6 @@ const ActionsMenu = ({ monitor, isAdmin, updateRowCallback, pauseCallback }) => ); if (action.meta.requestStatus === "fulfilled") { setIsOpen(false); // close modal - dispatch(getUptimeMonitorsByTeamId(authState.authToken)); updateRowCallback(); createToast({ body: "Monitor deleted successfully." }); } else { @@ -43,6 +47,7 @@ const ActionsMenu = ({ monitor, isAdmin, updateRowCallback, pauseCallback }) => const handlePause = async () => { try { + setIsLoading(true); const action = await dispatch( pauseUptimeMonitor({ authToken, monitorId: monitor._id }) ); @@ -223,6 +228,7 @@ ActionsMenu.propTypes = { isAdmin: PropTypes.bool, updateRowCallback: PropTypes.func, pauseCallback: PropTypes.func, + setIsLoading: PropTypes.func, }; export default ActionsMenu; diff --git a/Client/src/Pages/Uptime/Home/index.jsx b/Client/src/Pages/Uptime/Home/index.jsx index 3dffb3ac9..391be0aed 100644 --- a/Client/src/Pages/Uptime/Home/index.jsx +++ b/Client/src/Pages/Uptime/Home/index.jsx @@ -9,11 +9,7 @@ import { Pagination } from "../../../Components/Table/TablePagination"; // Utils import { useTheme } from "@emotion/react"; -import { useEffect, useState, useCallback, useMemo, useRef } from "react"; -import { - getUptimeSummaryByTeamId, - getUptimeMonitorsByTeamId, -} from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; +import { useEffect, useState, useCallback, useMemo } from "react"; import { setRowsPerPage } from "../../../Features/UI/uiSlice"; import { useIsAdmin } from "../../../Hooks/useIsAdmin"; import { useSelector, useDispatch } from "react-redux"; @@ -21,6 +17,7 @@ import { useNavigate } from "react-router-dom"; import { createToast } from "../../../Utils/toastUtils"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import useDebounce from "../../../Utils/debounce"; +import { networkService } from "../../../main"; const BREADCRUMBS = [{ name: `Uptime`, path: "/uptime" }]; @@ -33,7 +30,9 @@ const UptimeMonitors = () => { const [search, setSearch] = useState(""); const [page, setPage] = useState(0); const [isSearching, setIsSearching] = useState(false); + const [isLoading, setIsLoading] = useState(false); const [monitorUpdateTrigger, setMonitorUpdateTrigger] = useState(false); + const [monitorsSummary, setMonitorsSummary] = useState({}); // Utils const debouncedFilter = useDebounce(search, 500); @@ -41,7 +40,6 @@ const UptimeMonitors = () => { const theme = useTheme(); const navigate = useNavigate(); const isAdmin = useIsAdmin(); - const { isLoading, monitorsSummary } = useSelector((state) => state.uptimeMonitors); const authState = useSelector((state) => state.auth); const fetchParams = useMemo( @@ -89,30 +87,38 @@ const UptimeMonitors = () => { const fetchMonitors = useCallback(async () => { try { - const action = await dispatch(getUptimeMonitorsByTeamId(fetchParams)); - if (action.payload.success) { - const { monitors } = action.payload.data; - const mappedMonitors = monitors.map((monitor) => - getMonitorWithPercentage(monitor, theme) - ); - setMonitors(mappedMonitors); - } else { - // TODO: Check for other errors? - throw new Error("Error fetching monitors"); - } + setIsLoading(true); + const config = fetchParams; + const res = await networkService.getMonitorsByTeamId({ + authToken: config.authToken, + teamId: config.teamId, + limit: 25, + types: ["http", "ping", "docker", "port"], + page: config.page, + rowsPerPage: config.rowsPerPage, + filter: config.filter, + field: config.sort.field, + order: config.sort.order, + }); + const { monitors, summary } = res.data.data; + const mappedMonitors = monitors.map((monitor) => + getMonitorWithPercentage(monitor, theme) + ); + setMonitors(mappedMonitors); + setMonitorsSummary(summary); } catch (error) { createToast({ body: "Error fetching monitors", }); } finally { + setIsLoading(false); setIsSearching(false); } - }, [fetchParams, dispatch, getMonitorWithPercentage, theme]); + }, [fetchParams, getMonitorWithPercentage, theme]); useEffect(() => { - dispatch(getUptimeSummaryByTeamId(authState.authToken)); fetchMonitors(); - }, [fetchMonitors, monitorUpdateTrigger, authState.authToken, dispatch]); + }, [fetchMonitors, monitorUpdateTrigger]); const handleChangePage = (event, newPage) => { setPage(newPage); @@ -131,7 +137,7 @@ const UptimeMonitors = () => { const triggerUpdate = () => { setMonitorUpdateTrigger((prev) => !prev); }; - const totalMonitors = monitorsSummary?.monitorCounts?.total; + const totalMonitors = monitorsSummary.totalMonitors; const hasMonitors = totalMonitors > 0; const canAddMonitor = isAdmin && hasMonitors; @@ -176,19 +182,20 @@ const UptimeMonitors = () => { > { setSearch={setSearch} isSearching={isSearching} setIsSearching={setIsSearching} + setIsLoading={setIsLoading} triggerUpdate={triggerUpdate} /> } [config.types] - The types of monitors to retrieve. - * @param {string} [config.status] - The status of the monitors to retrieve. - * @param {string} [config.checkOrder] - The order in which to sort the retrieved monitors. - * @param {boolean} [config.normalize] - Whether to normalize the retrieved monitors. * @param {number} [config.page] - The page number for pagination. * @param {number} [config.rowsPerPage] - The number of rows per page for pagination. * @param {string} [config.filter] - The filter to apply to the monitors. @@ -167,21 +164,10 @@ class NetworkService { * @param {string} [config.order] - The order in which to sort the field. * @returns {Promise} The response from the axios GET request. */ + async getMonitorsByTeamId(config) { - const { - authToken, - teamId, - limit, - types, - status, - checkOrder, - normalize, - page, - rowsPerPage, - filter, - field, - order, - } = config; + const { authToken, teamId, limit, types, page, rowsPerPage, filter, field, order } = + config; const params = new URLSearchParams(); @@ -191,9 +177,6 @@ class NetworkService { params.append("type", type); }); } - if (status) params.append("status", status); - if (checkOrder) params.append("checkOrder", checkOrder); - if (normalize) params.append("normalize", normalize); if (page) params.append("page", page); if (rowsPerPage) params.append("rowsPerPage", rowsPerPage); if (filter) params.append("filter", filter); diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index f5e7994b2..686661a00 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -1,13 +1,11 @@ import { getMonitorByIdParamValidation, getMonitorByIdQueryValidation, - getMonitorsByTeamIdValidation, + getMonitorsByTeamIdParamValidation, + getMonitorsByTeamIdQueryValidation, createMonitorBodyValidation, getMonitorURLByQueryValidation, editMonitorBodyValidation, - getMonitorsSummaryByTeamIdParamValidation, - getMonitorsSummaryByTeamIdQueryValidation, - getMonitorsByTeamIdQueryValidation, pauseMonitorParamValidation, getMonitorStatsByIdParamValidation, getMonitorStatsByIdQueryValidation, @@ -204,77 +202,6 @@ class MonitorController { } }; - /** - * Retrieves all monitors and a summary for a team based on the team ID. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.teamId - The ID of the team. - * @property {Object} req.query - The query parameters of the request. - * @property {string} req.query.type - The type of the request. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message, and the data containing the monitors and summary for the team. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ - getMonitorsSummaryByTeamId = async (req, res, next) => { - try { - await getMonitorsSummaryByTeamIdParamValidation.validateAsync(req.params); - await getMonitorsSummaryByTeamIdQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const { teamId } = req.params; - const { type } = req.query; - const monitorsSummary = await this.db.getMonitorsSummaryByTeamId(teamId, type); - return res.status(200).json({ - success: true, - msg: successMessages.MONITOR_GET_BY_USER_ID(teamId), - data: monitorsSummary, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMonitorsAndSummaryByTeamId")); - } - }; - - /** - * Retrieves all monitors associated with a team by the team's ID. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.teamId - The ID of the team. - * @property {Object} req.query - The query parameters of the request. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message, and the data containing the monitors for the team. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ - getMonitorsByTeamId = async (req, res, next) => { - try { - await getMonitorsByTeamIdValidation.validateAsync(req.params); - await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const teamId = req.params.teamId; - const monitors = await this.db.getMonitorsByTeamId(req, res); - return res.status(200).json({ - success: true, - msg: successMessages.MONITOR_GET_BY_USER_ID(teamId), - data: monitors, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMonitorsByTeamId")); - next(error); - } - }; - /** * Creates a new monitor and adds it to the job queue. * @async @@ -564,6 +491,26 @@ class MonitorController { next(handleError(error, SERVICE_NAME, "addDemoMonitors")); } }; + + getMonitorsByTeamId = async (req, res, next) => { + try { + await getMonitorsByTeamIdParamValidation.validateAsync(req.params); + await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + } + + try { + const monitors = await this.db.getMonitorsByTeamId(req); + return res.status(200).json({ + success: true, + msg: "good", + data: monitors, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getMonitorsForDisplay")); + } + }; } export default MonitorController; diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index aa4cafbb7..77c290c52 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -505,202 +505,162 @@ const getMonitorById = async (monitorId) => { } }; -/** - * Get monitors and Summary by TeamID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise>} - * @throws {Error} - */ +const getMonitorsByTeamId = async (req) => { + let { limit, type, page, rowsPerPage, filter, field, order } = req.query; -const getMonitorsSummaryByTeamId = async (teamId, type) => { - try { - const monitorCounts = await Monitor.aggregate([ - { - $match: { - type: { $in: type }, - }, - }, - { - $facet: { - total: [{ $count: "count" }], - up: [{ $match: { status: true } }, { $count: "count" }], - down: [{ $match: { status: false } }, { $count: "count" }], - paused: [{ $match: { isActive: false } }, { $count: "count" }], - }, - }, - { - $project: { - total: { $arrayElemAt: ["$total.count", 0] }, - up: { $arrayElemAt: ["$up.count", 0] }, - down: { $arrayElemAt: ["$down.count", 0] }, - paused: { $arrayElemAt: ["$paused.count", 0] }, - }, - }, - ]); - return { monitorCounts: monitorCounts[0] }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorsAndSummaryByTeamId"; - throw error; + // Parse ints + limit = parseInt(limit); + page = parseInt(page); + rowsPerPage = parseInt(rowsPerPage); + + // Build the match stage + const matchStage = { teamId: ObjectId.createFromHexString(req.params.teamId) }; + if (type !== undefined) { + matchStage.type = Array.isArray(type) ? { $in: type } : type; } -}; -/** - * Get monitors by TeamID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise>} - * @throws {Error} - */ -const getMonitorsByTeamId = async (req, res) => { - try { - let { - limit, - type, - status, - checkOrder, - normalize, - page, - rowsPerPage, - filter, - field, - order, - } = req.query; + const skip = page && rowsPerPage ? page * rowsPerPage : 0; - const monitorQuery = { teamId: req.params.teamId }; - const monitorCount = await Monitor.countDocuments(monitorQuery); + const sort = { [field]: order === "asc" ? 1 : -1 }; - if (type !== undefined) { - monitorQuery.type = Array.isArray(type) ? { $in: type } : type; - } - // Add filter if provided - // $options: "i" makes the search case-insensitive - if (filter !== undefined) { - monitorQuery.$or = [ - { name: { $regex: filter, $options: "i" } }, - { url: { $regex: filter, $options: "i" } }, - ]; - } + if (filter !== undefined) { + matchStage.$or = [ + { name: { $regex: filter, $options: "i" } }, + { url: { $regex: filter, $options: "i" } }, + ]; + } - // Pagination - const skip = page && rowsPerPage ? page * rowsPerPage : 0; - - // Build Sort option - const sort = { [field]: order === "asc" ? 1 : -1 }; - - const matchStage = { teamId: new ObjectId(req.params.teamId) }; - if (type !== undefined) { - matchStage.type = Array.isArray(type) ? { $in: type } : type; - } - if (filter !== undefined) { - matchStage.$or = [ - { name: { $regex: filter, $options: "i" } }, - { url: { $regex: filter, $options: "i" } }, - ]; - } - - let result = await Monitor.aggregate([ - { $match: matchStage }, - { $skip: parseInt(skip) }, - ...(rowsPerPage ? [{ $limit: parseInt(rowsPerPage) }] : []), - { $sort: sort }, - { - $lookup: { - from: "checks", - let: { monitorId: "$_id" }, - pipeline: [ - { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, - ...(status && { status }), - }, - }, - { $sort: { createdAt: checkOrder === "asc" ? 1 : -1 } }, - { $limit: parseInt(limit) || 0 }, - ], - as: "standardchecks", - }, - }, - { - $lookup: { - from: "pagespeedchecks", - let: { monitorId: "$_id" }, - pipeline: [ - { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, - ...(status && { status }), - }, - }, - { $sort: { createdAt: checkOrder === "asc" ? 1 : -1 } }, - { $limit: parseInt(limit) || 0 }, - ], - as: "pagespeedchecks", - }, - }, - { - $lookup: { - from: "hardwarechecks", - let: { monitorId: "$_id" }, - pipeline: [ - { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, - ...(status && { status }), - }, - }, - { $sort: { createdAt: checkOrder === "asc" ? 1 : -1 } }, - { $limit: parseInt(limit) || 0 }, - ], - as: "hardwarechecks", - }, - }, - { - $addFields: { - checks: { - $switch: { - branches: [ - { - case: { $in: ["$type", ["http", "ping", "docker", "port"]] }, - then: "$standardchecks", + const results = await Monitor.aggregate([ + { $match: matchStage }, + { + $facet: { + summary: [ + { + $group: { + _id: null, + totalMonitors: { $sum: 1 }, + upMonitors: { + $sum: { + $cond: [{ $eq: ["$status", true] }, 1, 0], }, - { - case: { $eq: ["$type", "pagespeed"] }, - then: "$pagespeedchecks", + }, + downMonitors: { + $sum: { + $cond: [{ $eq: ["$status", false] }, 1, 0], }, - { - case: { $eq: ["$type", "hardware"] }, - then: "$hardwarechecks", + }, + pausedMonitors: { + $sum: { + $cond: [{ $eq: ["$isActive", false] }, 1, 0], }, - ], - default: [], + }, }, }, - }, + { + $project: { + _id: 0, + }, + }, + ], + monitors: [ + { $sort: sort }, + { $skip: skip }, + ...(rowsPerPage ? [{ $limit: rowsPerPage }] : []), + { + $lookup: { + from: "checks", + let: { monitorId: "$_id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + }, + }, + { $sort: { createdAt: -1 } }, + ...(limit ? [{ $limit: limit }] : []), + ], + as: "standardchecks", + }, + }, + { + $lookup: { + from: "pagespeedchecks", + let: { monitorId: "$_id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + }, + }, + { $sort: { createdAt: -1 } }, + ...(limit ? [{ $limit: limit }] : []), + ], + as: "pagespeedchecks", + }, + }, + { + $lookup: { + from: "hardwarechecks", + let: { monitorId: "$_id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + }, + }, + { $sort: { createdAt: -1 } }, + ...(limit ? [{ $limit: limit }] : []), + ], + as: "hardwarechecks", + }, + }, + { + $addFields: { + checks: { + $switch: { + branches: [ + { + case: { $in: ["$type", ["http", "ping", "docker", "port"]] }, + then: "$standardchecks", + }, + { + case: { $eq: ["$type", "pagespeed"] }, + then: "$pagespeedchecks", + }, + { + case: { $eq: ["$type", "hardware"] }, + then: "$hardwarechecks", + }, + ], + default: [], + }, + }, + }, + }, + { + $project: { + standardchecks: 0, + pagespeedchecks: 0, + hardwarechecks: 0, + }, + }, + ], }, - { - $project: { - standardchecks: 0, - pagespeedchecks: 0, - hardwarechecks: 0, - }, + }, + { + $project: { + summary: { $arrayElemAt: ["$summary", 0] }, + monitors: 1, }, - ]); - if (normalize) { - result = result.map((monitor) => { - monitor.checks = NormalizeData(monitor.checks, 10, 100); - return monitor; - }); - } - return { monitors: result, monitorCount }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorsByTeamId"; - throw error; - } + }, + ]); + + let { monitors, summary } = results[0]; + monitors = monitors.map((monitor) => { + monitor.checks = NormalizeData(monitor.checks, 10, 100); + return monitor; + }); + return { monitors, summary }; }; /** @@ -832,9 +792,8 @@ export { getAllMonitorsWithUptimeStats, getMonitorStatsById, getMonitorById, - getUptimeDetailsById, - getMonitorsSummaryByTeamId, getMonitorsByTeamId, + getUptimeDetailsById, createMonitor, deleteMonitor, deleteAllMonitors, diff --git a/Server/routes/monitorRoute.js b/Server/routes/monitorRoute.js index f3af8e806..92b529417 100644 --- a/Server/routes/monitorRoute.js +++ b/Server/routes/monitorRoute.js @@ -17,6 +17,7 @@ class MonitorRoutes { "/hardware/details/:monitorId", this.monitorController.getHardwareDetailsById ); + this.router.get( "/uptime/details/:monitorId", this.monitorController.getUptimeDetailsById @@ -30,10 +31,7 @@ class MonitorRoutes { ); }); this.router.get("/:monitorId", this.monitorController.getMonitorById); - this.router.get( - "/team/summary/:teamId", - this.monitorController.getMonitorsSummaryByTeamId - ); + this.router.get("/team/:teamId", this.monitorController.getMonitorsByTeamId); this.router.get( diff --git a/Server/validation/joi.js b/Server/validation/joi.js index 825c90cdb..d30235048 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -136,32 +136,12 @@ const getMonitorByIdQueryValidation = joi.object({ normalize: joi.boolean(), }); -const getMonitorsSummaryByTeamIdParamValidation = joi.object({ - teamId: joi.string().required(), -}); - -const getMonitorsSummaryByTeamIdQueryValidation = joi.object({ - type: joi - .alternatives() - .try( - joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port"), - joi - .array() - .items( - joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port") - ) - ), -}); - -const getMonitorsByTeamIdValidation = joi.object({ +const getMonitorsByTeamIdParamValidation = joi.object({ teamId: joi.string().required(), }); const getMonitorsByTeamIdQueryValidation = joi.object({ - status: joi.boolean(), - checkOrder: joi.string().valid("asc", "desc"), limit: joi.number(), - normalize: joi.boolean(), type: joi .alternatives() .try( @@ -467,9 +447,7 @@ export { createMonitorBodyValidation, getMonitorByIdParamValidation, getMonitorByIdQueryValidation, - getMonitorsSummaryByTeamIdParamValidation, - getMonitorsSummaryByTeamIdQueryValidation, - getMonitorsByTeamIdValidation, + getMonitorsByTeamIdParamValidation, getMonitorsByTeamIdQueryValidation, getMonitorStatsByIdParamValidation, getMonitorStatsByIdQueryValidation,