From cdbaad5bd90616a40cb167d16fe2a99c56f396a2 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 14 Jan 2026 23:34:05 +0000 Subject: [PATCH] initial --- client/src/Hooks/monitorHooks.js | 6 ++- client/src/Pages/Uptime/Monitors/index.jsx | 28 +++++------- server/src/db/modules/monitorModule.js | 13 +++--- .../monitors/IMonitorsRepository.ts | 7 +++ .../monitors/MongoMonitorsRepository.ts | 44 +++++++++++++++++-- server/src/service/business/monitorService.ts | 7 +-- .../service/infrastructure/statusService.js | 9 ---- 7 files changed, 72 insertions(+), 42 deletions(-) diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index bc391ebf6..92844ad16 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -50,6 +50,7 @@ export const useFetchMonitorsWithChecks = ({ const [isLoading, setIsLoading] = useState(false); const [count, setCount] = useState(undefined); const [monitors, setMonitors] = useState(undefined); + const [summary, setSummary] = useState(undefined); const [networkError, setNetworkError] = useState(false); const theme = useTheme(); @@ -68,10 +69,11 @@ export const useFetchMonitorsWithChecks = ({ order, }); - const { count, monitors } = res?.data?.data ?? {}; + const { count, monitors, summary } = res?.data?.data ?? {}; const mappedMonitors = monitors.map((monitor) => getMonitorWithPercentage(monitor, theme) ); + setSummary(summary); setMonitors(mappedMonitors); setCount(count || 0); } catch (error) { @@ -97,7 +99,7 @@ export const useFetchMonitorsWithChecks = ({ types, monitorUpdateTrigger, ]); - return [monitors, count, isLoading, networkError]; + return [summary, monitors, count, isLoading, networkError]; }; export const useFetchMonitorsByTeamId = ({ types, filter, updateTrigger }) => { diff --git a/client/src/Pages/Uptime/Monitors/index.jsx b/client/src/Pages/Uptime/Monitors/index.jsx index fb8d6246b..e5bcf45fb 100644 --- a/client/src/Pages/Uptime/Monitors/index.jsx +++ b/client/src/Pages/Uptime/Monitors/index.jsx @@ -26,10 +26,7 @@ import { useNavigate } from "react-router-dom"; import { useSelector, useDispatch } from "react-redux"; import { setRowsPerPage } from "../../../Features/UI/uiSlice.js"; import PropTypes from "prop-types"; -import { - useFetchMonitorsWithSummary, - useFetchMonitorsWithChecks, -} from "@/Hooks/monitorHooks.js"; +import { useFetchMonitorsWithChecks } from "@/Hooks/monitorHooks.js"; import { useTranslation } from "react-i18next"; const TYPES = ["http", "ping", "docker", "port", "game"]; @@ -103,12 +100,6 @@ const UptimeMonitors = () => { setMonitorUpdateTrigger((prev) => !prev); }, []); - const [monitors, monitorsSummary, monitorsWithSummaryIsLoading, networkError] = - useFetchMonitorsWithSummary({ - types: TYPES, - monitorUpdateTrigger, - }); - const handleReset = () => { setSelectedState(undefined); setSelectedTypes(undefined); @@ -129,10 +120,11 @@ const UptimeMonitors = () => { const effectiveTypes = selectedTypes?.length ? selectedTypes : TYPES; const [ + summary, monitorsWithChecks, monitorsWithChecksCount, monitorsWithChecksIsLoading, - monitorsWithChecksNetworkError, + networkError, ] = useFetchMonitorsWithChecks({ types: effectiveTypes, limit: 25, @@ -150,14 +142,14 @@ const UptimeMonitors = () => { } }, [isSearching]); - const isLoading = monitorsWithSummaryIsLoading || monitorsWithChecksIsLoading; + const isLoading = monitorsWithChecksIsLoading; return ( <> @@ -174,14 +166,14 @@ const UptimeMonitors = () => { /> { handleReset={handleReset} /> diff --git a/server/src/db/modules/monitorModule.js b/server/src/db/modules/monitorModule.js index 84ff86996..e13da4b59 100755 --- a/server/src/db/modules/monitorModule.js +++ b/server/src/db/modules/monitorModule.js @@ -318,23 +318,22 @@ class MonitorModule { } }; - getMonitorsAndSummaryByTeamId = async ({ type, explain, teamId }) => { + findMonitorsSummaryByTeamId = async ({ type, teamId, explain }) => { try { const matchStage = { teamId: new this.ObjectId(teamId) }; if (type !== undefined) { matchStage.type = Array.isArray(type) ? { $in: type } : type; } + const pipeline = buildMonitorsSummaryByTeamIdPipeline({ matchStage }); if (explain === true) { - return this.Monitor.aggregate(buildMonitorsAndSummaryByTeamIdPipeline({ matchStage })).explain("executionStats"); + return this.Monitor.aggregate(pipeline).explain("executionStats"); } - - const queryResult = await this.Monitor.aggregate(buildMonitorsAndSummaryByTeamIdPipeline({ matchStage })); - const { monitors, summary } = queryResult?.[0] ?? {}; - return { monitors, summary }; + const [summary] = await this.Monitor.aggregate(pipeline); + return summary ?? { totalMonitors: 0, upMonitors: 0, downMonitors: 0, pausedMonitors: 0 }; } catch (error) { error.service = SERVICE_NAME; - error.method = "getMonitorsAndSummaryByTeamId"; + error.method = "findMonitorsSummaryByTeamId"; throw error; } }; diff --git a/server/src/repositories/monitors/IMonitorsRepository.ts b/server/src/repositories/monitors/IMonitorsRepository.ts index 0da9c7279..50fc79b2b 100644 --- a/server/src/repositories/monitors/IMonitorsRepository.ts +++ b/server/src/repositories/monitors/IMonitorsRepository.ts @@ -10,6 +10,10 @@ export interface TeamQueryConfig { order?: "asc" | "desc"; } +export interface SummaryConfig { + type?: MonitorType | MonitorType[]; +} + export interface IMonitorsRepository { // create create(monitor: Monitor, teamId: string, userId: string): Promise; @@ -27,4 +31,7 @@ export interface IMonitorsRepository { // counts findMonitorCountByTeamIdAndType(teamId: string, config: TeamQueryConfig): Promise; + + // other + findMonitorsSummaryByTeamId(teamId: string, config?: SummaryConfig): Promise; } diff --git a/server/src/repositories/monitors/MongoMonitorsRepository.ts b/server/src/repositories/monitors/MongoMonitorsRepository.ts index c9af6668d..d839b1a78 100644 --- a/server/src/repositories/monitors/MongoMonitorsRepository.ts +++ b/server/src/repositories/monitors/MongoMonitorsRepository.ts @@ -1,8 +1,8 @@ import { MonitorModel } from "@/db/models/index.js"; -import type { MonitorDocument } from "@/db/models/Monitor.js"; -import type { Monitor, MonitorType } from "@/types/monitor.js"; +import type { MonitorDocument } from "@/db/models/index.js"; +import type { Monitor, MonitorType } from "@/types/index.js"; import mongoose, { type FilterQuery } from "mongoose"; -import type { IMonitorsRepository, TeamQueryConfig } from "./IMonitorsRepository.js"; +import type { IMonitorsRepository, TeamQueryConfig, SummaryConfig } from "./IMonitorsRepository.js"; import { AppError } from "@/utils/AppError.js"; class MongoMonitorsRepository implements IMonitorsRepository { @@ -120,6 +120,44 @@ class MongoMonitorsRepository implements IMonitorsRepository { return { monitors: this.mapDocuments(monitors), deletedCount }; }; + findMonitorsSummaryByTeamId = async ( + teamId: string, + config?: SummaryConfig + ): Promise<{ totalMonitors: number; upMonitors: number; downMonitors: number; pausedMonitors: number }> => { + const match: FilterQuery = { teamId: new mongoose.Types.ObjectId(teamId) }; + if (config?.type !== undefined) { + match.type = Array.isArray(config.type) ? { $in: config.type } : config.type; + } + const pipeline = [ + { $match: match }, + { + $group: { + _id: null, + totalMonitors: { $sum: 1 }, + upMonitors: { + $sum: { + $cond: [{ $eq: ["$status", true] }, 1, 0], + }, + }, + downMonitors: { + $sum: { + $cond: [{ $eq: ["$status", false] }, 1, 0], + }, + }, + pausedMonitors: { + $sum: { + $cond: [{ $eq: ["$isActive", false] }, 1, 0], + }, + }, + }, + }, + { $project: { _id: 0 } }, + ]; + + const [summary] = await MonitorModel.aggregate(pipeline); + return summary ?? { totalMonitors: 0, upMonitors: 0, downMonitors: 0, pausedMonitors: 0 }; + }; + private mapDocuments = (documents: MonitorDocument[]): Monitor[] => { if (!documents?.length) { return []; diff --git a/server/src/service/business/monitorService.ts b/server/src/service/business/monitorService.ts index 3793ac35e..e88fdbea3 100644 --- a/server/src/service/business/monitorService.ts +++ b/server/src/service/business/monitorService.ts @@ -47,7 +47,7 @@ export interface IMonitorService { field?: string; order?: "asc" | "desc"; explain?: boolean; - }): Promise<{ count: number; monitors: any[] }>; + }): Promise<{ summary: any; count: number; monitors: any[] }>; getAllGames(): any; getGroupsByTeamId(args: { teamId: string }): Promise; @@ -399,7 +399,8 @@ export class MonitorService implements IMonitorService { field?: string; order?: "asc" | "desc"; explain?: boolean; - }): Promise<{ count: number; monitors: any[] }> => { + }): Promise<{ summary: any; count: number; monitors: any[] }> => { + const summary = await this.monitorsRepository.findMonitorsSummaryByTeamId(teamId); const count = await this.monitorsRepository.findMonitorCountByTeamIdAndType(teamId, { type, filter }); const monitors = await this.monitorsRepository.findByTeamId(teamId, { limit, @@ -433,7 +434,7 @@ export class MonitorService implements IMonitorService { }; }); - return { count, monitors: monitorsWithChecks }; + return { summary: summary ?? null, count, monitors: monitorsWithChecks }; }; getAllGames = (): any => { diff --git a/server/src/service/infrastructure/statusService.js b/server/src/service/infrastructure/statusService.js index e849bbb83..105f89c44 100755 --- a/server/src/service/infrastructure/statusService.js +++ b/server/src/service/infrastructure/statusService.js @@ -5,15 +5,6 @@ const SERVICE_NAME = "StatusService"; class StatusService { static SERVICE_NAME = SERVICE_NAME; - /** - * @param {{ - * db: any - * logger: any - * buffer: import("./bufferService.js").BufferService - * incidentService: import("../business/incidentService.js").IncidentService - * monitorsRepository: any - * }} - */ constructor({ db, logger, buffer, incidentService, monitorsRepository }) { this.db = db; this.logger = logger;