Files
Checkmate/server/src/db/models/monitors/Monitor.ts
T
2025-12-30 20:30:20 +00:00

202 lines
5.7 KiB
TypeScript

import mongoose, { Schema, Document, Types } from "mongoose";
import type { IDockerContainerSummary } from "@/db/models/checks/Check.js";
import {
Check,
Incident,
MonitorStats,
StatsDaily,
StatsHourly,
} from "@/db/models/index.js";
export const MonitorTypes = [
"http",
"https",
"port",
"ping",
"infrastructure",
"pagespeed",
"docker",
] as const;
export type MonitorType = (typeof MonitorTypes)[number];
export const MonitorStatuses = [
"up",
"down",
"paused",
"initializing",
] as const;
export type MonitorStatus = (typeof MonitorStatuses)[number];
export interface IMonitorThresholds {
cpu?: number;
memory?: number;
disk?: number;
temperature?: number;
}
export interface LatestCheck {
status: MonitorStatus;
responseTime: number;
checkedAt: Date;
// Snapshot of containers from the latest docker check (array from payload.data)
containerSnapshot?: IDockerContainerSummary[];
}
export interface IMonitor extends Document {
_id: Types.ObjectId;
orgId: Types.ObjectId;
teamId: Types.ObjectId;
name: string;
url: string;
port?: number;
secret?: string;
type: MonitorType;
interval: number; // in ms
rejectUnauthorized: boolean;
status: MonitorStatus;
n: number; // Number of consecutive successes required to change status
thresholds?: IMonitorThresholds;
lastCheckedAt?: Date;
latestChecks: LatestCheck[];
notificationChannels?: Types.ObjectId[];
createdBy: Types.ObjectId;
updatedBy: Types.ObjectId;
createdAt: Date;
updatedAt: Date;
}
const ThresholdsSchema = new Schema<IMonitorThresholds>(
{
cpu: { type: Number, min: 0, max: 100 },
memory: { type: Number, min: 0, max: 100 },
disk: { type: Number, min: 0, max: 100 },
temperature: { type: Number, min: -50, max: 150 },
},
{ _id: false }
);
const MonitorSchema = new Schema<IMonitor>(
{
orgId: { type: Schema.Types.ObjectId, ref: "Org", required: true },
teamId: { type: Schema.Types.ObjectId, ref: "Team", required: true },
name: { type: String, required: true, trim: true, maxlength: 100 },
url: { type: String, required: true, trim: true },
port: { type: Number, required: false },
secret: { type: String, required: false },
type: {
type: String,
required: true,
enum: MonitorTypes,
},
interval: { type: Number, required: true, default: 60000 },
rejectUnauthorized: { type: Boolean, required: true, default: true },
status: {
type: String,
required: true,
default: "initializing",
enum: MonitorStatuses,
},
n: { type: Number, required: true, default: 1 },
thresholds: { type: ThresholdsSchema, required: false, default: undefined },
lastCheckedAt: { type: Date },
latestChecks: {
type: [
{
status: {
type: String,
required: true,
enum: MonitorStatuses,
},
responseTime: { type: Number, required: true },
checkedAt: { type: Date, required: true },
containerSnapshot: {
type: [
{
container_id: { type: String },
container_name: { type: String },
status: { type: String },
health: {
healthy: { type: Boolean },
source: { type: String },
message: { type: String },
},
running: { type: Boolean },
base_image: { type: String },
exposed_ports: [
{
port: { type: String },
protocol: { type: String },
},
],
started_at: { type: Number },
finished_at: { type: Number },
},
],
required: false,
default: undefined,
},
},
],
default: [],
},
notificationChannels: {
type: [{ type: Schema.Types.ObjectId, ref: "NotificationChannel" }],
default: [],
},
createdBy: { type: Schema.Types.ObjectId, ref: "User", required: true },
updatedBy: { type: Schema.Types.ObjectId, ref: "User", required: true },
},
{ timestamps: true }
);
MonitorSchema.pre(
"deleteOne",
{ document: true, query: false },
async function (next) {
try {
const monitorId = this._id;
await Check.deleteMany({ "metadata.monitorId": monitorId });
await MonitorStats.deleteMany({ monitorId });
await StatsHourly.deleteMany({ monitorId });
await StatsDaily.deleteMany({ monitorId });
await Incident.deleteMany({ monitorId });
next();
} catch (error: any) {
next(error);
}
}
);
MonitorSchema.pre(
"deleteMany",
{ document: false, query: true },
async function (next) {
try {
const filter = this.getFilter();
const monitors = await this.model.find(filter).select("_id");
const monitorIds = monitors.map((m) => m._id);
if (monitorIds.length > 0) {
await Check.deleteMany({ "metadata.monitorId": { $in: monitorIds } });
await MonitorStats.deleteMany({ monitorId: { $in: monitorIds } });
await StatsHourly.deleteMany({ monitorId: { $in: monitorIds } });
await StatsDaily.deleteMany({ monitorId: { $in: monitorIds } });
await Incident.deleteMany({ monitorId: { $in: monitorIds } });
}
next();
} catch (error: any) {
next(error);
}
}
);
MonitorSchema.index({ status: 1 });
MonitorSchema.index({ type: 1 });
MonitorSchema.index({ lastCheckedAt: 1 });
MonitorSchema.index({ createdBy: 1 });
MonitorSchema.index({ updatedBy: 1 });
// Common filter keys used across services and routes
MonitorSchema.index({ teamId: 1 });
MonitorSchema.index({ orgId: 1 });
export const Monitor = mongoose.model<IMonitor>("Monitor", MonitorSchema);