types
This commit is contained in:
1 parent
e62e4898b9
commit
36cdb20dfc
17 files changed
+159
-594
No files matched your search
@@ -4,7 +4,7 @@ import {
|
||||
MonitorTypes,
|
||||
MonitorStatus,
|
||||
MonitorStatuses,
|
||||
} from "../monitors/Monitor.js";
|
||||
} from "@/types/domain/index.js";
|
||||
import type { Response } from "got";
|
||||
export type GotTimings = Response["timings"];
|
||||
export interface ITimingPhases {
|
||||
|
||||
@@ -7,7 +7,7 @@ export type {
|
||||
IDockerContainerSummary,
|
||||
} from "./checks/Check.js";
|
||||
|
||||
export { Monitor, MonitorStatuses, MonitorTypes } from "./monitors/Monitor.js";
|
||||
export { Monitor } from "./monitors/Monitor.js";
|
||||
export type { IMonitor } from "./monitors/Monitor.js";
|
||||
|
||||
export { MonitorStats } from "./monitors/MonitorStats.js";
|
||||
|
||||
@@ -7,26 +7,8 @@ import {
|
||||
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",
|
||||
"resuming",
|
||||
] as const;
|
||||
export type MonitorStatus = (typeof MonitorStatuses)[number];
|
||||
import type { MonitorType, MonitorStatus } from "@/types/domain/index.js";
|
||||
import { MonitorStatuses, MonitorTypes } from "@/types/domain/index.js";
|
||||
|
||||
export interface IMonitorThresholds {
|
||||
cpu?: number;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import mongoose, { Schema, Document, Types } from "mongoose";
|
||||
import { MonitorStatus, MonitorStatuses } from "./Monitor.js";
|
||||
import mongoose, { Schema } from "mongoose";
|
||||
import { MonitorStatus, MonitorStatuses } from "@/types/domain/index.js";
|
||||
|
||||
export interface IMonitorStats extends mongoose.Document {
|
||||
monitorId: mongoose.Types.ObjectId;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import mongoose, { Schema, Types } from "mongoose";
|
||||
import type { MonitorType } from "@/db/models/monitors/Monitor.js";
|
||||
import mongoose, { Schema } from "mongoose";
|
||||
import type {
|
||||
IInfraCpu,
|
||||
IInfraDiskEntry,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import mongoose, { Schema, Types } from "mongoose";
|
||||
import type { MonitorType } from "@/db/models/monitors/Monitor.js";
|
||||
import type { MonitorType } from "@/types/domain/index.js";
|
||||
|
||||
// Common/base metrics across monitor types
|
||||
export interface IStatsBase {
|
||||
|
||||
@@ -88,7 +88,7 @@ export const initServices = async () => {
|
||||
checksRepository
|
||||
);
|
||||
const queueService = new QueueService(jobQueue);
|
||||
const teamService = new TeamService(jobQueue);
|
||||
const teamService = new TeamService(jobQueue, monitorRepository);
|
||||
const roleService = new RoleService();
|
||||
const teamMemberService = new TeamMemberService();
|
||||
const statusPageService = new StatusPageService();
|
||||
|
||||
@@ -15,6 +15,11 @@ export interface IChecksRepository {
|
||||
monitor: Monitor,
|
||||
startDate: Date
|
||||
): Promise<AggregateCheck[]>;
|
||||
findDateRangeChecksByMonitor(
|
||||
monitor: Monitor,
|
||||
startDate: Date,
|
||||
range: string
|
||||
): Promise<AggregateCheck[]>;
|
||||
// Update
|
||||
// Delete
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { CheckEntity, AggregateCheck, Monitor } from "@/types/domain/index.js";
|
||||
import type { ICheck } from "@/db/models/index.js";
|
||||
import { Check } from "@/db/models/index.js";
|
||||
import mongoose from "mongoose";
|
||||
import { StatsHourly, StatsDaily } from "@/db/models/index.js";
|
||||
|
||||
export type LatestChecksByMonitor = Array<{
|
||||
id: string;
|
||||
@@ -513,6 +514,74 @@ class MongoCheckRepository implements IChecksRepository {
|
||||
|
||||
return checks.map(this.toAggregateCheck);
|
||||
};
|
||||
|
||||
findDateRangeChecksByMonitor = async (
|
||||
monitor: Monitor,
|
||||
startDate: Date,
|
||||
range: string
|
||||
) => {
|
||||
const useDaily = range === "30d";
|
||||
const Model = useDaily ? StatsDaily : StatsHourly;
|
||||
const endDate = new Date();
|
||||
|
||||
const docs = await Model.find({
|
||||
monitorId: monitor.id,
|
||||
windowStart: { $gte: startDate, $lt: endDate },
|
||||
})
|
||||
.sort({ windowStart: -1 })
|
||||
.lean();
|
||||
|
||||
const checks = (docs as any[]).map((d) => {
|
||||
const base: any = {
|
||||
_id: new Date(d.windowStart).toISOString(),
|
||||
count: d.count ?? 0,
|
||||
avgResponseTime: d.avgResponseTime ?? 0,
|
||||
upChecks: d.upChecks ?? 0,
|
||||
downChecks: d.downChecks ?? 0,
|
||||
avgResponseTimeUp: d.avgResponseTimeUp ?? null,
|
||||
avgResponseTimeDown: d.avgResponseTimeDown ?? null,
|
||||
};
|
||||
if (monitor.type === "pagespeed") {
|
||||
return {
|
||||
...base,
|
||||
accessibility: d.accessibility,
|
||||
seo: d.seo,
|
||||
bestPractices: d.bestPractices,
|
||||
performance: d.performance,
|
||||
cls: d.cls,
|
||||
si: d.si,
|
||||
fcp: d.fcp,
|
||||
lcp: d.lcp,
|
||||
tbt: d.tbt,
|
||||
};
|
||||
}
|
||||
if (monitor.type === "infrastructure") {
|
||||
return {
|
||||
...base,
|
||||
cpu: d.cpu,
|
||||
memory: d.memory,
|
||||
disk: d.disk,
|
||||
host: d.host,
|
||||
net: d.net,
|
||||
};
|
||||
}
|
||||
if (monitor.type === "docker") {
|
||||
return {
|
||||
...base,
|
||||
totalContainers: d.dockerTotalContainers,
|
||||
runningContainers: d.dockerRunningContainers,
|
||||
healthyContainers: d.dockerHealthyContainers,
|
||||
totalExposedPorts: d.dockerTotalExposedPorts,
|
||||
uniqueImages: d.dockerUniqueImages,
|
||||
runningPercent: d.dockerRunningPercent,
|
||||
healthyPercent: d.dockerHealthyPercent,
|
||||
};
|
||||
}
|
||||
return base;
|
||||
});
|
||||
|
||||
return checks;
|
||||
};
|
||||
}
|
||||
|
||||
export default MongoCheckRepository;
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Monitor, IMonitor } from "@/db/models/index.js";
|
||||
import { IMonitorRepository, TeamQueryConfig } from "@/repositories/index.js";
|
||||
import { IImportedMonitor } from "@/services/business/MonitorService.js";
|
||||
import type { Monitor as MonitorEntity } from "@/types/domain/monitor.js";
|
||||
import mongoose from "mongoose";
|
||||
class MongoMonitorRepository implements IMonitorRepository {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ICheck, Check, Monitor, ISystemInfo } from "@/db/models/index.js";
|
||||
import type { IDockerPayload } from "@/services/infrastructure/NetworkService.js";
|
||||
import { MonitorStatus } from "@/db/models/monitors/Monitor.js";
|
||||
import { MonitorType } from "@/db/models/monitors/Monitor.js";
|
||||
import { MonitorStatus, MonitorType } from "@/types/domain/index.js";
|
||||
import { StatusResponse } from "../infrastructure/NetworkService.js";
|
||||
import type {
|
||||
ICapturePayload,
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import mongoose from "mongoose";
|
||||
|
||||
import { MonitorStats, Check } from "@/db/models/index.js";
|
||||
import { StatsHourly, StatsDaily } from "@/db/models/index.js";
|
||||
import { MonitorStats } from "@/db/models/index.js";
|
||||
import ApiError from "@/utils/ApiError.js";
|
||||
import { IJobQueue } from "@/services/infrastructure/JobQueue.js";
|
||||
import { MonitorWithChecksResponse } from "@/types/index.js";
|
||||
import { MonitorStatus, MonitorType } from "@/db/models/monitors/Monitor.js";
|
||||
import { MonitorStatus } from "@/types/domain/index.js";
|
||||
import { Entitlements } from "@/types/entitlements.js";
|
||||
import { getStartDate } from "@/utils/TimeUtils.js";
|
||||
import { IMonitorRepository } from "@/repositories/index.js";
|
||||
import type IChecksRepository from "@/repositories/checks/IChecksRepository.js";
|
||||
import type { Monitor } from "@/types/domain/index.js";
|
||||
import { IMonitorRepository, IChecksRepository } from "@/repositories/index.js";
|
||||
import type { Monitor, MonitorType } from "@/types/domain/index.js";
|
||||
|
||||
export enum MonitorImportCode {
|
||||
OK = "OK",
|
||||
@@ -176,518 +172,6 @@ class MonitorService implements IMonitorService {
|
||||
return monitor;
|
||||
};
|
||||
|
||||
private getBaseGroup = (): Record<string, any> => {
|
||||
return {
|
||||
_id: { $dateTrunc: { date: "$createdAt", unit: "minute" } },
|
||||
count: { $sum: 1 },
|
||||
avgResponseTime: { $avg: "$responseTime" },
|
||||
};
|
||||
};
|
||||
|
||||
private getBaseProjection = (): object => {
|
||||
return { status: 1, responseTime: 1, createdAt: 1 };
|
||||
};
|
||||
|
||||
private getPageSpeedGroup = (): Record<string, any> => {
|
||||
return {
|
||||
_id: { $dateTrunc: { date: "$createdAt", unit: "minute" } },
|
||||
count: { $sum: 1 },
|
||||
avgResponseTime: { $avg: "$responseTime" },
|
||||
accessibility: { $avg: "$lighthouse.accessibility" },
|
||||
bestPractices: { $avg: "$lighthouse.bestPractices" },
|
||||
seo: { $avg: "$lighthouse.seo" },
|
||||
performance: { $avg: "$lighthouse.performance" },
|
||||
cls: { $avg: "$lighthouse.audits.cls.score" },
|
||||
si: { $avg: "$lighthouse.audits.si.score" },
|
||||
fcp: { $avg: "$lighthouse.audits.fcp.score" },
|
||||
lcp: { $avg: "$lighthouse.audits.lcp.score" },
|
||||
tbt: { $avg: "$lighthouse.audits.tbt.score" },
|
||||
};
|
||||
};
|
||||
|
||||
private getPageSpeedProjection = (): object => {
|
||||
const projectStage: any = { status: 1, responseTime: 1, createdAt: 1 };
|
||||
projectStage["lighthouse.accessibility"] = 1;
|
||||
projectStage["lighthouse.seo"] = 1;
|
||||
projectStage["lighthouse.bestPractices"] = 1;
|
||||
projectStage["lighthouse.performance"] = 1;
|
||||
projectStage["lighthouse.audits.cls.score"] = 1;
|
||||
projectStage["lighthouse.audits.si.score"] = 1;
|
||||
projectStage["lighthouse.audits.fcp.score"] = 1;
|
||||
projectStage["lighthouse.audits.lcp.score"] = 1;
|
||||
projectStage["lighthouse.audits.tbt.score"] = 1;
|
||||
return projectStage;
|
||||
};
|
||||
|
||||
private getInfraGroup = (): Record<string, any> => {
|
||||
return {
|
||||
_id: { $dateTrunc: { date: "$createdAt", unit: "minute" } },
|
||||
count: { $sum: 1 },
|
||||
avgResponseTime: { $avg: "$responseTime" },
|
||||
physicalCores: { $last: "$system.cpu.physical_core" },
|
||||
logicalCores: { $last: "$system.cpu.logical_core" },
|
||||
frequency: { $avg: "$system.cpu.frequency" },
|
||||
currentFrequency: { $last: "$system.cpu.current_frequency" },
|
||||
tempsArrays: { $push: "$system.cpu.temperature" },
|
||||
freePercent: { $avg: "$system.cpu.free_percent" },
|
||||
usedPercent: { $avg: "$system.cpu.usage_percent" },
|
||||
total_bytes: { $last: "$system.memory.total_bytes" },
|
||||
available_bytes: { $last: "$system.memory.available_bytes" },
|
||||
used_bytes: { $last: "$system.memory.used_bytes" },
|
||||
memory_usage_percent: { $avg: "$system.memory.usage_percent" },
|
||||
disksArray: { $push: "$system.disk" },
|
||||
os: { $last: "$system.host.os" },
|
||||
platform: { $last: "$system.host.platform" },
|
||||
kernel_version: { $last: "$system.host.kernel_version" },
|
||||
pretty_name: { $last: "$system.host.pretty_name" },
|
||||
netsArray: { $push: "$system.net" },
|
||||
};
|
||||
};
|
||||
|
||||
private getInfraProjection = (): object => {
|
||||
const projectStage: any = { status: 1, responseTime: 1, createdAt: 1 };
|
||||
projectStage["system.cpu.physical_core"] = 1;
|
||||
projectStage["system.cpu.logical_core"] = 1;
|
||||
projectStage["system.cpu.frequency"] = 1;
|
||||
projectStage["system.cpu.current_frequency"] = 1;
|
||||
projectStage["system.cpu.temperature"] = 1;
|
||||
projectStage["system.cpu.free_percent"] = 1;
|
||||
projectStage["system.cpu.usage_percent"] = 1;
|
||||
projectStage["system.memory.total_bytes"] = 1;
|
||||
projectStage["system.memory.available_bytes"] = 1;
|
||||
projectStage["system.memory.used_bytes"] = 1;
|
||||
projectStage["system.memory.usage_percent"] = 1;
|
||||
projectStage["system.disk"] = 1;
|
||||
projectStage["system.host.os"] = 1;
|
||||
projectStage["system.host.platform"] = 1;
|
||||
projectStage["system.host.kernel_version"] = 1;
|
||||
projectStage["system.host.pretty_name"] = 1;
|
||||
projectStage["system.net"] = 1;
|
||||
return projectStage;
|
||||
};
|
||||
|
||||
private getDockerGroup = (): Record<string, any> => {
|
||||
return {
|
||||
_id: { $dateTrunc: { date: "$createdAt", unit: "minute" } },
|
||||
count: { $sum: 1 },
|
||||
avgResponseTime: { $last: "$responseTime" },
|
||||
totalContainers: {
|
||||
$last: { $size: { $ifNull: ["$dockerContainers", []] } },
|
||||
},
|
||||
runningContainers: {
|
||||
$last: {
|
||||
$size: {
|
||||
$filter: {
|
||||
input: { $ifNull: ["$dockerContainers", []] },
|
||||
as: "c",
|
||||
cond: { $eq: ["$$c.running", true] },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
healthyContainers: {
|
||||
$last: {
|
||||
$size: {
|
||||
$filter: {
|
||||
input: { $ifNull: ["$dockerContainers", []] },
|
||||
as: "c",
|
||||
cond: { $eq: ["$$c.health.healthy", true] },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
private getDockerProjection = (): object => {
|
||||
const projectStage: any = { status: 1, responseTime: 1, createdAt: 1 };
|
||||
projectStage["dockerContainers"] = 1;
|
||||
return projectStage;
|
||||
};
|
||||
|
||||
private getFinalProjection = (type: string): object => {
|
||||
if (type === "pagespeed") {
|
||||
return {
|
||||
_id: 1,
|
||||
count: 1,
|
||||
avgResponseTime: 1,
|
||||
accessibility: "$accessibility",
|
||||
seo: "$seo",
|
||||
bestPractices: "$bestPractices",
|
||||
performance: "$performance",
|
||||
cls: "$cls",
|
||||
si: "$si",
|
||||
fcp: "$fcp",
|
||||
lcp: "$lcp",
|
||||
tbt: "$tbt",
|
||||
};
|
||||
}
|
||||
|
||||
if (type === "infrastructure") {
|
||||
return {
|
||||
_id: 1,
|
||||
count: 1,
|
||||
avgResponseTime: 1,
|
||||
cpu: {
|
||||
physical_core: "$physicalCores",
|
||||
logical_core: "$logicalCores",
|
||||
frequency: "$frequency",
|
||||
current_frequency: "$currentFrequency",
|
||||
temperature: {
|
||||
$map: {
|
||||
input: {
|
||||
$range: [
|
||||
0,
|
||||
{
|
||||
$size: {
|
||||
$ifNull: [{ $arrayElemAt: ["$tempsArrays", 0] }, []],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
as: "idx",
|
||||
in: {
|
||||
$avg: {
|
||||
$map: {
|
||||
input: { $ifNull: ["$tempsArrays", []] },
|
||||
as: "arr",
|
||||
in: { $arrayElemAt: ["$$arr", "$$idx"] },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
free_percent: "$freePercent",
|
||||
used_percent: "$usedPercent",
|
||||
},
|
||||
memory: {
|
||||
total_bytes: "$total_bytes",
|
||||
available_bytes: "$available_bytes",
|
||||
used_bytes: "$used_bytes",
|
||||
usage_percent: "$memory_usage_percent",
|
||||
},
|
||||
disk: {
|
||||
$map: {
|
||||
input: {
|
||||
$range: [
|
||||
0,
|
||||
{
|
||||
$size: {
|
||||
$ifNull: [{ $arrayElemAt: ["$disksArray", 0] }, []],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
as: "idx",
|
||||
in: {
|
||||
$let: {
|
||||
vars: {
|
||||
diskGroup: {
|
||||
$map: {
|
||||
input: { $ifNull: ["$disksArray", []] },
|
||||
as: "diskArr",
|
||||
in: { $arrayElemAt: ["$$diskArr", "$$idx"] },
|
||||
},
|
||||
},
|
||||
},
|
||||
in: {
|
||||
device: { $arrayElemAt: ["$$diskGroup.device", 0] },
|
||||
total_bytes: { $avg: "$$diskGroup.total_bytes" },
|
||||
free_bytes: { $avg: "$$diskGroup.free_bytes" },
|
||||
used_bytes: { $avg: "$$diskGroup.used_bytes" },
|
||||
usage_percent: { $avg: "$$diskGroup.usage_percent" },
|
||||
total_inodes: { $avg: "$$diskGroup.total_inodes" },
|
||||
free_inodes: { $avg: "$$diskGroup.free_inodes" },
|
||||
used_inodes: { $avg: "$$diskGroup.used_inodes" },
|
||||
inodes_usage_percent: {
|
||||
$avg: "$$diskGroup.inodes_usage_percent",
|
||||
},
|
||||
read_bytes: { $avg: "$$diskGroup.read_bytes" },
|
||||
write_bytes: { $avg: "$$diskGroup.write_bytes" },
|
||||
read_time: { $avg: "$$diskGroup.read_time" },
|
||||
write_time: { $avg: "$$diskGroup.write_time" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
host: {
|
||||
os: "$os",
|
||||
platform: "$platform",
|
||||
kernel_version: "$kernel_version",
|
||||
pretty_name: "$pretty_name",
|
||||
},
|
||||
net: {
|
||||
$map: {
|
||||
input: {
|
||||
$range: [
|
||||
0,
|
||||
{
|
||||
$size: { $ifNull: [{ $arrayElemAt: ["$netsArray", 0] }, []] },
|
||||
},
|
||||
],
|
||||
},
|
||||
as: "idx",
|
||||
in: {
|
||||
$let: {
|
||||
vars: {
|
||||
netGroup: {
|
||||
$map: {
|
||||
input: { $ifNull: ["$netsArray", []] },
|
||||
as: "netArr",
|
||||
in: { $arrayElemAt: ["$$netArr", "$$idx"] },
|
||||
},
|
||||
},
|
||||
},
|
||||
in: {
|
||||
name: { $arrayElemAt: ["$$netGroup.name", 0] },
|
||||
bytes_sent: { $avg: "$$netGroup.bytes_sent" },
|
||||
bytes_recv: { $avg: "$$netGroup.bytes_recv" },
|
||||
packets_sent: { $avg: "$$netGroup.packets_sent" },
|
||||
packets_recv: { $avg: "$$netGroup.packets_recv" },
|
||||
err_in: { $avg: "$$netGroup.err_in" },
|
||||
err_out: { $avg: "$$netGroup.err_out" },
|
||||
drop_in: { $avg: "$$netGroup.drop_in" },
|
||||
drop_out: { $avg: "$$netGroup.drop_out" },
|
||||
fifo_in: { $avg: "$$netGroup.fifo_in" },
|
||||
fifo_out: { $avg: "$$netGroup.fifo_out" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
if (type === "docker") {
|
||||
return {
|
||||
_id: 1,
|
||||
count: 1,
|
||||
avgResponseTime: 1,
|
||||
totalContainers: "$totalContainers",
|
||||
runningContainers: "$runningContainers",
|
||||
healthyContainers: "$healthyContainers",
|
||||
runningContainersSnapshot: "$runningContainersSnapshot",
|
||||
totalExposedPorts: "$totalExposedPorts",
|
||||
uniqueImages: "$uniqueImages",
|
||||
runningPercent: {
|
||||
$cond: [
|
||||
{ $gt: ["$totalContainers", 0] },
|
||||
{
|
||||
$multiply: [
|
||||
{ $divide: ["$runningContainers", "$totalContainers"] },
|
||||
100,
|
||||
],
|
||||
},
|
||||
null,
|
||||
],
|
||||
},
|
||||
healthyPercent: {
|
||||
$cond: [
|
||||
{ $gt: ["$totalContainers", 0] },
|
||||
{
|
||||
$multiply: [
|
||||
{ $divide: ["$healthyContainers", "$totalContainers"] },
|
||||
100,
|
||||
],
|
||||
},
|
||||
null,
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
private getChecksRecent = async (monitor: Monitor, startDate: Date) => {
|
||||
const endDate = new Date();
|
||||
const matchStage: {
|
||||
"metadata.monitorId": mongoose.Types.ObjectId;
|
||||
createdAt: { $gte: Date; $lt: Date };
|
||||
} = {
|
||||
"metadata.monitorId": new mongoose.Types.ObjectId(monitor.id),
|
||||
createdAt: { $gte: startDate, $lt: endDate },
|
||||
};
|
||||
|
||||
let groupClause;
|
||||
if (monitor.type === "pagespeed") {
|
||||
groupClause = this.getPageSpeedGroup();
|
||||
} else if (monitor.type === "infrastructure") {
|
||||
groupClause = this.getInfraGroup();
|
||||
} else if (monitor.type === "docker") {
|
||||
groupClause = this.getDockerGroup();
|
||||
} else {
|
||||
groupClause = this.getBaseGroup();
|
||||
}
|
||||
|
||||
let projectStage;
|
||||
if (monitor.type === "pagespeed") {
|
||||
projectStage = this.getPageSpeedProjection();
|
||||
} else if (monitor.type === "infrastructure") {
|
||||
projectStage = this.getInfraProjection();
|
||||
} else if (monitor.type === "docker") {
|
||||
projectStage = this.getDockerProjection();
|
||||
} else {
|
||||
projectStage = this.getBaseProjection();
|
||||
}
|
||||
|
||||
let finalProjection = {};
|
||||
if (
|
||||
monitor.type === "pagespeed" ||
|
||||
monitor.type === "infrastructure" ||
|
||||
monitor.type === "docker"
|
||||
) {
|
||||
finalProjection = this.getFinalProjection(monitor.type);
|
||||
} else {
|
||||
finalProjection = { _id: 1, count: 1, avgResponseTime: 1 };
|
||||
}
|
||||
|
||||
const pipeline: any[] = [
|
||||
{ $match: matchStage },
|
||||
{ $project: { ...projectStage, status: 1 } },
|
||||
];
|
||||
|
||||
// For infrastructure and docker monitors we rely on $last in group, which requires ordering by createdAt
|
||||
if (monitor.type === "infrastructure" || monitor.type === "docker") {
|
||||
pipeline.push({ $sort: { createdAt: 1 } });
|
||||
}
|
||||
|
||||
pipeline.push(
|
||||
{
|
||||
$group: {
|
||||
...groupClause,
|
||||
upChecks: { $sum: { $cond: [{ $eq: ["$status", "up"] }, 1, 0] } },
|
||||
downChecks: { $sum: { $cond: [{ $eq: ["$status", "down"] }, 1, 0] } },
|
||||
sumResponseUp: {
|
||||
$sum: { $cond: [{ $eq: ["$status", "up"] }, "$responseTime", 0] },
|
||||
},
|
||||
sumResponseDown: {
|
||||
$sum: { $cond: [{ $eq: ["$status", "down"] }, "$responseTime", 0] },
|
||||
},
|
||||
},
|
||||
},
|
||||
{ $sort: { _id: -1 } },
|
||||
{
|
||||
$project: {
|
||||
...finalProjection,
|
||||
upChecks: 1,
|
||||
downChecks: 1,
|
||||
avgResponseTimeUp: {
|
||||
$cond: [
|
||||
{ $gt: ["$upChecks", 0] },
|
||||
{ $divide: ["$sumResponseUp", "$upChecks"] },
|
||||
null,
|
||||
],
|
||||
},
|
||||
avgResponseTimeDown: {
|
||||
$cond: [
|
||||
{ $gt: ["$downChecks", 0] },
|
||||
{ $divide: ["$sumResponseDown", "$downChecks"] },
|
||||
null,
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const checks = await Check.aggregate(pipeline)
|
||||
.allowDiskUse(true)
|
||||
.hint({ "metadata.monitorId": 1, createdAt: 1 });
|
||||
|
||||
// Get monitor stats
|
||||
const monitorStats = await MonitorStats.findOne({
|
||||
monitorId: monitor.id,
|
||||
});
|
||||
|
||||
if (!monitorStats) {
|
||||
throw new ApiError("Monitor stats not found", 404);
|
||||
}
|
||||
|
||||
return {
|
||||
monitor: monitor,
|
||||
checks,
|
||||
stats: monitorStats,
|
||||
};
|
||||
};
|
||||
|
||||
private getChecksOtherRanges = async (
|
||||
monitor: Monitor,
|
||||
range: string,
|
||||
startDate: Date
|
||||
) => {
|
||||
const monitorStats = await MonitorStats.findOne({
|
||||
monitorId: monitor.id,
|
||||
});
|
||||
if (!monitorStats) {
|
||||
throw new ApiError("Monitor stats not found", 404);
|
||||
}
|
||||
|
||||
const useDaily = range === "30d";
|
||||
const Model = useDaily ? StatsDaily : StatsHourly;
|
||||
const endDate = new Date();
|
||||
|
||||
const docs = await Model.find({
|
||||
monitorId: monitor.id,
|
||||
windowStart: { $gte: startDate, $lt: endDate },
|
||||
})
|
||||
.sort({ windowStart: -1 })
|
||||
.lean();
|
||||
|
||||
const checks = (docs as any[]).map((d) => {
|
||||
const base: any = {
|
||||
_id: new Date(d.windowStart).toISOString(),
|
||||
count: d.count ?? 0,
|
||||
avgResponseTime: d.avgResponseTime ?? 0,
|
||||
upChecks: d.upChecks ?? 0,
|
||||
downChecks: d.downChecks ?? 0,
|
||||
avgResponseTimeUp: d.avgResponseTimeUp ?? null,
|
||||
avgResponseTimeDown: d.avgResponseTimeDown ?? null,
|
||||
};
|
||||
if (monitor.type === "pagespeed") {
|
||||
return {
|
||||
...base,
|
||||
accessibility: d.accessibility,
|
||||
seo: d.seo,
|
||||
bestPractices: d.bestPractices,
|
||||
performance: d.performance,
|
||||
cls: d.cls,
|
||||
si: d.si,
|
||||
fcp: d.fcp,
|
||||
lcp: d.lcp,
|
||||
tbt: d.tbt,
|
||||
};
|
||||
}
|
||||
if (monitor.type === "infrastructure") {
|
||||
return {
|
||||
...base,
|
||||
cpu: d.cpu,
|
||||
memory: d.memory,
|
||||
disk: d.disk,
|
||||
host: d.host,
|
||||
net: d.net,
|
||||
};
|
||||
}
|
||||
if (monitor.type === "docker") {
|
||||
return {
|
||||
...base,
|
||||
totalContainers: d.dockerTotalContainers,
|
||||
runningContainers: d.dockerRunningContainers,
|
||||
healthyContainers: d.dockerHealthyContainers,
|
||||
totalExposedPorts: d.dockerTotalExposedPorts,
|
||||
uniqueImages: d.dockerUniqueImages,
|
||||
runningPercent: d.dockerRunningPercent,
|
||||
healthyPercent: d.dockerHealthyPercent,
|
||||
};
|
||||
}
|
||||
return base;
|
||||
});
|
||||
|
||||
return {
|
||||
monitor: monitor,
|
||||
checks,
|
||||
stats: monitorStats,
|
||||
};
|
||||
};
|
||||
|
||||
getEmbedChecks = async (
|
||||
teamId: string,
|
||||
monitorId: string,
|
||||
@@ -702,30 +186,39 @@ class MonitorService implements IMonitorService {
|
||||
throw new ApiError("Monitor not found", 404);
|
||||
}
|
||||
|
||||
const stats = await MonitorStats.findOne({
|
||||
monitorId: monitor.id,
|
||||
});
|
||||
|
||||
if (!stats) {
|
||||
throw new ApiError("Monitor stats not found", 404);
|
||||
}
|
||||
|
||||
const startDate = getStartDate(range);
|
||||
|
||||
if (range === "24h" || range === "7d" || range === "30d") {
|
||||
return await this.getChecksOtherRanges(monitor, range, startDate);
|
||||
} else {
|
||||
const checks = await this.checksRepository.findRecentChecksByMonitor(
|
||||
const checks = await this.checksRepository.findDateRangeChecksByMonitor(
|
||||
monitor,
|
||||
startDate
|
||||
startDate,
|
||||
range
|
||||
);
|
||||
|
||||
return {
|
||||
checks,
|
||||
monitor,
|
||||
stats,
|
||||
};
|
||||
} else {
|
||||
const checks = await this.checksRepository.findRecentChecksByMonitor(
|
||||
monitor,
|
||||
startDate
|
||||
);
|
||||
|
||||
const stats = await MonitorStats.findOne({
|
||||
monitorId: monitor.id,
|
||||
});
|
||||
|
||||
if (!stats) {
|
||||
throw new ApiError("Monitor stats not found", 404);
|
||||
}
|
||||
|
||||
return {
|
||||
checks,
|
||||
monitor,
|
||||
stats,
|
||||
};
|
||||
// return await this.getChecksRecent(monitor, startDate);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import mongoose from "mongoose";
|
||||
import { Check } from "@/db/models/index.js";
|
||||
import { StatsHourly } from "@/db/models/index.js";
|
||||
import { StatsDaily } from "@/db/models/index.js";
|
||||
import type { MonitorType } from "@/db/models/monitors/Monitor.js";
|
||||
import type { MonitorType } from "@/types/domain/index.js";
|
||||
|
||||
export interface IStatsAggregationService {
|
||||
upsertHourly: (windowStart: Date, windowEnd: Date) => Promise<number>;
|
||||
@@ -678,10 +678,20 @@ class StatsAggregationService implements IStatsAggregationService {
|
||||
addW(g.tbt, (h as any).tbt, w);
|
||||
|
||||
// docker
|
||||
if (!(g as any).dockerRunningPercent) (g as any).dockerRunningPercent = { sum: 0, w: 0 };
|
||||
if (!(g as any).dockerHealthyPercent) (g as any).dockerHealthyPercent = { sum: 0, w: 0 };
|
||||
addW((g as any).dockerRunningPercent!, (h as any).dockerRunningPercent, w);
|
||||
addW((g as any).dockerHealthyPercent!, (h as any).dockerHealthyPercent, w);
|
||||
if (!(g as any).dockerRunningPercent)
|
||||
(g as any).dockerRunningPercent = { sum: 0, w: 0 };
|
||||
if (!(g as any).dockerHealthyPercent)
|
||||
(g as any).dockerHealthyPercent = { sum: 0, w: 0 };
|
||||
addW(
|
||||
(g as any).dockerRunningPercent!,
|
||||
(h as any).dockerRunningPercent,
|
||||
w
|
||||
);
|
||||
addW(
|
||||
(g as any).dockerHealthyPercent!,
|
||||
(h as any).dockerHealthyPercent,
|
||||
w
|
||||
);
|
||||
|
||||
const ts = new Date(h.windowStart).getTime();
|
||||
if (ts >= g.lastTs) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
OrgMembership,
|
||||
IUserContext,
|
||||
} from "@/db/models/index.js";
|
||||
import type { IMonitorRepository } from "@/repositories/index.js";
|
||||
import type { IJobQueue } from "../infrastructure/JobQueue.js";
|
||||
import ApiError from "@/utils/ApiError.js";
|
||||
import { PERMISSIONS } from "@/types/permissions.js";
|
||||
@@ -35,10 +36,12 @@ export interface ITeamService {
|
||||
class TeamService implements ITeamService {
|
||||
public SERVICE_NAME: string;
|
||||
private jobQueue: IJobQueue;
|
||||
private monitorsRepository: IMonitorRepository;
|
||||
|
||||
constructor(jobQueue: IJobQueue) {
|
||||
constructor(jobQueue: IJobQueue, monitorsRepository: IMonitorRepository) {
|
||||
this.SERVICE_NAME = SERVICE_NAME;
|
||||
this.jobQueue = jobQueue;
|
||||
this.monitorsRepository = monitorsRepository;
|
||||
}
|
||||
|
||||
create = async (
|
||||
@@ -149,7 +152,7 @@ class TeamService implements ITeamService {
|
||||
await membership.deleteOne();
|
||||
}
|
||||
|
||||
const monitors = await Monitor.find({ teamId });
|
||||
const monitors = await this.monitorsRepository.findByTeamId(teamId);
|
||||
await Promise.all(
|
||||
monitors.map((monitor) => this.jobQueue.deleteJob(monitor))
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Got, HTTPError } from "got";
|
||||
import ping from "ping";
|
||||
import net from "net";
|
||||
import { IMonitor } from "@/db/models/index.js";
|
||||
import { GotTimings } from "@/db/models/checks/Check.js";
|
||||
import type { Response } from "got";
|
||||
import type {
|
||||
@@ -10,23 +9,23 @@ import type {
|
||||
ILighthouseResult,
|
||||
} from "@/db/models/index.js";
|
||||
import type { TLSSocket } from "node:tls";
|
||||
import { MonitorType, MonitorStatus } from "@/db/models/monitors/Monitor.js";
|
||||
import { MonitorType, MonitorStatus } from "@/types/domain/index.js";
|
||||
import ApiError from "@/utils/ApiError.js";
|
||||
import { config } from "@/config/index.js";
|
||||
import CacheableLookup from "cacheable-lookup";
|
||||
import { getChildLogger } from "@/logger/Logger.js";
|
||||
import type { IDockerContainerSummary } from "@/db/models/index.js";
|
||||
import type { Monitor as MonitorEntity } from "@/types/domain/monitor.js";
|
||||
import type { Monitor } from "@/types/domain/monitor.js";
|
||||
|
||||
const SERVICE_NAME = "NetworkService";
|
||||
const logger = getChildLogger(SERVICE_NAME);
|
||||
export interface INetworkService {
|
||||
requestHttp: (monitor: MonitorEntity) => Promise<StatusResponse>;
|
||||
requestInfrastructure: (monitor: MonitorEntity) => Promise<StatusResponse>;
|
||||
requestStatus: (monitor: MonitorEntity) => Promise<StatusResponse>;
|
||||
requestPagespeed: (monitor: MonitorEntity) => Promise<StatusResponse>;
|
||||
requestPing: (monitor: MonitorEntity) => Promise<StatusResponse>;
|
||||
requestPort: (monitor: MonitorEntity) => Promise<StatusResponse>;
|
||||
requestHttp: (monitor: Monitor) => Promise<StatusResponse>;
|
||||
requestInfrastructure: (monitor: Monitor) => Promise<StatusResponse>;
|
||||
requestStatus: (monitor: Monitor) => Promise<StatusResponse>;
|
||||
requestPagespeed: (monitor: Monitor) => Promise<StatusResponse>;
|
||||
requestPing: (monitor: Monitor) => Promise<StatusResponse>;
|
||||
requestPort: (monitor: Monitor) => Promise<StatusResponse>;
|
||||
}
|
||||
|
||||
export interface ICapturePayload {
|
||||
@@ -93,7 +92,7 @@ class NetworkService implements INetworkService {
|
||||
}
|
||||
|
||||
private buildStatusResponse = <T>(
|
||||
monitor: MonitorEntity,
|
||||
monitor: Monitor,
|
||||
response: Response<T> | null,
|
||||
certificateExpiryOrError?: Date | null | any,
|
||||
maybeError?: any | null
|
||||
@@ -145,7 +144,7 @@ class NetworkService implements INetworkService {
|
||||
return statusResponse;
|
||||
};
|
||||
|
||||
requestHttp = async (monitor: MonitorEntity) => {
|
||||
requestHttp = async (monitor: Monitor) => {
|
||||
try {
|
||||
const url = monitor.url;
|
||||
if (!url) {
|
||||
@@ -191,7 +190,7 @@ class NetworkService implements INetworkService {
|
||||
}
|
||||
};
|
||||
|
||||
requestInfrastructure = async (monitor: MonitorEntity) => {
|
||||
requestInfrastructure = async (monitor: Monitor) => {
|
||||
const url = monitor.url;
|
||||
if (!url) {
|
||||
throw new Error("No URL provided");
|
||||
@@ -226,7 +225,7 @@ class NetworkService implements INetworkService {
|
||||
return statusResponse;
|
||||
};
|
||||
|
||||
requestPagespeed = async (monitor: MonitorEntity) => {
|
||||
requestPagespeed = async (monitor: Monitor) => {
|
||||
const apiKey = config.PAGESPEED_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error("No API key provided for pagespeed monitor");
|
||||
@@ -266,7 +265,7 @@ class NetworkService implements INetworkService {
|
||||
}
|
||||
};
|
||||
|
||||
requestPing = async (monitor: MonitorEntity) => {
|
||||
requestPing = async (monitor: Monitor) => {
|
||||
const response = await ping.promise.probe(monitor.url);
|
||||
const status = response?.alive === true ? "up" : "down";
|
||||
|
||||
@@ -287,7 +286,7 @@ class NetworkService implements INetworkService {
|
||||
};
|
||||
};
|
||||
|
||||
requestPort = async (monitor: MonitorEntity) => {
|
||||
requestPort = async (monitor: Monitor) => {
|
||||
const response = {
|
||||
monitorId: monitor.id,
|
||||
teamId: monitor.teamId,
|
||||
@@ -359,7 +358,7 @@ class NetworkService implements INetworkService {
|
||||
}
|
||||
};
|
||||
|
||||
requestStatus = async (monitor: MonitorEntity) => {
|
||||
requestStatus = async (monitor: Monitor) => {
|
||||
switch (monitor?.type) {
|
||||
case "http":
|
||||
return await this.requestHttp(monitor); // uses GOT
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod";
|
||||
import mongoose from "mongoose";
|
||||
import { MonitorStatuses } from "@/db/models/index.js";
|
||||
import { MonitorStatuses } from "@/types/domain/index.js";
|
||||
|
||||
export const checksStatusIdQuerySchema = z.object({
|
||||
status: z.enum(MonitorStatuses, { error: "Invalid status" }),
|
||||
@@ -29,15 +29,20 @@ export const checksStatusIdQuerySchema = z.object({
|
||||
return z.NEVER;
|
||||
}
|
||||
if (num < 0) {
|
||||
ctx.addIssue({ code: "custom", message: "rowsPerPage must be greater than 0" });
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: "rowsPerPage must be greater than 0",
|
||||
});
|
||||
return z.NEVER;
|
||||
}
|
||||
if (num > 100) {
|
||||
ctx.addIssue({ code: "custom", message: "rowsPerPage must be less than or equal to 100" });
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: "rowsPerPage must be less than or equal to 100",
|
||||
});
|
||||
return z.NEVER;
|
||||
}
|
||||
return num;
|
||||
}),
|
||||
range: z.string().min(1),
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import { MonitorTypes } from "@/db/models/index.js";
|
||||
import { MonitorTypes } from "@/types/domain/index.js";
|
||||
import { urlRegex } from "./common.js";
|
||||
|
||||
export const monitorSchema = z
|
||||
@@ -43,7 +43,10 @@ export const monitorSchema = z
|
||||
});
|
||||
}
|
||||
|
||||
if ((data.type === "infrastructure" || data.type === "docker") && !data.secret) {
|
||||
if (
|
||||
(data.type === "infrastructure" || data.type === "docker") &&
|
||||
!data.secret
|
||||
) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: `Secret is required for ${data.type} monitors`,
|
||||
@@ -118,4 +121,3 @@ export const monitorPatchSchema = monitorSchema
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user