mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-14 21:48:39 -05:00
Merge pull request #3130 from bluewave-labs/fix/monitor-service
Fix/monitor service
This commit is contained in:
@@ -66,7 +66,7 @@ const MonitorDetailsControlHeader = ({
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Tooltip
|
||||
key={monitor?._id}
|
||||
key={monitor?.id}
|
||||
placement="bottom"
|
||||
title={tooltipTitle}
|
||||
>
|
||||
@@ -78,7 +78,7 @@ const MonitorDetailsControlHeader = ({
|
||||
startIcon={<EmailIcon />}
|
||||
disabled={isTestNotificationsDisabled}
|
||||
onClick={() => {
|
||||
testAllNotifications({ monitorId: monitor?._id });
|
||||
testAllNotifications({ monitorId: monitor?.id });
|
||||
}}
|
||||
sx={{
|
||||
whiteSpace: "nowrap",
|
||||
@@ -92,7 +92,7 @@ const MonitorDetailsControlHeader = ({
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={(e) => {
|
||||
navigate(`/incidents/${monitor?._id}`);
|
||||
navigate(`/incidents/${monitor?.id}`);
|
||||
}}
|
||||
>
|
||||
{t("menu.incidents")}
|
||||
@@ -107,7 +107,7 @@ const MonitorDetailsControlHeader = ({
|
||||
}
|
||||
onClick={() => {
|
||||
pauseMonitor({
|
||||
monitorId: monitor?._id,
|
||||
monitorId: monitor?.id,
|
||||
triggerUpdate,
|
||||
});
|
||||
}}
|
||||
@@ -120,7 +120,7 @@ const MonitorDetailsControlHeader = ({
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
startIcon={<SettingsOutlinedIcon />}
|
||||
onClick={() => navigate(`/${path}/configure/${monitor._id}`)}
|
||||
onClick={() => navigate(`/${path}/configure/${monitor.id}`)}
|
||||
>
|
||||
Configure
|
||||
</Button>
|
||||
|
||||
@@ -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 }) => {
|
||||
@@ -385,7 +387,7 @@ export const useUpdateMonitor = () => {
|
||||
}),
|
||||
};
|
||||
await networkService.updateMonitor({
|
||||
monitorId: monitor._id,
|
||||
monitorId: monitor.id,
|
||||
updatedFields,
|
||||
});
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ const InfrastructureMonitors = () => {
|
||||
|
||||
const field = toFilterStatus !== undefined ? "status" : undefined;
|
||||
|
||||
const [monitors, count, isLoading, networkError] = useFetchMonitorsWithChecks({
|
||||
const [summary, monitors, count, isLoading, networkError] = useFetchMonitorsWithChecks({
|
||||
types: TYPES,
|
||||
limit: 1,
|
||||
page: page,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Grid, Grid2 } from "@mui/material";
|
||||
import Card from "../Card/index.jsx";
|
||||
|
||||
const MonitorGrid = ({ shouldRender, monitors }) => {
|
||||
console.log(monitors);
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
|
||||
@@ -21,6 +21,7 @@ const PageSpeed = () => {
|
||||
const isAdmin = useIsAdmin();
|
||||
|
||||
const [
|
||||
summary,
|
||||
monitorsWithChecks,
|
||||
monitorsWithChecksCount,
|
||||
monitorsWithChecksIsLoading,
|
||||
|
||||
@@ -199,7 +199,7 @@ const UptimeCreate = ({ isClone = false }) => {
|
||||
};
|
||||
} else {
|
||||
form = {
|
||||
_id: monitor._id,
|
||||
id: monitor.id,
|
||||
url: monitor.url,
|
||||
name: monitor.name || monitor.url.substring(0, 50),
|
||||
statusWindowSize: monitor.statusWindowSize,
|
||||
@@ -292,7 +292,7 @@ const UptimeCreate = ({ isClone = false }) => {
|
||||
|
||||
const handleRemove = async (event) => {
|
||||
event.preventDefault();
|
||||
const TEMP_MONITOR = { id: monitor._id };
|
||||
const TEMP_MONITOR = { id: monitor.id };
|
||||
await deleteMonitor({ monitor: TEMP_MONITOR, redirect: "/uptime" });
|
||||
};
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ import { useSelector, useDispatch } from "react-redux";
|
||||
import { setRowsPerPage } from "../../../Features/UI/uiSlice.js";
|
||||
import PropTypes from "prop-types";
|
||||
import {
|
||||
useFetchMonitorsWithSummary,
|
||||
useFetchMonitorsWithChecks,
|
||||
useFetchMonitorsByTeamId,
|
||||
} from "@/Hooks/monitorHooks.js";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -103,12 +103,6 @@ const UptimeMonitors = () => {
|
||||
setMonitorUpdateTrigger((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
const [monitors, monitorsSummary, monitorsWithSummaryIsLoading, networkError] =
|
||||
useFetchMonitorsWithSummary({
|
||||
types: TYPES,
|
||||
monitorUpdateTrigger,
|
||||
});
|
||||
|
||||
const handleReset = () => {
|
||||
setSelectedState(undefined);
|
||||
setSelectedTypes(undefined);
|
||||
@@ -123,16 +117,17 @@ const UptimeMonitors = () => {
|
||||
]);
|
||||
|
||||
const activeFilter = [...filterLookup].find(([key]) => key !== undefined);
|
||||
const field = activeFilter?.[1] || sort?.field;
|
||||
const field = activeFilter?.[1] || (search ? "name" : sort?.field);
|
||||
const filter = activeFilter?.[0] || search;
|
||||
|
||||
const effectiveTypes = selectedTypes?.length ? selectedTypes : TYPES;
|
||||
|
||||
const [
|
||||
summary,
|
||||
monitorsWithChecks,
|
||||
monitorsWithChecksCount,
|
||||
monitorsWithChecksIsLoading,
|
||||
monitorsWithChecksNetworkError,
|
||||
networkError,
|
||||
] = useFetchMonitorsWithChecks({
|
||||
types: effectiveTypes,
|
||||
limit: 25,
|
||||
@@ -144,13 +139,18 @@ const UptimeMonitors = () => {
|
||||
monitorUpdateTrigger,
|
||||
});
|
||||
|
||||
const [monitors, listIsLoading, listNetworkError] = useFetchMonitorsByTeamId({
|
||||
type: ["http", "ping", "docker", "port", "game"],
|
||||
});
|
||||
|
||||
console.log(monitors);
|
||||
useEffect(() => {
|
||||
if (isSearching) {
|
||||
setPage(undefined);
|
||||
}
|
||||
}, [isSearching]);
|
||||
|
||||
const isLoading = monitorsWithSummaryIsLoading || monitorsWithChecksIsLoading;
|
||||
const isLoading = monitorsWithChecksIsLoading;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -174,14 +174,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}
|
||||
|
||||
@@ -112,7 +112,7 @@ const loginCredentials = joi.object({
|
||||
});
|
||||
|
||||
const monitorValidation = joi.object({
|
||||
_id: joi.string(),
|
||||
id: joi.string(),
|
||||
userId: joi.string(),
|
||||
teamId: joi.string(),
|
||||
statusWindowSize: joi.number().min(1).max(20).default(5).messages({
|
||||
|
||||
@@ -70,7 +70,7 @@ import SettingsModule from "../db/modules/settingsModule.js";
|
||||
import IncidentModule from "../db/modules/incidentModule.js";
|
||||
|
||||
// repositories
|
||||
import { MongoMonitorsRepository, MongoChecksRepository, MongoMonitorStatsRepository } from "@/repositories/index.js";
|
||||
import { MongoMonitorsRepository, MongoChecksRepository, MongoMonitorStatsRepository, MongoStatusPagesRepository } from "@/repositories/index.js";
|
||||
|
||||
export const initializeServices = async ({ logger, envSettings, settingsService }: { logger: any; envSettings: any; settingsService: any }) => {
|
||||
const serviceRegistry = new ServiceRegistry({ logger });
|
||||
@@ -124,6 +124,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService
|
||||
const monitorsRepository = new MongoMonitorsRepository();
|
||||
const checksRepository = new MongoChecksRepository();
|
||||
const monitorStatsRepository = new MongoMonitorStatsRepository();
|
||||
const statusPagesRepository = new MongoStatusPagesRepository();
|
||||
|
||||
const networkService = new NetworkService({
|
||||
axios,
|
||||
@@ -217,7 +218,6 @@ export const initializeServices = async ({ logger, envSettings, settingsService
|
||||
errorService,
|
||||
});
|
||||
const monitorService = new MonitorService({
|
||||
db,
|
||||
jobQueue: superSimpleQueue,
|
||||
stringService,
|
||||
emailService,
|
||||
@@ -228,6 +228,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService
|
||||
monitorsRepository,
|
||||
checksRepository,
|
||||
monitorStatsRepository,
|
||||
statusPagesRepository,
|
||||
});
|
||||
|
||||
const services = {
|
||||
|
||||
@@ -339,26 +339,6 @@ class MonitorController {
|
||||
}
|
||||
};
|
||||
|
||||
getMonitorsAndSummaryByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
|
||||
await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
|
||||
|
||||
const explain = optionalBoolean(req?.query?.explain, "explain");
|
||||
const type = parseMonitorTypeFilter(req?.query?.type);
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
|
||||
const result = await this.monitorService.getMonitorsAndSummaryByTeamId({ teamId, type, explain });
|
||||
|
||||
return res.status(200).json({
|
||||
msg: "Monitors and summary retrieved successfully",
|
||||
data: result,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getMonitorsWithChecksByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
|
||||
@@ -394,21 +374,6 @@ class MonitorController {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
exportMonitorsToCSV = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const teamId = req?.user?.teamId;
|
||||
if (!teamId) {
|
||||
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||
}
|
||||
|
||||
const csv = await this.monitorService.exportMonitorsToCSV({ teamId });
|
||||
res.setHeader("Content-Type", "text/csv");
|
||||
res.setHeader("Content-Disposition", "attachment; filename=monitors.csv");
|
||||
return res.send(csv);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
exportMonitorsToJSON = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
|
||||
@@ -171,35 +171,6 @@ const MonitorSchema = new Schema<MonitorDocument>(
|
||||
}
|
||||
);
|
||||
|
||||
MonitorSchema.pre("findOneAndDelete", async function (next) {
|
||||
try {
|
||||
const doc = await this.model.findOne(this.getFilter());
|
||||
|
||||
if (!doc) {
|
||||
throw new Error("Monitor not found");
|
||||
}
|
||||
|
||||
await Check.deleteMany({ monitorId: doc._id });
|
||||
await StatusPage.updateMany({ monitors: doc?._id }, { $pull: { monitors: doc?._id } });
|
||||
await MonitorStats.deleteMany({ monitorId: doc?._id.toString() });
|
||||
next();
|
||||
} catch (error) {
|
||||
next(error as Error);
|
||||
}
|
||||
});
|
||||
|
||||
MonitorSchema.pre("deleteMany", async function (next) {
|
||||
const filter = this.getFilter();
|
||||
const monitors = (await this.model.find(filter).select(["_id", "type"]).lean()) as { _id: Types.ObjectId }[];
|
||||
|
||||
for (const monitor of monitors) {
|
||||
await Check.deleteMany({ monitorId: monitor._id });
|
||||
await StatusPage.updateMany({ monitors: monitor._id }, { $pull: { monitors: monitor._id } });
|
||||
await MonitorStats.deleteMany({ monitorId: monitor._id.toString() });
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
MonitorSchema.pre("save", function (next) {
|
||||
if (!this.cpuAlertThreshold || this.isModified("alertThreshold")) {
|
||||
this.cpuAlertThreshold = this.alertThreshold;
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
import mongoose from "mongoose";
|
||||
|
||||
const StatusPageSchema = mongoose.Schema(
|
||||
{
|
||||
userId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "User",
|
||||
immutable: true,
|
||||
required: true,
|
||||
},
|
||||
teamId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "Team",
|
||||
immutable: true,
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "uptime",
|
||||
enum: ["uptime"],
|
||||
},
|
||||
companyName: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "",
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
unique: true,
|
||||
required: true,
|
||||
default: "",
|
||||
},
|
||||
timezone: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "#4169E1",
|
||||
},
|
||||
monitors: [
|
||||
{
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "Monitor",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
subMonitors: [
|
||||
{
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "Monitor",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
logo: {
|
||||
data: Buffer,
|
||||
contentType: String,
|
||||
},
|
||||
isPublished: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showCharts: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showUptimePercentage: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showAdminLoginLink: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
customCSS: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
export default mongoose.model("StatusPage", StatusPageSchema);
|
||||
@@ -0,0 +1,115 @@
|
||||
import { Schema, model, type Types } from "mongoose";
|
||||
import type { StatusPage, StatusPageLogo } from "@/types/statusPage.js";
|
||||
import { StatusPageTypes } from "@/types/statusPage.js";
|
||||
|
||||
type StatusPageDocumentBase = Omit<
|
||||
StatusPage,
|
||||
"id" | "userId" | "teamId" | "monitors" | "subMonitors" | "originalMonitors" | "createdAt" | "updatedAt"
|
||||
> & {
|
||||
monitors: Types.ObjectId[];
|
||||
subMonitors: Types.ObjectId[];
|
||||
originalMonitors?: Types.ObjectId[];
|
||||
logo?: StatusPageLogo | null;
|
||||
};
|
||||
|
||||
interface StatusPageDocument extends StatusPageDocumentBase {
|
||||
_id: Types.ObjectId;
|
||||
userId: Types.ObjectId;
|
||||
teamId: Types.ObjectId;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
const logoSchema = new Schema<StatusPageLogo & { data: Buffer }>(
|
||||
{
|
||||
data: { type: Buffer },
|
||||
contentType: { type: String },
|
||||
},
|
||||
{ _id: false }
|
||||
);
|
||||
|
||||
const StatusPageSchema = new Schema<StatusPageDocument>(
|
||||
{
|
||||
userId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "User",
|
||||
immutable: true,
|
||||
required: true,
|
||||
},
|
||||
teamId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Team",
|
||||
immutable: true,
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "uptime",
|
||||
enum: StatusPageTypes,
|
||||
},
|
||||
companyName: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "",
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
unique: true,
|
||||
required: true,
|
||||
default: "",
|
||||
},
|
||||
timezone: {
|
||||
type: String,
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: "#4169E1",
|
||||
},
|
||||
monitors: [
|
||||
{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Monitor",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
subMonitors: [
|
||||
{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Monitor",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
logo: {
|
||||
type: logoSchema,
|
||||
default: null,
|
||||
},
|
||||
isPublished: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showCharts: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showUptimePercentage: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showAdminLoginLink: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
customCSS: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
const StatusPageModel = model<StatusPageDocument>("StatusPage", StatusPageSchema);
|
||||
|
||||
export type { StatusPageDocument };
|
||||
export { StatusPageModel };
|
||||
export default StatusPageModel;
|
||||
@@ -6,3 +6,6 @@ export { default as CheckModel } from "@/db/models/Check.js";
|
||||
|
||||
export * from "@/db/models/MonitorStats.js";
|
||||
export { default as MonitorStatsModel } from "@/db/models/MonitorStats.js";
|
||||
|
||||
export * from "@/db/models/StatusPage.js";
|
||||
export { default as StatusPageModel } from "@/db/models/StatusPage.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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -287,6 +287,7 @@ class StatusPageModule {
|
||||
deleteStatusPagesByMonitorId = async (monitorId) => {
|
||||
try {
|
||||
await this.StatusPage.deleteMany({ monitors: { $in: [monitorId] } });
|
||||
await this.StatusPage.deleteMany({ subMonitors: { $in: [monitorId] } });
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "deleteStatusPageByMonitorId";
|
||||
|
||||
@@ -54,6 +54,9 @@ export interface UptimeChecksResult {
|
||||
}
|
||||
|
||||
export interface IChecksRepository {
|
||||
// create
|
||||
// single fetch
|
||||
// collection fetch
|
||||
findLatestChecksByMonitorIds(monitorIds: string[], options?: { limitPerMonitor?: number }): Promise<LatestChecksMap>;
|
||||
findDateRangeChecksByMonitor(
|
||||
monitorId: string,
|
||||
@@ -62,4 +65,7 @@ export interface IChecksRepository {
|
||||
dateString: string,
|
||||
options?: { type?: MonitorType }
|
||||
): Promise<UptimeChecksResult | HardwareChecksResult | PageSpeedChecksResult>;
|
||||
// update
|
||||
//delete
|
||||
deleteByMonitorId(monitorId: string): Promise<number>;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
|
||||
export type LatestChecksMap = Record<string, Check[]>;
|
||||
|
||||
class MongoChecksRepistory implements IChecksRepository {
|
||||
class MongoChecksRepository implements IChecksRepository {
|
||||
private toEntity = (doc: CheckDocument): Check => {
|
||||
const toStringId = (value: mongoose.Types.ObjectId | string | undefined | null): string => {
|
||||
if (!value) {
|
||||
@@ -184,18 +184,13 @@ class MongoChecksRepistory implements IChecksRepository {
|
||||
}
|
||||
const mongoIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id));
|
||||
const limitPerMonitor = options?.limitPerMonitor ?? 25;
|
||||
const maxIntervalMs = Number(10 * 60 * 1000);
|
||||
const bufferMs = Number(maxIntervalMs);
|
||||
const lookbackMs = limitPerMonitor * maxIntervalMs + bufferMs;
|
||||
|
||||
const cutoffDate = new Date(Date.now() - lookbackMs);
|
||||
const checkGroups = await CheckModel.aggregate([
|
||||
{
|
||||
$match: {
|
||||
"metadata.monitorId": { $in: mongoIds },
|
||||
createdAt: { $gte: cutoffDate },
|
||||
},
|
||||
},
|
||||
{ $sort: { "metadata.monitorId": 1, createdAt: -1 } },
|
||||
{
|
||||
$group: {
|
||||
_id: "$metadata.monitorId",
|
||||
@@ -390,6 +385,11 @@ class MongoChecksRepistory implements IChecksRepository {
|
||||
checks: checks.map((doc) => this.toEntity(doc)),
|
||||
};
|
||||
};
|
||||
|
||||
deleteByMonitorId = async (monitorId: string): Promise<number> => {
|
||||
const result = await CheckModel.deleteMany({ "metadata.monitorId": monitorId });
|
||||
return result.deletedCount;
|
||||
};
|
||||
}
|
||||
|
||||
export default MongoChecksRepistory;
|
||||
export default MongoChecksRepository;
|
||||
|
||||
@@ -6,3 +6,6 @@ export { default as MongoChecksRepository } from "@/repositories/checks/MongoChe
|
||||
|
||||
export * from "@/repositories/monitor-stats/IMonitorStatsRepository.js";
|
||||
export { default as MongoMonitorStatsRepository } from "@/repositories/monitor-stats/MongoMonitorStatsRepository.js";
|
||||
|
||||
export * from "@/repositories/status-pages/IStatusPagesRepository.js";
|
||||
export { default as MongoStatusPagesRepository } from "@/repositories/status-pages/MongoStatusPagesRepository.js";
|
||||
|
||||
@@ -5,5 +5,6 @@ export interface IMonitorStatsRepository {
|
||||
findByMonitorId(monitorId: string): Promise<MonitorStats>;
|
||||
// update
|
||||
// delete
|
||||
deleteByMonitorId(monitorId: string): Promise<MonitorStats>;
|
||||
// other
|
||||
}
|
||||
|
||||
@@ -39,6 +39,14 @@ class MongoMonitorStatsRepository implements IMonitorStatsRepository {
|
||||
}
|
||||
return this.toEntity(monitorStats);
|
||||
};
|
||||
|
||||
deleteByMonitorId = async (monitorId: string) => {
|
||||
const deleted = await MonitorStatsModel.findOneAndDelete({ monitorId: new mongoose.Types.ObjectId(monitorId) });
|
||||
if (!deleted) {
|
||||
throw new AppError({ message: "Monitor stats not found", status: 404 });
|
||||
}
|
||||
return this.toEntity(deleted);
|
||||
};
|
||||
}
|
||||
|
||||
export default MongoMonitorStatsRepository;
|
||||
|
||||
@@ -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>;
|
||||
@@ -20,11 +24,18 @@ export interface IMonitorsRepository {
|
||||
// collection fetch
|
||||
findAll(): Promise<Monitor[] | null>;
|
||||
findByTeamId(teamId: string, config: TeamQueryConfig): Promise<Monitor[] | null>;
|
||||
|
||||
// update
|
||||
update(monitorId: string, updates: Partial<Monitor>): Promise<Monitor>;
|
||||
updateById(monitorId: string, teamId: string, updates: Partial<Monitor>): Promise<Monitor>;
|
||||
togglePauseById(monitorId: string, teamId: string): Promise<Monitor>;
|
||||
// delete
|
||||
deleteById(monitorId: string, teamId: string): Promise<Monitor>;
|
||||
deleteByTeamId(teamId: string): Promise<{ monitors: Monitor[]; deletedCount: number }>;
|
||||
|
||||
// counts
|
||||
findMonitorCountByTeamIdAndType(teamId: string, config: TeamQueryConfig): Promise<number>;
|
||||
|
||||
// other
|
||||
findMonitorsSummaryByTeamId(teamId: string, config?: SummaryConfig): Promise<any>;
|
||||
findGroupsByTeamId(teamId: string): Promise<string[]>;
|
||||
}
|
||||
|
||||
@@ -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 } 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 {
|
||||
@@ -44,7 +44,7 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
||||
};
|
||||
|
||||
findByTeamId = async (teamId: string, config: TeamQueryConfig): Promise<Monitor[] | null> => {
|
||||
const { page = 0, rowsPerPage = 25, filter, field = "createdAt", order = "desc", type, limit } = config ?? {};
|
||||
const { page = 0, rowsPerPage = 0, filter, field = "createdAt", order = "desc", type, limit } = config ?? {};
|
||||
|
||||
const query: Record<string, unknown> = {
|
||||
teamId: new mongoose.Types.ObjectId(teamId),
|
||||
@@ -75,9 +75,8 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
||||
|
||||
const sort = { [field]: order === "asc" ? 1 : -1 } as const;
|
||||
const skip = Math.max(page, 0) * rowsPerPage;
|
||||
const limitValue = limit ?? rowsPerPage;
|
||||
|
||||
const documents = await MonitorModel.find(query).sort(sort).skip(skip).limit(limitValue).exec();
|
||||
const documents = await MonitorModel.find(query).sort(sort).skip(skip).limit(rowsPerPage);
|
||||
|
||||
return this.mapDocuments(documents);
|
||||
};
|
||||
@@ -97,9 +96,9 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
||||
return count;
|
||||
};
|
||||
|
||||
update = async (monitorId: string, patch: Partial<Monitor>) => {
|
||||
updateById = async (monitorId: string, teamId: string, patch: Partial<Monitor>) => {
|
||||
const updatedMonitor = await MonitorModel.findOneAndUpdate(
|
||||
{ _id: monitorId },
|
||||
{ _id: monitorId, teamId },
|
||||
{
|
||||
$set: {
|
||||
...patch,
|
||||
@@ -113,6 +112,35 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
||||
return this.toEntity(updatedMonitor);
|
||||
};
|
||||
|
||||
togglePauseById = async (monitorId: string, teamId: string) => {
|
||||
const monitor = await MonitorModel.findOneAndUpdate(
|
||||
{ _id: monitorId, teamId },
|
||||
[
|
||||
{
|
||||
$set: {
|
||||
isActive: { $not: "$isActive" },
|
||||
status: "$$REMOVE",
|
||||
},
|
||||
},
|
||||
],
|
||||
{ new: true }
|
||||
);
|
||||
if (!monitor) {
|
||||
throw new AppError({ message: `Monitor with ID ${monitorId} not found for the given team.`, status: 404 });
|
||||
}
|
||||
return this.toEntity(monitor);
|
||||
};
|
||||
|
||||
deleteById = async (monitorId: string, teamId: string) => {
|
||||
const deletedMonitor = await MonitorModel.findOneAndDelete({ _id: monitorId, teamId });
|
||||
|
||||
if (!deletedMonitor) {
|
||||
throw new AppError({ message: `Monitor with ID ${monitorId} not found for the given team.`, status: 404 });
|
||||
}
|
||||
|
||||
return this.toEntity(deletedMonitor);
|
||||
};
|
||||
|
||||
deleteByTeamId = async (teamId: string) => {
|
||||
const monitors = await MonitorModel.find({ teamId });
|
||||
const { deletedCount } = await MonitorModel.deleteMany({ teamId });
|
||||
@@ -120,6 +148,52 @@ 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 };
|
||||
};
|
||||
|
||||
findGroupsByTeamId = async (teamId: string): Promise<string[]> => {
|
||||
const groups = await MonitorModel.distinct("group", {
|
||||
teamId: new mongoose.Types.ObjectId(teamId),
|
||||
group: { $nin: [null, ""] },
|
||||
});
|
||||
return groups.sort();
|
||||
};
|
||||
|
||||
private mapDocuments = (documents: MonitorDocument[]): Monitor[] => {
|
||||
if (!documents?.length) {
|
||||
return [];
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { StatusPage } from "@/types/statusPage.js";
|
||||
|
||||
export interface IStatusPagesRepository {
|
||||
// create
|
||||
// single fetch
|
||||
// collection fetch
|
||||
// update
|
||||
// delete
|
||||
// other
|
||||
removeMonitorFromStatusPages(monitorId: string): Promise<number>;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { IStatusPagesRepository } from "@/repositories/index.js";
|
||||
import { type StatusPageDocument, StatusPageModel } from "@/db/models/StatusPage.js";
|
||||
import type { StatusPage } from "@/types/statusPage.js";
|
||||
import mongoose from "mongoose";
|
||||
|
||||
class MongoStatusPagesRepository implements IStatusPagesRepository {
|
||||
private toEntity(doc: StatusPageDocument): StatusPage {
|
||||
const toStringId = (value: unknown): string => {
|
||||
if (value instanceof mongoose.Types.ObjectId) {
|
||||
return value.toString();
|
||||
}
|
||||
return value?.toString() ?? "";
|
||||
};
|
||||
|
||||
const toDateString = (value: Date | string): string => {
|
||||
return value instanceof Date ? value.toISOString() : value;
|
||||
};
|
||||
|
||||
const mapIdArray = (values?: mongoose.Types.ObjectId[]): string[] => {
|
||||
return values?.map((value) => toStringId(value)) ?? [];
|
||||
};
|
||||
|
||||
return {
|
||||
id: toStringId(doc._id),
|
||||
userId: toStringId(doc.userId),
|
||||
teamId: toStringId(doc.teamId),
|
||||
type: doc.type,
|
||||
companyName: doc.companyName,
|
||||
url: doc.url,
|
||||
timezone: doc.timezone ?? undefined,
|
||||
color: doc.color,
|
||||
monitors: mapIdArray(doc.monitors),
|
||||
subMonitors: mapIdArray(doc.subMonitors),
|
||||
originalMonitors: doc.originalMonitors?.map((value) => toStringId(value)),
|
||||
logo: doc.logo ?? undefined,
|
||||
isPublished: doc.isPublished,
|
||||
showCharts: doc.showCharts,
|
||||
showUptimePercentage: doc.showUptimePercentage,
|
||||
showAdminLoginLink: doc.showAdminLoginLink,
|
||||
customCSS: doc.customCSS,
|
||||
createdAt: toDateString(doc.createdAt),
|
||||
updatedAt: toDateString(doc.updatedAt),
|
||||
};
|
||||
}
|
||||
|
||||
removeMonitorFromStatusPages = async (monitorId: string): Promise<number> => {
|
||||
const res = await StatusPageModel.updateMany({ monitors: monitorId }, { $pull: { monitors: monitorId } });
|
||||
return res.modifiedCount;
|
||||
};
|
||||
}
|
||||
|
||||
export default MongoStatusPagesRepository;
|
||||
@@ -19,7 +19,6 @@ class MonitorRoutes {
|
||||
// Team routes
|
||||
this.router.get("/team", this.monitorController.getMonitorsByTeamId);
|
||||
this.router.get("/team/with-checks", this.monitorController.getMonitorsWithChecksByTeamId);
|
||||
this.router.get("/team/summary", this.monitorController.getMonitorsAndSummaryByTeamId);
|
||||
this.router.get("/team/groups", this.monitorController.getGroupsByTeamId);
|
||||
|
||||
// Uptime routes
|
||||
@@ -44,7 +43,6 @@ class MonitorRoutes {
|
||||
|
||||
// Other static routes
|
||||
this.router.post("/demo", isAllowed(["admin", "superadmin"]), this.monitorController.addDemoMonitors);
|
||||
this.router.get("/export", isAllowed(["admin", "superadmin"]), this.monitorController.exportMonitorsToCSV);
|
||||
this.router.get("/export/json", isAllowed(["admin", "superadmin"]), this.monitorController.exportMonitorsToJSON);
|
||||
this.router.post("/bulk", isAllowed(["admin", "superadmin"]), upload.single("csvFile"), this.monitorController.createBulkMonitors);
|
||||
this.router.post("/test-email", isAllowed(["admin", "superadmin"]), this.monitorController.sendTestEmail);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createMonitorsBodyValidation } from "@/validation/joi.js";
|
||||
import { NormalizeData, NormalizeDataUptimeDetails } from "@/utils/dataUtils.js";
|
||||
import { type Monitor } from "@/types/index.js";
|
||||
import type { MonitorType } from "@/types/monitor.js";
|
||||
import type { IChecksRepository, IMonitorsRepository, IMonitorStatsRepository } from "@/repositories/index.js";
|
||||
import type { IChecksRepository, IMonitorsRepository, IMonitorStatsRepository, IStatusPagesRepository } from "@/repositories/index.js";
|
||||
import fs from "fs";
|
||||
import { fileURLToPath } from "url";
|
||||
import path from "path";
|
||||
@@ -14,7 +14,6 @@ type DateRangeKey = "recent" | "day" | "week" | "month" | "all";
|
||||
|
||||
export interface IMonitorService {
|
||||
readonly serviceName: string;
|
||||
verifyTeamAccess(args: { teamId: string; monitorId: string }): Promise<void>;
|
||||
|
||||
// create
|
||||
createMonitor(teamId: string, userId: string, body: Monitor): Promise<void>;
|
||||
@@ -36,7 +35,6 @@ export interface IMonitorService {
|
||||
field?: string;
|
||||
order?: "asc" | "desc";
|
||||
}): Promise<any>;
|
||||
getMonitorsAndSummaryByTeamId(args: { teamId: string; type?: string | string[]; explain?: boolean }): Promise<any>;
|
||||
getMonitorsWithChecksByTeamId(args: {
|
||||
teamId: string;
|
||||
limit?: number;
|
||||
@@ -47,21 +45,20 @@ 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[]>;
|
||||
getGroupsByTeamId(args: { teamId: string }): Promise<string[]>;
|
||||
|
||||
// update
|
||||
editMonitor(args: { teamId: string; monitorId: string; body: any }): Promise<void>;
|
||||
pauseMonitor(args: { teamId: string; monitorId: string }): Promise<any>;
|
||||
editMonitor(args: { teamId: string; monitorId: string; body: Monitor }): Promise<Monitor>;
|
||||
pauseMonitor(args: { teamId: string; monitorId: string }): Promise<Monitor>;
|
||||
|
||||
// delete
|
||||
deleteMonitor(args: { teamId: string; monitorId: string }): Promise<any>;
|
||||
deleteMonitor(args: { teamId: string; monitorId: string }): Promise<Monitor>;
|
||||
deleteAllMonitors(args: { teamId: string }): Promise<number>;
|
||||
|
||||
// other
|
||||
sendTestEmail(args: { to: string }): Promise<string>;
|
||||
exportMonitorsToCSV(args: { teamId: string }): Promise<string>;
|
||||
exportMonitorsToJSON(args: { teamId: string }): Promise<any[]>;
|
||||
}
|
||||
|
||||
@@ -79,9 +76,9 @@ export class MonitorService implements IMonitorService {
|
||||
private monitorsRepository: IMonitorsRepository;
|
||||
private checksRepository: IChecksRepository;
|
||||
private monitorStatsRepository: IMonitorStatsRepository;
|
||||
private statusPagesRepository: IStatusPagesRepository;
|
||||
|
||||
constructor({
|
||||
db,
|
||||
jobQueue,
|
||||
stringService,
|
||||
emailService,
|
||||
@@ -92,8 +89,8 @@ export class MonitorService implements IMonitorService {
|
||||
monitorsRepository,
|
||||
checksRepository,
|
||||
monitorStatsRepository,
|
||||
statusPagesRepository,
|
||||
}: {
|
||||
db: any;
|
||||
jobQueue: any;
|
||||
stringService: any;
|
||||
emailService: any;
|
||||
@@ -104,8 +101,8 @@ export class MonitorService implements IMonitorService {
|
||||
monitorsRepository: IMonitorsRepository;
|
||||
checksRepository: IChecksRepository;
|
||||
monitorStatsRepository: IMonitorStatsRepository;
|
||||
statusPagesRepository: IStatusPagesRepository;
|
||||
}) {
|
||||
this.db = db;
|
||||
this.jobQueue = jobQueue;
|
||||
this.stringService = stringService;
|
||||
this.emailService = emailService;
|
||||
@@ -116,6 +113,7 @@ export class MonitorService implements IMonitorService {
|
||||
this.monitorsRepository = monitorsRepository;
|
||||
this.checksRepository = checksRepository;
|
||||
this.monitorStatsRepository = monitorStatsRepository;
|
||||
this.statusPagesRepository = statusPagesRepository;
|
||||
}
|
||||
|
||||
get serviceName(): string {
|
||||
@@ -147,13 +145,6 @@ export class MonitorService implements IMonitorService {
|
||||
return formatLookup[dateRange];
|
||||
};
|
||||
|
||||
verifyTeamAccess = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise<void> => {
|
||||
const monitor = await this.db.monitorModule.getMonitorById(monitorId);
|
||||
if (!monitor?.teamId?.equals(teamId)) {
|
||||
throw this.errorService.createAuthorizationError();
|
||||
}
|
||||
};
|
||||
|
||||
createMonitor = async (teamId: string, userId: string, body: Monitor): Promise<void> => {
|
||||
const monitor = await this.monitorsRepository.create(body, teamId, userId);
|
||||
if (!monitor) {
|
||||
@@ -256,8 +247,6 @@ export class MonitorService implements IMonitorService {
|
||||
dateRange: string;
|
||||
normalize?: boolean;
|
||||
}): Promise<any> => {
|
||||
await this.verifyTeamAccess({ teamId, monitorId });
|
||||
|
||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||
if (!monitor) {
|
||||
throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 });
|
||||
@@ -293,7 +282,6 @@ export class MonitorService implements IMonitorService {
|
||||
};
|
||||
|
||||
getHardwareDetailsById = async ({ teamId, monitorId, dateRange }: { teamId: string; monitorId: string; dateRange: string }): Promise<any> => {
|
||||
await this.verifyTeamAccess({ teamId, monitorId });
|
||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||
if (!monitor) {
|
||||
throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 });
|
||||
@@ -325,7 +313,6 @@ export class MonitorService implements IMonitorService {
|
||||
};
|
||||
|
||||
getPageSpeedDetailsById = async ({ teamId, monitorId, dateRange }: { teamId: string; monitorId: string; dateRange: string }): Promise<any> => {
|
||||
await this.verifyTeamAccess({ teamId, monitorId });
|
||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||
if (!monitor) {
|
||||
throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 });
|
||||
@@ -349,7 +336,6 @@ export class MonitorService implements IMonitorService {
|
||||
};
|
||||
};
|
||||
getMonitorById = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise<any> => {
|
||||
await this.verifyTeamAccess({ teamId, monitorId });
|
||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||
return monitor;
|
||||
};
|
||||
@@ -362,23 +348,6 @@ export class MonitorService implements IMonitorService {
|
||||
return monitors;
|
||||
};
|
||||
|
||||
getMonitorsAndSummaryByTeamId = async ({
|
||||
teamId,
|
||||
type,
|
||||
explain,
|
||||
}: {
|
||||
teamId: string;
|
||||
type?: string | string[];
|
||||
explain?: boolean;
|
||||
}): Promise<any> => {
|
||||
const result = await this.db.monitorModule.getMonitorsAndSummaryByTeamId({
|
||||
type,
|
||||
explain,
|
||||
teamId,
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
getMonitorsWithChecksByTeamId = async ({
|
||||
teamId,
|
||||
limit,
|
||||
@@ -399,7 +368,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,36 +403,36 @@ export class MonitorService implements IMonitorService {
|
||||
};
|
||||
});
|
||||
|
||||
return { count, monitors: monitorsWithChecks };
|
||||
return { summary: summary ?? null, count, monitors: monitorsWithChecks };
|
||||
};
|
||||
|
||||
getAllGames = (): any => {
|
||||
return this.games;
|
||||
};
|
||||
|
||||
getGroupsByTeamId = async ({ teamId }: { teamId: string }): Promise<any[]> => {
|
||||
const groups = await this.db.monitorModule.getGroupsByTeamId({ teamId });
|
||||
getGroupsByTeamId = async ({ teamId }: { teamId: string }): Promise<string[]> => {
|
||||
const groups = await this.monitorsRepository.findGroupsByTeamId(teamId);
|
||||
return groups;
|
||||
};
|
||||
|
||||
editMonitor = async ({ teamId, monitorId, body }: { teamId: string; monitorId: string; body: any }): Promise<void> => {
|
||||
await this.verifyTeamAccess({ teamId, monitorId });
|
||||
const editedMonitor = await this.db.monitorModule.editMonitor({ monitorId, body });
|
||||
editMonitor = async ({ teamId, monitorId, body }: { teamId: string; monitorId: string; body: Monitor }) => {
|
||||
const editedMonitor = await this.monitorsRepository.updateById(monitorId, teamId, body);
|
||||
await this.jobQueue.updateJob(editedMonitor);
|
||||
return editedMonitor;
|
||||
};
|
||||
|
||||
pauseMonitor = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise<any> => {
|
||||
await this.verifyTeamAccess({ teamId, monitorId });
|
||||
const monitor = await this.db.monitorModule.pauseMonitor({ monitorId });
|
||||
monitor.isActive === true ? await this.jobQueue.resumeJob(monitor._id, monitor) : await this.jobQueue.pauseJob(monitor);
|
||||
pauseMonitor = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise<Monitor> => {
|
||||
const monitor = await this.monitorsRepository.togglePauseById(monitorId, teamId);
|
||||
monitor.isActive === true ? await this.jobQueue.resumeJob(monitor) : await this.jobQueue.pauseJob(monitor);
|
||||
return monitor;
|
||||
};
|
||||
|
||||
deleteMonitor = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise<any> => {
|
||||
await this.verifyTeamAccess({ teamId, monitorId });
|
||||
const monitor = await this.db.monitorModule.deleteMonitor({ teamId, monitorId });
|
||||
deleteMonitor = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise<Monitor> => {
|
||||
const monitor = await this.monitorsRepository.deleteById(monitorId, teamId);
|
||||
await this.monitorStatsRepository.deleteByMonitorId(monitor.id);
|
||||
await this.checksRepository.deleteByMonitorId(monitor.id);
|
||||
await this.statusPagesRepository.removeMonitorFromStatusPages(monitor.id);
|
||||
await this.jobQueue.deleteJob(monitor);
|
||||
await this.db.statusPageModule.deleteStatusPagesByMonitorId(monitor._id);
|
||||
return monitor;
|
||||
};
|
||||
|
||||
@@ -472,9 +442,9 @@ export class MonitorService implements IMonitorService {
|
||||
monitors.map(async (monitor) => {
|
||||
try {
|
||||
await this.jobQueue.deleteJob(monitor);
|
||||
await this.db.checkModule.deleteChecks(monitor.id);
|
||||
await this.db.pageSpeedCheckModule.deletePageSpeedChecksByMonitorId(monitor.id);
|
||||
await this.db.notificationsModule.deleteNotificationsByMonitorId(monitor.id);
|
||||
await this.checksRepository.deleteByMonitorId(monitor.id);
|
||||
await this.statusPagesRepository.removeMonitorFromStatusPages(monitor.id);
|
||||
await this.monitorStatsRepository.deleteByMonitorId(monitor.id);
|
||||
} catch (error: any) {
|
||||
this.logger.warn({
|
||||
message: `Error deleting associated records for monitor ${monitor.id} with name ${monitor.name}`,
|
||||
@@ -502,65 +472,13 @@ export class MonitorService implements IMonitorService {
|
||||
return messageId;
|
||||
};
|
||||
|
||||
exportMonitorsToCSV = async ({ teamId }: { teamId: string }): Promise<string> => {
|
||||
const monitors = await this.db.monitorModule.getMonitorsByTeamId({ teamId });
|
||||
|
||||
if (!monitors || monitors.length === 0) {
|
||||
throw this.errorService.createNotFoundError("No monitors to export");
|
||||
}
|
||||
|
||||
const csvData = monitors?.filteredMonitors?.map((monitor: any) => ({
|
||||
name: monitor.name,
|
||||
description: monitor.description,
|
||||
type: monitor.type,
|
||||
url: monitor.url,
|
||||
interval: monitor.interval,
|
||||
port: monitor.port,
|
||||
ignoreTlsErrors: monitor.ignoreTlsErrors,
|
||||
isActive: monitor.isActive,
|
||||
}));
|
||||
|
||||
const csv = this.papaparse.unparse(csvData);
|
||||
return csv;
|
||||
};
|
||||
exportMonitorsToJSON = async ({ teamId }: { teamId: string }): Promise<any[]> => {
|
||||
const monitors = await this.db.monitorModule.getMonitorsByTeamId({ teamId });
|
||||
const monitors = await this.monitorsRepository.findByTeamId(teamId, {});
|
||||
|
||||
if (!monitors || monitors.length === 0) {
|
||||
throw this.errorService.createNotFoundError("No monitors to export");
|
||||
}
|
||||
|
||||
const json = monitors?.filteredMonitors
|
||||
?.map((monitor: any) => {
|
||||
const initialType = monitor.type;
|
||||
let parsedType;
|
||||
|
||||
if (initialType === "hardware") {
|
||||
parsedType = "infrastructure";
|
||||
} else if (initialType === "http") {
|
||||
if (monitor.url.startsWith("https://")) {
|
||||
parsedType = "https";
|
||||
} else {
|
||||
parsedType = "http";
|
||||
}
|
||||
} else if (initialType === "pagespeed") {
|
||||
parsedType = initialType;
|
||||
} else {
|
||||
// Skip unsupported types
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
name: monitor.name,
|
||||
url: monitor.url,
|
||||
type: parsedType,
|
||||
interval: monitor.interval,
|
||||
n: monitor.statusWindowSize,
|
||||
secret: monitor.secret,
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
return json;
|
||||
return monitors;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -204,7 +195,7 @@ class StatusService {
|
||||
|
||||
// Return early if not enough data points
|
||||
if (monitor.statusWindow.length < monitor.statusWindowSize) {
|
||||
const updated = await this.monitorsRepository.update(monitor.id, monitor);
|
||||
const updated = await this.monitorsRepository.updateById(monitor.id, monitor.teamId, monitor);
|
||||
return {
|
||||
monitor: updated,
|
||||
statusChanged: false,
|
||||
@@ -294,7 +285,7 @@ class StatusService {
|
||||
}
|
||||
|
||||
monitor.status = newStatus;
|
||||
const updated = await this.monitorsRepository.update(monitor.id, monitor);
|
||||
const updated = await this.monitorsRepository.updateById(monitor.id, monitor.teamId, monitor);
|
||||
|
||||
return {
|
||||
monitor: updated,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "@/types/check.js";
|
||||
export * from "@/types/monitor.js";
|
||||
export * from "@/types/monitorStats.js";
|
||||
export * from "@/types/statusPage.js";
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
export const StatusPageTypes = ["uptime"] as const;
|
||||
export type StatusPageType = (typeof StatusPageTypes)[number];
|
||||
|
||||
export interface StatusPageLogo {
|
||||
data: Buffer;
|
||||
contentType: string;
|
||||
}
|
||||
|
||||
export interface StatusPage {
|
||||
id: string;
|
||||
userId: string;
|
||||
teamId: string;
|
||||
type: StatusPageType;
|
||||
companyName: string;
|
||||
url: string;
|
||||
timezone?: string;
|
||||
color: string;
|
||||
monitors: string[];
|
||||
subMonitors: string[];
|
||||
originalMonitors?: string[];
|
||||
logo?: StatusPageLogo | null;
|
||||
isPublished: boolean;
|
||||
showCharts: boolean;
|
||||
showUptimePercentage: boolean;
|
||||
showAdminLoginLink: boolean;
|
||||
customCSS: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
Reference in New Issue
Block a user