Compare commits
37
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4182a964a6 | ||
|
|
80ea09b864 | ||
|
|
e0228918fb | ||
|
|
8369db5ca2 | ||
|
|
49e8d26bfc | ||
|
|
c28be97fb0 | ||
|
|
ed36db172a | ||
|
|
c021d04f53 | ||
|
|
86eeda3f0e | ||
|
|
25d81f4238 | ||
|
|
af8ee643e1 | ||
|
|
61e442493a | ||
|
|
9540084425 | ||
|
|
f50ba1bf23 | ||
|
|
b4e42bfd68 | ||
|
|
3af00281c4 | ||
|
|
4a90ab8e78 | ||
|
|
7598cf3007 | ||
|
|
e1c89cd093 | ||
|
|
80d4e818f1 | ||
|
|
4949acac1c | ||
|
|
586e79efb2 | ||
|
|
c961f5292d | ||
|
|
d53785b768 | ||
|
|
b9cbf199e6 | ||
|
|
ac6678a986 | ||
|
|
ed1ef56184 | ||
|
|
ac16126e2e | ||
|
|
8e600e9a87 | ||
|
|
790d83b7ed | ||
|
|
c567e4be5e | ||
|
|
936d9cd9a0 | ||
|
|
6e047d2cdb | ||
|
|
aad03b787f | ||
|
|
bb374db463 | ||
|
|
57e008c7ba | ||
|
|
5d3dc03c89 |
No files matched your search
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "client",
|
"name": "client",
|
||||||
"version": "3.9.2",
|
"version": "3.10.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "client",
|
"name": "client",
|
||||||
"version": "3.9.2",
|
"version": "3.10.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@csstools/css-tokenizer": "^3.0.4",
|
"@csstools/css-tokenizer": "^3.0.4",
|
||||||
"@emotion/react": "^11.13.3",
|
"@emotion/react": "^11.13.3",
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "client",
|
"name": "client",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "3.9.2",
|
"version": "3.10.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export const BaseChart = ({
|
|||||||
<Typography
|
<Typography
|
||||||
variant="eyebrow"
|
variant="eyebrow"
|
||||||
color="text.secondary"
|
color="text.secondary"
|
||||||
|
sx={{ overflowWrap: "anywhere" }}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
@@ -25,10 +25,11 @@ export const InfraDetailsGauges = ({
|
|||||||
<Stack
|
<Stack
|
||||||
direction={isSmall ? "column" : "row"}
|
direction={isSmall ? "column" : "row"}
|
||||||
spacing={theme.spacing(8)}
|
spacing={theme.spacing(8)}
|
||||||
alignItems={"center"}
|
alignItems={"stretch"}
|
||||||
>
|
>
|
||||||
<DetailGauge
|
<DetailGauge
|
||||||
title={t("pages.infrastructure.gauges.memory.title")}
|
title={t("pages.infrastructure.gauges.memory.title")}
|
||||||
|
maxWidth={260}
|
||||||
progress={(snapshot?.memory?.usage_percent || 0) * 100}
|
progress={(snapshot?.memory?.usage_percent || 0) * 100}
|
||||||
upperLabel={t("pages.infrastructure.gauges.memory.upperLabel")}
|
upperLabel={t("pages.infrastructure.gauges.memory.upperLabel")}
|
||||||
upperValue={prettyBytes(snapshot?.memory?.used_bytes || 0)}
|
upperValue={prettyBytes(snapshot?.memory?.used_bytes || 0)}
|
||||||
@@ -37,6 +38,7 @@ export const InfraDetailsGauges = ({
|
|||||||
/>
|
/>
|
||||||
<DetailGauge
|
<DetailGauge
|
||||||
title={t("pages.infrastructure.gauges.cpu.title")}
|
title={t("pages.infrastructure.gauges.cpu.title")}
|
||||||
|
maxWidth={260}
|
||||||
progress={(snapshot?.cpu?.usage_percent || 0) * 100}
|
progress={(snapshot?.cpu?.usage_percent || 0) * 100}
|
||||||
upperLabel={t("pages.infrastructure.gauges.cpu.upperLabel")}
|
upperLabel={t("pages.infrastructure.gauges.cpu.upperLabel")}
|
||||||
upperValue={getFrequency(snapshot?.cpu?.current_frequency || 0)}
|
upperValue={getFrequency(snapshot?.cpu?.current_frequency || 0)}
|
||||||
@@ -49,6 +51,7 @@ export const InfraDetailsGauges = ({
|
|||||||
key={disk?.device || 0 + idx}
|
key={disk?.device || 0 + idx}
|
||||||
// title={`Disk ${idx} usage`}
|
// title={`Disk ${idx} usage`}
|
||||||
title={t("pages.infrastructure.gauges.disk.title", { idx })}
|
title={t("pages.infrastructure.gauges.disk.title", { idx })}
|
||||||
|
maxWidth={260}
|
||||||
progress={(disk.usage_percent || 0) * 100}
|
progress={(disk.usage_percent || 0) * 100}
|
||||||
upperLabel={t("pages.infrastructure.gauges.disk.upperLabel")}
|
upperLabel={t("pages.infrastructure.gauges.disk.upperLabel")}
|
||||||
upperValue={prettyBytes(disk?.used_bytes || 0)}
|
upperValue={prettyBytes(disk?.used_bytes || 0)}
|
||||||
|
|||||||
@@ -825,7 +825,7 @@
|
|||||||
"netBytesRecv": "{{name}} - Empfangene Bytes",
|
"netBytesRecv": "{{name}} - Empfangene Bytes",
|
||||||
"netBytesSent": "{{name}} - Gesendete Bytes",
|
"netBytesSent": "{{name}} - Gesendete Bytes",
|
||||||
"netNoTraffic": "Capture meldet null Datenverkehr auf allen Schnittstellen. Das ist normal, wenn Capture in einem Container läuft, der nur Loopback-Schnittstellen sieht — führe Capture direkt auf dem Host aus, um echten Netzwerkverkehr zu sehen.",
|
"netNoTraffic": "Capture meldet null Datenverkehr auf allen Schnittstellen. Das ist normal, wenn Capture in einem Container läuft, der nur Loopback-Schnittstellen sieht — führe Capture direkt auf dem Host aus, um echten Netzwerkverkehr zu sehen.",
|
||||||
"temp": "Temp."
|
"temp": "CPU-Temperatur"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gauges": {
|
"gauges": {
|
||||||
@@ -841,13 +841,13 @@
|
|||||||
},
|
},
|
||||||
"memory": {
|
"memory": {
|
||||||
"lowerLabel": "Frei",
|
"lowerLabel": "Frei",
|
||||||
"title": "Arbeitsspeicherauslastung",
|
"title": "Speicherauslastung",
|
||||||
"upperLabel": "Belegt"
|
"upperLabel": "Belegt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"statBoxes": {
|
"statBoxes": {
|
||||||
"avgCpuTemperature": "Durchschn. CPU Termperatur",
|
"avgCpuTemperature": "Durchschn. CPU-Temperatur",
|
||||||
"cpuFrequency": "CPU Frequenz",
|
"cpuFrequency": "CPU-Frequenz",
|
||||||
"cpuLogical": "CPU (logisch)",
|
"cpuLogical": "CPU (logisch)",
|
||||||
"cpuPhysical": "CPU (physisch)",
|
"cpuPhysical": "CPU (physisch)",
|
||||||
"disk": "Festplatte",
|
"disk": "Festplatte",
|
||||||
|
|||||||
@@ -845,7 +845,7 @@
|
|||||||
"netBytesRecv": "{{name}} - Bytes Received",
|
"netBytesRecv": "{{name}} - Bytes Received",
|
||||||
"netBytesSent": "{{name}} - Bytes Sent",
|
"netBytesSent": "{{name}} - Bytes Sent",
|
||||||
"loopbackOnlyNotice": "Capture is only reporting loopback or container-bridge interfaces (lo, lo0, docker0). This is normal when Capture runs inside a container that can only see its own network namespace — run Capture directly on the host to see real network activity.",
|
"loopbackOnlyNotice": "Capture is only reporting loopback or container-bridge interfaces (lo, lo0, docker0). This is normal when Capture runs inside a container that can only see its own network namespace — run Capture directly on the host to see real network activity.",
|
||||||
"temp": "Temp"
|
"temp": "CPU temperature"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gauges": {
|
"gauges": {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"worker:dev": "QUEUE_MODE=worker nodemon --exec tsx src/index.js",
|
"worker:dev": "QUEUE_MODE=worker nodemon --exec tsx src/index.js",
|
||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"build": "npm run clean && npm run build:openapi && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && cp -r src/templates dist/templates",
|
"build": "npm run clean && npm run build:openapi && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && cp -r src/templates dist/templates",
|
||||||
|
"typeCheck": "tsc --noEmit",
|
||||||
"build:openapi": "tsc --noEmit && tsx scripts/buildOpenApi.ts",
|
"build:openapi": "tsc --noEmit && tsx scripts/buildOpenApi.ts",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint-fix": "eslint --fix .",
|
"lint-fix": "eslint --fix .",
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ import {
|
|||||||
} from "@/api/validation/userValidation.js";
|
} from "@/api/validation/userValidation.js";
|
||||||
import { IUserService } from "@/domain/users/user.service.js";
|
import { IUserService } from "@/domain/users/user.service.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "authController";
|
|
||||||
|
|
||||||
export interface IAuthController {
|
export interface IAuthController {
|
||||||
registerUser: RequestHandler;
|
registerUser: RequestHandler;
|
||||||
createUser: RequestHandler;
|
createUser: RequestHandler;
|
||||||
@@ -42,18 +40,12 @@ export interface IAuthController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AuthController implements IAuthController {
|
class AuthController implements IAuthController {
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
|
||||||
|
|
||||||
private userService: IUserService;
|
private userService: IUserService;
|
||||||
|
|
||||||
constructor(userService: IUserService) {
|
constructor(userService: IUserService) {
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return AuthController.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
registerUser = catchAsync(async (req: Request, res: Response) => {
|
registerUser = catchAsync(async (req: Request, res: Response) => {
|
||||||
const newUser = req.body.user;
|
const newUser = req.body.user;
|
||||||
const newUserToken = req.body.token;
|
const newUserToken = req.body.token;
|
||||||
|
|||||||
@@ -11,10 +11,7 @@ import {
|
|||||||
import { ICheckService } from "@/domain/checks/check.service.js";
|
import { ICheckService } from "@/domain/checks/check.service.js";
|
||||||
import { requireTeamId } from "@/api/controllers/controllerUtils.js";
|
import { requireTeamId } from "@/api/controllers/controllerUtils.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "checkController";
|
|
||||||
|
|
||||||
export interface ICheckController {
|
export interface ICheckController {
|
||||||
serviceName: string;
|
|
||||||
getChecksByMonitor: RequestHandler;
|
getChecksByMonitor: RequestHandler;
|
||||||
getChecksByTeam: RequestHandler;
|
getChecksByTeam: RequestHandler;
|
||||||
getChecksSummaryByTeamId: RequestHandler;
|
getChecksSummaryByTeamId: RequestHandler;
|
||||||
@@ -23,17 +20,11 @@ export interface ICheckController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class CheckController implements ICheckController {
|
class CheckController implements ICheckController {
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
|
||||||
|
|
||||||
private checkService: ICheckService;
|
private checkService: ICheckService;
|
||||||
constructor(checkService: ICheckService) {
|
constructor(checkService: ICheckService) {
|
||||||
this.checkService = checkService;
|
this.checkService = checkService;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return CheckController.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
getChecksByMonitor = catchAsync(async (req: Request, res: Response) => {
|
getChecksByMonitor = catchAsync(async (req: Request, res: Response) => {
|
||||||
const validatedParams = getChecksParamValidation.parse(req.params);
|
const validatedParams = getChecksParamValidation.parse(req.params);
|
||||||
const validatedQuery = getChecksQueryValidation.parse(req.query);
|
const validatedQuery = getChecksQueryValidation.parse(req.query);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import { Monitor, MonitorTypes, type MonitorType } from "@/domain/monitors/monitor.types.js";
|
import { Monitor, MonitorTypes, type MonitorType } from "@/domain/monitors/monitor.type.js";
|
||||||
import { UserRole } from "@/domain/users/user.type.js";
|
import { UserRole } from "@/domain/users/user.type.js";
|
||||||
import sslChecker, { SSLDetails } from "ssl-checker";
|
import sslChecker, { SSLDetails } from "ssl-checker";
|
||||||
type SSLCheckerType = typeof sslChecker;
|
type SSLCheckerType = typeof sslChecker;
|
||||||
@@ -54,24 +54,6 @@ export const optionalNumber = (value: unknown, fieldName: string): number | unde
|
|||||||
throw new AppError({ message: `${fieldName} must be a number`, status: 400 });
|
throw new AppError({ message: `${fieldName} must be a number`, status: 400 });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const optionalBoolean = (value: unknown, fieldName: string): boolean | undefined => {
|
|
||||||
if (value === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (typeof value === "boolean") {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
if (typeof value === "string") {
|
|
||||||
if (value === "true") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (value === "false") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new AppError({ message: `${fieldName} must be a boolean`, status: 400 });
|
|
||||||
};
|
|
||||||
|
|
||||||
export const parseMonitorTypeFilter = (value: unknown): MonitorType | MonitorType[] | undefined => {
|
export const parseMonitorTypeFilter = (value: unknown): MonitorType | MonitorType[] | undefined => {
|
||||||
const parseSingle = (input: unknown): MonitorType => {
|
const parseSingle = (input: unknown): MonitorType => {
|
||||||
if (typeof input !== "string") {
|
if (typeof input !== "string") {
|
||||||
|
|||||||
@@ -2,25 +2,17 @@ import { IDiagnosticService } from "@/domain/diagnostics/diagnostic.service.js";
|
|||||||
import { Request, Response, RequestHandler } from "express";
|
import { Request, Response, RequestHandler } from "express";
|
||||||
import { catchAsync } from "@/utils/catchAsync.js";
|
import { catchAsync } from "@/utils/catchAsync.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "diagnosticController";
|
|
||||||
|
|
||||||
export interface IDiagnosticController {
|
export interface IDiagnosticController {
|
||||||
getSystemStats: RequestHandler;
|
getSystemStats: RequestHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
class DiagnosticController implements IDiagnosticController {
|
class DiagnosticController implements IDiagnosticController {
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
|
||||||
|
|
||||||
private diagnosticService: IDiagnosticService;
|
private diagnosticService: IDiagnosticService;
|
||||||
|
|
||||||
constructor(diagnosticService: IDiagnosticService) {
|
constructor(diagnosticService: IDiagnosticService) {
|
||||||
this.diagnosticService = diagnosticService;
|
this.diagnosticService = diagnosticService;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return DiagnosticController.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
getSystemStats = catchAsync(async (req: Request, res: Response) => {
|
getSystemStats = catchAsync(async (req: Request, res: Response) => {
|
||||||
const diagnostics = await this.diagnosticService.getSystemStats();
|
const diagnostics = await this.diagnosticService.getSystemStats();
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
|
|||||||
@@ -4,23 +4,15 @@ import { getChecksParamValidation, getChecksQueryValidation } from "@/api/valida
|
|||||||
import type { IGeoChecksService } from "@/domain/geo-checks/geo-check.service.js";
|
import type { IGeoChecksService } from "@/domain/geo-checks/geo-check.service.js";
|
||||||
import { requireTeamId } from "./controllerUtils.js";
|
import { requireTeamId } from "./controllerUtils.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "geoCheckController";
|
|
||||||
|
|
||||||
export interface IGeoCheckController {
|
export interface IGeoCheckController {
|
||||||
getGeoChecksByMonitor: RequestHandler;
|
getGeoChecksByMonitor: RequestHandler;
|
||||||
}
|
}
|
||||||
class GeoCheckController implements IGeoCheckController {
|
class GeoCheckController implements IGeoCheckController {
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
|
||||||
|
|
||||||
private geoChecksService: IGeoChecksService;
|
private geoChecksService: IGeoChecksService;
|
||||||
constructor(geoChecksService: IGeoChecksService) {
|
constructor(geoChecksService: IGeoChecksService) {
|
||||||
this.geoChecksService = geoChecksService;
|
this.geoChecksService = geoChecksService;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return GeoCheckController.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
getGeoChecksByMonitor = catchAsync(async (req: Request, res: Response) => {
|
getGeoChecksByMonitor = catchAsync(async (req: Request, res: Response) => {
|
||||||
const validatedParams = getChecksParamValidation.parse(req.params);
|
const validatedParams = getChecksParamValidation.parse(req.params);
|
||||||
const validatedQuery = getChecksQueryValidation.parse(req.query);
|
const validatedQuery = getChecksQueryValidation.parse(req.query);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { catchAsync } from "@/utils/catchAsync.js";
|
|||||||
import { inviteBodyValidation, inviteVerificationBodyValidation } from "@/api/validation/authValidation.js";
|
import { inviteBodyValidation, inviteVerificationBodyValidation } from "@/api/validation/authValidation.js";
|
||||||
import { requireFirstName, requireTeamId, requireUserRoles } from "@/api/controllers/controllerUtils.js";
|
import { requireFirstName, requireTeamId, requireUserRoles } from "@/api/controllers/controllerUtils.js";
|
||||||
import { IInviteService } from "@/domain/invites/invite.service.js";
|
import { IInviteService } from "@/domain/invites/invite.service.js";
|
||||||
const SERVICE_NAME = "inviteController";
|
|
||||||
|
|
||||||
export interface IInviteController {
|
export interface IInviteController {
|
||||||
getInviteToken: RequestHandler;
|
getInviteToken: RequestHandler;
|
||||||
@@ -12,16 +11,11 @@ export interface IInviteController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class InviteController implements IInviteController {
|
class InviteController implements IInviteController {
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
|
||||||
private inviteService: IInviteService;
|
private inviteService: IInviteService;
|
||||||
constructor(inviteService: IInviteService) {
|
constructor(inviteService: IInviteService) {
|
||||||
this.inviteService = inviteService;
|
this.inviteService = inviteService;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return InviteController.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
getInviteToken = catchAsync(async (req: Request, res: Response) => {
|
getInviteToken = catchAsync(async (req: Request, res: Response) => {
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = requireTeamId(req.user?.teamId);
|
||||||
const userRoles = requireUserRoles(req.user?.role);
|
const userRoles = requireUserRoles(req.user?.role);
|
||||||
|
|||||||
@@ -2,21 +2,15 @@ import { ILogger } from "@/utils/logger.js";
|
|||||||
import { Request, Response, RequestHandler } from "express";
|
import { Request, Response, RequestHandler } from "express";
|
||||||
import { catchAsync } from "@/utils/catchAsync.js";
|
import { catchAsync } from "@/utils/catchAsync.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "LogController";
|
|
||||||
|
|
||||||
export interface ILogController {
|
export interface ILogController {
|
||||||
getLogs: RequestHandler;
|
getLogs: RequestHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
class LogController {
|
class LogController {
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
|
||||||
private logger: ILogger;
|
private logger: ILogger;
|
||||||
constructor(logger: ILogger) {
|
constructor(logger: ILogger) {
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
get serviceName() {
|
|
||||||
return LogController.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
getLogs = catchAsync(async (req: Request, res: Response) => {
|
getLogs = catchAsync(async (req: Request, res: Response) => {
|
||||||
const logs = this.logger.getLogs();
|
const logs = this.logger.getLogs();
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ import { AppError } from "@/utils/AppError.js";
|
|||||||
import { IMonitorService } from "@/domain/monitors/monitor.service.js";
|
import { IMonitorService } from "@/domain/monitors/monitor.service.js";
|
||||||
import { INotificationsService } from "@/domain/notifications/notification.service.js";
|
import { INotificationsService } from "@/domain/notifications/notification.service.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "monitorController";
|
|
||||||
|
|
||||||
export interface IMonitorController {
|
export interface IMonitorController {
|
||||||
getMonitorCertificate: RequestHandler;
|
getMonitorCertificate: RequestHandler;
|
||||||
getUptimeDetailsById: RequestHandler;
|
getUptimeDetailsById: RequestHandler;
|
||||||
@@ -47,8 +45,6 @@ export interface IMonitorController {
|
|||||||
updateNotifications: RequestHandler;
|
updateNotifications: RequestHandler;
|
||||||
}
|
}
|
||||||
class MonitorController implements IMonitorController {
|
class MonitorController implements IMonitorController {
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
|
||||||
|
|
||||||
private monitorService: IMonitorService;
|
private monitorService: IMonitorService;
|
||||||
private notificationsService: INotificationsService;
|
private notificationsService: INotificationsService;
|
||||||
|
|
||||||
@@ -57,10 +53,6 @@ class MonitorController implements IMonitorController {
|
|||||||
this.notificationsService = notificationsService;
|
this.notificationsService = notificationsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return MonitorController.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
getMonitorCertificate = catchAsync(async (req: Request, res: Response) => {
|
getMonitorCertificate = catchAsync(async (req: Request, res: Response) => {
|
||||||
const validatedParams = getCertificateParamValidation.parse(req.params);
|
const validatedParams = getCertificateParamValidation.parse(req.params);
|
||||||
const teamId = requireTeamId(req.user?.teamId);
|
const teamId = requireTeamId(req.user?.teamId);
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { getQueueJobsQueryValidation } from "@/api/validation/queueValidation.js
|
|||||||
import { Request, Response, RequestHandler } from "express";
|
import { Request, Response, RequestHandler } from "express";
|
||||||
import { catchAsync } from "@/utils/catchAsync.js";
|
import { catchAsync } from "@/utils/catchAsync.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "JobQueueController";
|
|
||||||
|
|
||||||
export interface IJobQueueController {
|
export interface IJobQueueController {
|
||||||
getMetrics: RequestHandler;
|
getMetrics: RequestHandler;
|
||||||
getJobs: RequestHandler;
|
getJobs: RequestHandler;
|
||||||
@@ -13,13 +11,8 @@ export interface IJobQueueController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class JobQueueController implements IJobQueueController {
|
class JobQueueController implements IJobQueueController {
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
|
||||||
constructor(private scheduler: IJobScheduler) {}
|
constructor(private scheduler: IJobScheduler) {}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return JobQueueController.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
getMetrics = catchAsync(async (req: Request, res: Response) => {
|
getMetrics = catchAsync(async (req: Request, res: Response) => {
|
||||||
const metrics = await this.scheduler.getMetrics();
|
const metrics = await this.scheduler.getMetrics();
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
|
|||||||
@@ -7,17 +7,13 @@ import { ISettingsService } from "@/domain/app-settings/app-settings.service.js"
|
|||||||
import { IEmailService } from "@/service/emailService.js";
|
import { IEmailService } from "@/service/emailService.js";
|
||||||
import { Settings } from "@/domain/app-settings/app-settings.type.js";
|
import { Settings } from "@/domain/app-settings/app-settings.type.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "SettingsController";
|
|
||||||
|
|
||||||
export interface ISettingsController {
|
export interface ISettingsController {
|
||||||
serviceName: string;
|
|
||||||
getAppSettings: RequestHandler;
|
getAppSettings: RequestHandler;
|
||||||
updateAppSettings: RequestHandler;
|
updateAppSettings: RequestHandler;
|
||||||
sendTestEmail: RequestHandler;
|
sendTestEmail: RequestHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SettingsController implements ISettingsController {
|
class SettingsController implements ISettingsController {
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
|
||||||
private settingsService: ISettingsService;
|
private settingsService: ISettingsService;
|
||||||
private emailService: IEmailService;
|
private emailService: IEmailService;
|
||||||
constructor(settingsService: ISettingsService, emailService: IEmailService) {
|
constructor(settingsService: ISettingsService, emailService: IEmailService) {
|
||||||
@@ -25,10 +21,6 @@ class SettingsController implements ISettingsController {
|
|||||||
this.emailService = emailService;
|
this.emailService = emailService;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return SettingsController.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
buildAppSettings = (dbSettings: Settings) => {
|
buildAppSettings = (dbSettings: Settings) => {
|
||||||
const sanitizedSettings: Record<string, unknown> = { ...dbSettings };
|
const sanitizedSettings: Record<string, unknown> = { ...dbSettings };
|
||||||
delete sanitizedSettings.version;
|
delete sanitizedSettings.version;
|
||||||
|
|||||||
@@ -13,10 +13,7 @@ import { requireTeamId, requireUserId } from "@/api/controllers/controllerUtils.
|
|||||||
import { IStatusPageService } from "@/domain/status-pages/status-page.service.js";
|
import { IStatusPageService } from "@/domain/status-pages/status-page.service.js";
|
||||||
import { resolveStatusPageDomainFromRequest } from "@/utils/statusPageDomain.js";
|
import { resolveStatusPageDomainFromRequest } from "@/utils/statusPageDomain.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "statusPageController";
|
|
||||||
|
|
||||||
export interface IStatusPageController {
|
export interface IStatusPageController {
|
||||||
readonly serviceName: string;
|
|
||||||
createStatusPage: RequestHandler;
|
createStatusPage: RequestHandler;
|
||||||
updateStatusPage: RequestHandler;
|
updateStatusPage: RequestHandler;
|
||||||
getStatusPageByUrl: RequestHandler;
|
getStatusPageByUrl: RequestHandler;
|
||||||
@@ -26,16 +23,11 @@ export interface IStatusPageController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class StatusPageController implements IStatusPageController {
|
class StatusPageController implements IStatusPageController {
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
|
||||||
private statusPageService: IStatusPageService;
|
private statusPageService: IStatusPageService;
|
||||||
constructor(statusPageService: IStatusPageService) {
|
constructor(statusPageService: IStatusPageService) {
|
||||||
this.statusPageService = statusPageService;
|
this.statusPageService = statusPageService;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return StatusPageController.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
createStatusPage = catchAsync(async (req: Request, res: Response) => {
|
createStatusPage = catchAsync(async (req: Request, res: Response) => {
|
||||||
createStatusPageBodyValidation.parse(req.body);
|
createStatusPageBodyValidation.parse(req.body);
|
||||||
if (req.file) {
|
if (req.file) {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { booleanCoercion } from "./shared.js";
|
import { booleanCoercion } from "./shared.js";
|
||||||
import { GeoContinents } from "@/domain/geo-checks/geo-check.type.js";
|
import { GeoContinents } from "@/domain/geo-checks/geo-check.type.js";
|
||||||
import { MonitorTypes } from "@/domain/monitors/monitor.types.js";
|
import { MonitorTypes } from "@/domain/monitors/monitor.type.js";
|
||||||
|
import { CheckFilters, DateRanges, SortOrders } from "@/types/query.js";
|
||||||
|
|
||||||
//****************************************
|
//****************************************
|
||||||
// Check Validations
|
// Check Validations
|
||||||
@@ -13,10 +14,10 @@ export const getChecksParamValidation = z.object({
|
|||||||
|
|
||||||
export const getChecksQueryValidation = z.object({
|
export const getChecksQueryValidation = z.object({
|
||||||
type: z.enum(MonitorTypes).optional(),
|
type: z.enum(MonitorTypes).optional(),
|
||||||
sortOrder: z.enum(["asc", "desc"]),
|
sortOrder: z.enum(SortOrders),
|
||||||
limit: z.coerce.number().optional(),
|
limit: z.coerce.number().optional(),
|
||||||
dateRange: z.enum(["recent", "hour", "day", "week", "month", "all"]),
|
dateRange: z.enum(DateRanges),
|
||||||
filter: z.enum(["all", "up", "down", "resolve"]).optional(),
|
filter: z.enum(CheckFilters).optional(),
|
||||||
ack: booleanCoercion.optional(),
|
ack: booleanCoercion.optional(),
|
||||||
page: z.coerce.number(),
|
page: z.coerce.number(),
|
||||||
rowsPerPage: z.coerce.number(),
|
rowsPerPage: z.coerce.number(),
|
||||||
@@ -25,10 +26,10 @@ export const getChecksQueryValidation = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const getTeamChecksQueryValidation = z.object({
|
export const getTeamChecksQueryValidation = z.object({
|
||||||
sortOrder: z.enum(["asc", "desc"]),
|
sortOrder: z.enum(SortOrders),
|
||||||
limit: z.coerce.number().optional(),
|
limit: z.coerce.number().optional(),
|
||||||
dateRange: z.enum(["recent", "hour", "day", "week", "month", "all"]),
|
dateRange: z.enum(DateRanges),
|
||||||
filter: z.enum(["all", "up", "down", "resolve"]).optional(),
|
filter: z.enum(CheckFilters).optional(),
|
||||||
ack: booleanCoercion.optional(),
|
ack: booleanCoercion.optional(),
|
||||||
page: z.coerce.number(),
|
page: z.coerce.number(),
|
||||||
rowsPerPage: z.coerce.number(),
|
rowsPerPage: z.coerce.number(),
|
||||||
@@ -39,5 +40,5 @@ export const deleteChecksParamValidation = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const getChecksSummaryByTeamIdQueryValidation = z.object({
|
export const getChecksSummaryByTeamIdQueryValidation = z.object({
|
||||||
dateRange: z.enum(["recent", "hour", "day", "week", "month", "all"]).optional(),
|
dateRange: z.enum(DateRanges).optional(),
|
||||||
});
|
});
|
||||||
@@ -1,18 +1,20 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { booleanCoercion } from "./shared.js";
|
import { booleanCoercion } from "./shared.js";
|
||||||
|
import { DateRanges, SortOrders } from "@/types/query.js";
|
||||||
|
import { IncidentResolutionTypes } from "@/domain/incidents/incident.type.js";
|
||||||
|
|
||||||
//****************************************
|
//****************************************
|
||||||
// Incident Validations
|
// Incident Validations
|
||||||
//****************************************
|
//****************************************
|
||||||
|
|
||||||
export const getIncidentsByTeamQueryValidation = z.object({
|
export const getIncidentsByTeamQueryValidation = z.object({
|
||||||
sortOrder: z.enum(["asc", "desc"]),
|
sortOrder: z.enum(SortOrders),
|
||||||
dateRange: z.enum(["recent", "hour", "day", "week", "month", "all"]).default("all"),
|
dateRange: z.enum(DateRanges).default("all"),
|
||||||
page: z.coerce.number().int().min(0),
|
page: z.coerce.number().int().min(0),
|
||||||
rowsPerPage: z.coerce.number().int().min(1),
|
rowsPerPage: z.coerce.number().int().min(1),
|
||||||
status: booleanCoercion.optional(),
|
status: booleanCoercion.optional(),
|
||||||
monitorId: z.string().optional(),
|
monitorId: z.string().optional(),
|
||||||
resolutionType: z.enum(["manual", "automatic"]).optional(),
|
resolutionType: z.enum(IncidentResolutionTypes).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getIncidentSummaryQueryValidation = z.object({
|
export const getIncidentSummaryQueryValidation = z.object({
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { booleanCoercion } from "./shared.js";
|
import { booleanCoercion } from "./shared.js";
|
||||||
import { DurationUnits } from "@/domain/maintenance-windows/maintenance-window.type.js";
|
import { DurationUnits } from "@/domain/maintenance-windows/maintenance-window.type.js";
|
||||||
|
import { SortOrders } from "@/types/query.js";
|
||||||
|
|
||||||
const dateToString = z.coerce.date().transform((d) => d.toISOString());
|
const dateToString = z.coerce.date().transform((d) => d.toISOString());
|
||||||
|
|
||||||
@@ -49,7 +50,7 @@ export const getMaintenanceWindowsByTeamIdQueryValidation = z.object({
|
|||||||
page: z.coerce.number().optional(),
|
page: z.coerce.number().optional(),
|
||||||
rowsPerPage: z.coerce.number().optional(),
|
rowsPerPage: z.coerce.number().optional(),
|
||||||
field: z.string().optional(),
|
field: z.string().optional(),
|
||||||
order: z.enum(["asc", "desc"]).optional(),
|
order: z.enum(SortOrders).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getMaintenanceWindowsByMonitorIdParamValidation = z.object({
|
export const getMaintenanceWindowsByMonitorIdParamValidation = z.object({
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import {
|
|||||||
MonitorStatuses,
|
MonitorStatuses,
|
||||||
MonitorTypes,
|
MonitorTypes,
|
||||||
PageSpeedStrategies,
|
PageSpeedStrategies,
|
||||||
} from "@/domain/monitors/monitor.types.js";
|
} from "@/domain/monitors/monitor.type.js";
|
||||||
|
import { DateRanges, SortOrders } from "@/types/query.js";
|
||||||
|
|
||||||
const httpStatusCode = z.number().refine((code) => HttpStatusCodeSet.has(code), { message: "Must be a valid HTTP status code" });
|
const httpStatusCode = z.number().refine((code) => HttpStatusCodeSet.has(code), { message: "Must be a valid HTTP status code" });
|
||||||
|
|
||||||
@@ -20,9 +21,9 @@ export const getMonitorByIdParamValidation = z.object({
|
|||||||
|
|
||||||
export const getMonitorByIdQueryValidation = z.object({
|
export const getMonitorByIdQueryValidation = z.object({
|
||||||
status: booleanCoercion.optional(),
|
status: booleanCoercion.optional(),
|
||||||
sortOrder: z.enum(["asc", "desc"]).optional(),
|
sortOrder: z.enum(SortOrders).optional(),
|
||||||
limit: z.coerce.number().optional(),
|
limit: z.coerce.number().optional(),
|
||||||
dateRange: z.enum(["recent", "hour", "day", "week", "month", "all"]).optional(),
|
dateRange: z.enum(DateRanges).optional(),
|
||||||
numToDisplay: z.coerce.number().optional(),
|
numToDisplay: z.coerce.number().optional(),
|
||||||
normalize: booleanCoercion.optional(),
|
normalize: booleanCoercion.optional(),
|
||||||
continent: z.union([z.enum(GeoContinents), z.array(z.enum(GeoContinents))]).optional(),
|
continent: z.union([z.enum(GeoContinents), z.array(z.enum(GeoContinents))]).optional(),
|
||||||
@@ -42,7 +43,7 @@ export const getMonitorsWithChecksQueryValidation = z.object({
|
|||||||
rowsPerPage: z.coerce.number().int().min(1).max(100).optional(),
|
rowsPerPage: z.coerce.number().int().min(1).max(100).optional(),
|
||||||
filter: z.union([z.string(), z.literal("")]).optional(),
|
filter: z.union([z.string(), z.literal("")]).optional(),
|
||||||
field: z.string().optional(),
|
field: z.string().optional(),
|
||||||
order: z.enum(["asc", "desc"]).optional(),
|
order: z.enum(SortOrders).optional(),
|
||||||
type: z.union([z.enum(MonitorTypes), z.array(z.enum(MonitorTypes))]).optional(),
|
type: z.union([z.enum(MonitorTypes), z.array(z.enum(MonitorTypes))]).optional(),
|
||||||
tags: z.union([z.string(), z.array(z.string())]).optional(),
|
tags: z.union([z.string(), z.array(z.string())]).optional(),
|
||||||
explain: booleanCoercion.optional(),
|
explain: booleanCoercion.optional(),
|
||||||
@@ -195,7 +196,7 @@ export const getUptimeDetailsByIdParamValidation = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const getUptimeDetailsByIdQueryValidation = z.object({
|
export const getUptimeDetailsByIdQueryValidation = z.object({
|
||||||
dateRange: z.enum(["recent", "hour", "day", "week", "month", "all"]),
|
dateRange: z.enum(DateRanges),
|
||||||
normalize: booleanCoercion.optional(),
|
normalize: booleanCoercion.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -206,7 +207,7 @@ const importedMonitorSchema = z
|
|||||||
teamId: z.string().optional(),
|
teamId: z.string().optional(),
|
||||||
name: z.string().min(1, "Name is required"),
|
name: z.string().min(1, "Name is required"),
|
||||||
description: z.union([z.string(), z.literal("")]).optional(),
|
description: z.union([z.string(), z.literal("")]).optional(),
|
||||||
status: z.enum(["up", "down", "paused", "initializing", "maintenance", "breached"]).default("initializing"),
|
status: z.enum(MonitorStatuses).default("initializing"),
|
||||||
statusWindow: z.array(z.boolean()).default([]),
|
statusWindow: z.array(z.boolean()).default([]),
|
||||||
statusWindowSize: z.number().min(1).max(20).default(5),
|
statusWindowSize: z.number().min(1).max(20).default(5),
|
||||||
statusWindowThreshold: z.number().min(1).max(100).default(60),
|
statusWindowThreshold: z.number().min(1).max(100).default(60),
|
||||||
@@ -263,7 +264,7 @@ export const getHardwareDetailsByIdParamValidation = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const getHardwareDetailsByIdQueryValidation = z.object({
|
export const getHardwareDetailsByIdQueryValidation = z.object({
|
||||||
dateRange: z.enum(["recent", "hour", "day", "week", "month", "all"]).optional(),
|
dateRange: z.enum(DateRanges).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Canonical monitor shape returned by /monitors endpoints. Keep aligned with
|
// Canonical monitor shape returned by /monitors endpoints. Keep aligned with
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { booleanCoercion, dnsHostnameRegex } from "./shared.js";
|
|||||||
import { StatusPageTypes, StatusPageThemes, StatusPageThemeModes } from "@/domain/status-pages/status-page.type.js";
|
import { StatusPageTypes, StatusPageThemes, StatusPageThemeModes } from "@/domain/status-pages/status-page.type.js";
|
||||||
import { normalizeStatusPageDomain } from "@/utils/statusPageDomain.js";
|
import { normalizeStatusPageDomain } from "@/utils/statusPageDomain.js";
|
||||||
import { cssReferencesExternalResource } from "@/utils/customCss.js";
|
import { cssReferencesExternalResource } from "@/utils/customCss.js";
|
||||||
|
import { ImageMimeTypes } from "@/types/upload.js";
|
||||||
|
|
||||||
//****************************************
|
//****************************************
|
||||||
// Status Page Validations
|
// Status Page Validations
|
||||||
@@ -67,7 +68,7 @@ export const imageValidation = z
|
|||||||
fieldname: z.string().min(1, "Field name is required"),
|
fieldname: z.string().min(1, "Field name is required"),
|
||||||
originalname: z.string().min(1, "Original name is required"),
|
originalname: z.string().min(1, "Original name is required"),
|
||||||
encoding: z.string().min(1, "Encoding is required"),
|
encoding: z.string().min(1, "Encoding is required"),
|
||||||
mimetype: z.enum(["image/jpeg", "image/png", "image/jpg"], {
|
mimetype: z.enum(ImageMimeTypes, {
|
||||||
message: "File must be a valid image (jpeg, jpg, or png)",
|
message: "File must be a valid image (jpeg, jpg, or png)",
|
||||||
}),
|
}),
|
||||||
size: z.number().max(3145728, "File size must be less than 3MB"),
|
size: z.number().max(3145728, "File size must be less than 3MB"),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { UserRoles } from "@/domain/users/user.type.js";
|
import { UserRoles } from "@/domain/users/user.type.js";
|
||||||
import { nameValidation, lowercaseEmailValidation, passwordValidation } from "./shared.js";
|
import { nameValidation, lowercaseEmailValidation, passwordValidation, booleanCoercion } from "@/api/validation/shared.js";
|
||||||
|
|
||||||
//****************************************
|
//****************************************
|
||||||
// User Validations
|
// User Validations
|
||||||
@@ -46,6 +46,7 @@ export const editUserBodyValidation = z
|
|||||||
profilePicture: z.string().optional(),
|
profilePicture: z.string().optional(),
|
||||||
password: passwordValidation.optional(),
|
password: passwordValidation.optional(),
|
||||||
newPassword: passwordValidation.optional(),
|
newPassword: passwordValidation.optional(),
|
||||||
|
deleteProfileImage: booleanCoercion.optional(),
|
||||||
})
|
})
|
||||||
// Changing a password requires both the current password and the new one.
|
// Changing a password requires both the current password and the new one.
|
||||||
// The service only runs the change-password flow when both are present, so
|
// The service only runs the change-password flow when both are present, so
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { GlobalPingService } from "@/service/globalPingService.js";
|
|||||||
import { ILogger } from "@/utils/logger.js";
|
import { ILogger } from "@/utils/logger.js";
|
||||||
|
|
||||||
// Notification providers
|
// Notification providers
|
||||||
|
import type { NotificationProviderRegistry } from "@/domain/notifications/notification.service.js";
|
||||||
import { WebhookProvider } from "@/domain/notifications/providers/webhook.js";
|
import { WebhookProvider } from "@/domain/notifications/providers/webhook.js";
|
||||||
import { SlackProvider } from "@/domain/notifications/providers/slack.js";
|
import { SlackProvider } from "@/domain/notifications/providers/slack.js";
|
||||||
import { EmailProvider } from "@/domain/notifications/providers/email.js";
|
import { EmailProvider } from "@/domain/notifications/providers/email.js";
|
||||||
@@ -125,7 +126,7 @@ export const buildShared = async ({
|
|||||||
const checksRepository = new MongoChecksRepository(logger);
|
const checksRepository = new MongoChecksRepository(logger);
|
||||||
const incidentsRepository = new MongoIncidentsRepository();
|
const incidentsRepository = new MongoIncidentsRepository();
|
||||||
const usersRepository = new MongoUsersRepository();
|
const usersRepository = new MongoUsersRepository();
|
||||||
const geoChecksRepository = new MongoGeoChecksRepository(logger);
|
const geoChecksRepository = new MongoGeoChecksRepository();
|
||||||
const notificationsRepository = new MongoNotificationsRepository();
|
const notificationsRepository = new MongoNotificationsRepository();
|
||||||
const jobsRepository = new MongoJobsRepository(workerId);
|
const jobsRepository = new MongoJobsRepository(workerId);
|
||||||
const queueWorkersRepository = new MongoQueueWorkersRepository();
|
const queueWorkersRepository = new MongoQueueWorkersRepository();
|
||||||
@@ -167,24 +168,28 @@ export const buildShared = async ({
|
|||||||
const twilioProvider = new TwilioProvider(logger);
|
const twilioProvider = new TwilioProvider(logger);
|
||||||
const ntfyProvider = new NtfyProvider(logger);
|
const ntfyProvider = new NtfyProvider(logger);
|
||||||
|
|
||||||
const notificationsService = new NotificationsService(
|
const notificationProviders: NotificationProviderRegistry = {
|
||||||
|
webhook: webhookProvider,
|
||||||
|
email: emailProvider,
|
||||||
|
slack: slackProvider,
|
||||||
|
discord: discordProvider,
|
||||||
|
pager_duty: pagerDutyProvider,
|
||||||
|
matrix: matrixProvider,
|
||||||
|
teams: teamsProvider,
|
||||||
|
telegram: telegramProvider,
|
||||||
|
pushover: pushoverProvider,
|
||||||
|
twilio: twilioProvider,
|
||||||
|
ntfy: ntfyProvider,
|
||||||
|
};
|
||||||
|
|
||||||
|
const notificationsService = new NotificationsService({
|
||||||
notificationsRepository,
|
notificationsRepository,
|
||||||
monitorsRepository,
|
monitorsRepository,
|
||||||
webhookProvider,
|
providers: notificationProviders,
|
||||||
emailProvider,
|
|
||||||
slackProvider,
|
|
||||||
discordProvider,
|
|
||||||
pagerDutyProvider,
|
|
||||||
matrixProvider,
|
|
||||||
teamsProvider,
|
|
||||||
telegramProvider,
|
|
||||||
pushoverProvider,
|
|
||||||
twilioProvider,
|
|
||||||
ntfyProvider,
|
|
||||||
settingsService,
|
settingsService,
|
||||||
logger,
|
logger,
|
||||||
notificationMessageBuilder
|
notificationMessageBuilder,
|
||||||
);
|
});
|
||||||
|
|
||||||
const sharedServices: SharedServices = {
|
const sharedServices: SharedServices = {
|
||||||
logger,
|
logger,
|
||||||
|
|||||||
@@ -131,9 +131,9 @@ export const buildWorker = async (shared: SharedServices, envSettings: EnvConfig
|
|||||||
geoChecksRepository
|
geoChecksRepository
|
||||||
);
|
);
|
||||||
|
|
||||||
const worker = await DBQueueWorker.create(
|
const worker = await DBQueueWorker.create({
|
||||||
logger,
|
logger,
|
||||||
() => mongoose.connection.readyState === 1,
|
isDbConnected: () => mongoose.connection.readyState === 1,
|
||||||
jobsRepository,
|
jobsRepository,
|
||||||
monitorsRepository,
|
monitorsRepository,
|
||||||
checksRepository,
|
checksRepository,
|
||||||
@@ -142,13 +142,13 @@ export const buildWorker = async (shared: SharedServices, envSettings: EnvConfig
|
|||||||
checkProducer,
|
checkProducer,
|
||||||
checkEvaluator,
|
checkEvaluator,
|
||||||
geoCheckPipeline,
|
geoCheckPipeline,
|
||||||
reactorDispatcher,
|
dispatcher: reactorDispatcher,
|
||||||
workerHelper,
|
helper: workerHelper,
|
||||||
queueWorkersRepository,
|
queueWorkersRepository,
|
||||||
envSettings.queueMode,
|
queueMode: envSettings.queueMode,
|
||||||
envSettings.queuePrimaryProcesses,
|
queuePrimaryProcesses: envSettings.queuePrimaryProcesses,
|
||||||
workerId
|
workerId,
|
||||||
);
|
});
|
||||||
|
|
||||||
return { worker, networkService, bufferService, statusService };
|
return { worker, networkService, bufferService, statusService };
|
||||||
};
|
};
|
||||||
@@ -21,10 +21,6 @@ class MongoDB implements IDb<Mongoose> {
|
|||||||
this.db = null;
|
this.db = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return MongoDB.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
connect = async () => {
|
connect = async () => {
|
||||||
try {
|
try {
|
||||||
const connectionString = this.envSettings.dbConnectionString || "mongodb://localhost:27017/uptime_db";
|
const connectionString = this.envSettings.dbConnectionString || "mongodb://localhost:27017/uptime_db";
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ export type EnvConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface ISettingsService {
|
export interface ISettingsService {
|
||||||
readonly serviceName: string;
|
|
||||||
loadSettings(): EnvConfig;
|
|
||||||
getSettings(): EnvConfig;
|
getSettings(): EnvConfig;
|
||||||
areStatusPageThemesEnabled(): boolean;
|
areStatusPageThemesEnabled(): boolean;
|
||||||
getDBSettings(): Promise<Settings>;
|
getDBSettings(): Promise<Settings>;
|
||||||
@@ -57,14 +55,6 @@ export class SettingsService implements ISettingsService {
|
|||||||
this.settingsRepository = settingsRepository;
|
this.settingsRepository = settingsRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return SettingsService.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
loadSettings() {
|
|
||||||
return this.settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
getSettings() {
|
getSettings() {
|
||||||
return this.settings;
|
return this.settings;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,22 @@
|
|||||||
import { CheckModel } from "@/domain/checks/check.model.js";
|
import { CheckModel } from "@/domain/checks/check.model.js";
|
||||||
import mongoose from "mongoose";
|
import mongoose from "mongoose";
|
||||||
|
import type { HardwareDiskStats, HardwareNetStats } from "@/domain/checks/check.type.js";
|
||||||
|
|
||||||
type DateRange = { start: Date; end: Date };
|
type DateRange = { start: Date; end: Date };
|
||||||
type HardwareUpChecks = { totalChecks: number };
|
type HardwareUpChecks = { totalChecks: number };
|
||||||
|
|
||||||
|
// $avg yields null when no values are present, so metrics are nullable until the repository applies fallbacks
|
||||||
|
type NullableValues<T> = { [K in keyof T]: T[K] | null };
|
||||||
|
|
||||||
|
export type HardwareStatsBucket = {
|
||||||
|
_id: string;
|
||||||
|
avgCpuUsage: number | null;
|
||||||
|
avgMemoryUsage: number | null;
|
||||||
|
avgTemperature: number[];
|
||||||
|
disks: NullableValues<HardwareDiskStats>[];
|
||||||
|
net: NullableValues<HardwareNetStats>[];
|
||||||
|
};
|
||||||
|
|
||||||
const NET_RATE_FIELDS = [
|
const NET_RATE_FIELDS = [
|
||||||
["bytesSentPerSecond", "bytes_sent"],
|
["bytesSentPerSecond", "bytes_sent"],
|
||||||
["deltaBytesRecv", "bytes_recv"],
|
["deltaBytesRecv", "bytes_recv"],
|
||||||
@@ -70,13 +83,13 @@ export const getHardwareUpChecks = async (monitorId: string, dates: DateRange):
|
|||||||
return { totalChecks: count };
|
return { totalChecks: count };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getHardwareStats = async (monitorId: string, dates: DateRange, dateString: string) => {
|
export const getHardwareStats = async (monitorId: string, dates: DateRange, dateString: string): Promise<HardwareStatsBucket[]> => {
|
||||||
const netProjection = {
|
const netProjection = {
|
||||||
name: { $arrayElemAt: ["$sampleDoc.net.name", "$$nIdx"] },
|
name: { $arrayElemAt: ["$sampleDoc.net.name", "$$nIdx"] },
|
||||||
...Object.fromEntries(NET_RATE_FIELDS.map(([outputKey, sourceField]) => [outputKey, netRatePerSecond(sourceField)])),
|
...Object.fromEntries(NET_RATE_FIELDS.map(([outputKey, sourceField]) => [outputKey, netRatePerSecond(sourceField)])),
|
||||||
};
|
};
|
||||||
|
|
||||||
return await CheckModel.aggregate([
|
return await CheckModel.aggregate<HardwareStatsBucket>([
|
||||||
{
|
{
|
||||||
$match: {
|
$match: {
|
||||||
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Schema, model, Types } from "mongoose";
|
import { Schema, model, Types } from "mongoose";
|
||||||
import { MonitorTypes, type MonitorType } from "@/domain/monitors/monitor.types.js";
|
import { MonitorTypes, type MonitorType } from "@/domain/monitors/monitor.type.js";
|
||||||
import type {
|
import type {
|
||||||
Check,
|
Check,
|
||||||
CheckAudits,
|
CheckAudits,
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ import type {
|
|||||||
PageSpeedChecksResult,
|
PageSpeedChecksResult,
|
||||||
UptimeChecksResult,
|
UptimeChecksResult,
|
||||||
} from "@/domain/checks/check.type.js";
|
} from "@/domain/checks/check.type.js";
|
||||||
import type { MonitorType } from "@/domain/monitors/monitor.types.js";
|
import type { MonitorType } from "@/domain/monitors/monitor.type.js";
|
||||||
import type { LatestChecksMap } from "@/domain/checks/check.repository.mongo.js";
|
import type { LatestChecksMap } from "@/domain/checks/check.repository.mongo.js";
|
||||||
|
import { CheckFilter, DateRange } from "@/types/query.js";
|
||||||
|
|
||||||
export interface IChecksRepository {
|
export interface IChecksRepository {
|
||||||
// create
|
// create
|
||||||
@@ -19,29 +20,27 @@ export interface IChecksRepository {
|
|||||||
findByMonitorId(
|
findByMonitorId(
|
||||||
monitorId: string,
|
monitorId: string,
|
||||||
sortOrder: string,
|
sortOrder: string,
|
||||||
dateRange: string,
|
dateRange: DateRange,
|
||||||
filter: string | undefined,
|
|
||||||
page: number,
|
page: number,
|
||||||
rowsPerPage: number,
|
rowsPerPage: number,
|
||||||
status: boolean | undefined
|
status: boolean | undefined,
|
||||||
|
filter?: CheckFilter
|
||||||
): Promise<ChecksQueryResult>;
|
): Promise<ChecksQueryResult>;
|
||||||
findByTeamId(
|
findByTeamId(
|
||||||
sortOrder: string,
|
sortOrder: string,
|
||||||
dateRange: string,
|
dateRange: DateRange,
|
||||||
filter: string | undefined,
|
|
||||||
page: number,
|
page: number,
|
||||||
rowsPerPage: number,
|
rowsPerPage: number,
|
||||||
teamId: string
|
teamId: string,
|
||||||
|
filter?: CheckFilter
|
||||||
): Promise<ChecksQueryResult>;
|
): Promise<ChecksQueryResult>;
|
||||||
findLatestByMonitorIds(monitorIds: string[], options?: { limitPerMonitor?: number }): Promise<LatestChecksMap>;
|
findLatestByMonitorIds(monitorIds: string[], options?: { limitPerMonitor?: number }): Promise<LatestChecksMap>;
|
||||||
findByDateRangeAndMonitorId(
|
findByDateRangeAndMonitorId(
|
||||||
monitorId: string,
|
monitorId: string,
|
||||||
startDate: Date,
|
dateRange: DateRange,
|
||||||
endDate: Date,
|
|
||||||
dateString: string,
|
|
||||||
options?: { type?: MonitorType }
|
options?: { type?: MonitorType }
|
||||||
): Promise<UptimeChecksResult | HardwareChecksResult | PageSpeedChecksResult>;
|
): Promise<UptimeChecksResult | HardwareChecksResult | PageSpeedChecksResult>;
|
||||||
findSummaryByTeamId(teamId: string, dateRange: string): Promise<ChecksSummary>;
|
findSummaryByTeamId(teamId: string, dateRange: DateRange): Promise<ChecksSummary>;
|
||||||
findUnevaluatedByMonitorId(monitorId: string, since: number): Promise<Check[]>;
|
findUnevaluatedByMonitorId(monitorId: string, since: number): Promise<Check[]>;
|
||||||
// update
|
// update
|
||||||
//delete
|
//delete
|
||||||
|
|||||||
@@ -11,15 +11,19 @@ import type {
|
|||||||
CheckMetadata,
|
CheckMetadata,
|
||||||
CheckNetworkInterfaceInfo,
|
CheckNetworkInterfaceInfo,
|
||||||
GotTimings,
|
GotTimings,
|
||||||
|
HardwareCheckStats,
|
||||||
} from "@/domain/checks/check.type.js";
|
} from "@/domain/checks/check.type.js";
|
||||||
import type { MonitorType } from "@/domain/monitors/monitor.types.js";
|
import type { MonitorType } from "@/domain/monitors/monitor.type.js";
|
||||||
import { CheckModel, type CheckDocument } from "@/domain/checks/check.model.js";
|
import { CheckModel, type CheckDocument } from "@/domain/checks/check.model.js";
|
||||||
import mongoose from "mongoose";
|
import mongoose from "mongoose";
|
||||||
import { getDateForRange } from "@/utils/dataUtils.js";
|
import { getDateFormat, getDateForRange } from "@/utils/dataUtils.js";
|
||||||
import { ILogger } from "@/utils/logger.js";
|
import { ILogger } from "@/utils/logger.js";
|
||||||
import { toStringId, toDateString } from "@/utils/mongoMappers.js";
|
import { toStringId, toDateString } from "@/utils/mongoMappers.js";
|
||||||
|
|
||||||
import { getHardwareUpChecks, getHardwareStats, getHardwareTotalChecks } from "@/domain/checks/check.hardware.aggregations.js";
|
import { getHardwareUpChecks, getHardwareStats, getHardwareTotalChecks } from "@/domain/checks/check.hardware.aggregations.js";
|
||||||
|
import { CheckFilter, DateRange } from "@/types/query.js";
|
||||||
|
import { AppError } from "@/utils/AppError.js";
|
||||||
|
import { NETWORK_ERROR } from "@/types/network.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "StatusService";
|
const SERVICE_NAME = "StatusService";
|
||||||
|
|
||||||
@@ -180,7 +184,12 @@ class MongoChecksRepository implements IChecksRepository {
|
|||||||
// Map id to _id for MongoDB storage
|
// Map id to _id for MongoDB storage
|
||||||
const { id, metadata, ...rest } = check;
|
const { id, metadata, ...rest } = check;
|
||||||
if (!metadata || !metadata.monitorId || !metadata.teamId) {
|
if (!metadata || !metadata.monitorId || !metadata.teamId) {
|
||||||
throw new Error(`Check must have valid metadata with monitorId and teamId. Got: ${JSON.stringify({ id, metadata })}`);
|
throw new AppError({
|
||||||
|
message: `Check must have valid metadata with monitorId and teamId. Got: ${JSON.stringify({ id, metadata })}`,
|
||||||
|
status: 500,
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "toDocument",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
_id: id ? new mongoose.Types.ObjectId(id) : new mongoose.Types.ObjectId(),
|
_id: id ? new mongoose.Types.ObjectId(id) : new mongoose.Types.ObjectId(),
|
||||||
@@ -204,50 +213,41 @@ class MongoChecksRepository implements IChecksRepository {
|
|||||||
return this.mapDocuments(inserted as unknown as CheckDocument[]);
|
return this.mapDocuments(inserted as unknown as CheckDocument[]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private filterToMatch = (filter: CheckFilter | undefined): Record<string, unknown> => {
|
||||||
|
switch (filter) {
|
||||||
|
case "up":
|
||||||
|
return { status: true };
|
||||||
|
case "down":
|
||||||
|
return { status: false };
|
||||||
|
case "resolve":
|
||||||
|
return { status: false, statusCode: NETWORK_ERROR };
|
||||||
|
default:
|
||||||
|
this.logger.warn({
|
||||||
|
message: "invalid filter",
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "filterToMatch",
|
||||||
|
});
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
findByMonitorId = async (
|
findByMonitorId = async (
|
||||||
monitorId: string,
|
monitorId: string,
|
||||||
sortOrder: string,
|
sortOrder: string,
|
||||||
dateRange: string,
|
dateRange: DateRange,
|
||||||
filter: string | undefined,
|
|
||||||
page: number,
|
page: number,
|
||||||
rowsPerPage: number,
|
rowsPerPage: number,
|
||||||
status: boolean | undefined
|
status: boolean | undefined,
|
||||||
|
filter?: CheckFilter
|
||||||
) => {
|
) => {
|
||||||
// Match
|
// Match
|
||||||
const matchStage: Record<string, unknown> = {
|
const matchStage: Record<string, unknown> = {
|
||||||
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
||||||
...(typeof status !== "undefined" && { status }),
|
...(typeof status !== "undefined" && { status }),
|
||||||
...(getDateForRange(dateRange) && {
|
createdAt: { $gte: getDateForRange(dateRange) },
|
||||||
createdAt: {
|
...this.filterToMatch(filter),
|
||||||
$gte: getDateForRange(dateRange),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (filter !== undefined) {
|
|
||||||
switch (filter) {
|
|
||||||
case "all":
|
|
||||||
break;
|
|
||||||
case "up":
|
|
||||||
matchStage.status = true;
|
|
||||||
break;
|
|
||||||
case "down":
|
|
||||||
matchStage.status = false;
|
|
||||||
break;
|
|
||||||
case "resolve":
|
|
||||||
matchStage.status = false;
|
|
||||||
matchStage.statusCode = 5000;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this.logger.warn({
|
|
||||||
message: "invalid filter",
|
|
||||||
service: SERVICE_NAME,
|
|
||||||
method: "getChecks",
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Sort
|
//Sort
|
||||||
const convertedSortOrder = sortOrder === "asc" ? 1 : -1;
|
const convertedSortOrder = sortOrder === "asc" ? 1 : -1;
|
||||||
|
|
||||||
@@ -265,39 +265,12 @@ class MongoChecksRepository implements IChecksRepository {
|
|||||||
return { checksCount, checks: this.mapDocuments(checks) };
|
return { checksCount, checks: this.mapDocuments(checks) };
|
||||||
};
|
};
|
||||||
|
|
||||||
findByTeamId = async (sortOrder: string, dateRange: string, filter: string, page: number, rowsPerPage: number, teamId: string) => {
|
findByTeamId = async (sortOrder: string, dateRange: DateRange, page: number, rowsPerPage: number, teamId: string, filter?: CheckFilter) => {
|
||||||
const matchStage: Record<string, unknown> = {
|
const matchStage: Record<string, unknown> = {
|
||||||
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
|
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
|
||||||
...(getDateForRange(dateRange) && {
|
createdAt: { $gte: getDateForRange(dateRange) },
|
||||||
createdAt: {
|
...this.filterToMatch(filter),
|
||||||
$gte: getDateForRange(dateRange),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
// Add filter to match stage
|
|
||||||
if (filter !== undefined) {
|
|
||||||
switch (filter) {
|
|
||||||
case "all":
|
|
||||||
break;
|
|
||||||
case "up":
|
|
||||||
matchStage.status = true;
|
|
||||||
break;
|
|
||||||
case "down":
|
|
||||||
matchStage.status = false;
|
|
||||||
break;
|
|
||||||
case "resolve":
|
|
||||||
matchStage.status = false;
|
|
||||||
matchStage.statusCode = 5000;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this.logger.warn({
|
|
||||||
message: "invalid filter",
|
|
||||||
service: SERVICE_NAME,
|
|
||||||
method: "getChecksByTeam",
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsedSortOrder = sortOrder === "asc" ? 1 : -1;
|
const parsedSortOrder = sortOrder === "asc" ? 1 : -1;
|
||||||
|
|
||||||
@@ -341,25 +314,25 @@ class MongoChecksRepository implements IChecksRepository {
|
|||||||
return mapped;
|
return mapped;
|
||||||
};
|
};
|
||||||
|
|
||||||
findByDateRangeAndMonitorId = async (monitorId: string, startDate: Date, endDate: Date, dateString: string, options?: { type?: MonitorType }) => {
|
findByDateRangeAndMonitorId = async (monitorId: string, dateRange: DateRange, options?: { type?: MonitorType }) => {
|
||||||
const monitorObjectId = new mongoose.Types.ObjectId(monitorId);
|
const monitorObjectId = new mongoose.Types.ObjectId(monitorId);
|
||||||
|
const start = getDateForRange(dateRange);
|
||||||
|
const dateString = getDateFormat(dateRange);
|
||||||
|
|
||||||
|
const end = new Date();
|
||||||
if (options?.type === "hardware") {
|
if (options?.type === "hardware") {
|
||||||
return this.findHardwareDateRangeChecks(monitorObjectId, startDate, endDate, dateString);
|
return this.findHardwareDateRangeChecks(monitorObjectId, start, end, dateString);
|
||||||
}
|
}
|
||||||
if (options?.type === "pagespeed") {
|
if (options?.type === "pagespeed") {
|
||||||
return this.findPageSpeedDateRangeChecks(monitorObjectId, startDate, endDate, dateString);
|
return this.findPageSpeedDateRangeChecks(monitorObjectId, start, end, dateString);
|
||||||
}
|
}
|
||||||
return this.findUptimeDateRangeChecks(options?.type ?? "http", monitorObjectId, startDate, endDate, dateString);
|
return this.findUptimeDateRangeChecks(options?.type ?? "http", monitorObjectId, start, end, dateString);
|
||||||
};
|
};
|
||||||
|
|
||||||
findSummaryByTeamId = async (teamId: string, dateRange: string) => {
|
findSummaryByTeamId = async (teamId: string, dateRange: DateRange) => {
|
||||||
const baseMatch = {
|
const baseMatch = {
|
||||||
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
|
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
|
||||||
...(getDateForRange(dateRange) && {
|
createdAt: { $gte: getDateForRange(dateRange) },
|
||||||
createdAt: {
|
|
||||||
$gte: getDateForRange(dateRange),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const [totalResult, downResult] = await Promise.all([
|
const [totalResult, downResult] = await Promise.all([
|
||||||
@@ -540,33 +513,35 @@ class MongoChecksRepository implements IChecksRepository {
|
|||||||
totalChecks: upChecksDoc?.totalChecks ?? 0,
|
totalChecks: upChecksDoc?.totalChecks ?? 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const checks = (hardwareMetrics ?? []).map((metric) => ({
|
const checks = (hardwareMetrics ?? []).map(
|
||||||
bucketDate: metric._id,
|
(metric): HardwareCheckStats => ({
|
||||||
avgCpuUsage: metric.avgCpuUsage ?? 0,
|
bucketDate: metric._id,
|
||||||
avgMemoryUsage: metric.avgMemoryUsage ?? 0,
|
avgCpuUsage: metric.avgCpuUsage ?? 0,
|
||||||
avgTemperature: metric.avgTemperature ?? [],
|
avgMemoryUsage: metric.avgMemoryUsage ?? 0,
|
||||||
disks: (metric.disks ?? []).map((disk: { [key: string]: number | string | undefined }) => ({
|
avgTemperature: metric.avgTemperature ?? [],
|
||||||
name: disk?.name ?? "",
|
disks: (metric.disks ?? []).map((disk) => ({
|
||||||
readSpeed: disk?.readSpeed ?? 0,
|
name: disk?.name ?? "",
|
||||||
writeSpeed: disk?.writeSpeed ?? 0,
|
readSpeed: disk?.readSpeed ?? 0,
|
||||||
totalBytes: disk?.totalBytes ?? 0,
|
writeSpeed: disk?.writeSpeed ?? 0,
|
||||||
freeBytes: disk?.freeBytes ?? 0,
|
totalBytes: disk?.totalBytes ?? 0,
|
||||||
usagePercent: disk?.usagePercent ?? 0,
|
freeBytes: disk?.freeBytes ?? 0,
|
||||||
})),
|
usagePercent: disk?.usagePercent ?? 0,
|
||||||
net: (metric.net ?? []).map((iface: { [key: string]: number | string | undefined }) => ({
|
})),
|
||||||
name: iface?.name ?? "",
|
net: (metric.net ?? []).map((iface) => ({
|
||||||
bytesSentPerSecond: iface?.bytesSentPerSecond ?? 0,
|
name: iface?.name ?? "",
|
||||||
deltaBytesRecv: iface?.deltaBytesRecv ?? 0,
|
bytesSentPerSecond: iface?.bytesSentPerSecond ?? 0,
|
||||||
deltaPacketsSent: iface?.deltaPacketsSent ?? 0,
|
deltaBytesRecv: iface?.deltaBytesRecv ?? 0,
|
||||||
deltaPacketsRecv: iface?.deltaPacketsRecv ?? 0,
|
deltaPacketsSent: iface?.deltaPacketsSent ?? 0,
|
||||||
deltaErrIn: iface?.deltaErrIn ?? 0,
|
deltaPacketsRecv: iface?.deltaPacketsRecv ?? 0,
|
||||||
deltaErrOut: iface?.deltaErrOut ?? 0,
|
deltaErrIn: iface?.deltaErrIn ?? 0,
|
||||||
deltaDropIn: iface?.deltaDropIn ?? 0,
|
deltaErrOut: iface?.deltaErrOut ?? 0,
|
||||||
deltaDropOut: iface?.deltaDropOut ?? 0,
|
deltaDropIn: iface?.deltaDropIn ?? 0,
|
||||||
deltaFifoIn: iface?.deltaFifoIn ?? 0,
|
deltaDropOut: iface?.deltaDropOut ?? 0,
|
||||||
deltaFifoOut: iface?.deltaFifoOut ?? 0,
|
deltaFifoIn: iface?.deltaFifoIn ?? 0,
|
||||||
})),
|
deltaFifoOut: iface?.deltaFifoOut ?? 0,
|
||||||
}));
|
})),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
monitorType: "hardware" as const,
|
monitorType: "hardware" as const,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type { MonitorPayloadMap, MonitorStatusResponse } from "@/types/network.j
|
|||||||
import type { HardwareStatusPayload, PageSpeedStatusPayload } from "@/types/network.js";
|
import type { HardwareStatusPayload, PageSpeedStatusPayload } from "@/types/network.js";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import { ILogger } from "@/utils/logger.js";
|
import { ILogger } from "@/utils/logger.js";
|
||||||
|
import { CheckFilter, DateRange } from "@/types/query.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "checkService";
|
const SERVICE_NAME = "checkService";
|
||||||
|
|
||||||
@@ -18,21 +19,21 @@ export interface ICheckService {
|
|||||||
monitorId: string;
|
monitorId: string;
|
||||||
teamId: string;
|
teamId: string;
|
||||||
sortOrder: string;
|
sortOrder: string;
|
||||||
dateRange: string;
|
dateRange: DateRange;
|
||||||
page: number;
|
page: number;
|
||||||
rowsPerPage: number;
|
rowsPerPage: number;
|
||||||
filter?: string;
|
filter?: CheckFilter;
|
||||||
status?: boolean;
|
status?: boolean;
|
||||||
}): Promise<ChecksQueryResult>;
|
}): Promise<ChecksQueryResult>;
|
||||||
getChecksByTeam(params: {
|
getChecksByTeam(params: {
|
||||||
teamId: string;
|
teamId: string;
|
||||||
sortOrder: string;
|
sortOrder: string;
|
||||||
dateRange: string;
|
dateRange: DateRange;
|
||||||
page: number;
|
page: number;
|
||||||
rowsPerPage: number;
|
rowsPerPage: number;
|
||||||
filter?: string;
|
filter?: CheckFilter;
|
||||||
}): Promise<ChecksQueryResult>;
|
}): Promise<ChecksQueryResult>;
|
||||||
getChecksSummaryByTeamId(params: { teamId: string; dateRange: string }): Promise<ChecksSummary>;
|
getChecksSummaryByTeamId(params: { teamId: string; dateRange: DateRange }): Promise<ChecksSummary>;
|
||||||
deleteChecks(params: { monitorId: string; teamId: string }): Promise<number>;
|
deleteChecks(params: { monitorId: string; teamId: string }): Promise<number>;
|
||||||
deleteChecksByTeamId(params: { teamId: string }): Promise<number>;
|
deleteChecksByTeamId(params: { teamId: string }): Promise<number>;
|
||||||
deleteOlderThan(date: Date): Promise<number>;
|
deleteOlderThan(date: Date): Promise<number>;
|
||||||
@@ -50,10 +51,6 @@ export class CheckService implements ICheckService {
|
|||||||
this.checksRepository = checksRepository;
|
this.checksRepository = checksRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return CheckService.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
createChecks = async (checks: Check[]) => {
|
createChecks = async (checks: Check[]) => {
|
||||||
return this.checksRepository.createChecks(checks);
|
return this.checksRepository.createChecks(checks);
|
||||||
};
|
};
|
||||||
@@ -168,11 +165,11 @@ export class CheckService implements ICheckService {
|
|||||||
monitorId: string;
|
monitorId: string;
|
||||||
teamId: string;
|
teamId: string;
|
||||||
sortOrder: string;
|
sortOrder: string;
|
||||||
dateRange: string;
|
dateRange: DateRange;
|
||||||
page: number;
|
page: number;
|
||||||
rowsPerPage: number;
|
rowsPerPage: number;
|
||||||
status?: boolean;
|
status?: boolean;
|
||||||
filter?: string;
|
filter: CheckFilter;
|
||||||
}) => {
|
}) => {
|
||||||
if (!monitorId) {
|
if (!monitorId) {
|
||||||
throw new AppError({ message: "No monitor ID in request", service: SERVICE_NAME, method: "getChecksByMonitor", status: 400 });
|
throw new AppError({ message: "No monitor ID in request", service: SERVICE_NAME, method: "getChecksByMonitor", status: 400 });
|
||||||
@@ -187,7 +184,7 @@ export class CheckService implements ICheckService {
|
|||||||
const parsedPage = page ?? 0;
|
const parsedPage = page ?? 0;
|
||||||
const parsedRowsPerPage = rowsPerPage ?? 5;
|
const parsedRowsPerPage = rowsPerPage ?? 5;
|
||||||
|
|
||||||
const result = await this.checksRepository.findByMonitorId(monitorId, sortOrder, dateRange, filter, parsedPage, parsedRowsPerPage, status);
|
const result = await this.checksRepository.findByMonitorId(monitorId, sortOrder, dateRange, parsedPage, parsedRowsPerPage, status, filter);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
@@ -196,25 +193,25 @@ export class CheckService implements ICheckService {
|
|||||||
teamId,
|
teamId,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
dateRange,
|
dateRange,
|
||||||
filter,
|
|
||||||
page,
|
page,
|
||||||
rowsPerPage,
|
rowsPerPage,
|
||||||
|
filter,
|
||||||
}: {
|
}: {
|
||||||
teamId: string;
|
teamId: string;
|
||||||
sortOrder: string;
|
sortOrder: string;
|
||||||
dateRange: string;
|
dateRange: DateRange;
|
||||||
page: number;
|
page: number;
|
||||||
rowsPerPage: number;
|
rowsPerPage: number;
|
||||||
filter?: string;
|
filter?: CheckFilter;
|
||||||
}) => {
|
}) => {
|
||||||
const parsedPage = page ?? 0;
|
const parsedPage = page ?? 0;
|
||||||
const parsedRowsPerPage = rowsPerPage ?? 5;
|
const parsedRowsPerPage = rowsPerPage ?? 5;
|
||||||
|
|
||||||
const checkData = await this.checksRepository.findByTeamId(sortOrder, dateRange, filter, parsedPage, parsedRowsPerPage, teamId);
|
const checkData = await this.checksRepository.findByTeamId(sortOrder, dateRange, parsedPage, parsedRowsPerPage, teamId, filter);
|
||||||
return checkData;
|
return checkData;
|
||||||
};
|
};
|
||||||
|
|
||||||
getChecksSummaryByTeamId = async ({ teamId, dateRange }: { teamId: string; dateRange: string }) => {
|
getChecksSummaryByTeamId = async ({ teamId, dateRange }: { teamId: string; dateRange: DateRange }) => {
|
||||||
const summary = await this.checksRepository.findSummaryByTeamId(teamId, dateRange);
|
const summary = await this.checksRepository.findSummaryByTeamId(teamId, dateRange);
|
||||||
return summary;
|
return summary;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { MonitorType } from "@/domain/monitors/monitor.types.js";
|
import type { MonitorType } from "@/domain/monitors/monitor.type.js";
|
||||||
import type { Response } from "got";
|
import type { Response } from "got";
|
||||||
|
|
||||||
export const CHECK_TTL_SENTINEL = 366;
|
export const CHECK_TTL_SENTINEL = 366;
|
||||||
@@ -126,43 +126,6 @@ export interface PageSpeedChecksResult {
|
|||||||
groupedChecks: PageSpeedGroupedCheck[];
|
groupedChecks: PageSpeedGroupedCheck[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HardwareChecksResult {
|
|
||||||
monitorType: "hardware";
|
|
||||||
aggregateData: {
|
|
||||||
totalChecks: number;
|
|
||||||
};
|
|
||||||
upChecks: {
|
|
||||||
totalChecks: number;
|
|
||||||
};
|
|
||||||
checks: Array<{
|
|
||||||
bucketDate: string;
|
|
||||||
avgCpuUsage: number;
|
|
||||||
avgMemoryUsage: number;
|
|
||||||
avgTemperature: number[];
|
|
||||||
disks: Array<{
|
|
||||||
name: string;
|
|
||||||
readSpeed: number;
|
|
||||||
writeSpeed: number;
|
|
||||||
totalBytes: number;
|
|
||||||
freeBytes: number;
|
|
||||||
usagePercent: number;
|
|
||||||
}>;
|
|
||||||
net: Array<{
|
|
||||||
name: string;
|
|
||||||
bytesSentPerSecond: number;
|
|
||||||
deltaBytesRecv: number;
|
|
||||||
deltaPacketsSent: number;
|
|
||||||
deltaPacketsRecv: number;
|
|
||||||
deltaErrIn: number;
|
|
||||||
deltaErrOut: number;
|
|
||||||
deltaDropIn: number;
|
|
||||||
deltaDropOut: number;
|
|
||||||
deltaFifoIn: number;
|
|
||||||
deltaFifoOut: number;
|
|
||||||
}>;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GroupedCheck {
|
export interface GroupedCheck {
|
||||||
bucketDate: string;
|
bucketDate: string;
|
||||||
avgResponseTime: number;
|
avgResponseTime: number;
|
||||||
@@ -205,3 +168,49 @@ export type NormalizedUptimeCheck<T extends GroupedCheck = GroupedCheck> = T & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type CheckSnapshot = Omit<Check, "metadata" | "updatedAt">;
|
export type CheckSnapshot = Omit<Check, "metadata" | "updatedAt">;
|
||||||
|
|
||||||
|
export interface HardwareDiskStats {
|
||||||
|
name: string;
|
||||||
|
readSpeed: number;
|
||||||
|
writeSpeed: number;
|
||||||
|
totalBytes: number;
|
||||||
|
freeBytes: number;
|
||||||
|
usagePercent: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HardwareNetStats {
|
||||||
|
name: string;
|
||||||
|
bytesSentPerSecond: number;
|
||||||
|
deltaBytesRecv: number;
|
||||||
|
deltaPacketsSent: number;
|
||||||
|
deltaPacketsRecv: number;
|
||||||
|
deltaErrIn: number;
|
||||||
|
deltaErrOut: number;
|
||||||
|
deltaDropIn: number;
|
||||||
|
deltaDropOut: number;
|
||||||
|
deltaFifoIn: number;
|
||||||
|
deltaFifoOut: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HardwareCheckStats {
|
||||||
|
bucketDate: string;
|
||||||
|
avgCpuUsage: number;
|
||||||
|
avgMemoryUsage: number;
|
||||||
|
avgTemperature: number[];
|
||||||
|
disks: HardwareDiskStats[];
|
||||||
|
net: HardwareNetStats[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HardwareStats {
|
||||||
|
aggregateData: {
|
||||||
|
totalChecks: number;
|
||||||
|
};
|
||||||
|
upChecks: {
|
||||||
|
totalChecks: number;
|
||||||
|
};
|
||||||
|
checks: HardwareCheckStats[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HardwareChecksResult extends HardwareStats {
|
||||||
|
monitorType: "hardware";
|
||||||
|
}
|
||||||
@@ -79,10 +79,6 @@ export class DiagnosticService implements IDiagnosticService {
|
|||||||
obs.observe({ entryTypes: ["measure"] });
|
obs.observe({ entryTypes: ["measure"] });
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return DiagnosticService.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getCPUUsagePercentage = async (): Promise<number> => {
|
private getCPUUsagePercentage = async (): Promise<number> => {
|
||||||
const startUsage = process.cpuUsage();
|
const startUsage = process.cpuUsage();
|
||||||
const timingPeriod = 1000; // measured in ms
|
const timingPeriod = 1000; // measured in ms
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Schema, model, Types } from "mongoose";
|
import { Schema, model, Types } from "mongoose";
|
||||||
import { MonitorTypes, type MonitorType } from "@/domain/monitors/monitor.types.js";
|
import { MonitorTypes, type MonitorType } from "@/domain/monitors/monitor.type.js";
|
||||||
import type { GeoCheck, GeoCheckLocation, GeoCheckMetadata, GeoCheckResult, GeoCheckTimings } from "@/domain/geo-checks/geo-check.type.js";
|
import type { GeoCheck, GeoCheckLocation, GeoCheckMetadata, GeoCheckResult, GeoCheckTimings } from "@/domain/geo-checks/geo-check.type.js";
|
||||||
|
|
||||||
type GeoCheckMetadataDocument = Omit<GeoCheckMetadata, "monitorId" | "teamId"> & {
|
type GeoCheckMetadataDocument = Omit<GeoCheckMetadata, "monitorId" | "teamId"> & {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { GeoCheck, GroupedGeoCheck } from "@/domain/geo-checks/geo-check.type.js";
|
import type { GeoCheck, GroupedGeoCheck } from "@/domain/geo-checks/geo-check.type.js";
|
||||||
import type { GeoContinent, FlatGeoCheck } from "@/domain/geo-checks/geo-check.type.js";
|
import type { GeoContinent, FlatGeoCheck } from "@/domain/geo-checks/geo-check.type.js";
|
||||||
|
import { DateRange } from "@/types/query.js";
|
||||||
|
|
||||||
export interface GeoChecksQueryResult {
|
export interface GeoChecksQueryResult {
|
||||||
geoChecksCount: number;
|
geoChecksCount: number;
|
||||||
@@ -16,19 +17,12 @@ export interface IGeoChecksRepository {
|
|||||||
findByMonitorId(
|
findByMonitorId(
|
||||||
monitorId: string,
|
monitorId: string,
|
||||||
sortOrder: string,
|
sortOrder: string,
|
||||||
dateRange: string,
|
dateRange: DateRange,
|
||||||
page: number,
|
page: number,
|
||||||
rowsPerPage: number,
|
rowsPerPage: number,
|
||||||
continents?: GeoContinent[]
|
continents?: GeoContinent[]
|
||||||
): Promise<FlatGeoChecksQueryResult>;
|
): Promise<FlatGeoChecksQueryResult>;
|
||||||
findByMonitorIdAndDateRange(monitorId: string, startDate: Date, endDate: Date): Promise<GeoCheck[]>;
|
findGroupedByMonitorIdAndDateRange(monitorId: string, DateRange: DateRange, continents?: GeoContinent[]): Promise<GroupedGeoCheck[]>;
|
||||||
findGroupedByMonitorIdAndDateRange(
|
|
||||||
monitorId: string,
|
|
||||||
startDate: Date,
|
|
||||||
endDate: Date,
|
|
||||||
dateFormat: string,
|
|
||||||
continents?: GeoContinent[]
|
|
||||||
): Promise<GroupedGeoCheck[]>;
|
|
||||||
deleteByMonitorId(monitorId: string): Promise<number>;
|
deleteByMonitorId(monitorId: string): Promise<number>;
|
||||||
deleteByTeamId(teamId: string): Promise<number>;
|
deleteByTeamId(teamId: string): Promise<number>;
|
||||||
deleteByMonitorIdsNotIn(monitorIds: string[]): Promise<number>;
|
deleteByMonitorIdsNotIn(monitorIds: string[]): Promise<number>;
|
||||||
|
|||||||
@@ -3,20 +3,15 @@ import type { GeoCheck, GeoCheckMetadata, GeoCheckResult, GroupedGeoCheck, GeoCo
|
|||||||
import type { FlatGeoChecksQueryResult } from "./geo-check.repository.interface.js";
|
import type { FlatGeoChecksQueryResult } from "./geo-check.repository.interface.js";
|
||||||
import { GeoCheckMetadataDocument, GeoCheckModel, type GeoCheckDocument } from "@/domain/geo-checks/geo-check.model.js";
|
import { GeoCheckMetadataDocument, GeoCheckModel, type GeoCheckDocument } from "@/domain/geo-checks/geo-check.model.js";
|
||||||
import mongoose, { PipelineStage } from "mongoose";
|
import mongoose, { PipelineStage } from "mongoose";
|
||||||
import { getDateForRange } from "@/utils/dataUtils.js";
|
import { getDateForRange, getDateFormat } from "@/utils/dataUtils.js";
|
||||||
import { ILogger } from "@/utils/logger.js";
|
|
||||||
import { toStringId, toDateString } from "@/utils/mongoMappers.js";
|
import { toStringId, toDateString } from "@/utils/mongoMappers.js";
|
||||||
|
import { DateRange } from "@/types/query.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "GeoChecksRepository";
|
const SERVICE_NAME = "GeoChecksRepository";
|
||||||
|
|
||||||
class MongoGeoChecksRepository implements IGeoChecksRepository {
|
class MongoGeoChecksRepository implements IGeoChecksRepository {
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
|
|
||||||
private logger: ILogger;
|
|
||||||
constructor(logger: ILogger) {
|
|
||||||
this.logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
private toEntity = (doc: GeoCheckDocument): GeoCheck => {
|
private toEntity = (doc: GeoCheckDocument): GeoCheck => {
|
||||||
const mapMetadata = (metadata: GeoCheckMetadataDocument): GeoCheckMetadata => ({
|
const mapMetadata = (metadata: GeoCheckMetadataDocument): GeoCheckMetadata => ({
|
||||||
monitorId: toStringId(metadata.monitorId),
|
monitorId: toStringId(metadata.monitorId),
|
||||||
@@ -63,304 +58,214 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
|
|||||||
};
|
};
|
||||||
|
|
||||||
createGeoChecks = async (geoChecks: Omit<GeoCheck, "id" | "__v" | "createdAt" | "updatedAt">[]): Promise<GeoCheck[]> => {
|
createGeoChecks = async (geoChecks: Omit<GeoCheck, "id" | "__v" | "createdAt" | "updatedAt">[]): Promise<GeoCheck[]> => {
|
||||||
try {
|
const docs = await GeoCheckModel.insertMany(
|
||||||
const docs = await GeoCheckModel.insertMany(
|
geoChecks.map((geoCheck) => ({
|
||||||
geoChecks.map((geoCheck) => ({
|
metadata: {
|
||||||
metadata: {
|
monitorId: new mongoose.Types.ObjectId(geoCheck.metadata.monitorId),
|
||||||
monitorId: new mongoose.Types.ObjectId(geoCheck.metadata.monitorId),
|
teamId: new mongoose.Types.ObjectId(geoCheck.metadata.teamId),
|
||||||
teamId: new mongoose.Types.ObjectId(geoCheck.metadata.teamId),
|
type: geoCheck.metadata.type,
|
||||||
type: geoCheck.metadata.type,
|
},
|
||||||
},
|
results: geoCheck.results,
|
||||||
results: geoCheck.results,
|
expiry: new Date(geoCheck.expiry),
|
||||||
expiry: new Date(geoCheck.expiry),
|
}))
|
||||||
}))
|
);
|
||||||
);
|
return docs.map((doc) => this.toEntity(doc));
|
||||||
return docs.map((doc) => this.toEntity(doc));
|
|
||||||
} catch (error: unknown) {
|
|
||||||
this.logger.error({
|
|
||||||
message: error instanceof Error ? `Failed to createGeoChecks: ${error.message}` : "Failed to createGeoChecks",
|
|
||||||
service: SERVICE_NAME,
|
|
||||||
method: "createGeoChecks",
|
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
});
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
findByMonitorId = async (
|
findByMonitorId = async (
|
||||||
monitorId: string,
|
monitorId: string,
|
||||||
sortOrder: string,
|
sortOrder: string,
|
||||||
dateRange: string,
|
dateRange: DateRange,
|
||||||
page: number,
|
page: number,
|
||||||
rowsPerPage: number,
|
rowsPerPage: number,
|
||||||
continents?: GeoContinent[]
|
continents?: GeoContinent[]
|
||||||
): Promise<FlatGeoChecksQueryResult> => {
|
): Promise<FlatGeoChecksQueryResult> => {
|
||||||
try {
|
const matchStage: Record<string, unknown> = {
|
||||||
const matchStage: Record<string, unknown> = {
|
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
||||||
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
createdAt: { $gte: getDateForRange(dateRange) },
|
||||||
...(getDateForRange(dateRange) && {
|
};
|
||||||
|
|
||||||
|
const convertedSortOrder = sortOrder === "asc" ? 1 : -1;
|
||||||
|
|
||||||
|
let skip = 0;
|
||||||
|
if (page && rowsPerPage) {
|
||||||
|
skip = page * rowsPerPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common pipeline stages for both paths
|
||||||
|
const pipeline: PipelineStage[] = [
|
||||||
|
{ $match: matchStage },
|
||||||
|
{ $unwind: "$results" },
|
||||||
|
// Filter by continent if specified
|
||||||
|
...(continents && continents.length > 0
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
$match: {
|
||||||
|
"results.location.continent": { $in: continents },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
// Project to flat structure
|
||||||
|
{
|
||||||
|
$project: {
|
||||||
|
_id: 0,
|
||||||
|
monitorId: "$metadata.monitorId",
|
||||||
|
teamId: "$metadata.teamId",
|
||||||
|
type: "$metadata.type",
|
||||||
|
location: "$results.location",
|
||||||
|
status: "$results.status",
|
||||||
|
statusCode: "$results.statusCode",
|
||||||
|
timings: "$results.timings",
|
||||||
|
createdAt: 1,
|
||||||
|
updatedAt: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ $sort: { createdAt: convertedSortOrder } },
|
||||||
|
{ $skip: skip },
|
||||||
|
{ $limit: rowsPerPage },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Count pipeline
|
||||||
|
const countPipeline: PipelineStage[] = [
|
||||||
|
{ $match: matchStage },
|
||||||
|
{ $unwind: "$results" },
|
||||||
|
...(continents && continents.length > 0
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
$match: {
|
||||||
|
"results.location.continent": { $in: continents },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
{ $count: "count" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const [countResult, dataResults] = await Promise.all([GeoCheckModel.aggregate(countPipeline), GeoCheckModel.aggregate(pipeline)]);
|
||||||
|
|
||||||
|
const geoChecksCount = countResult[0]?.count || 0;
|
||||||
|
const geoChecks: FlatGeoCheck[] = dataResults.map((doc) => ({
|
||||||
|
id: `${doc.monitorId.toString()}-${new Date(doc.createdAt).getTime()}-${doc.location.continent}-${doc.location.city}-${Math.random().toString(36).substring(2, 15)}`,
|
||||||
|
monitorId: doc.monitorId.toString(),
|
||||||
|
teamId: doc.teamId.toString(),
|
||||||
|
type: doc.type,
|
||||||
|
location: doc.location,
|
||||||
|
status: doc.status,
|
||||||
|
statusCode: doc.statusCode,
|
||||||
|
timings: doc.timings,
|
||||||
|
createdAt: new Date(doc.createdAt).toISOString(),
|
||||||
|
updatedAt: new Date(doc.updatedAt).toISOString(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return { geoChecksCount, geoChecks };
|
||||||
|
};
|
||||||
|
|
||||||
|
findGroupedByMonitorIdAndDateRange = async (monitorId: string, dateRange: DateRange, continents?: GeoContinent[]): Promise<GroupedGeoCheck[]> => {
|
||||||
|
const start = getDateForRange(dateRange);
|
||||||
|
const dateString = getDateFormat(dateRange);
|
||||||
|
|
||||||
|
const end = new Date();
|
||||||
|
const pipeline: PipelineStage[] = [
|
||||||
|
// Match geo checks for this monitor in date range
|
||||||
|
{
|
||||||
|
$match: {
|
||||||
|
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
||||||
createdAt: {
|
createdAt: {
|
||||||
$gte: getDateForRange(dateRange),
|
$gte: start,
|
||||||
|
$lte: end,
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
};
|
},
|
||||||
|
// Unwind the results array to process each location separately
|
||||||
const convertedSortOrder = sortOrder === "asc" ? 1 : -1;
|
{
|
||||||
|
$unwind: "$results",
|
||||||
let skip = 0;
|
},
|
||||||
if (page && rowsPerPage) {
|
// Filter by continent if specified
|
||||||
skip = page * rowsPerPage;
|
...(continents && continents.length > 0
|
||||||
}
|
? [
|
||||||
|
{
|
||||||
// Common pipeline stages for both paths
|
$match: {
|
||||||
const pipeline: PipelineStage[] = [
|
"results.location.continent": { $in: continents },
|
||||||
{ $match: matchStage },
|
},
|
||||||
{ $unwind: "$results" },
|
},
|
||||||
// Filter by continent if specified
|
]
|
||||||
...(continents && continents.length > 0
|
: []),
|
||||||
? [
|
// Group by date bucket and continent
|
||||||
|
{
|
||||||
|
$group: {
|
||||||
|
_id: {
|
||||||
|
bucketDate: {
|
||||||
|
$dateToString: {
|
||||||
|
format: dateString,
|
||||||
|
date: "$createdAt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
continent: "$results.location.continent",
|
||||||
|
},
|
||||||
|
avgResponseTime: { $avg: "$results.timings.total" },
|
||||||
|
totalChecks: { $sum: 1 },
|
||||||
|
upChecks: {
|
||||||
|
$sum: {
|
||||||
|
$cond: ["$results.status", 1, 0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Calculate uptime percentage
|
||||||
|
{
|
||||||
|
$project: {
|
||||||
|
_id: 0,
|
||||||
|
bucketDate: "$_id.bucketDate",
|
||||||
|
continent: "$_id.continent",
|
||||||
|
avgResponseTime: { $round: ["$avgResponseTime", 2] },
|
||||||
|
totalChecks: 1,
|
||||||
|
uptimePercentage: {
|
||||||
|
$round: [
|
||||||
{
|
{
|
||||||
$match: {
|
$multiply: [
|
||||||
"results.location.continent": { $in: continents },
|
{
|
||||||
},
|
$divide: ["$upChecks", "$totalChecks"],
|
||||||
|
},
|
||||||
|
100,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
]
|
2,
|
||||||
: []),
|
],
|
||||||
// Project to flat structure
|
|
||||||
{
|
|
||||||
$project: {
|
|
||||||
_id: 0,
|
|
||||||
monitorId: "$metadata.monitorId",
|
|
||||||
teamId: "$metadata.teamId",
|
|
||||||
type: "$metadata.type",
|
|
||||||
location: "$results.location",
|
|
||||||
status: "$results.status",
|
|
||||||
statusCode: "$results.statusCode",
|
|
||||||
timings: "$results.timings",
|
|
||||||
createdAt: 1,
|
|
||||||
updatedAt: 1,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ $sort: { createdAt: convertedSortOrder } },
|
},
|
||||||
{ $skip: skip },
|
// Sort by date and continent
|
||||||
{ $limit: rowsPerPage },
|
{
|
||||||
];
|
$sort: {
|
||||||
|
bucketDate: 1,
|
||||||
// Count pipeline
|
continent: 1,
|
||||||
const countPipeline: PipelineStage[] = [
|
|
||||||
{ $match: matchStage },
|
|
||||||
{ $unwind: "$results" },
|
|
||||||
...(continents && continents.length > 0
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
$match: {
|
|
||||||
"results.location.continent": { $in: continents },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
{ $count: "count" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const [countResult, dataResults] = await Promise.all([GeoCheckModel.aggregate(countPipeline), GeoCheckModel.aggregate(pipeline)]);
|
|
||||||
|
|
||||||
const geoChecksCount = countResult[0]?.count || 0;
|
|
||||||
const geoChecks: FlatGeoCheck[] = dataResults.map((doc) => ({
|
|
||||||
id: `${doc.monitorId.toString()}-${new Date(doc.createdAt).getTime()}-${doc.location.continent}-${doc.location.city}-${Math.random().toString(36).substring(2, 15)}`,
|
|
||||||
monitorId: doc.monitorId.toString(),
|
|
||||||
teamId: doc.teamId.toString(),
|
|
||||||
type: doc.type,
|
|
||||||
location: doc.location,
|
|
||||||
status: doc.status,
|
|
||||||
statusCode: doc.statusCode,
|
|
||||||
timings: doc.timings,
|
|
||||||
createdAt: new Date(doc.createdAt).toISOString(),
|
|
||||||
updatedAt: new Date(doc.updatedAt).toISOString(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
return { geoChecksCount, geoChecks };
|
|
||||||
} catch (error: unknown) {
|
|
||||||
this.logger.error({
|
|
||||||
message: error instanceof Error ? `Error finding geo checks by monitor ID: ${error.message}` : "Error finding geo checks by monitor ID",
|
|
||||||
service: SERVICE_NAME,
|
|
||||||
method: "findByMonitorId",
|
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
});
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
findByMonitorIdAndDateRange = async (monitorId: string, startDate: Date, endDate: Date): Promise<GeoCheck[]> => {
|
|
||||||
try {
|
|
||||||
const docs = await GeoCheckModel.find({
|
|
||||||
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
|
||||||
createdAt: {
|
|
||||||
$gte: startDate,
|
|
||||||
$lte: endDate,
|
|
||||||
},
|
},
|
||||||
}).sort({ createdAt: -1 });
|
},
|
||||||
return docs.map(this.toEntity);
|
];
|
||||||
} catch (error: unknown) {
|
|
||||||
this.logger.error({
|
|
||||||
message:
|
|
||||||
error instanceof Error
|
|
||||||
? `Error finding geo checks by monitor ID and date range: ${error.message}`
|
|
||||||
: "Error finding geo checks by monitor ID and date range",
|
|
||||||
service: SERVICE_NAME,
|
|
||||||
method: "findByMonitorIdAndDateRange",
|
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
});
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
findGroupedByMonitorIdAndDateRange = async (
|
const results = await GeoCheckModel.aggregate(pipeline);
|
||||||
monitorId: string,
|
return results as GroupedGeoCheck[];
|
||||||
startDate: Date,
|
|
||||||
endDate: Date,
|
|
||||||
dateFormat: string,
|
|
||||||
continents?: GeoContinent[]
|
|
||||||
): Promise<GroupedGeoCheck[]> => {
|
|
||||||
try {
|
|
||||||
const pipeline: PipelineStage[] = [
|
|
||||||
// Match geo checks for this monitor in date range
|
|
||||||
{
|
|
||||||
$match: {
|
|
||||||
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
|
||||||
createdAt: {
|
|
||||||
$gte: startDate,
|
|
||||||
$lte: endDate,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Unwind the results array to process each location separately
|
|
||||||
{
|
|
||||||
$unwind: "$results",
|
|
||||||
},
|
|
||||||
// Filter by continent if specified
|
|
||||||
...(continents && continents.length > 0
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
$match: {
|
|
||||||
"results.location.continent": { $in: continents },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
// Group by date bucket and continent
|
|
||||||
{
|
|
||||||
$group: {
|
|
||||||
_id: {
|
|
||||||
bucketDate: {
|
|
||||||
$dateToString: {
|
|
||||||
format: dateFormat,
|
|
||||||
date: "$createdAt",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
continent: "$results.location.continent",
|
|
||||||
},
|
|
||||||
avgResponseTime: { $avg: "$results.timings.total" },
|
|
||||||
totalChecks: { $sum: 1 },
|
|
||||||
upChecks: {
|
|
||||||
$sum: {
|
|
||||||
$cond: ["$results.status", 1, 0],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Calculate uptime percentage
|
|
||||||
{
|
|
||||||
$project: {
|
|
||||||
_id: 0,
|
|
||||||
bucketDate: "$_id.bucketDate",
|
|
||||||
continent: "$_id.continent",
|
|
||||||
avgResponseTime: { $round: ["$avgResponseTime", 2] },
|
|
||||||
totalChecks: 1,
|
|
||||||
uptimePercentage: {
|
|
||||||
$round: [
|
|
||||||
{
|
|
||||||
$multiply: [
|
|
||||||
{
|
|
||||||
$divide: ["$upChecks", "$totalChecks"],
|
|
||||||
},
|
|
||||||
100,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
2,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Sort by date and continent
|
|
||||||
{
|
|
||||||
$sort: {
|
|
||||||
bucketDate: 1,
|
|
||||||
continent: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const results = await GeoCheckModel.aggregate(pipeline);
|
|
||||||
return results as GroupedGeoCheck[];
|
|
||||||
} catch (error: unknown) {
|
|
||||||
this.logger.error({
|
|
||||||
message: error instanceof Error ? `Error finding grouped geo checks: ${error.message}` : "Error finding grouped geo checks",
|
|
||||||
service: SERVICE_NAME,
|
|
||||||
method: "findGroupedByMonitorIdAndDateRange",
|
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
});
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteByMonitorId = async (monitorId: string): Promise<number> => {
|
deleteByMonitorId = async (monitorId: string): Promise<number> => {
|
||||||
try {
|
const result = await GeoCheckModel.deleteMany({
|
||||||
const result = await GeoCheckModel.deleteMany({
|
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
||||||
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
});
|
||||||
});
|
return result.deletedCount || 0;
|
||||||
return result.deletedCount || 0;
|
|
||||||
} catch (error: unknown) {
|
|
||||||
this.logger.error({
|
|
||||||
message: error instanceof Error ? `Error deleting geo checks by monitor ID: ${error.message}` : "Error deleting geo checks by monitor ID",
|
|
||||||
service: SERVICE_NAME,
|
|
||||||
method: "deleteByMonitorId",
|
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
});
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteByTeamId = async (teamId: string): Promise<number> => {
|
deleteByTeamId = async (teamId: string): Promise<number> => {
|
||||||
try {
|
const result = await GeoCheckModel.deleteMany({
|
||||||
const result = await GeoCheckModel.deleteMany({
|
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
|
||||||
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
|
});
|
||||||
});
|
return result.deletedCount || 0;
|
||||||
return result.deletedCount || 0;
|
|
||||||
} catch (error: unknown) {
|
|
||||||
this.logger.error({
|
|
||||||
message: error instanceof Error ? `Error deleting geo checks by team ID: ${error.message}` : "Error deleting geo checks by team ID",
|
|
||||||
service: SERVICE_NAME,
|
|
||||||
method: "deleteByTeamId",
|
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
});
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteByMonitorIdsNotIn = async (monitorIds: string[]): Promise<number> => {
|
deleteByMonitorIdsNotIn = async (monitorIds: string[]): Promise<number> => {
|
||||||
try {
|
const objectIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id));
|
||||||
const objectIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id));
|
const result = await GeoCheckModel.deleteMany({ "metadata.monitorId": { $nin: objectIds } });
|
||||||
const result = await GeoCheckModel.deleteMany({ "metadata.monitorId": { $nin: objectIds } });
|
return result.deletedCount || 0;
|
||||||
return result.deletedCount || 0;
|
|
||||||
} catch (error: unknown) {
|
|
||||||
this.logger.error({
|
|
||||||
message: error instanceof Error ? `Error deleting orphaned geo checks: ${error.message}` : "Error deleting orphaned geo checks",
|
|
||||||
service: SERVICE_NAME,
|
|
||||||
method: "deleteByMonitorIdsNotIn",
|
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
});
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { GeoCheck } from "@/domain/geo-checks/geo-check.type.js";
|
import type { GeoCheck } from "@/domain/geo-checks/geo-check.type.js";
|
||||||
import type { Monitor } from "@/domain/monitors/monitor.types.js";
|
import type { Monitor } from "@/domain/monitors/monitor.type.js";
|
||||||
import type { GeoCheckResult, GeoContinent } from "@/domain/geo-checks/geo-check.type.js";
|
import type { GeoCheckResult, GeoContinent } from "@/domain/geo-checks/geo-check.type.js";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import type { FlatGeoChecksQueryResult, IGeoChecksRepository } from "@/domain/geo-checks/geo-check.repository.interface.js";
|
import type { FlatGeoChecksQueryResult, IGeoChecksRepository } from "@/domain/geo-checks/geo-check.repository.interface.js";
|
||||||
@@ -7,18 +7,18 @@ import type { IMonitorsRepository } from "@/domain/monitors/monitor.repository.i
|
|||||||
import type { IGlobalPingService } from "@/service/globalPingService.js";
|
import type { IGlobalPingService } from "@/service/globalPingService.js";
|
||||||
import type { ILogger } from "@/utils/logger.js";
|
import type { ILogger } from "@/utils/logger.js";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
|
import { DateRange } from "@/types/query.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "GeoChecksService";
|
const SERVICE_NAME = "GeoChecksService";
|
||||||
|
|
||||||
export interface IGeoChecksService {
|
export interface IGeoChecksService {
|
||||||
readonly serviceName: string;
|
|
||||||
buildGeoCheck(monitor: Monitor): Promise<GeoCheck | null>;
|
buildGeoCheck(monitor: Monitor): Promise<GeoCheck | null>;
|
||||||
createGeoChecks(geoChecks: GeoCheck[]): Promise<GeoCheck[]>;
|
createGeoChecks(geoChecks: GeoCheck[]): Promise<GeoCheck[]>;
|
||||||
getGeoChecksByMonitor(args: {
|
getGeoChecksByMonitor(args: {
|
||||||
monitorId: string;
|
monitorId: string;
|
||||||
teamId: string;
|
teamId: string;
|
||||||
sortOrder: string;
|
sortOrder: string;
|
||||||
dateRange: string;
|
dateRange: DateRange;
|
||||||
page?: number;
|
page?: number;
|
||||||
rowsPerPage?: number;
|
rowsPerPage?: number;
|
||||||
continent?: GeoContinent | GeoContinent[];
|
continent?: GeoContinent | GeoContinent[];
|
||||||
@@ -50,10 +50,6 @@ export class GeoChecksService implements IGeoChecksService {
|
|||||||
this.monitorsRepository = monitorsRepository;
|
this.monitorsRepository = monitorsRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return GeoChecksService.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
async buildGeoCheck(monitor: Monitor): Promise<GeoCheck | null> {
|
async buildGeoCheck(monitor: Monitor): Promise<GeoCheck | null> {
|
||||||
try {
|
try {
|
||||||
if (!monitor.url) {
|
if (!monitor.url) {
|
||||||
@@ -160,7 +156,7 @@ export class GeoChecksService implements IGeoChecksService {
|
|||||||
monitorId: string;
|
monitorId: string;
|
||||||
teamId: string;
|
teamId: string;
|
||||||
sortOrder: string;
|
sortOrder: string;
|
||||||
dateRange: string;
|
dateRange: DateRange;
|
||||||
page?: number;
|
page?: number;
|
||||||
rowsPerPage?: number;
|
rowsPerPage?: number;
|
||||||
continent: GeoContinent | GeoContinent[];
|
continent: GeoContinent | GeoContinent[];
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { MonitorType } from "@/domain/monitors/monitor.types.js";
|
import type { MonitorType } from "@/domain/monitors/monitor.type.js";
|
||||||
|
|
||||||
export const GeoContinents = ["EU", "NA", "AS", "SA", "AF", "OC"] as const;
|
export const GeoContinents = ["EU", "NA", "AS", "SA", "AF", "OC"] as const;
|
||||||
export type GeoContinent = (typeof GeoContinents)[number];
|
export type GeoContinent = (typeof GeoContinents)[number];
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const SERVICE_NAME = "incidentService";
|
const SERVICE_NAME = "incidentService";
|
||||||
import type { Monitor } from "@/domain/monitors/monitor.types.js";
|
import type { Monitor } from "@/domain/monitors/monitor.type.js";
|
||||||
import type { MonitorStatusResponse } from "@/types/network.js";
|
import type { MonitorStatusResponse } from "@/types/network.js";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import { getDateForRange } from "@/utils/dataUtils.js";
|
import { getDateForRange } from "@/utils/dataUtils.js";
|
||||||
@@ -11,6 +11,7 @@ import type { User } from "@/domain/users/user.type.js";
|
|||||||
import type { MonitorActionDecision } from "@/worker/worker.helper.js";
|
import type { MonitorActionDecision } from "@/worker/worker.helper.js";
|
||||||
import type { INotificationMessageBuilder } from "@/domain/notifications/notification.message-builder.js";
|
import type { INotificationMessageBuilder } from "@/domain/notifications/notification.message-builder.js";
|
||||||
import type { ILogger } from "@/utils/logger.js";
|
import type { ILogger } from "@/utils/logger.js";
|
||||||
|
import { DateRange } from "@/types/query.js";
|
||||||
|
|
||||||
export interface IIncidentService {
|
export interface IIncidentService {
|
||||||
handleIncident(
|
handleIncident(
|
||||||
@@ -23,7 +24,7 @@ export interface IIncidentService {
|
|||||||
getIncidentsByTeam(
|
getIncidentsByTeam(
|
||||||
teamId: string,
|
teamId: string,
|
||||||
sortOrder: string,
|
sortOrder: string,
|
||||||
dateRange: string,
|
dateRange: DateRange,
|
||||||
page: number,
|
page: number,
|
||||||
rowsPerPage: number,
|
rowsPerPage: number,
|
||||||
status: boolean | undefined,
|
status: boolean | undefined,
|
||||||
@@ -57,10 +58,6 @@ export class IncidentService implements IIncidentService {
|
|||||||
this.notificationMessageBuilder = notificationMessageBuilder;
|
this.notificationMessageBuilder = notificationMessageBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return IncidentService.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleIncident = async (
|
handleIncident = async (
|
||||||
monitor: Monitor,
|
monitor: Monitor,
|
||||||
code: number,
|
code: number,
|
||||||
@@ -178,7 +175,7 @@ export class IncidentService implements IIncidentService {
|
|||||||
getIncidentsByTeam = async (
|
getIncidentsByTeam = async (
|
||||||
teamId: string,
|
teamId: string,
|
||||||
sortOrder: string,
|
sortOrder: string,
|
||||||
dateRange: string,
|
dateRange: DateRange,
|
||||||
page: number,
|
page: number,
|
||||||
rowsPerPage: number,
|
rowsPerPage: number,
|
||||||
status: boolean | undefined,
|
status: boolean | undefined,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// export type IncidentResolutionType = "automatic" | "manual" | null;
|
// export type IncidentResolutionType = "automatic" | "manual" | null;
|
||||||
|
|
||||||
export const IncidentResolutionTypes = ["automatic", "manual", null] as const;
|
export const IncidentResolutionTypes = ["automatic", "manual"] as const;
|
||||||
export type IncidentResolutionType = (typeof IncidentResolutionTypes)[number];
|
export type IncidentResolutionType = (typeof IncidentResolutionTypes)[number] | null;
|
||||||
|
|
||||||
export interface Incident {
|
export interface Incident {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -35,10 +35,6 @@ export class InviteService implements IInviteService {
|
|||||||
this.emailService = emailService;
|
this.emailService = emailService;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return InviteService.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
getInviteToken = async ({ invite, teamId, userRoles }: { invite: Partial<Invite>; teamId: string; userRoles: UserRole[] }) => {
|
getInviteToken = async ({ invite, teamId, userRoles }: { invite: Partial<Invite>; teamId: string; userRoles: UserRole[] }) => {
|
||||||
invite.teamId = teamId;
|
invite.teamId = teamId;
|
||||||
|
|
||||||
|
|||||||
@@ -62,10 +62,6 @@ export class MaintenanceWindowService implements IMaintenanceWindowService {
|
|||||||
this.scheduler = scheduler;
|
this.scheduler = scheduler;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return MaintenanceWindowService.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
private flipMonitorsLeavingMaintenance = async (monitorIds: string[], teamId: string, excludeWindowId: string, now: Date): Promise<void> => {
|
private flipMonitorsLeavingMaintenance = async (monitorIds: string[], teamId: string, excludeWindowId: string, now: Date): Promise<void> => {
|
||||||
if (monitorIds.length === 0) return;
|
if (monitorIds.length === 0) return;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Schema, model, Types } from "mongoose";
|
import { Schema, model, Types } from "mongoose";
|
||||||
import type { Monitor, MonitorMatchMethod, CheckSnapshot } from "@/domain/monitors/monitor.types.js";
|
import type { Monitor, MonitorMatchMethod, CheckSnapshot } from "@/domain/monitors/monitor.type.js";
|
||||||
import { DnsRecordTypes, MonitorTypes, MonitorStatuses, PageSpeedStrategies, HttpMethods } from "@/domain/monitors/monitor.types.js";
|
import { DnsRecordTypes, MonitorTypes, MonitorStatuses, PageSpeedStrategies, HttpMethods } from "@/domain/monitors/monitor.type.js";
|
||||||
import type {
|
import type {
|
||||||
CheckAudits,
|
CheckAudits,
|
||||||
CheckCaptureInfo,
|
CheckCaptureInfo,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { MonitorType, Monitor, MonitorStatus, MonitorsSummary, CheckSnapshot } from "@/domain/monitors/monitor.types.js";
|
import type { MonitorType, Monitor, MonitorStatus, MonitorsSummary, CheckSnapshot } from "@/domain/monitors/monitor.type.js";
|
||||||
|
|
||||||
export interface TeamQueryConfig {
|
export interface TeamQueryConfig {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
@@ -26,9 +26,9 @@ export interface IMonitorsRepository {
|
|||||||
findByIdLean(monitorId: string): Promise<Monitor | null>;
|
findByIdLean(monitorId: string): Promise<Monitor | null>;
|
||||||
|
|
||||||
// collection fetch
|
// collection fetch
|
||||||
findAll(): Promise<Monitor[] | null>;
|
findAll(): Promise<Monitor[]>;
|
||||||
findByTeamId(teamId: string, config: TeamQueryConfig): Promise<Monitor[] | null>;
|
findByTeamId(teamId: string, config: TeamQueryConfig): Promise<Monitor[]>;
|
||||||
findByTeamIdWithStats(teamId: string, config: TeamQueryConfig): Promise<Monitor[] | null>;
|
findByTeamIdWithStats(teamId: string, config: TeamQueryConfig): Promise<Monitor[]>;
|
||||||
findByIds(monitorIds: string[]): Promise<Monitor[]>;
|
findByIds(monitorIds: string[]): Promise<Monitor[]>;
|
||||||
findByIdsWithChecks(monitorIds: string[], checksCount?: number): Promise<Monitor[]>;
|
findByIdsWithChecks(monitorIds: string[], checksCount?: number): Promise<Monitor[]>;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { MonitorModel } from "@/domain/monitors/monitor.model.js";
|
import { MonitorModel } from "@/domain/monitors/monitor.model.js";
|
||||||
import type { MonitorDocument, CheckSnapshotDocument } from "@/domain/monitors/monitor.model.js";
|
import type { MonitorDocument, CheckSnapshotDocument } from "@/domain/monitors/monitor.model.js";
|
||||||
import type { CheckSnapshot } from "@/domain/checks/check.type.js";
|
import type { CheckSnapshot } from "@/domain/checks/check.type.js";
|
||||||
import type { Monitor, MonitorStatus, MonitorsSummary } from "@/domain/monitors/monitor.types.js";
|
import type { Monitor, MonitorStatus, MonitorsSummary } from "@/domain/monitors/monitor.type.js";
|
||||||
import mongoose, { type FilterQuery, type PipelineStage } from "mongoose";
|
import mongoose, { type FilterQuery, type PipelineStage } from "mongoose";
|
||||||
import { MongoBulkWriteError } from "mongodb";
|
import { MongoBulkWriteError } from "mongodb";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
@@ -91,27 +91,26 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
|||||||
{ $project: { stats: 0 } },
|
{ $project: { stats: 0 } },
|
||||||
];
|
];
|
||||||
|
|
||||||
findByTeamId = async (teamId: string, config: TeamQueryConfig): Promise<Monitor[] | null> => {
|
private pageOptions = (config: TeamQueryConfig) => {
|
||||||
const { page = 0, rowsPerPage = 0, field = "createdAt", order = "desc" } = config ?? {};
|
const { page = 0, rowsPerPage = 0, field = "createdAt", order = "desc" } = config ?? {};
|
||||||
|
return { sort: { [field]: order === "asc" ? 1 : -1 } as const, skip: Math.max(page, 0) * rowsPerPage, limit: rowsPerPage };
|
||||||
|
};
|
||||||
|
|
||||||
|
findByTeamId = async (teamId: string, config: TeamQueryConfig): Promise<Monitor[]> => {
|
||||||
const query = this.queryBuilder(config, teamId);
|
const query = this.queryBuilder(config, teamId);
|
||||||
const sort = { [field]: order === "asc" ? 1 : -1 } as const;
|
const { sort, skip, limit } = this.pageOptions(config);
|
||||||
const skip = Math.max(page, 0) * rowsPerPage;
|
const documents = await MonitorModel.find(query).sort(sort).skip(skip).limit(limit);
|
||||||
const documents = await MonitorModel.find(query).sort(sort).skip(skip).limit(rowsPerPage);
|
|
||||||
return this.mapDocuments(documents);
|
return this.mapDocuments(documents);
|
||||||
};
|
};
|
||||||
|
|
||||||
findByTeamIdWithStats = async (teamId: string, config: TeamQueryConfig): Promise<Monitor[] | null> => {
|
findByTeamIdWithStats = async (teamId: string, config: TeamQueryConfig): Promise<Monitor[]> => {
|
||||||
const { page = 0, rowsPerPage = 0, field = "createdAt", order = "desc" } = config ?? {};
|
const { sort, skip, limit } = this.pageOptions(config);
|
||||||
|
|
||||||
const query = this.queryBuilder(config, teamId);
|
const query = this.queryBuilder(config, teamId);
|
||||||
|
|
||||||
const sort = { [field]: order === "asc" ? 1 : -1 } as const;
|
|
||||||
const skip = Math.max(page, 0) * rowsPerPage;
|
|
||||||
|
|
||||||
const pipeline: PipelineStage[] = [
|
const pipeline: PipelineStage[] = [
|
||||||
{ $match: query },
|
{ $match: query },
|
||||||
{ $sort: sort },
|
{ $sort: sort },
|
||||||
...(rowsPerPage ? [{ $skip: skip }, { $limit: rowsPerPage }] : []),
|
...(limit ? [{ $skip: skip }, { $limit: limit }] : []),
|
||||||
...this.uptimeStatsLookupStages,
|
...this.uptimeStatsLookupStages,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { NormalizeData, NormalizeDataUptimeDetails } from "@/utils/dataUtils.js";
|
import { NormalizeData, NormalizeDataUptimeDetails } from "@/utils/dataUtils.js";
|
||||||
import { type Monitor } from "@/domain/monitors/monitor.types.js";
|
import { type Monitor } from "@/domain/monitors/monitor.type.js";
|
||||||
import type {
|
import type {
|
||||||
MonitorType,
|
MonitorType,
|
||||||
MonitorsWithChecksByTeamIdResult,
|
MonitorsWithChecksByTeamIdResult,
|
||||||
@@ -8,8 +8,8 @@ import type {
|
|||||||
PageSpeedDetailsResult,
|
PageSpeedDetailsResult,
|
||||||
GamesMap,
|
GamesMap,
|
||||||
GroupedGeoCheckResult,
|
GroupedGeoCheckResult,
|
||||||
} from "@/domain/monitors/monitor.types.js";
|
} from "@/domain/monitors/monitor.type.js";
|
||||||
import { supportsGeoCheck, supportsUptimeDetails } from "@/domain/monitors/monitor.types.js";
|
import { supportsGeoCheck, supportsUptimeDetails } from "@/domain/monitors/monitor.type.js";
|
||||||
import type { UptimeChecksResult, HardwareChecksResult, PageSpeedChecksResult } from "@/domain/checks/check.type.js";
|
import type { UptimeChecksResult, HardwareChecksResult, PageSpeedChecksResult } from "@/domain/checks/check.type.js";
|
||||||
import type { GeoContinent } from "@/domain/geo-checks/geo-check.type.js";
|
import type { GeoContinent } from "@/domain/geo-checks/geo-check.type.js";
|
||||||
import type { IChecksRepository } from "@/domain/checks/check.repository.interface.js";
|
import type { IChecksRepository } from "@/domain/checks/check.repository.interface.js";
|
||||||
@@ -23,29 +23,27 @@ import { AppError } from "@/utils/AppError.js";
|
|||||||
import type { ImportedMonitor } from "@/api/validation/monitorValidation.js";
|
import type { ImportedMonitor } from "@/api/validation/monitorValidation.js";
|
||||||
import { ILogger } from "@/utils/logger.js";
|
import { ILogger } from "@/utils/logger.js";
|
||||||
import { IJobScheduler } from "@/worker/worker.interface.js";
|
import { IJobScheduler } from "@/worker/worker.interface.js";
|
||||||
|
import { DateRange } from "@/types/query.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "MonitorService";
|
const SERVICE_NAME = "MonitorService";
|
||||||
type DateRangeKey = "recent" | "day" | "week" | "month" | "all";
|
|
||||||
|
|
||||||
const isUptimeChecksResult = (result: UptimeChecksResult | HardwareChecksResult | PageSpeedChecksResult): result is UptimeChecksResult =>
|
const isUptimeChecksResult = (result: UptimeChecksResult | HardwareChecksResult | PageSpeedChecksResult): result is UptimeChecksResult =>
|
||||||
supportsUptimeDetails(result.monitorType);
|
supportsUptimeDetails(result.monitorType);
|
||||||
|
|
||||||
export interface IMonitorService {
|
export interface IMonitorService {
|
||||||
readonly serviceName: string;
|
|
||||||
|
|
||||||
// create
|
// create
|
||||||
createMonitor(teamId: string, userId: string, body: Partial<Monitor>): Promise<void>;
|
createMonitor(teamId: string, userId: string, body: Partial<Monitor>): Promise<void>;
|
||||||
createMonitors(monitors: Array<Monitor>): Promise<Monitor[] | null>;
|
createMonitors(monitors: Array<Monitor>): Promise<Monitor[] | null>;
|
||||||
addDemoMonitors(args: { userId: string; teamId: string }): Promise<Monitor[]>;
|
addDemoMonitors(args: { userId: string; teamId: string }): Promise<Monitor[]>;
|
||||||
|
|
||||||
// read
|
// read
|
||||||
getUptimeDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise<UptimeDetailsResult>;
|
getUptimeDetailsById(args: { teamId: string; monitorId: string; dateRange: DateRange }): Promise<UptimeDetailsResult>;
|
||||||
getHardwareDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise<HardwareDetailsResult>;
|
getHardwareDetailsById(args: { teamId: string; monitorId: string; dateRange: DateRange }): Promise<HardwareDetailsResult>;
|
||||||
getPageSpeedDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise<PageSpeedDetailsResult>;
|
getPageSpeedDetailsById(args: { teamId: string; monitorId: string; dateRange: DateRange }): Promise<PageSpeedDetailsResult>;
|
||||||
getGeoChecksByMonitorId(args: {
|
getGeoChecksByMonitorId(args: {
|
||||||
teamId: string;
|
teamId: string;
|
||||||
monitorId: string;
|
monitorId: string;
|
||||||
dateRange: string;
|
dateRange: DateRange;
|
||||||
continents?: GeoContinent[];
|
continents?: GeoContinent[];
|
||||||
}): Promise<GroupedGeoCheckResult>;
|
}): Promise<GroupedGeoCheckResult>;
|
||||||
getMonitorById(args: { teamId: string; monitorId: string }): Promise<Monitor>;
|
getMonitorById(args: { teamId: string; monitorId: string }): Promise<Monitor>;
|
||||||
@@ -135,35 +133,6 @@ export class MonitorService implements IMonitorService {
|
|||||||
this.incidentsRepository = incidentsRepository;
|
this.incidentsRepository = incidentsRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName(): string {
|
|
||||||
return MonitorService.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getDateRange = (dateRange: DateRangeKey) => {
|
|
||||||
const startDates = {
|
|
||||||
recent: new Date(new Date().setHours(new Date().getHours() - 2)),
|
|
||||||
day: new Date(new Date().setDate(new Date().getDate() - 1)),
|
|
||||||
week: new Date(new Date().setDate(new Date().getDate() - 7)),
|
|
||||||
month: new Date(new Date().setMonth(new Date().getMonth() - 1)),
|
|
||||||
all: new Date(0),
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
start: startDates[dateRange],
|
|
||||||
end: new Date(),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
private getDateFormat = (dateRange: DateRangeKey): string => {
|
|
||||||
const formatLookup = {
|
|
||||||
recent: "%Y-%m-%dT%H:%M:00Z",
|
|
||||||
day: "%Y-%m-%dT%H:00:00Z",
|
|
||||||
week: "%Y-%m-%dT00:00:00Z",
|
|
||||||
month: "%Y-%m-%dT00:00:00Z",
|
|
||||||
all: "%Y-%m-%dT00:00:00Z",
|
|
||||||
};
|
|
||||||
return formatLookup[dateRange];
|
|
||||||
};
|
|
||||||
|
|
||||||
createMonitor = async (teamId: string, userId: string, body: Monitor): Promise<void> => {
|
createMonitor = async (teamId: string, userId: string, body: Monitor): Promise<void> => {
|
||||||
const monitor = await this.monitorsRepository.create(body, teamId, userId);
|
const monitor = await this.monitorsRepository.create(body, teamId, userId);
|
||||||
if (!monitor) {
|
if (!monitor) {
|
||||||
@@ -206,15 +175,13 @@ export class MonitorService implements IMonitorService {
|
|||||||
}: {
|
}: {
|
||||||
teamId: string;
|
teamId: string;
|
||||||
monitorId: string;
|
monitorId: string;
|
||||||
dateRange: string;
|
dateRange: DateRange;
|
||||||
}): Promise<UptimeDetailsResult> => {
|
}): Promise<UptimeDetailsResult> => {
|
||||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||||
if (!monitor) {
|
if (!monitor) {
|
||||||
throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 });
|
throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 });
|
||||||
}
|
}
|
||||||
const rangeKey = dateRange as DateRangeKey;
|
const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, dateRange, {
|
||||||
const { start, end } = this.getDateRange(rangeKey);
|
|
||||||
const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, start, end, this.getDateFormat(rangeKey), {
|
|
||||||
type: monitor.type,
|
type: monitor.type,
|
||||||
});
|
});
|
||||||
const monitorStats = await this.monitorStatsRepository.findByMonitorId(monitor.id);
|
const monitorStats = await this.monitorStatsRepository.findByMonitorId(monitor.id);
|
||||||
@@ -243,7 +210,7 @@ export class MonitorService implements IMonitorService {
|
|||||||
}: {
|
}: {
|
||||||
teamId: string;
|
teamId: string;
|
||||||
monitorId: string;
|
monitorId: string;
|
||||||
dateRange: string;
|
dateRange: DateRange;
|
||||||
}): Promise<HardwareDetailsResult> => {
|
}): Promise<HardwareDetailsResult> => {
|
||||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||||
if (!monitor) {
|
if (!monitor) {
|
||||||
@@ -253,9 +220,7 @@ export class MonitorService implements IMonitorService {
|
|||||||
throw new AppError({ message: `${monitor.type} monitors are not supported for hardware details`, status: 400 });
|
throw new AppError({ message: `${monitor.type} monitors are not supported for hardware details`, status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const rangeKey = dateRange as DateRangeKey;
|
const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, dateRange, {
|
||||||
const { start, end } = this.getDateRange(rangeKey);
|
|
||||||
const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, start, end, this.getDateFormat(rangeKey), {
|
|
||||||
type: monitor.type,
|
type: monitor.type,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -285,7 +250,7 @@ export class MonitorService implements IMonitorService {
|
|||||||
}: {
|
}: {
|
||||||
teamId: string;
|
teamId: string;
|
||||||
monitorId: string;
|
monitorId: string;
|
||||||
dateRange: string;
|
dateRange: DateRange;
|
||||||
}): Promise<PageSpeedDetailsResult> => {
|
}): Promise<PageSpeedDetailsResult> => {
|
||||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||||
if (!monitor) {
|
if (!monitor) {
|
||||||
@@ -295,9 +260,7 @@ export class MonitorService implements IMonitorService {
|
|||||||
throw new AppError({ message: `${monitor.type} monitors are not supported for pagespeed details`, status: 400 });
|
throw new AppError({ message: `${monitor.type} monitors are not supported for pagespeed details`, status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const rangeKey = dateRange as DateRangeKey;
|
const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, dateRange, {
|
||||||
const { start, end } = this.getDateRange(rangeKey);
|
|
||||||
const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, start, end, this.getDateFormat(rangeKey), {
|
|
||||||
type: monitor.type,
|
type: monitor.type,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -324,7 +287,7 @@ export class MonitorService implements IMonitorService {
|
|||||||
}: {
|
}: {
|
||||||
teamId: string;
|
teamId: string;
|
||||||
monitorId: string;
|
monitorId: string;
|
||||||
dateRange: string;
|
dateRange: DateRange;
|
||||||
continents?: GeoContinent[];
|
continents?: GeoContinent[];
|
||||||
}): Promise<GroupedGeoCheckResult> => {
|
}): Promise<GroupedGeoCheckResult> => {
|
||||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||||
@@ -336,15 +299,7 @@ export class MonitorService implements IMonitorService {
|
|||||||
return { groupedGeoChecks: [] };
|
return { groupedGeoChecks: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
const rangeKey = dateRange as DateRangeKey;
|
const groupedGeoChecks = await this.geoChecksRepository.findGroupedByMonitorIdAndDateRange(monitor.id, dateRange, continents);
|
||||||
const { start, end } = this.getDateRange(rangeKey);
|
|
||||||
const groupedGeoChecks = await this.geoChecksRepository.findGroupedByMonitorIdAndDateRange(
|
|
||||||
monitor.id,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
this.getDateFormat(rangeKey),
|
|
||||||
continents
|
|
||||||
);
|
|
||||||
|
|
||||||
return { groupedGeoChecks };
|
return { groupedGeoChecks };
|
||||||
};
|
};
|
||||||
@@ -363,7 +318,7 @@ export class MonitorService implements IMonitorService {
|
|||||||
type?: MonitorType | MonitorType[];
|
type?: MonitorType | MonitorType[];
|
||||||
tags?: string | string[];
|
tags?: string | string[];
|
||||||
filter?: string;
|
filter?: string;
|
||||||
}): Promise<Monitor[] | null> => {
|
}): Promise<Monitor[]> => {
|
||||||
return await this.monitorsRepository.findByTeamId(teamId, { type, tags, filter });
|
return await this.monitorsRepository.findByTeamId(teamId, { type, tags, filter });
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -401,13 +356,12 @@ export class MonitorService implements IMonitorService {
|
|||||||
order,
|
order,
|
||||||
});
|
});
|
||||||
|
|
||||||
const monitorsList = (monitors ?? []) as Monitor[];
|
|
||||||
const snapshotTypes: MonitorType[] = ["hardware"];
|
const snapshotTypes: MonitorType[] = ["hardware"];
|
||||||
const requestedTypes = Array.isArray(type) ? type : type ? [type] : [];
|
const requestedTypes = Array.isArray(type) ? type : type ? [type] : [];
|
||||||
const snapshotOnlyRequest =
|
const snapshotOnlyRequest =
|
||||||
requestedTypes.length > 0 && requestedTypes.every((requestedType) => snapshotTypes.includes(requestedType as MonitorType));
|
requestedTypes.length > 0 && requestedTypes.every((requestedType) => snapshotTypes.includes(requestedType as MonitorType));
|
||||||
|
|
||||||
const monitorsWithChecks = monitorsList.map((monitor: Monitor) => {
|
const monitorsWithChecks = monitors.map((monitor: Monitor) => {
|
||||||
const rawChecks = monitor.recentChecks ?? [];
|
const rawChecks = monitor.recentChecks ?? [];
|
||||||
const isSnapshotType = snapshotOnlyRequest || snapshotTypes.includes(monitor.type);
|
const isSnapshotType = snapshotOnlyRequest || snapshotTypes.includes(monitor.type);
|
||||||
const checks = isSnapshotType ? rawChecks.slice(0, 1) : NormalizeData(rawChecks, 10, 100);
|
const checks = isSnapshotType ? rawChecks.slice(0, 1) : NormalizeData(rawChecks, 10, 100);
|
||||||
@@ -496,15 +450,7 @@ export class MonitorService implements IMonitorService {
|
|||||||
return { monitors, failedCount };
|
return { monitors, failedCount };
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteMonitor = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise<Monitor> => {
|
private deleteMonitorChildren = async (monitor: Monitor, teamId: string) => {
|
||||||
const monitor = await this.monitorsRepository.deleteById(monitorId, teamId);
|
|
||||||
await this.monitorStatsRepository.deleteByMonitorId(monitor.id).catch((err: unknown) => {
|
|
||||||
this.logger.warn({
|
|
||||||
message: `Error deleting monitor stats for monitor ${monitor.id} with name ${monitor.name}`,
|
|
||||||
service: SERVICE_NAME,
|
|
||||||
stack: err instanceof Error ? err.stack : undefined,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
await this.checksRepository.deleteByMonitorId(monitor.id).catch((err: unknown) => {
|
await this.checksRepository.deleteByMonitorId(monitor.id).catch((err: unknown) => {
|
||||||
this.logger.warn({
|
this.logger.warn({
|
||||||
message: `Error deleting checks for monitor ${monitor.id} with name ${monitor.name}`,
|
message: `Error deleting checks for monitor ${monitor.id} with name ${monitor.name}`,
|
||||||
@@ -536,6 +482,19 @@ export class MonitorService implements IMonitorService {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await this.monitorStatsRepository.deleteByMonitorId(monitor.id).catch((err: unknown) => {
|
||||||
|
this.logger.warn({
|
||||||
|
message: `Error deleting monitor stats for monitor ${monitor.id} with name ${monitor.name}`,
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
stack: err instanceof Error ? err.stack : undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
deleteMonitor = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise<Monitor> => {
|
||||||
|
const monitor = await this.monitorsRepository.deleteById(monitorId, teamId);
|
||||||
|
|
||||||
|
await this.deleteMonitorChildren(monitor, teamId);
|
||||||
await this.scheduler.deleteJob(monitor);
|
await this.scheduler.deleteJob(monitor);
|
||||||
return monitor;
|
return monitor;
|
||||||
};
|
};
|
||||||
@@ -545,12 +504,8 @@ export class MonitorService implements IMonitorService {
|
|||||||
await Promise.all(
|
await Promise.all(
|
||||||
monitors.map(async (monitor) => {
|
monitors.map(async (monitor) => {
|
||||||
try {
|
try {
|
||||||
|
await this.deleteMonitorChildren(monitor, teamId);
|
||||||
await this.scheduler.deleteJob(monitor);
|
await this.scheduler.deleteJob(monitor);
|
||||||
await this.checksRepository.deleteByMonitorId(monitor.id);
|
|
||||||
await this.geoChecksRepository.deleteByMonitorId(monitor.id);
|
|
||||||
await this.statusPagesRepository.removeMonitorFromStatusPages(monitor.id);
|
|
||||||
await this.monitorStatsRepository.deleteByMonitorId(monitor.id);
|
|
||||||
await this.incidentsRepository.deleteByMonitorId(monitor.id, teamId);
|
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
this.logger.warn({
|
this.logger.warn({
|
||||||
message: `Error deleting associated records for monitor ${monitor.id} with name ${monitor.name}`,
|
message: `Error deleting associated records for monitor ${monitor.id} with name ${monitor.name}`,
|
||||||
@@ -567,7 +522,7 @@ export class MonitorService implements IMonitorService {
|
|||||||
exportMonitorsToJSON = async ({ teamId }: { teamId: string }): Promise<Monitor[]> => {
|
exportMonitorsToJSON = async ({ teamId }: { teamId: string }): Promise<Monitor[]> => {
|
||||||
const monitors = await this.monitorsRepository.findByTeamId(teamId, {});
|
const monitors = await this.monitorsRepository.findByTeamId(teamId, {});
|
||||||
|
|
||||||
if (!monitors || monitors.length === 0) {
|
if (monitors.length === 0) {
|
||||||
throw new AppError({ message: "No monitors found to export.", service: SERVICE_NAME, method: "exportMonitorsToJSON", status: 400 });
|
throw new AppError({ message: "No monitors found to export.", service: SERVICE_NAME, method: "exportMonitorsToJSON", status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-42
@@ -3,6 +3,7 @@ export type { CheckSnapshot } from "@/domain/checks/check.type.js";
|
|||||||
import type { GeoContinent, GroupedGeoCheck } from "@/domain/geo-checks/geo-check.type.js";
|
import type { GeoContinent, GroupedGeoCheck } from "@/domain/geo-checks/geo-check.type.js";
|
||||||
export type { GeoContinent } from "@/domain/geo-checks/geo-check.type.js";
|
export type { GeoContinent } from "@/domain/geo-checks/geo-check.type.js";
|
||||||
import http from "node:http";
|
import http from "node:http";
|
||||||
|
import { HardwareStats } from "@/domain/checks/check.type.js";
|
||||||
|
|
||||||
export const HttpStatusCodes = [
|
export const HttpStatusCodes = [
|
||||||
...Object.keys(http.STATUS_CODES).map(Number),
|
...Object.keys(http.STATUS_CODES).map(Number),
|
||||||
@@ -149,48 +150,6 @@ export interface UptimeDetailsResult {
|
|||||||
monitorStats: import("../monitor-stats/monitor-stats.type.js").MonitorStats | null;
|
monitorStats: import("../monitor-stats/monitor-stats.type.js").MonitorStats | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HardwareDiskStats {
|
|
||||||
name: string;
|
|
||||||
readSpeed: number;
|
|
||||||
writeSpeed: number;
|
|
||||||
totalBytes: number;
|
|
||||||
freeBytes: number;
|
|
||||||
usagePercent: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HardwareNetStats {
|
|
||||||
name: string;
|
|
||||||
bytesSentPerSecond: number;
|
|
||||||
deltaBytesRecv: number;
|
|
||||||
deltaPacketsSent: number;
|
|
||||||
deltaPacketsRecv: number;
|
|
||||||
deltaErrIn: number;
|
|
||||||
deltaErrOut: number;
|
|
||||||
deltaDropIn: number;
|
|
||||||
deltaDropOut: number;
|
|
||||||
deltaFifoIn: number;
|
|
||||||
deltaFifoOut: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HardwareCheckStats {
|
|
||||||
bucketDate: string;
|
|
||||||
avgCpuUsage: number;
|
|
||||||
avgMemoryUsage: number;
|
|
||||||
avgTemperature: number[];
|
|
||||||
disks: HardwareDiskStats[];
|
|
||||||
net: HardwareNetStats[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HardwareStats {
|
|
||||||
aggregateData: {
|
|
||||||
totalChecks: number;
|
|
||||||
};
|
|
||||||
upChecks: {
|
|
||||||
totalChecks: number;
|
|
||||||
};
|
|
||||||
checks: HardwareCheckStats[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HardwareDetailsResult {
|
export interface HardwareDetailsResult {
|
||||||
monitor: Monitor;
|
monitor: Monitor;
|
||||||
stats: HardwareStats;
|
stats: HardwareStats;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Monitor } from "@/domain/monitors/monitor.types.js";
|
import type { Monitor } from "@/domain/monitors/monitor.type.js";
|
||||||
import type { HardwareStatusPayload, MonitorStatusResponse } from "@/types/network.js";
|
import type { HardwareStatusPayload, MonitorStatusResponse } from "@/types/network.js";
|
||||||
import type { MonitorActionDecision } from "@/worker/worker.helper.js";
|
import type { MonitorActionDecision } from "@/worker/worker.helper.js";
|
||||||
import type {
|
import type {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Monitor } from "@/domain/monitors/monitor.types.js";
|
import type { Monitor } from "@/domain/monitors/monitor.type.js";
|
||||||
import type { Notification } from "@/domain/notifications/notification.type.js";
|
import type { Notification } from "@/domain/notifications/notification.type.js";
|
||||||
import type { MonitorStatusResponse } from "@/types/network.js";
|
import type { MonitorStatusResponse } from "@/types/network.js";
|
||||||
import type { NotificationMessage } from "@/domain/notifications/notification.type.js";
|
import type { NotificationMessage } from "@/domain/notifications/notification.type.js";
|
||||||
@@ -9,6 +9,9 @@ import type { MonitorActionDecision } from "@/worker/worker.helper.js";
|
|||||||
import type { ISettingsService } from "@/domain/app-settings/app-settings.service.js";
|
import type { ISettingsService } from "@/domain/app-settings/app-settings.service.js";
|
||||||
import { ILogger } from "@/utils/logger.js";
|
import { ILogger } from "@/utils/logger.js";
|
||||||
import type { INotificationMessageBuilder } from "@/domain/notifications/notification.message-builder.js";
|
import type { INotificationMessageBuilder } from "@/domain/notifications/notification.message-builder.js";
|
||||||
|
import type { NotificationChannel } from "@/domain/notifications/notification.type.js";
|
||||||
|
|
||||||
|
export type NotificationProviderRegistry = Record<NotificationChannel, INotificationProvider>;
|
||||||
|
|
||||||
export interface INotificationsService {
|
export interface INotificationsService {
|
||||||
createNotification: (notificationData: Partial<Notification>, userId: string, teamId: string) => Promise<Notification>;
|
createNotification: (notificationData: Partial<Notification>, userId: string, teamId: string) => Promise<Notification>;
|
||||||
@@ -29,52 +32,29 @@ export class NotificationsService implements INotificationsService {
|
|||||||
|
|
||||||
private notificationsRepository: INotificationsRepository;
|
private notificationsRepository: INotificationsRepository;
|
||||||
private monitorsRepository: IMonitorsRepository;
|
private monitorsRepository: IMonitorsRepository;
|
||||||
private webhookProvider: INotificationProvider;
|
private providers: NotificationProviderRegistry;
|
||||||
private emailProvider: INotificationProvider;
|
|
||||||
private slackProvider: INotificationProvider;
|
|
||||||
private discordProvider: INotificationProvider;
|
|
||||||
private pagerDutyProvider: INotificationProvider;
|
|
||||||
private matrixProvider: INotificationProvider;
|
|
||||||
private teamsProvider: INotificationProvider;
|
|
||||||
private telegramProvider: INotificationProvider;
|
|
||||||
private pushoverProvider: INotificationProvider;
|
|
||||||
private twilioProvider: INotificationProvider;
|
|
||||||
private ntfyProvider: INotificationProvider;
|
|
||||||
private logger: ILogger;
|
|
||||||
private settingsService: ISettingsService;
|
private settingsService: ISettingsService;
|
||||||
|
private logger: ILogger;
|
||||||
private notificationMessageBuilder: INotificationMessageBuilder;
|
private notificationMessageBuilder: INotificationMessageBuilder;
|
||||||
|
|
||||||
constructor(
|
constructor({
|
||||||
notificationsRepository: INotificationsRepository,
|
notificationsRepository,
|
||||||
monitorsRepository: IMonitorsRepository,
|
monitorsRepository,
|
||||||
webhookProvider: INotificationProvider,
|
providers,
|
||||||
emailProvider: INotificationProvider,
|
settingsService,
|
||||||
slackProvider: INotificationProvider,
|
logger,
|
||||||
discordProvider: INotificationProvider,
|
notificationMessageBuilder,
|
||||||
pagerDutyProvider: INotificationProvider,
|
}: {
|
||||||
matrixProvider: INotificationProvider,
|
notificationsRepository: INotificationsRepository;
|
||||||
teamsProvider: INotificationProvider,
|
monitorsRepository: IMonitorsRepository;
|
||||||
telegramProvider: INotificationProvider,
|
providers: NotificationProviderRegistry;
|
||||||
pushoverProvider: INotificationProvider,
|
settingsService: ISettingsService;
|
||||||
twilioProvider: INotificationProvider,
|
logger: ILogger;
|
||||||
ntfyProvider: INotificationProvider,
|
notificationMessageBuilder: INotificationMessageBuilder;
|
||||||
settingsService: ISettingsService,
|
}) {
|
||||||
logger: ILogger,
|
|
||||||
notificationMessageBuilder: INotificationMessageBuilder
|
|
||||||
) {
|
|
||||||
this.notificationsRepository = notificationsRepository;
|
this.notificationsRepository = notificationsRepository;
|
||||||
this.monitorsRepository = monitorsRepository;
|
this.monitorsRepository = monitorsRepository;
|
||||||
this.webhookProvider = webhookProvider;
|
this.providers = providers;
|
||||||
this.emailProvider = emailProvider;
|
|
||||||
this.slackProvider = slackProvider;
|
|
||||||
this.discordProvider = discordProvider;
|
|
||||||
this.pagerDutyProvider = pagerDutyProvider;
|
|
||||||
this.matrixProvider = matrixProvider;
|
|
||||||
this.teamsProvider = teamsProvider;
|
|
||||||
this.telegramProvider = telegramProvider;
|
|
||||||
this.pushoverProvider = pushoverProvider;
|
|
||||||
this.twilioProvider = twilioProvider;
|
|
||||||
this.ntfyProvider = ntfyProvider;
|
|
||||||
this.settingsService = settingsService;
|
this.settingsService = settingsService;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.notificationMessageBuilder = notificationMessageBuilder;
|
this.notificationMessageBuilder = notificationMessageBuilder;
|
||||||
@@ -97,37 +77,16 @@ export class NotificationsService implements INotificationsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Route to provider based on notification type
|
// Route to provider based on notification type
|
||||||
switch (notification.type) {
|
const provider = this.providers[notification.type];
|
||||||
case "webhook":
|
if (!provider) {
|
||||||
return await this.webhookProvider.sendMessage!(notification, notificationMessage);
|
this.logger.warn({
|
||||||
case "slack":
|
message: `Unknown notification type: ${notification.type}`,
|
||||||
return await this.slackProvider.sendMessage!(notification, notificationMessage);
|
service: SERVICE_NAME,
|
||||||
case "matrix":
|
method: "send",
|
||||||
return await this.matrixProvider.sendMessage!(notification, notificationMessage);
|
});
|
||||||
case "pager_duty":
|
return false;
|
||||||
return await this.pagerDutyProvider.sendMessage!(notification, notificationMessage);
|
|
||||||
case "discord":
|
|
||||||
return await this.discordProvider.sendMessage!(notification, notificationMessage);
|
|
||||||
case "email":
|
|
||||||
return await this.emailProvider.sendMessage!(notification, notificationMessage);
|
|
||||||
case "teams":
|
|
||||||
return await this.teamsProvider.sendMessage!(notification, notificationMessage);
|
|
||||||
case "telegram":
|
|
||||||
return await this.telegramProvider.sendMessage!(notification, notificationMessage);
|
|
||||||
case "pushover":
|
|
||||||
return await this.pushoverProvider.sendMessage!(notification, notificationMessage);
|
|
||||||
case "twilio":
|
|
||||||
return await this.twilioProvider.sendMessage!(notification, notificationMessage);
|
|
||||||
case "ntfy":
|
|
||||||
return await this.ntfyProvider.sendMessage!(notification, notificationMessage);
|
|
||||||
default:
|
|
||||||
this.logger.warn({
|
|
||||||
message: `Unknown notification type: ${notification.type}`,
|
|
||||||
service: SERVICE_NAME,
|
|
||||||
method: "send",
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return await provider.sendMessage(notification, notificationMessage);
|
||||||
};
|
};
|
||||||
|
|
||||||
private sendNotifications = async (monitor: Monitor, monitorStatusResponse: MonitorStatusResponse, decision: MonitorActionDecision) => {
|
private sendNotifications = async (monitor: Monitor, monitorStatusResponse: MonitorStatusResponse, decision: MonitorActionDecision) => {
|
||||||
@@ -165,32 +124,26 @@ export class NotificationsService implements INotificationsService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
sendTestNotification = async (notification: Partial<Notification>) => {
|
sendTestNotification = async (notification: Partial<Notification>) => {
|
||||||
switch (notification.type) {
|
const type = notification.type;
|
||||||
case "email":
|
if (!type) {
|
||||||
return await this.emailProvider.sendTestAlert(notification);
|
this.logger.warn({
|
||||||
case "slack":
|
message: "Notification type not provided",
|
||||||
return await this.slackProvider.sendTestAlert(notification);
|
service: SERVICE_NAME,
|
||||||
case "discord":
|
method: "sendTestNotification",
|
||||||
return await this.discordProvider.sendTestAlert(notification);
|
});
|
||||||
case "pager_duty":
|
return false;
|
||||||
return await this.pagerDutyProvider.sendTestAlert(notification);
|
|
||||||
case "matrix":
|
|
||||||
return await this.matrixProvider.sendTestAlert(notification);
|
|
||||||
case "webhook":
|
|
||||||
return await this.webhookProvider.sendTestAlert(notification);
|
|
||||||
case "teams":
|
|
||||||
return await this.teamsProvider.sendTestAlert(notification);
|
|
||||||
case "telegram":
|
|
||||||
return await this.telegramProvider.sendTestAlert(notification);
|
|
||||||
case "pushover":
|
|
||||||
return await this.pushoverProvider.sendTestAlert(notification);
|
|
||||||
case "twilio":
|
|
||||||
return await this.twilioProvider.sendTestAlert(notification);
|
|
||||||
case "ntfy":
|
|
||||||
return await this.ntfyProvider.sendTestAlert(notification);
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const provider = this.providers[type];
|
||||||
|
if (!provider) {
|
||||||
|
this.logger.warn({
|
||||||
|
message: `Unknown notification type: ${notification.type}`,
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "sendTestNotification",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return await provider.sendTestAlert(notification);
|
||||||
};
|
};
|
||||||
|
|
||||||
testAllNotifications = async (notificationIds: string[]) => {
|
testAllNotifications = async (notificationIds: string[]) => {
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import { QueueWorkerDocument } from "@/domain/queue-workers/queue-worker.type.js
|
|||||||
|
|
||||||
// Single source of truth for worker liveness: the TTL that GCs stale records and
|
// Single source of truth for worker liveness: the TTL that GCs stale records and
|
||||||
// the window readers use to count a worker as alive. Heartbeat cadence must stay well below it.
|
// the window readers use to count a worker as alive. Heartbeat cadence must stay well below it.
|
||||||
export const WORKER_TTL_SECONDS = 180;
|
const WORKER_TTL_SECONDS = 180;
|
||||||
|
export const WORKER_STALE_MS = WORKER_TTL_SECONDS * 1000;
|
||||||
|
|
||||||
const QueueWorkerSchema = new Schema<QueueWorkerDocument>(
|
const QueueWorkerSchema = new Schema<QueueWorkerDocument>(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
} from "@/domain/status-pages/status-page.type.js";
|
} from "@/domain/status-pages/status-page.type.js";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import { normalizeStatusPageDomain } from "@/utils/statusPageDomain.js";
|
import { normalizeStatusPageDomain } from "@/utils/statusPageDomain.js";
|
||||||
import { Monitor } from "@/domain/monitors/monitor.types.js";
|
import { Monitor } from "@/domain/monitors/monitor.type.js";
|
||||||
import { NormalizeData } from "@/utils/dataUtils.js";
|
import { NormalizeData } from "@/utils/dataUtils.js";
|
||||||
|
|
||||||
export interface IStatusPageService {
|
export interface IStatusPageService {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Monitor } from "@/domain/monitors/monitor.types.js";
|
import type { Monitor } from "@/domain/monitors/monitor.type.js";
|
||||||
import type { NormalizedCheck, CheckSnapshot } from "@/domain/checks/check.type.js";
|
import type { NormalizedCheck, CheckSnapshot } from "@/domain/checks/check.type.js";
|
||||||
export const StatusPageTypes = ["uptime", "infrastructure"] as const;
|
export const StatusPageTypes = ["uptime", "infrastructure"] as const;
|
||||||
export type StatusPageType = (typeof StatusPageTypes)[number];
|
export type StatusPageType = (typeof StatusPageTypes)[number];
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { IUsersRepository } from "@/domain/users/user.repository.interface.js";
|
|||||||
import { UserModel, type UserDocument } from "@/domain/users/user.model.js";
|
import { UserModel, type UserDocument } from "@/domain/users/user.model.js";
|
||||||
import type { User, UserProfileImage } from "@/domain/users/user.type.js";
|
import type { User, UserProfileImage } from "@/domain/users/user.type.js";
|
||||||
import { GenerateAvatarImage } from "@/utils/imageProcessing.js";
|
import { GenerateAvatarImage } from "@/utils/imageProcessing.js";
|
||||||
import { ParseBoolean } from "@/utils/utils.js";
|
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import { toStringId, toDateString } from "@/utils/mongoMappers.js";
|
import { toStringId, toDateString } from "@/utils/mongoMappers.js";
|
||||||
const SERVICE_NAME = "MongoUsersRepository";
|
const SERVICE_NAME = "MongoUsersRepository";
|
||||||
@@ -72,7 +71,7 @@ class MongoUsersRepository implements IUsersRepository {
|
|||||||
findById = async (id: string) => {
|
findById = async (id: string) => {
|
||||||
const user = await UserModel.findById(id).select("-password").select("-profileImage");
|
const user = await UserModel.findById(id).select("-password").select("-profileImage");
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error("User not found");
|
throw new AppError({ message: "User not found", service: SERVICE_NAME, status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.toEntity(user);
|
return this.toEntity(user);
|
||||||
@@ -87,9 +86,8 @@ class MongoUsersRepository implements IUsersRepository {
|
|||||||
const candidateUser = { ...patch };
|
const candidateUser = { ...patch };
|
||||||
let unsetFields: Record<string, 1> | undefined;
|
let unsetFields: Record<string, 1> | undefined;
|
||||||
|
|
||||||
if (ParseBoolean(candidateUser.deleteProfileImage) === true) {
|
if (candidateUser.deleteProfileImage === true) {
|
||||||
unsetFields = { profileImage: 1, avatarImage: 1 };
|
unsetFields = { profileImage: 1, avatarImage: 1 };
|
||||||
delete candidateUser.deleteProfileImage;
|
|
||||||
} else if (file) {
|
} else if (file) {
|
||||||
// 1. Save the full size image
|
// 1. Save the full size image
|
||||||
candidateUser.profileImage = {
|
candidateUser.profileImage = {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export interface IUserService {
|
|||||||
createUser(userData: Partial<User>, teamId: string, actorRoles: UserRole[], file: Express.Multer.File | null): Promise<User>;
|
createUser(userData: Partial<User>, teamId: string, actorRoles: UserRole[], file: Express.Multer.File | null): Promise<User>;
|
||||||
loginUser(email: string, password: string): Promise<{ user: User; token: string }>;
|
loginUser(email: string, password: string): Promise<{ user: User; token: string }>;
|
||||||
editUser(
|
editUser(
|
||||||
updates: Partial<User & { newPassword?: string }>,
|
updates: Partial<User & { newPassword?: string; deleteProfileImage?: boolean }>,
|
||||||
file: Express.Multer.File | null,
|
file: Express.Multer.File | null,
|
||||||
currentUserId: string,
|
currentUserId: string,
|
||||||
currentUserEmail: string
|
currentUserEmail: string
|
||||||
@@ -103,10 +103,6 @@ export class UserService implements IUserService {
|
|||||||
this.teamsRepository = teamsRepository;
|
this.teamsRepository = teamsRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return UserService.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
issueToken = (payload: Partial<User>, appSettings: EnvConfig) => {
|
issueToken = (payload: Partial<User>, appSettings: EnvConfig) => {
|
||||||
return this.jwt.sign(payload, appSettings.jwtSecret, { expiresIn: appSettings.jwtTTL });
|
return this.jwt.sign(payload, appSettings.jwtSecret, { expiresIn: appSettings.jwtTTL });
|
||||||
};
|
};
|
||||||
@@ -242,7 +238,7 @@ export class UserService implements IUserService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
editUser = async (
|
editUser = async (
|
||||||
updates: Partial<User & { newPassword?: string }>,
|
updates: Partial<User & { newPassword?: string; deleteProfileImage?: boolean }>,
|
||||||
file: Express.Multer.File | null,
|
file: Express.Multer.File | null,
|
||||||
currentUserId: string,
|
currentUserId: string,
|
||||||
currentUserEmail: string
|
currentUserEmail: string
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ const startApp = async () => {
|
|||||||
const env = validateEnv(logger);
|
const env = validateEnv(logger);
|
||||||
logger.setLogLevel(env.LOG_LEVEL);
|
logger.setLogLevel(env.LOG_LEVEL);
|
||||||
const settingsService = new SettingsService(env);
|
const settingsService = new SettingsService(env);
|
||||||
const envSettings = settingsService.loadSettings();
|
const envSettings = settingsService.getSettings();
|
||||||
const { queueMode, queuePrimaryProcesses } = envSettings;
|
const { queueMode, queuePrimaryProcesses } = envSettings;
|
||||||
|
|
||||||
logger.info({
|
logger.info({
|
||||||
|
|||||||
@@ -51,10 +51,6 @@ export class BufferService implements IBufferService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return BufferService.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
addToBuffer(check: Check) {
|
addToBuffer(check: Check) {
|
||||||
try {
|
try {
|
||||||
this.buffer.push(check);
|
this.buffer.push(check);
|
||||||
|
|||||||
@@ -56,10 +56,6 @@ export class EmailService implements IEmailService {
|
|||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return EmailService.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
init = () => {
|
init = () => {
|
||||||
const loadTemplate = (templateName: string) => {
|
const loadTemplate = (templateName: string) => {
|
||||||
try {
|
try {
|
||||||
@@ -79,7 +75,6 @@ export class EmailService implements IEmailService {
|
|||||||
this.templateLookup = {
|
this.templateLookup = {
|
||||||
welcomeEmailTemplate: loadTemplate("welcomeEmail"),
|
welcomeEmailTemplate: loadTemplate("welcomeEmail"),
|
||||||
employeeActivationTemplate: loadTemplate("employeeActivation"),
|
employeeActivationTemplate: loadTemplate("employeeActivation"),
|
||||||
noIncidentsThisWeekTemplate: loadTemplate("noIncidentsThisWeek"),
|
|
||||||
passwordResetTemplate: loadTemplate("passwordReset"),
|
passwordResetTemplate: loadTemplate("passwordReset"),
|
||||||
testEmailTemplate: loadTemplate("testEmailTemplate"),
|
testEmailTemplate: loadTemplate("testEmailTemplate"),
|
||||||
unifiedNotificationTemplate: loadTemplate("unifiedNotification"),
|
unifiedNotificationTemplate: loadTemplate("unifiedNotification"),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { GeoContinent, GeoCheckResult, GeoCheckTimings, GeoCheckLocation } from "@/domain/geo-checks/geo-check.type.js";
|
import type { GeoContinent, GeoCheckResult, GeoCheckTimings, GeoCheckLocation } from "@/domain/geo-checks/geo-check.type.js";
|
||||||
import { supportsGeoCheck, type HttpStatusCode } from "@/domain/monitors/monitor.types.js";
|
import { supportsGeoCheck, type HttpStatusCode } from "@/domain/monitors/monitor.type.js";
|
||||||
import { MonitorType } from "@/domain/monitors/monitor.types.js";
|
import { MonitorType } from "@/domain/monitors/monitor.type.js";
|
||||||
import type { ILogger } from "@/utils/logger.js";
|
import type { ILogger } from "@/utils/logger.js";
|
||||||
import got from "got";
|
import got from "got";
|
||||||
import { isStatusUp } from "@/service/network/utils.js";
|
import { isStatusUp } from "@/service/network/utils.js";
|
||||||
@@ -61,7 +61,6 @@ interface GlobalPingProbeResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IGlobalPingService {
|
export interface IGlobalPingService {
|
||||||
readonly serviceName: string;
|
|
||||||
createMeasurement(monitorType: MonitorType, url: string, locations: GeoContinent[]): Promise<string | null>;
|
createMeasurement(monitorType: MonitorType, url: string, locations: GeoContinent[]): Promise<string | null>;
|
||||||
pollForResults(measurementId: string, timeoutMs?: number, customUpCodes?: HttpStatusCode[]): Promise<GeoCheckResult[]>;
|
pollForResults(measurementId: string, timeoutMs?: number, customUpCodes?: HttpStatusCode[]): Promise<GeoCheckResult[]>;
|
||||||
}
|
}
|
||||||
@@ -75,10 +74,6 @@ export class GlobalPingService implements IGlobalPingService {
|
|||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return GlobalPingService.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
async createMeasurement(monitorType: MonitorType, url: string, locations: GeoContinent[]): Promise<string | null> {
|
async createMeasurement(monitorType: MonitorType, url: string, locations: GeoContinent[]): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
if (!supportsGeoCheck(monitorType)) {
|
if (!supportsGeoCheck(monitorType)) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Monitor } from "@/domain/monitors/monitor.types.js";
|
import { Monitor } from "@/domain/monitors/monitor.type.js";
|
||||||
import jmespath from "jmespath";
|
import jmespath from "jmespath";
|
||||||
import RE2 from "re2";
|
import RE2 from "re2";
|
||||||
type JmesPath = typeof jmespath;
|
type JmesPath = typeof jmespath;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { DNSStatusPayload, MonitorStatusResponse } from "@/types/network.js";
|
import { DNSStatusPayload, MonitorStatusResponse } from "@/types/network.js";
|
||||||
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
|
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
|
||||||
import type { Resolver } from "dns/promises";
|
import type { Resolver } from "dns/promises";
|
||||||
import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js";
|
import { Monitor, MonitorType } from "@/domain/monitors/monitor.type.js";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import { NETWORK_ERROR, timeRequest } from "@/service/network/utils.js";
|
import { timeRequest } from "@/service/network/utils.js";
|
||||||
|
import { NETWORK_ERROR } from "@/types/network.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "DNSProvider";
|
const SERVICE_NAME = "DNSProvider";
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
|
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
|
||||||
import { DockerStatusPayload, MonitorStatusResponse } from "@/types/network.js";
|
import { DockerStatusPayload, MonitorStatusResponse } from "@/types/network.js";
|
||||||
import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js";
|
import { Monitor, MonitorType } from "@/domain/monitors/monitor.type.js";
|
||||||
import { ILogger } from "@/utils/logger.js";
|
import { ILogger } from "@/utils/logger.js";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import Dockerode from "dockerode";
|
import Dockerode from "dockerode";
|
||||||
import { NETWORK_ERROR, timeRequest } from "@/service/network/utils.js";
|
import { timeRequest } from "@/service/network/utils.js";
|
||||||
|
import { NETWORK_ERROR } from "@/types/network.js";
|
||||||
type DockerodeType = typeof Dockerode;
|
type DockerodeType = typeof Dockerode;
|
||||||
|
|
||||||
const SERVICE_NAME = "DockerProvider";
|
const SERVICE_NAME = "DockerProvider";
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
|
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
|
||||||
import { GameStatusPayload, MonitorStatusResponse } from "@/types/network.js";
|
import { GameStatusPayload, MonitorStatusResponse } from "@/types/network.js";
|
||||||
import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js";
|
import { Monitor, MonitorType } from "@/domain/monitors/monitor.type.js";
|
||||||
import { GameDig } from "gamedig";
|
import { GameDig } from "gamedig";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import { NETWORK_ERROR } from "@/service/network/utils.js";
|
import { NETWORK_ERROR } from "@/types/network.js";
|
||||||
import { ILogger } from "@/utils/logger.js";
|
import { ILogger } from "@/utils/logger.js";
|
||||||
|
|
||||||
type GameDigType = typeof GameDig;
|
type GameDigType = typeof GameDig;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
|
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
|
||||||
import { GrpcStatusPayload, MonitorStatusResponse } from "@/types/network.js";
|
import { GrpcStatusPayload, MonitorStatusResponse } from "@/types/network.js";
|
||||||
import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js";
|
import { Monitor, MonitorType } from "@/domain/monitors/monitor.type.js";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import { timeRequest } from "@/service/network/utils.js";
|
import { timeRequest } from "@/service/network/utils.js";
|
||||||
import * as grpc from "@grpc/grpc-js";
|
import * as grpc from "@grpc/grpc-js";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
|
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
|
||||||
import { HardwareStatusPayload, MonitorStatusResponse } from "@/types/network.js";
|
import { HardwareStatusPayload, MonitorStatusResponse } from "@/types/network.js";
|
||||||
import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js";
|
import { Monitor, MonitorType } from "@/domain/monitors/monitor.type.js";
|
||||||
import { HttpProvider } from "@/service/network/HttpProvider.js";
|
import { HttpProvider } from "@/service/network/HttpProvider.js";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import { HttpStatusPayload } from "@/types/network.js";
|
|||||||
import { MonitorStatusResponse } from "@/types/network.js";
|
import { MonitorStatusResponse } from "@/types/network.js";
|
||||||
import { Agent as HttpsAgent } from "https";
|
import { Agent as HttpsAgent } from "https";
|
||||||
import { Agent as HttpAgent } from "http";
|
import { Agent as HttpAgent } from "http";
|
||||||
import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js";
|
import { Monitor, MonitorType } from "@/domain/monitors/monitor.type.js";
|
||||||
import { NETWORK_ERROR, isStatusUp } from "@/service/network/utils.js";
|
import { isStatusUp } from "@/service/network/utils.js";
|
||||||
|
import { NETWORK_ERROR } from "@/types/network.js";
|
||||||
import CacheableLookup from "cacheable-lookup";
|
import CacheableLookup from "cacheable-lookup";
|
||||||
|
|
||||||
export class HttpProvider implements IStatusProvider<HttpStatusPayload> {
|
export class HttpProvider implements IStatusProvider<HttpStatusPayload> {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js";
|
import { Monitor, MonitorType } from "@/domain/monitors/monitor.type.js";
|
||||||
import { MonitorStatusResponse } from "@/types/network.js";
|
import { MonitorStatusResponse } from "@/types/network.js";
|
||||||
|
|
||||||
export interface IStatusProvider<T> {
|
export interface IStatusProvider<T> {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
|
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
|
||||||
import { MonitorStatusResponse, PageSpeedStatusPayload } from "@/types/network.js";
|
import { MonitorStatusResponse, PageSpeedStatusPayload } from "@/types/network.js";
|
||||||
import { Monitor, MonitorType, DefaultPageSpeedStrategy } from "@/domain/monitors/monitor.types.js";
|
import { Monitor, MonitorType, DefaultPageSpeedStrategy } from "@/domain/monitors/monitor.type.js";
|
||||||
import { HttpProvider } from "@/service/network/HttpProvider.js";
|
import { HttpProvider } from "@/service/network/HttpProvider.js";
|
||||||
import { ISettingsService } from "@/domain/app-settings/app-settings.service.js";
|
import { ISettingsService } from "@/domain/app-settings/app-settings.service.js";
|
||||||
import { ILogger } from "@/utils/logger.js";
|
import { ILogger } from "@/utils/logger.js";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { PingStatusPayload } from "@/types/network.js";
|
import { PingStatusPayload } from "@/types/network.js";
|
||||||
import { IStatusProvider } from "./IStatusProvider.js";
|
import { IStatusProvider } from "./IStatusProvider.js";
|
||||||
import { MonitorType, Monitor } from "@/domain/monitors/monitor.types.js";
|
import { MonitorType, Monitor } from "@/domain/monitors/monitor.type.js";
|
||||||
import { MonitorStatusResponse } from "@/types/network.js";
|
import { MonitorStatusResponse } from "@/types/network.js";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import ping from "ping";
|
import ping from "ping";
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
|
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
|
||||||
import { PortStatusPayload, MonitorStatusResponse } from "@/types/network.js";
|
import { PortStatusPayload, MonitorStatusResponse } from "@/types/network.js";
|
||||||
import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js";
|
import { Monitor, MonitorType } from "@/domain/monitors/monitor.type.js";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import { NETWORK_ERROR, timeRequest } from "@/service/network/utils.js";
|
import { timeRequest } from "@/service/network/utils.js";
|
||||||
|
import { NETWORK_ERROR } from "@/types/network.js";
|
||||||
import * as net from "net";
|
import * as net from "net";
|
||||||
type NetType = typeof net;
|
type NetType = typeof net;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
|
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
|
||||||
import { WebSocketStatusPayload, MonitorStatusResponse } from "@/types/network.js";
|
import { WebSocketStatusPayload, MonitorStatusResponse } from "@/types/network.js";
|
||||||
import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js";
|
import { Monitor, MonitorType } from "@/domain/monitors/monitor.type.js";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import { NETWORK_ERROR, timeRequest } from "@/service/network/utils.js";
|
import { timeRequest } from "@/service/network/utils.js";
|
||||||
|
import { NETWORK_ERROR } from "@/types/network.js";
|
||||||
import type WebSocket from "ws";
|
import type WebSocket from "ws";
|
||||||
|
|
||||||
type WebSocketConstructor = typeof WebSocket;
|
type WebSocketConstructor = typeof WebSocket;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { HttpStatusCode } from "@/domain/monitors/monitor.types.js";
|
import type { HttpStatusCode } from "@/domain/monitors/monitor.type.js";
|
||||||
|
|
||||||
export const timeRequest = async <T>(operation: () => Promise<T>): Promise<{ response: T | null; responseTime: number; error: unknown }> => {
|
export const timeRequest = async <T>(operation: () => Promise<T>): Promise<{ response: T | null; responseTime: number; error: unknown }> => {
|
||||||
const start = process.hrtime.bigint();
|
const start = process.hrtime.bigint();
|
||||||
@@ -12,9 +12,6 @@ export const timeRequest = async <T>(operation: () => Promise<T>): Promise<{ res
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NETWORK_ERROR = 5000;
|
|
||||||
export const PING_ERROR = 5001;
|
|
||||||
|
|
||||||
export const isStatusUp = (statusCode: number | undefined, customUpCodes: HttpStatusCode[] = []): boolean => {
|
export const isStatusUp = (statusCode: number | undefined, customUpCodes: HttpStatusCode[] = []): boolean => {
|
||||||
if (statusCode === undefined) return false;
|
if (statusCode === undefined) return false;
|
||||||
return (statusCode >= 200 && statusCode < 300) || customUpCodes.includes(statusCode);
|
return (statusCode >= 200 && statusCode < 300) || customUpCodes.includes(statusCode);
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import type { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js";
|
import type { Monitor, MonitorType } from "@/domain/monitors/monitor.type.js";
|
||||||
import type { MonitorPayloadMap, MonitorStatusResponse } from "@/types/network.js";
|
import type { MonitorPayloadMap, MonitorStatusResponse } from "@/types/network.js";
|
||||||
import type { AxiosStatic } from "axios";
|
import type { AxiosStatic } from "axios";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import { NETWORK_ERROR } from "@/service/network/utils.js";
|
import { NETWORK_ERROR } from "@/types/network.js";
|
||||||
import { ILogger } from "@/utils/logger.js";
|
import { ILogger } from "@/utils/logger.js";
|
||||||
import { IStatusProvider } from "./network/IStatusProvider.js";
|
import { IStatusProvider } from "./network/IStatusProvider.js";
|
||||||
const SERVICE_NAME = "NetworkService";
|
const SERVICE_NAME = "NetworkService";
|
||||||
|
|
||||||
export interface INetworkService {
|
export interface INetworkService {
|
||||||
readonly serviceName: string;
|
|
||||||
requestStatus<T extends MonitorType>(monitor: Monitor & { type: T }): Promise<MonitorStatusResponse<MonitorPayloadMap[T]>>;
|
requestStatus<T extends MonitorType>(monitor: Monitor & { type: T }): Promise<MonitorStatusResponse<MonitorPayloadMap[T]>>;
|
||||||
requestWebhook(
|
requestWebhook(
|
||||||
type: string,
|
type: string,
|
||||||
@@ -39,10 +38,6 @@ export class NetworkService implements INetworkService {
|
|||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName(): string {
|
|
||||||
return NetworkService.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main entry point
|
// Main entry point
|
||||||
async requestStatus<T extends MonitorType>(monitor: Monitor & { type: T }): Promise<MonitorStatusResponse<MonitorPayloadMap[T]>> {
|
async requestStatus<T extends MonitorType>(monitor: Monitor & { type: T }): Promise<MonitorStatusResponse<MonitorPayloadMap[T]>> {
|
||||||
const provider = this.providers.find((p) => p.supports(monitor.type));
|
const provider = this.providers.find((p) => p.supports(monitor.type));
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { IMonitorStatsRepository } from "@/domain/monitor-stats/monitor-stats.repository.interface.js";
|
import { IMonitorStatsRepository } from "@/domain/monitor-stats/monitor-stats.repository.interface.js";
|
||||||
import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js";
|
import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js";
|
||||||
import type { Check, CheckDiskInfo, CheckSnapshot } from "@/domain/checks/check.type.js";
|
import type { Check, CheckDiskInfo, CheckSnapshot } from "@/domain/checks/check.type.js";
|
||||||
import type { Monitor, MonitorStatus } from "@/domain/monitors/monitor.types.js";
|
import type { Monitor, MonitorStatus } from "@/domain/monitors/monitor.type.js";
|
||||||
import type {
|
import type {
|
||||||
DockerStatusPayload,
|
DockerStatusPayload,
|
||||||
GameStatusPayload,
|
GameStatusPayload,
|
||||||
@@ -17,7 +17,7 @@ import type {
|
|||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import { ILogger } from "@/utils/logger.js";
|
import { ILogger } from "@/utils/logger.js";
|
||||||
import type { HardwareStatusMetrics } from "@/types/network.js";
|
import type { HardwareStatusMetrics } from "@/types/network.js";
|
||||||
import { MAX_RECENT_CHECKS } from "@/domain/monitors/monitor.types.js";
|
import { MAX_RECENT_CHECKS } from "@/domain/monitors/monitor.type.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "StatusService";
|
const SERVICE_NAME = "StatusService";
|
||||||
const HARDWARE_ALERT_COUNTER_START = 5;
|
const HARDWARE_ALERT_COUNTER_START = 5;
|
||||||
@@ -57,10 +57,6 @@ export class StatusService implements IStatusService {
|
|||||||
this.monitorStatsRepository = monitorStatsRepository;
|
this.monitorStatsRepository = monitorStatsRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return StatusService.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateRunningStats(monitor: Monitor, networkResponse: MonitorStatusResponse) {
|
async updateRunningStats(monitor: Monitor, networkResponse: MonitorStatusResponse) {
|
||||||
try {
|
try {
|
||||||
await this.monitorStatsRepository.updateByMonitorId(monitor.id, {
|
await this.monitorStatsRepository.updateByMonitorId(monitor.id, {
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
<!-- name -->
|
|
||||||
<mjml>
|
|
||||||
<mj-head>
|
|
||||||
<mj-font
|
|
||||||
name="Roboto"
|
|
||||||
href="https://fonts.googleapis.com/css?family=Roboto:300,500"
|
|
||||||
></mj-font>
|
|
||||||
<mj-attributes>
|
|
||||||
<mj-all font-family="Roboto, Helvetica, sans-serif"></mj-all>
|
|
||||||
<mj-text
|
|
||||||
font-weight="300"
|
|
||||||
font-size="16px"
|
|
||||||
color="#616161"
|
|
||||||
line-height="24px"
|
|
||||||
></mj-text>
|
|
||||||
<mj-section padding="0px"></mj-section>
|
|
||||||
</mj-attributes>
|
|
||||||
</mj-head>
|
|
||||||
<mj-body>
|
|
||||||
<mj-section padding="20px 0">
|
|
||||||
<mj-column width="100%">
|
|
||||||
<mj-text
|
|
||||||
align="left"
|
|
||||||
font-size="10px"
|
|
||||||
>
|
|
||||||
Message from Checkmate Service
|
|
||||||
</mj-text>
|
|
||||||
</mj-column>
|
|
||||||
</mj-section>
|
|
||||||
<mj-section>
|
|
||||||
<mj-column width="100%">
|
|
||||||
<mj-text>
|
|
||||||
<p>Hello {{ name }}!</p>
|
|
||||||
<p>
|
|
||||||
We hope you’re finding Checkmate helpful in monitoring your infrastructure. Your support means a lot to us, and we
|
|
||||||
<b>truly appreciate</b> having you as part of our community.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
If you’re happy with Checkmate, we’d love to hear about your experience! Leaving a review on G2 helps others discover Checkmate and
|
|
||||||
supports our ongoing improvements.
|
|
||||||
</p>
|
|
||||||
G2 Link: TBD
|
|
||||||
<p>Thank you for taking the time to share your thoughts - we greatly appreciate it!</p>
|
|
||||||
Checkmate Team
|
|
||||||
</mj-text>
|
|
||||||
</mj-column>
|
|
||||||
<mj-column width="100%">
|
|
||||||
<mj-divider
|
|
||||||
border-width="1px"
|
|
||||||
border-color="#E0E0E0"
|
|
||||||
></mj-divider>
|
|
||||||
<mj-text font-size="12px">
|
|
||||||
<p>This email was sent by Checkmate.</p>
|
|
||||||
</mj-text>
|
|
||||||
</mj-column>
|
|
||||||
</mj-section>
|
|
||||||
</mj-body>
|
|
||||||
</mjml>
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
<mjml>
|
|
||||||
<mj-head>
|
|
||||||
<mj-font
|
|
||||||
name="Roboto"
|
|
||||||
href="https://fonts.googleapis.com/css?family=Roboto:300,500"
|
|
||||||
></mj-font>
|
|
||||||
<mj-attributes>
|
|
||||||
<mj-all font-family="Roboto, Helvetica, sans-serif"></mj-all>
|
|
||||||
<mj-text
|
|
||||||
font-weight="300"
|
|
||||||
font-size="16px"
|
|
||||||
color="#616161"
|
|
||||||
line-height="24px"
|
|
||||||
></mj-text>
|
|
||||||
<mj-section padding="0px"></mj-section>
|
|
||||||
</mj-attributes>
|
|
||||||
</mj-head>
|
|
||||||
<mj-body>
|
|
||||||
<mj-section padding="20px 0">
|
|
||||||
<mj-column width="100%">
|
|
||||||
<mj-text
|
|
||||||
align="left"
|
|
||||||
font-size="10px"
|
|
||||||
>Message from Checkmate Service</mj-text
|
|
||||||
>
|
|
||||||
</mj-column>
|
|
||||||
<mj-column
|
|
||||||
width="45%"
|
|
||||||
padding-top="20px"
|
|
||||||
>
|
|
||||||
<mj-text
|
|
||||||
align="center"
|
|
||||||
font-weight="500"
|
|
||||||
padding="0px"
|
|
||||||
font-size="18px"
|
|
||||||
color="green"
|
|
||||||
>No incidents this week!</mj-text
|
|
||||||
>
|
|
||||||
<mj-divider
|
|
||||||
border-width="2px"
|
|
||||||
border-color="#616161"
|
|
||||||
></mj-divider>
|
|
||||||
</mj-column>
|
|
||||||
</mj-section>
|
|
||||||
<mj-section>
|
|
||||||
<mj-column width="100%">
|
|
||||||
<mj-text>
|
|
||||||
<p>Hello {{ name }}!</p>
|
|
||||||
<p>There were no incidents this week. Good job!</p>
|
|
||||||
<p><b>Current monitors:</b></p>
|
|
||||||
<p><b>Google:</b> 100% availability</p>
|
|
||||||
<p><b>Canada.ca:</b>100% availability</p>
|
|
||||||
</mj-text>
|
|
||||||
</mj-column>
|
|
||||||
<mj-column width="100%">
|
|
||||||
<mj-divider
|
|
||||||
border-width="1px"
|
|
||||||
border-color="#E0E0E0"
|
|
||||||
></mj-divider>
|
|
||||||
<mj-text font-size="12px">
|
|
||||||
<p>This email was sent by Checkmate.</p>
|
|
||||||
</mj-text>
|
|
||||||
</mj-column>
|
|
||||||
</mj-section>
|
|
||||||
</mj-body>
|
|
||||||
</mjml>
|
|
||||||
@@ -9,10 +9,12 @@ import type {
|
|||||||
GotTimings,
|
GotTimings,
|
||||||
ILighthouseAudit,
|
ILighthouseAudit,
|
||||||
} from "@/domain/checks/check.type.js";
|
} from "@/domain/checks/check.type.js";
|
||||||
import type { DnsRecordType, Monitor, MonitorMatchMethod, MonitorStatus, MonitorType } from "@/domain/monitors/monitor.types.js";
|
import type { DnsRecordType, Monitor, MonitorMatchMethod, MonitorStatus, MonitorType } from "@/domain/monitors/monitor.type.js";
|
||||||
|
|
||||||
import type { QueryResult } from "gamedig";
|
import type { QueryResult } from "gamedig";
|
||||||
|
|
||||||
|
export const NETWORK_ERROR = 5000;
|
||||||
|
|
||||||
export interface MonitorStatusResponse<
|
export interface MonitorStatusResponse<
|
||||||
T =
|
T =
|
||||||
| HttpStatusPayload
|
| HttpStatusPayload
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export const DateRanges = ["recent", "hour", "day", "week", "month", "all"] as const;
|
||||||
|
export type DateRange = (typeof DateRanges)[number];
|
||||||
|
|
||||||
|
export const SortOrders = ["asc", "desc"] as const;
|
||||||
|
export type SortOrder = (typeof SortOrders)[number];
|
||||||
|
|
||||||
|
export const CheckFilters = ["all", "up", "down", "resolve"] as const;
|
||||||
|
export type CheckFilter = (typeof CheckFilters)[number];
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { GroupedCheck, HasResponseTime, NormalizedCheck, NormalizedUptimeCheck } from "@/domain/checks/check.type.js";
|
import type { GroupedCheck, HasResponseTime, NormalizedCheck, NormalizedUptimeCheck } from "@/domain/checks/check.type.js";
|
||||||
|
import { type DateRange } from "@/types/query.js";
|
||||||
|
|
||||||
export const getDateForRange = (dateRange: string): Date | undefined => {
|
export const getDateForRange = (dateRange: DateRange): Date => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
switch (dateRange) {
|
switch (dateRange) {
|
||||||
case "recent":
|
case "recent":
|
||||||
@@ -14,78 +15,63 @@ export const getDateForRange = (dateRange: string): Date | undefined => {
|
|||||||
case "month":
|
case "month":
|
||||||
return new Date(now - 30 * 24 * 60 * 60 * 1000); // 30 days
|
return new Date(now - 30 * 24 * 60 * 60 * 1000); // 30 days
|
||||||
case "all":
|
case "all":
|
||||||
return undefined;
|
return new Date(0);
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return new Date(0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const calculatePercentile = <T extends HasResponseTime>(arr: T[], percentile: number): number => {
|
export const getDateFormat = (dateRange: DateRange): string => {
|
||||||
const sorted = arr.slice().sort((a, b) => a.responseTime - b.responseTime);
|
const formatLookup = {
|
||||||
const index = (percentile / 100) * (sorted.length - 1);
|
hour: "%Y-%m-%dT%H:%M:00Z",
|
||||||
const lower = Math.floor(index);
|
recent: "%Y-%m-%dT%H:%M:00Z",
|
||||||
const upper = lower + 1;
|
day: "%Y-%m-%dT%H:00:00Z",
|
||||||
const weight = index % 1;
|
week: "%Y-%m-%dT00:00:00Z",
|
||||||
if (upper >= sorted.length) return sorted[lower]!.responseTime;
|
month: "%Y-%m-%dT00:00:00Z",
|
||||||
return sorted[lower]!.responseTime * (1 - weight) + sorted[upper]!.responseTime * weight;
|
all: "%Y-%m-%dT00:00:00Z",
|
||||||
|
};
|
||||||
|
return formatLookup[dateRange];
|
||||||
};
|
};
|
||||||
|
|
||||||
const calculatePercentileUptimeDetails = (arr: GroupedCheck[], percentile: number): number => {
|
const percentileBy = <T>(arr: T[], percentile: number, value: (item: T) => number): number => {
|
||||||
const sorted = arr.slice().sort((a, b) => a.avgResponseTime - b.avgResponseTime);
|
const sorted = arr.slice().sort((a, b) => value(a) - value(b));
|
||||||
const index = (percentile / 100) * (sorted.length - 1);
|
const index = (percentile / 100) * (sorted.length - 1);
|
||||||
const lower = Math.floor(index);
|
const lower = Math.floor(index);
|
||||||
const upper = lower + 1;
|
const upper = lower + 1;
|
||||||
const weight = index % 1;
|
const weight = index % 1;
|
||||||
if (upper >= sorted.length) return sorted[lower]!.avgResponseTime;
|
if (upper >= sorted.length) return value(sorted[lower]!);
|
||||||
return sorted[lower]!.avgResponseTime * (1 - weight) + sorted[upper]!.avgResponseTime * weight;
|
return value(sorted[lower]!) * (1 - weight) + value(sorted[upper]!) * weight;
|
||||||
|
};
|
||||||
|
|
||||||
|
const rescale = (raw: number, min: number, max: number, rangeMin: number, rangeMax: number): number => {
|
||||||
|
const scaled = rangeMin + ((raw - min) * (rangeMax - rangeMin)) / (max - min);
|
||||||
|
return Math.max(rangeMin, Math.min(rangeMax, scaled));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NormalizeData = <T extends HasResponseTime>(checks: T[], rangeMin: number, rangeMax: number): NormalizedCheck<T>[] => {
|
export const NormalizeData = <T extends HasResponseTime>(checks: T[], rangeMin: number, rangeMax: number): NormalizedCheck<T>[] => {
|
||||||
if (checks.length > 1) {
|
const original = (check: T) => (typeof check.responseTime === "number" ? check.responseTime : 0);
|
||||||
const min = calculatePercentile(checks, 0);
|
if (checks.length <= 1) {
|
||||||
const max = calculatePercentile(checks, 95);
|
return checks.map((check) => ({ ...check, originalResponseTime: original(check) }));
|
||||||
const normalizedChecks = checks.map((check) => {
|
|
||||||
const originalResponseTime = typeof check.responseTime === "number" ? check.responseTime : 0;
|
|
||||||
let normalizedResponseTime = rangeMin + ((check.responseTime - min) * (rangeMax - rangeMin)) / (max - min);
|
|
||||||
|
|
||||||
normalizedResponseTime = Math.max(rangeMin, Math.min(rangeMax, normalizedResponseTime));
|
|
||||||
return {
|
|
||||||
...check,
|
|
||||||
responseTime: normalizedResponseTime,
|
|
||||||
originalResponseTime: originalResponseTime,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return normalizedChecks;
|
|
||||||
} else {
|
|
||||||
return checks.map((check) => {
|
|
||||||
const originalResponseTime = typeof check.responseTime === "number" ? check.responseTime : 0;
|
|
||||||
return { ...check, originalResponseTime: originalResponseTime };
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
const min = percentileBy(checks, 0, (c) => c.responseTime);
|
||||||
|
const max = percentileBy(checks, 95, (c) => c.responseTime);
|
||||||
|
return checks.map((check) => ({
|
||||||
|
...check,
|
||||||
|
responseTime: rescale(check.responseTime, min, max, rangeMin, rangeMax),
|
||||||
|
originalResponseTime: original(check),
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NormalizeDataUptimeDetails = <T extends GroupedCheck>(checks: T[], rangeMin: number, rangeMax: number): NormalizedUptimeCheck<T>[] => {
|
export const NormalizeDataUptimeDetails = <T extends GroupedCheck>(checks: T[], rangeMin: number, rangeMax: number): NormalizedUptimeCheck<T>[] => {
|
||||||
if (checks.length > 1) {
|
const original = (check: T) => (typeof check.avgResponseTime === "number" ? check.avgResponseTime : 0);
|
||||||
const min = calculatePercentileUptimeDetails(checks, 0);
|
if (checks.length <= 1) {
|
||||||
const max = calculatePercentileUptimeDetails(checks, 95);
|
return checks.map((check) => ({ ...check, originalAvgResponseTime: original(check) }));
|
||||||
|
|
||||||
const normalizedChecks = checks.map((check) => {
|
|
||||||
const originalResponseTime = check.avgResponseTime;
|
|
||||||
let normalizedResponseTime = rangeMin + ((check.avgResponseTime - min) * (rangeMax - rangeMin)) / (max - min);
|
|
||||||
|
|
||||||
normalizedResponseTime = Math.max(rangeMin, Math.min(rangeMax, normalizedResponseTime));
|
|
||||||
return {
|
|
||||||
...check,
|
|
||||||
avgResponseTime: normalizedResponseTime,
|
|
||||||
originalAvgResponseTime: originalResponseTime,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return normalizedChecks;
|
|
||||||
} else {
|
|
||||||
return checks.map((check) => {
|
|
||||||
return { ...check, originalAvgResponseTime: check.avgResponseTime };
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
const min = percentileBy(checks, 0, (c) => c.avgResponseTime);
|
||||||
|
const max = percentileBy(checks, 95, (c) => c.avgResponseTime);
|
||||||
|
return checks.map((check) => ({
|
||||||
|
...check,
|
||||||
|
avgResponseTime: rescale(check.avgResponseTime, min, max, rangeMin, rangeMax),
|
||||||
|
originalAvgResponseTime: original(check),
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
@@ -19,7 +19,6 @@ interface LogEntry extends LogConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ILogger {
|
export interface ILogger {
|
||||||
readonly serviceName: string;
|
|
||||||
info(config: LogConfig): void;
|
info(config: LogConfig): void;
|
||||||
warn(config: LogConfig): void;
|
warn(config: LogConfig): void;
|
||||||
error(config: LogConfig): void;
|
error(config: LogConfig): void;
|
||||||
@@ -97,10 +96,6 @@ class Logger implements ILogger {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return Logger.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
info(config: LogConfig) {
|
info(config: LogConfig) {
|
||||||
const logEntry = this.buildLogEntry("info", config);
|
const logEntry = this.buildLogEntry("info", config);
|
||||||
this.cacheLog(logEntry);
|
this.cacheLog(logEntry);
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
export const ParseBoolean = (value: boolean | string | null | undefined) => {
|
|
||||||
if (value === true || value === "true") {
|
|
||||||
return true;
|
|
||||||
} else if (value === false || value === "false" || value === null || value === undefined) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getTokenFromHeaders = (headers: Record<string, string>) => {
|
|
||||||
const authorizationHeader = headers.authorization;
|
|
||||||
if (!authorizationHeader) throw new Error("No auth headers");
|
|
||||||
|
|
||||||
const parts = authorizationHeader.split(" ");
|
|
||||||
if (parts.length !== 2 || parts[0] !== "Bearer") throw new Error("Invalid auth headers");
|
|
||||||
|
|
||||||
return parts[1];
|
|
||||||
};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { MonitorStatusResponse } from "@/types/network.js";
|
import { MonitorStatusResponse } from "@/types/network.js";
|
||||||
import { MonitorEvaluation } from "@/worker/worker.interface.js";
|
import { MonitorEvaluation } from "@/worker/worker.interface.js";
|
||||||
import { Check } from "@/domain/checks/check.type.js";
|
import { Check } from "@/domain/checks/check.type.js";
|
||||||
import { Monitor } from "@/domain/monitors/monitor.types.js";
|
import { Monitor } from "@/domain/monitors/monitor.type.js";
|
||||||
import { IMonitorStatusPolicy } from "@/worker/worker.monitor-status-policy.js";
|
import { IMonitorStatusPolicy } from "@/worker/worker.monitor-status-policy.js";
|
||||||
import { IStatusService } from "@/service/statusService.js";
|
import { IStatusService } from "@/service/statusService.js";
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Monitor } from "@/domain/monitors/monitor.types.js";
|
import { Monitor } from "@/domain/monitors/monitor.type.js";
|
||||||
import { MonitorEvaluation } from "@/worker/worker.interface.js";
|
import { MonitorEvaluation } from "@/worker/worker.interface.js";
|
||||||
import { IMaintenanceWindowsRepository } from "@/domain/maintenance-windows/maintenance-window.repository.interface.js";
|
import { IMaintenanceWindowsRepository } from "@/domain/maintenance-windows/maintenance-window.repository.interface.js";
|
||||||
import { isWindowActive } from "@/utils/maintenanceWindow.js";
|
import { isWindowActive } from "@/utils/maintenanceWindow.js";
|
||||||
import { ILogger } from "@/utils/logger.js";
|
import { ILogger } from "@/utils/logger.js";
|
||||||
import { IBufferService } from "@/service/bufferService.js";
|
import { IBufferService } from "@/service/bufferService.js";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import { supportsGeoCheck } from "@/domain/monitors/monitor.types.js";
|
import { supportsGeoCheck } from "@/domain/monitors/monitor.type.js";
|
||||||
import { IGeoChecksService } from "@/domain/geo-checks/geo-check.service.js";
|
import { IGeoChecksService } from "@/domain/geo-checks/geo-check.service.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "CheckPipeline";
|
const SERVICE_NAME = "CheckPipeline";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Monitor } from "@/domain/monitors/monitor.types.js";
|
import { Monitor } from "@/domain/monitors/monitor.type.js";
|
||||||
import { Check } from "@/domain/checks/check.type.js";
|
import { Check } from "@/domain/checks/check.type.js";
|
||||||
import { MonitorStatusResponse } from "@/types/network.js";
|
import { MonitorStatusResponse } from "@/types/network.js";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
|
|||||||
@@ -11,14 +11,13 @@ import { ICheckPipeline } from "@/worker/worker.check-pipeline.js";
|
|||||||
import { IReactorDispatcher } from "@/worker/reactors/reactor.dispatcher.js";
|
import { IReactorDispatcher } from "@/worker/reactors/reactor.dispatcher.js";
|
||||||
import { IWorkerHelper } from "@/worker/worker.helper.js";
|
import { IWorkerHelper } from "@/worker/worker.helper.js";
|
||||||
import { IQueueWorkersRepository } from "@/domain/queue-workers/queue-worker.repository.interface.js";
|
import { IQueueWorkersRepository } from "@/domain/queue-workers/queue-worker.repository.interface.js";
|
||||||
import { WORKER_TTL_SECONDS } from "@/domain/queue-workers/queue-worker.model.js";
|
import { WORKER_STALE_MS } from "@/domain/queue-workers/queue-worker.model.js";
|
||||||
import { QueueMode } from "@/domain/app-settings/app-settings.type.js";
|
import { QueueMode } from "@/domain/app-settings/app-settings.type.js";
|
||||||
import { JobScheduler } from "@/worker/worker.job-scheduler.js";
|
import { JobScheduler } from "@/worker/worker.job-scheduler.js";
|
||||||
import { IBufferService } from "@/service/bufferService.js";
|
import { IBufferService } from "@/service/bufferService.js";
|
||||||
|
import { POLL_MS } from "@/worker/worker.job-scheduler.js";
|
||||||
const SERVICE_NAME = "JobQueue";
|
const SERVICE_NAME = "JobQueue";
|
||||||
const POLL_MS = 250; // base poll interval while a loop is actively claiming work
|
|
||||||
const POLL_MAX_MS = 5000; // back off poll interval to 5s if no jobs
|
const POLL_MAX_MS = 5000; // back off poll interval to 5s if no jobs
|
||||||
const WORKER_STALE_MS = WORKER_TTL_SECONDS * 1000; // a worker counts as alive if seen within this window
|
|
||||||
const LOCK_RENEW_MS = LOCK_MS / 3; // renew an in-flight job's lock at 1/3 the lease, so it survives two missed renewals
|
const LOCK_RENEW_MS = LOCK_MS / 3; // renew an in-flight job's lock at 1/3 the lease, so it survives two missed renewals
|
||||||
const DRAIN_TIMEOUT_MS = 25_000;
|
const DRAIN_TIMEOUT_MS = 25_000;
|
||||||
const DRAIN_POLL_MS = 200;
|
const DRAIN_POLL_MS = 200;
|
||||||
@@ -31,6 +30,25 @@ const CONCURRENCY: Record<JobType, number> = {
|
|||||||
"cleanup-retention": 1,
|
"cleanup-retention": 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface DBQueueWorkerDependencies {
|
||||||
|
logger: ILogger; // forwarded to super
|
||||||
|
isDbConnected: () => boolean;
|
||||||
|
jobsRepository: IJobsRepository; // forwarded
|
||||||
|
monitorsRepository: IMonitorsRepository; // forwarded
|
||||||
|
checksRepository: IChecksRepository;
|
||||||
|
checkService: ICheckService;
|
||||||
|
bufferService: IBufferService;
|
||||||
|
checkProducer: ICheckProducer;
|
||||||
|
checkEvaluator: ICheckEvaluator;
|
||||||
|
geoCheckPipeline: ICheckPipeline;
|
||||||
|
dispatcher: IReactorDispatcher;
|
||||||
|
helper: IWorkerHelper;
|
||||||
|
queueWorkersRepository: IQueueWorkersRepository; // forwarded
|
||||||
|
queueMode: QueueMode; // forwarded
|
||||||
|
queuePrimaryProcesses: boolean; // consumed immediately
|
||||||
|
workerId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class DBQueueWorker extends JobScheduler implements IQueueWorker {
|
export class DBQueueWorker extends JobScheduler implements IQueueWorker {
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
|
|
||||||
@@ -45,68 +63,39 @@ export class DBQueueWorker extends JobScheduler implements IQueueWorker {
|
|||||||
"cleanup-retention": 0,
|
"cleanup-retention": 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(
|
private checksRepository: IChecksRepository;
|
||||||
logger: ILogger, // forwarded to super
|
private checkProducer: ICheckProducer;
|
||||||
private isDbConnected: () => boolean,
|
private checkService: ICheckService;
|
||||||
jobsRepository: IJobsRepository, // forwarded
|
private checkEvaluator: ICheckEvaluator;
|
||||||
monitorsRepository: IMonitorsRepository, // forwarded
|
private dispatcher: IReactorDispatcher;
|
||||||
private checksRepository: IChecksRepository,
|
private geoCheckPipeline: ICheckPipeline;
|
||||||
private checkService: ICheckService,
|
private helper: IWorkerHelper;
|
||||||
private bufferService: IBufferService,
|
private bufferService: IBufferService;
|
||||||
private checkProducer: ICheckProducer,
|
private isDbConnected: () => boolean;
|
||||||
private checkEvaluator: ICheckEvaluator,
|
|
||||||
private geoCheckPipeline: ICheckPipeline,
|
|
||||||
private dispatcher: IReactorDispatcher,
|
|
||||||
private helper: IWorkerHelper,
|
|
||||||
queueWorkersRepository: IQueueWorkersRepository, // forwarded
|
|
||||||
queueMode: QueueMode, // forwarded
|
|
||||||
queuePrimaryProcesses: boolean, // consumed immediately
|
|
||||||
workerId: string
|
|
||||||
) {
|
|
||||||
super(jobsRepository, queueWorkersRepository, monitorsRepository, queueMode, logger, workerId);
|
|
||||||
this.processesJobs = queuePrimaryProcesses === true || queueMode === "worker";
|
|
||||||
}
|
|
||||||
|
|
||||||
get serviceName() {
|
constructor(dependencies: DBQueueWorkerDependencies) {
|
||||||
return DBQueueWorker.SERVICE_NAME;
|
super(
|
||||||
}
|
dependencies.jobsRepository,
|
||||||
|
dependencies.queueWorkersRepository,
|
||||||
static async create(
|
dependencies.monitorsRepository,
|
||||||
logger: ILogger,
|
dependencies.queueMode,
|
||||||
isDbConnected: () => boolean,
|
dependencies.logger,
|
||||||
jobsRepository: IJobsRepository,
|
dependencies.workerId
|
||||||
monitorsRepository: IMonitorsRepository,
|
|
||||||
checksRepository: IChecksRepository,
|
|
||||||
checkService: ICheckService,
|
|
||||||
bufferService: IBufferService,
|
|
||||||
checkProducer: ICheckProducer,
|
|
||||||
checkEvaluator: ICheckEvaluator,
|
|
||||||
geoCheckPipeline: ICheckPipeline,
|
|
||||||
dispatcher: IReactorDispatcher,
|
|
||||||
helper: IWorkerHelper,
|
|
||||||
queueWorkersRepository: IQueueWorkersRepository,
|
|
||||||
queueMode: QueueMode,
|
|
||||||
queuePrimaryProcesses: boolean,
|
|
||||||
workerId: string
|
|
||||||
): Promise<DBQueueWorker> {
|
|
||||||
const instance = new DBQueueWorker(
|
|
||||||
logger,
|
|
||||||
isDbConnected,
|
|
||||||
jobsRepository,
|
|
||||||
monitorsRepository,
|
|
||||||
checksRepository,
|
|
||||||
checkService,
|
|
||||||
bufferService,
|
|
||||||
checkProducer,
|
|
||||||
checkEvaluator,
|
|
||||||
geoCheckPipeline,
|
|
||||||
dispatcher,
|
|
||||||
helper,
|
|
||||||
queueWorkersRepository,
|
|
||||||
queueMode,
|
|
||||||
queuePrimaryProcesses,
|
|
||||||
workerId
|
|
||||||
);
|
);
|
||||||
|
this.processesJobs = dependencies.queuePrimaryProcesses === true || dependencies.queueMode === "worker";
|
||||||
|
this.checksRepository = dependencies.checksRepository;
|
||||||
|
this.checkProducer = dependencies.checkProducer;
|
||||||
|
this.checkService = dependencies.checkService;
|
||||||
|
this.checkEvaluator = dependencies.checkEvaluator;
|
||||||
|
this.dispatcher = dependencies.dispatcher;
|
||||||
|
this.geoCheckPipeline = dependencies.geoCheckPipeline;
|
||||||
|
this.helper = dependencies.helper;
|
||||||
|
this.bufferService = dependencies.bufferService;
|
||||||
|
this.isDbConnected = dependencies.isDbConnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async create(dependencies: DBQueueWorkerDependencies): Promise<DBQueueWorker> {
|
||||||
|
const instance = new DBQueueWorker(dependencies);
|
||||||
await instance.init();
|
await instance.init();
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { ITeamsRepository } from "@/domain/teams/team.repository.interface.js";
|
|||||||
import { ILogger } from "@/utils/logger.js";
|
import { ILogger } from "@/utils/logger.js";
|
||||||
|
|
||||||
export interface IWorkerHelper {
|
export interface IWorkerHelper {
|
||||||
readonly serviceName: string;
|
|
||||||
getCleanupOrphanedJob(): () => Promise<void>;
|
getCleanupOrphanedJob(): () => Promise<void>;
|
||||||
getCleanupRetentionJob(): () => Promise<void>;
|
getCleanupRetentionJob(): () => Promise<void>;
|
||||||
}
|
}
|
||||||
@@ -69,10 +68,6 @@ export class WorkerHelper implements IWorkerHelper {
|
|||||||
this.geoChecksRepository = geoChecksRepository;
|
this.geoChecksRepository = geoChecksRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return WorkerHelper.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
getCleanupOrphanedJob = () => {
|
getCleanupOrphanedJob = () => {
|
||||||
return async () => {
|
return async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Monitor } from "@/domain/monitors/monitor.types.js";
|
import { Monitor } from "@/domain/monitors/monitor.type.js";
|
||||||
import { Check } from "@/domain/checks/check.type.js";
|
import { Check } from "@/domain/checks/check.type.js";
|
||||||
import { JobType } from "@/domain/jobs/job.type.js";
|
import { JobType } from "@/domain/jobs/job.type.js";
|
||||||
import { MonitorPayloadMap, MonitorStatusResponse, StatusChangeResult } from "@/types/network.js";
|
import { MonitorPayloadMap, MonitorStatusResponse, StatusChangeResult } from "@/types/network.js";
|
||||||
@@ -67,7 +67,6 @@ export type WorkerHealth = {
|
|||||||
inFlight: number; // total in-flight jobs across types
|
inFlight: number; // total in-flight jobs across types
|
||||||
};
|
};
|
||||||
export interface IJobScheduler {
|
export interface IJobScheduler {
|
||||||
readonly serviceName: string;
|
|
||||||
addJob(monitorId: string, monitor: Monitor): Promise<void>;
|
addJob(monitorId: string, monitor: Monitor): Promise<void>;
|
||||||
deleteJob(monitor: Monitor): Promise<void>;
|
deleteJob(monitor: Monitor): Promise<void>;
|
||||||
pauseJob(monitor: Monitor): Promise<void>;
|
pauseJob(monitor: Monitor): Promise<void>;
|
||||||
@@ -83,7 +82,6 @@ export interface IJobScheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IQueueWorker extends IJobScheduler {
|
export interface IQueueWorker extends IJobScheduler {
|
||||||
readonly serviceName: string;
|
|
||||||
getHealth(): WorkerHealth;
|
getHealth(): WorkerHealth;
|
||||||
countDueBacklog(): Promise<number>;
|
countDueBacklog(): Promise<number>;
|
||||||
countAliveWorkers(): Promise<number>;
|
countAliveWorkers(): Promise<number>;
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
import { IJobScheduler } from "@/worker/worker.interface.js";
|
import { IJobScheduler } from "@/worker/worker.interface.js";
|
||||||
import { IJobsRepository } from "@/domain/jobs/job.repository.interface.js";
|
import { IJobsRepository } from "@/domain/jobs/job.repository.interface.js";
|
||||||
import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js";
|
import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js";
|
||||||
import { Monitor, supportsGeoCheck } from "@/domain/monitors/monitor.types.js";
|
import { Monitor, supportsGeoCheck } from "@/domain/monitors/monitor.type.js";
|
||||||
import { type Job, type JobSeed, type JobType, jobId } from "@/domain/jobs/job.type.js";
|
import { type Job, type JobSeed, type JobType, jobId } from "@/domain/jobs/job.type.js";
|
||||||
import { IQueueWorkersRepository } from "@/domain/queue-workers/queue-worker.repository.interface.js";
|
import { IQueueWorkersRepository } from "@/domain/queue-workers/queue-worker.repository.interface.js";
|
||||||
import { WorkerJobsPagination, WorkerJobSummary, WorkerMetrics } from "@/worker/worker.interface.js";
|
import { WorkerJobsPagination, WorkerJobSummary, WorkerMetrics } from "@/worker/worker.interface.js";
|
||||||
import { WORKER_TTL_SECONDS } from "@/domain/queue-workers/queue-worker.model.js";
|
import { WORKER_STALE_MS } from "@/domain/queue-workers/queue-worker.model.js";
|
||||||
import { QueueMode } from "@/domain/app-settings/app-settings.type.js";
|
import { QueueMode } from "@/domain/app-settings/app-settings.type.js";
|
||||||
import { ILogger } from "@/utils/logger.js";
|
import { ILogger } from "@/utils/logger.js";
|
||||||
|
|
||||||
const POLL_MS = 250; // base poll interval while a loop is actively claiming work
|
export const POLL_MS = 250; // base poll interval while a loop is actively claiming work
|
||||||
const SERVICE_NAME = "JobScheduler";
|
const SERVICE_NAME = "JobScheduler";
|
||||||
const WORKER_STALE_MS = WORKER_TTL_SECONDS * 1000; // a worker counts as alive if seen within this window
|
|
||||||
const HEARTBEAT_MS = WORKER_STALE_MS / 3; // Worker can miss two beats without being considered stale
|
const HEARTBEAT_MS = WORKER_STALE_MS / 3; // Worker can miss two beats without being considered stale
|
||||||
const CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
const CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
@@ -26,9 +25,6 @@ export class JobScheduler implements IJobScheduler {
|
|||||||
protected readonly workerId: string // same id MongoJobsRepository stamps into lockedBy
|
protected readonly workerId: string // same id MongoJobsRepository stamps into lockedBy
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
get serviceName() {
|
|
||||||
return JobScheduler.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
private heartbeatInFlight: Promise<void> = Promise.resolve();
|
private heartbeatInFlight: Promise<void> = Promise.resolve();
|
||||||
|
|
||||||
protected processesJobs = false; // scheduler-only by default; DBQueueWorker flips this on when it runs job loops
|
protected processesJobs = false; // scheduler-only by default; DBQueueWorker flips this on when it runs job loops
|
||||||
@@ -191,7 +187,7 @@ export class JobScheduler implements IJobScheduler {
|
|||||||
// Seed queue
|
// Seed queue
|
||||||
protected reconcile = async () => {
|
protected reconcile = async () => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const monitors = (await this.monitorsRepository.findAll()) ?? [];
|
const monitors = await this.monitorsRepository.findAll();
|
||||||
for (const monitor of monitors) {
|
for (const monitor of monitors) {
|
||||||
await this.jobsRepository.upsertJob(this.toCheckJob(monitor, now));
|
await this.jobsRepository.upsertJob(this.toCheckJob(monitor, now));
|
||||||
if (supportsGeoCheck(monitor.type) && monitor.geoCheckEnabled) {
|
if (supportsGeoCheck(monitor.type) && monitor.geoCheckEnabled) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { IMonitorsRepository, TeamQueryConfig, SummaryConfig } from "../../src/domain/monitors/monitor.repository.interface.ts";
|
import type { IMonitorsRepository, TeamQueryConfig, SummaryConfig } from "../../src/domain/monitors/monitor.repository.interface.ts";
|
||||||
import type { CheckSnapshot } from "../../src/domain/checks/check.type.ts";
|
import type { CheckSnapshot } from "../../src/domain/checks/check.type.ts";
|
||||||
import type { Monitor, MonitorsSummary } from "../../src/domain/monitors/monitor.types.ts";
|
import type { Monitor, MonitorsSummary } from "../../src/domain/monitors/monitor.type.ts";
|
||||||
|
|
||||||
export class InMemoryMonitorsRepository implements IMonitorsRepository {
|
export class InMemoryMonitorsRepository implements IMonitorsRepository {
|
||||||
private monitors: Monitor[] = [];
|
private monitors: Monitor[] = [];
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { IncidentService } from "../../src/domain/incidents/incident.service.ts"
|
|||||||
import { InMemoryMonitorsRepository } from "./InMemoryMonitorsRepository.ts";
|
import { InMemoryMonitorsRepository } from "./InMemoryMonitorsRepository.ts";
|
||||||
import { InMemoryIncidentsRepository } from "./InMemoryIncidentsRepository.ts";
|
import { InMemoryIncidentsRepository } from "./InMemoryIncidentsRepository.ts";
|
||||||
import { createMockLogger } from "./createMockLogger.ts";
|
import { createMockLogger } from "./createMockLogger.ts";
|
||||||
import type { Monitor } from "../../src/domain/monitors/monitor.types.ts";
|
import type { Monitor } from "../../src/domain/monitors/monitor.type.ts";
|
||||||
import type { MonitorStatusResponse } from "../../src/types/network.ts";
|
import type { MonitorStatusResponse } from "../../src/types/network.ts";
|
||||||
import type { Check } from "../../src/domain/checks/check.type.ts";
|
import type { Check } from "../../src/domain/checks/check.type.ts";
|
||||||
import type { MaintenanceWindow } from "../../src/domain/maintenance-windows/maintenance-window.type.ts";
|
import type { MaintenanceWindow } from "../../src/domain/maintenance-windows/maintenance-window.type.ts";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, expect, it } from "@jest/globals";
|
import { describe, expect, it } from "@jest/globals";
|
||||||
import type { IStatusProvider } from "../../src/service/network/IStatusProvider.ts";
|
import type { IStatusProvider } from "../../src/service/network/IStatusProvider.ts";
|
||||||
import type { Monitor } from "../../src/domain/monitors/monitor.types.ts";
|
import type { Monitor } from "../../src/domain/monitors/monitor.type.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shared contract tests for all IStatusProvider implementations.
|
* Shared contract tests for all IStatusProvider implementations.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { createMockLogger } from "../helpers/createMockLogger.ts";
|
|||||||
import type { IMonitorsRepository } from "../../src/domain/monitors/monitor.repository.interface.ts";
|
import type { IMonitorsRepository } from "../../src/domain/monitors/monitor.repository.interface.ts";
|
||||||
import type { IUsersRepository } from "../../src/domain/users/user.repository.interface.ts";
|
import type { IUsersRepository } from "../../src/domain/users/user.repository.interface.ts";
|
||||||
import type { INotificationMessageBuilder } from "../../src/domain/notifications/notification.message-builder.ts";
|
import type { INotificationMessageBuilder } from "../../src/domain/notifications/notification.message-builder.ts";
|
||||||
import type { Monitor } from "../../src/domain/monitors/monitor.types.ts";
|
import type { Monitor } from "../../src/domain/monitors/monitor.type.ts";
|
||||||
import type { MonitorActionDecision } from "../../src/worker/worker.helper.ts";
|
import type { MonitorActionDecision } from "../../src/worker/worker.helper.ts";
|
||||||
|
|
||||||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, expect, it, jest } from "@jest/globals";
|
import { describe, expect, it, jest } from "@jest/globals";
|
||||||
import { AdvancedMatcher } from "../../../../src/service/network/AdvancedMatcher.ts";
|
import { AdvancedMatcher } from "../../../../src/service/network/AdvancedMatcher.ts";
|
||||||
import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts";
|
import type { Monitor } from "../../../../src/domain/monitors/monitor.type.ts";
|
||||||
|
|
||||||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
|
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
|
||||||
import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts";
|
import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts";
|
||||||
import { NETWORK_ERROR } from "../../../../src/service/network/utils.ts";
|
import { NETWORK_ERROR } from "../../../../src/types/network.ts";
|
||||||
import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts";
|
import type { Monitor } from "../../../../src/domain/monitors/monitor.type.ts";
|
||||||
import { DNSProvider } from "../../../../src/service/network/DNSProvider.ts";
|
import { DNSProvider } from "../../../../src/service/network/DNSProvider.ts";
|
||||||
|
|
||||||
// ── Mocks ────────────────────────────────────────────────────────────────────
|
// ── Mocks ────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { describe, expect, it, jest } from "@jest/globals";
|
|||||||
import { DockerProvider } from "../../../../src/service/network/DockerProvider.ts";
|
import { DockerProvider } from "../../../../src/service/network/DockerProvider.ts";
|
||||||
import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts";
|
import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts";
|
||||||
import { createMockLogger } from "../../../helpers/createMockLogger.ts";
|
import { createMockLogger } from "../../../helpers/createMockLogger.ts";
|
||||||
import { NETWORK_ERROR } from "../../../../src/service/network/utils.ts";
|
import { NETWORK_ERROR } from "../../../../src/types/network.ts";
|
||||||
import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts";
|
import type { Monitor } from "../../../../src/domain/monitors/monitor.type.ts";
|
||||||
|
|
||||||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { describe, expect, it, jest } from "@jest/globals";
|
|||||||
import { GameProvider } from "../../../../src/service/network/GameProvider.ts";
|
import { GameProvider } from "../../../../src/service/network/GameProvider.ts";
|
||||||
import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts";
|
import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts";
|
||||||
import { createMockLogger } from "../../../helpers/createMockLogger.ts";
|
import { createMockLogger } from "../../../helpers/createMockLogger.ts";
|
||||||
import { NETWORK_ERROR } from "../../../../src/service/network/utils.ts";
|
import { NETWORK_ERROR } from "../../../../src/types/network.ts";
|
||||||
import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts";
|
import type { Monitor } from "../../../../src/domain/monitors/monitor.type.ts";
|
||||||
|
|
||||||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { describe, expect, it, jest } from "@jest/globals";
|
import { describe, expect, it, jest } from "@jest/globals";
|
||||||
import { GrpcProvider } from "../../../../src/service/network/GrpcProvider.ts";
|
import { GrpcProvider } from "../../../../src/service/network/GrpcProvider.ts";
|
||||||
import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts";
|
import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts";
|
||||||
import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts";
|
import type { Monitor } from "../../../../src/domain/monitors/monitor.type.ts";
|
||||||
|
|
||||||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { describe, expect, it, jest } from "@jest/globals";
|
|||||||
import { HardwareProvider } from "../../../../src/service/network/HardwareProvider.ts";
|
import { HardwareProvider } from "../../../../src/service/network/HardwareProvider.ts";
|
||||||
import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts";
|
import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts";
|
||||||
import type { HttpProvider } from "../../../../src/service/network/HttpProvider.ts";
|
import type { HttpProvider } from "../../../../src/service/network/HttpProvider.ts";
|
||||||
import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts";
|
import type { Monitor } from "../../../../src/domain/monitors/monitor.type.ts";
|
||||||
|
|
||||||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { describe, expect, it, jest } from "@jest/globals";
|
import { describe, expect, it, jest } from "@jest/globals";
|
||||||
import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts";
|
import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts";
|
||||||
import { NETWORK_ERROR } from "../../../../src/service/network/utils.ts";
|
import { NETWORK_ERROR } from "../../../../src/types/network.ts";
|
||||||
import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts";
|
import type { Monitor } from "../../../../src/domain/monitors/monitor.type.ts";
|
||||||
|
|
||||||
// ── Mocks ────────────────────────────────────────────────────────────────────
|
// ── Mocks ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
Loaded 100 of 130 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user