This commit is contained in:
Alex Holliday committed 2026-01-14 23:34:05 +00:00
1 parent b1b27be731
commit cdbaad5bd9
7 files changed
+72 -42

No files matched your search

+4 -2
View File
@@ -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 }) => {
+10 -18
View File
@@ -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 (
<>
<PageStateWrapper
networkError={networkError}
isLoading={isLoading}
items={monitors}
items={monitorsWithChecks}
type="uptimeMonitor"
fallbackLink="/uptime/create"
>
@@ -174,14 +166,14 @@ const UptimeMonitors = () => {
/>
<Greeting type="uptime" />
<StatusBoxes
monitorsSummary={monitorsSummary}
shouldRender={!monitorsWithSummaryIsLoading}
monitorsSummary={summary}
shouldRender={!monitorsWithChecksIsLoading}
/>
<Stack direction={"row"}>
<MonitorCountHeader
isLoading={monitorsWithSummaryIsLoading}
monitorCount={monitorsSummary?.totalMonitors}
isLoading={monitorsWithChecksIsLoading}
monitorCount={summary?.totalMonitors}
/>
<Filter
selectedTypes={selectedTypes}
@@ -195,7 +187,7 @@ const UptimeMonitors = () => {
handleReset={handleReset}
/>
<SearchComponent
monitors={monitors}
monitors={monitorsWithChecks}
onSearchChange={setSearch}
setIsSearching={setIsSearching}
/>
+6 -7
View File
@@ -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;
}
};
@@ -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<Monitor | null>;
@@ -27,4 +31,7 @@ export interface IMonitorsRepository {
// counts
findMonitorCountByTeamIdAndType(teamId: string, config: TeamQueryConfig): Promise<number>;
// other
findMonitorsSummaryByTeamId(teamId: string, config?: SummaryConfig): Promise<any>;
}
@@ -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<MonitorDocument> = { 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 [];
@@ -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<any[]>;
@@ -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 => {
@@ -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;