Merge branch 'develop' into incidents-feat-modal
This commit is contained in:
commit
0ea4a5024a
48 files changed
+540
-1707
No files matched your search
@@ -124,7 +124,7 @@ const MonitorDetailsControlHeader = ({
|
||||
});
|
||||
}}
|
||||
>
|
||||
{monitor?.isActive ? "Pause" : "Resume"}
|
||||
{monitor?.isActive ? t("pause") : t("resume")}
|
||||
</Button>
|
||||
)}
|
||||
{isAdmin && (
|
||||
|
||||
@@ -80,13 +80,13 @@ const UptimeCreate = ({ isClone = false }) => {
|
||||
const [isFetchingMonitor] = useFetchMonitorById({
|
||||
monitorId,
|
||||
setMonitor,
|
||||
updateTrigger: true,
|
||||
updateTrigger,
|
||||
});
|
||||
|
||||
// Fetch games
|
||||
const [isFetchingGames] = useFetchMonitorGames({
|
||||
setGames,
|
||||
triggerUpdate: true,
|
||||
updateTrigger,
|
||||
});
|
||||
|
||||
// Combine the loading states
|
||||
@@ -283,10 +283,6 @@ const UptimeCreate = ({ isClone = false }) => {
|
||||
}));
|
||||
};
|
||||
|
||||
const handlePause = async () => {
|
||||
await pauseMonitor({ monitorId, triggerUpdate });
|
||||
};
|
||||
|
||||
const handleRemove = async (event) => {
|
||||
event.preventDefault();
|
||||
const TEMP_MONITOR = { id: monitor.id };
|
||||
@@ -438,7 +434,12 @@ const UptimeCreate = ({ isClone = false }) => {
|
||||
/>
|
||||
)
|
||||
}
|
||||
onClick={handlePause}
|
||||
onClick={() => {
|
||||
pauseMonitor({
|
||||
monitorId: monitor?.id,
|
||||
triggerUpdate,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{monitor?.isActive ? t("pause") : t("resume")}
|
||||
</Button>
|
||||
|
||||
Generated
+47
-7
@@ -36,7 +36,6 @@
|
||||
"mongoose": "^8.3.3",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"nodemailer": "^6.9.14",
|
||||
"papaparse": "^5.5.2",
|
||||
"ping": "0.4.4",
|
||||
"sharp": "0.33.5",
|
||||
"ssl-checker": "2.0.10",
|
||||
@@ -46,6 +45,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@types/compression": "1.8.1",
|
||||
"@types/cookie-parser": "1.4.10",
|
||||
"@types/cors": "2.8.19",
|
||||
"@types/dockerode": "^4.0.0",
|
||||
"@types/express": "5.0.3",
|
||||
"@types/gamedig": "^5.0.3",
|
||||
@@ -58,6 +60,7 @@
|
||||
"@types/nodemailer": "7.0.1",
|
||||
"@types/papaparse": "^5.5.2",
|
||||
"@types/ping": "0.4.4",
|
||||
"@types/swagger-ui-express": "4.1.8",
|
||||
"c8": "10.1.3",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-plugin-mocha": "^10.5.0",
|
||||
@@ -4324,6 +4327,17 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/compression": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz",
|
||||
"integrity": "sha512-kCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+HVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/express": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/connect": {
|
||||
"version": "3.4.38",
|
||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
|
||||
@@ -4334,6 +4348,26 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/cookie-parser": {
|
||||
"version": "1.4.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz",
|
||||
"integrity": "sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/express": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/cors": {
|
||||
"version": "2.8.19",
|
||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
|
||||
"integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/docker-modem": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz",
|
||||
@@ -4370,6 +4404,7 @@
|
||||
"integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/body-parser": "*",
|
||||
"@types/express-serve-static-core": "^5.0.0",
|
||||
@@ -4650,6 +4685,17 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/swagger-ui-express": {
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz",
|
||||
"integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/express": "*",
|
||||
"@types/serve-static": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/tough-cookie": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
|
||||
@@ -11567,12 +11613,6 @@
|
||||
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
|
||||
"license": "BlueOak-1.0.0"
|
||||
},
|
||||
"node_modules/papaparse": {
|
||||
"version": "5.5.3",
|
||||
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz",
|
||||
"integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
|
||||
+4
-1
@@ -48,7 +48,6 @@
|
||||
"mongoose": "^8.3.3",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"nodemailer": "^6.9.14",
|
||||
"papaparse": "^5.5.2",
|
||||
"ping": "0.4.4",
|
||||
"sharp": "0.33.5",
|
||||
"ssl-checker": "2.0.10",
|
||||
@@ -58,6 +57,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@types/compression": "1.8.1",
|
||||
"@types/cookie-parser": "1.4.10",
|
||||
"@types/cors": "2.8.19",
|
||||
"@types/dockerode": "^4.0.0",
|
||||
"@types/express": "5.0.3",
|
||||
"@types/gamedig": "^5.0.3",
|
||||
@@ -70,6 +72,7 @@
|
||||
"@types/nodemailer": "7.0.1",
|
||||
"@types/papaparse": "^5.5.2",
|
||||
"@types/ping": "0.4.4",
|
||||
"@types/swagger-ui-express": "4.1.8",
|
||||
"c8": "10.1.3",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-plugin-mocha": "^10.5.0",
|
||||
|
||||
@@ -5,12 +5,27 @@ import helmet from "helmet";
|
||||
import compression from "compression";
|
||||
import cookieParser from "cookie-parser";
|
||||
import swaggerUi from "swagger-ui-express";
|
||||
import { handleErrors } from "./middleware/handleErrors.js";
|
||||
import { setupRoutes } from "./config/routes.js";
|
||||
import { generalApiLimiter } from "./middleware/rateLimiter.js";
|
||||
import { sanitizeBody, sanitizeQuery } from "./middleware/sanitization.js";
|
||||
import { handleErrors } from "@/middleware/handleErrors.js";
|
||||
import { generalApiLimiter } from "@/middleware/rateLimiter.js";
|
||||
import { sanitizeBody, sanitizeQuery } from "@/middleware/sanitization.js";
|
||||
import { setupRoutes } from "@/config/routes.js";
|
||||
import { InitializedServices } from "@/config/services.js";
|
||||
import { InitializedControllers } from "@/config/controllers.js";
|
||||
import { EnvConfig } from "@/service/system/settingsService.js";
|
||||
|
||||
export const createApp = ({ services, controllers, envSettings, frontendPath, openApiSpec }) => {
|
||||
export const createApp = ({
|
||||
services,
|
||||
controllers,
|
||||
envSettings,
|
||||
frontendPath,
|
||||
openApiSpec,
|
||||
}: {
|
||||
services: InitializedServices;
|
||||
controllers: InitializedControllers;
|
||||
envSettings: EnvConfig;
|
||||
frontendPath: string;
|
||||
openApiSpec: any;
|
||||
}) => {
|
||||
const allowedOrigin = envSettings.clientHost;
|
||||
const app = express();
|
||||
app.use(generalApiLimiter);
|
||||
@@ -10,26 +10,35 @@ import StatusPageController from "../controllers/statusPageController.js";
|
||||
import NotificationController from "../controllers/notificationController.js";
|
||||
import DiagnosticController from "../controllers/diagnosticController.js";
|
||||
import IncidentController from "../controllers/incidentController.js";
|
||||
import type { InitializedSerivces } from "@/config/services.js";
|
||||
export const initializeControllers = (services: InitializedSerivces) => {
|
||||
const controllers: Record<string, any> = {};
|
||||
import type { InitializedServices } from "@/config/services.js";
|
||||
|
||||
controllers.authController = new AuthController(services.userService);
|
||||
|
||||
controllers.monitorController = new MonitorController(services.monitorService);
|
||||
|
||||
controllers.settingsController = new SettingsController(services.settingsService, services.emailService, services.db);
|
||||
controllers.checkController = new CheckController(services.checkService);
|
||||
controllers.inviteController = new InviteController(services.inviteService);
|
||||
|
||||
controllers.maintenanceWindowController = new MaintenanceWindowController(services.maintenanceWindowService);
|
||||
controllers.queueController = new QueueController(services.jobQueue);
|
||||
controllers.logController = new LogController(services.logger);
|
||||
controllers.statusPageController = new StatusPageController(services.db);
|
||||
controllers.notificationController = new NotificationController(services.notificationsService, services.db, services.monitorsRepository);
|
||||
controllers.diagnosticController = new DiagnosticController(services.diagnosticService);
|
||||
|
||||
controllers.incidentController = new IncidentController(services.incidentService);
|
||||
|
||||
return controllers;
|
||||
export interface InitializedControllers {
|
||||
authController: AuthController;
|
||||
monitorController: MonitorController;
|
||||
settingsController: SettingsController;
|
||||
checkController: CheckController;
|
||||
inviteController: InviteController;
|
||||
maintenanceWindowController: MaintenanceWindowController;
|
||||
queueController: QueueController;
|
||||
logController: LogController;
|
||||
statusPageController: StatusPageController;
|
||||
notificationController: NotificationController;
|
||||
diagnosticController: DiagnosticController;
|
||||
incidentController: IncidentController;
|
||||
}
|
||||
export const initializeControllers = (services: InitializedServices): InitializedControllers => {
|
||||
return {
|
||||
authController: new AuthController(services.userService),
|
||||
monitorController: new MonitorController(services.monitorService),
|
||||
settingsController: new SettingsController(services.settingsService, services.emailService, services.db),
|
||||
checkController: new CheckController(services.checkService),
|
||||
inviteController: new InviteController(services.inviteService),
|
||||
maintenanceWindowController: new MaintenanceWindowController(services.maintenanceWindowService),
|
||||
queueController: new QueueController(services.jobQueue),
|
||||
logController: new LogController(services.logger),
|
||||
statusPageController: new StatusPageController(services.db),
|
||||
notificationController: new NotificationController(services.notificationsService, services.db, services.monitorsRepository),
|
||||
diagnosticController: new DiagnosticController(services.diagnosticService),
|
||||
incidentController: new IncidentController(services.incidentService),
|
||||
};
|
||||
};
|
||||
Whitespace-only changes.
@@ -1,25 +0,0 @@
|
||||
import dotenv from "dotenv";
|
||||
|
||||
export interface IEevConfig {
|
||||
NODE_ENV: string;
|
||||
JWT_SECRET: string;
|
||||
PORT: number;
|
||||
PAGESPEED_API_KEY: string;
|
||||
SMTP_HOST: string;
|
||||
SMTP_PORT: number;
|
||||
SMTP_USER: string;
|
||||
SMTP_PASS: string;
|
||||
}
|
||||
|
||||
dotenv.config();
|
||||
|
||||
export const config: IEevConfig = {
|
||||
NODE_ENV: process.env.NODE_ENV || "development",
|
||||
JWT_SECRET: process.env.JWT_SECRET || "your_jwt_secret",
|
||||
PORT: process.env.PORT ? parseInt(process.env.PORT, 10) : 3000,
|
||||
PAGESPEED_API_KEY: process.env.PAGESPEED_API_KEY || "",
|
||||
SMTP_HOST: process.env.SMTP_HOST || "smtp.example.com",
|
||||
SMTP_PORT: process.env.SMTP_PORT ? parseInt(process.env.SMTP_PORT, 10) : 587,
|
||||
SMTP_USER: process.env.SMTP_USER || "user@example.com",
|
||||
SMTP_PASS: process.env.SMTP_PASS || "your_smtp_password",
|
||||
};
|
||||
+12
-13
@@ -1,23 +1,22 @@
|
||||
import { createVerifyJWT } from "../middleware/verifyJWT.js";
|
||||
import { authApiLimiter } from "../middleware/rateLimiter.js";
|
||||
|
||||
import AuthRoutes from "../routes/v1/authRoute.js";
|
||||
import InviteRoutes from "../routes/v1/inviteRoute.js";
|
||||
import MonitorRoutes from "../routes/v1/monitorRoute.js";
|
||||
import CheckRoutes from "../routes/v1/checkRoute.js";
|
||||
import SettingsRoutes from "../routes/v1/settingsRoute.js";
|
||||
import MaintenanceWindowRoutes from "../routes/v1/maintenanceWindowRoute.js";
|
||||
import StatusPageRoutes from "../routes/v1/statusPageRoute.js";
|
||||
import QueueRoutes from "../routes/v1/queueRoute.js";
|
||||
import LogRoutes from "../routes/v1/logRoutes.js";
|
||||
import DiagnosticRoutes from "../routes/v1/diagnosticRoute.js";
|
||||
import NotificationRoutes from "../routes/v1/notificationRoute.js";
|
||||
import AuthRoutes from "../routes/authRoute.js";
|
||||
import InviteRoutes from "../routes/inviteRoute.js";
|
||||
import MonitorRoutes from "../routes/monitorRoute.js";
|
||||
import CheckRoutes from "../routes/checkRoute.js";
|
||||
import SettingsRoutes from "../routes/settingsRoute.js";
|
||||
import MaintenanceWindowRoutes from "../routes/maintenanceWindowRoute.js";
|
||||
import StatusPageRoutes from "../routes/statusPageRoute.js";
|
||||
import QueueRoutes from "../routes/queueRoute.js";
|
||||
import LogRoutes from "../routes/logRoutes.js";
|
||||
import DiagnosticRoutes from "../routes/diagnosticRoute.js";
|
||||
import NotificationRoutes from "../routes/notificationRoute.js";
|
||||
|
||||
import IncidentRoutes from "../routes/v1/incidentRoute.js";
|
||||
import IncidentRoutes from "../routes/incidentRoute.js";
|
||||
|
||||
export const setupRoutes = (app: any, controllers: Record<string, any>, services: Record<string, any>) => {
|
||||
const verifyJWT = createVerifyJWT(services.settingsService);
|
||||
// V1
|
||||
const authRoutes = new AuthRoutes(controllers.authController, verifyJWT);
|
||||
const monitorRoutes = new MonitorRoutes(controllers.monitorController);
|
||||
const settingsRoutes = new SettingsRoutes(controllers.settingsController);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import TranslationService from "../service/system/translationService.js";
|
||||
import MongoDB from "../db/MongoDB.js";
|
||||
import NetworkService from "../service/infrastructure/networkService.js";
|
||||
import EmailService from "../service/infrastructure/emailService.js";
|
||||
@@ -24,7 +23,6 @@ import InviteService from "../service/business/inviteService.js";
|
||||
import MaintenanceWindowService from "../service/business/maintenanceWindowService.js";
|
||||
import { MonitorService } from "@/service/index.js";
|
||||
import IncidentService from "../service/business/incidentService.js";
|
||||
import papaparse from "papaparse";
|
||||
import axios from "axios";
|
||||
import got from "got";
|
||||
import ping from "ping";
|
||||
@@ -82,6 +80,7 @@ import {
|
||||
MongoSettingsRepository,
|
||||
MongoNotificationsRepository,
|
||||
MongoIncidentRepository,
|
||||
MongoTeamsRepository,
|
||||
IMonitorsRepository,
|
||||
IChecksRepository,
|
||||
IMonitorStatsRepository,
|
||||
@@ -92,12 +91,13 @@ import {
|
||||
ISettingsRepository,
|
||||
INotificationsRepository,
|
||||
IIncidentsRepository,
|
||||
ITeamsRepository,
|
||||
} from "@/repositories/index.js";
|
||||
import { ILogger } from "@/utils/logger.js";
|
||||
import { EnvConfig } from "@/service/system/settingsService.js";
|
||||
|
||||
export type InitializedSerivces = {
|
||||
//v1
|
||||
export type InitializedServices = {
|
||||
settingsService: any;
|
||||
translationService: any;
|
||||
db: any;
|
||||
networkService: any;
|
||||
emailService: any;
|
||||
@@ -126,6 +126,7 @@ export type InitializedSerivces = {
|
||||
settingsRepository: ISettingsRepository;
|
||||
notificationsRepository: INotificationsRepository;
|
||||
incidentsRepository: IIncidentsRepository;
|
||||
teamsRepository: ITeamsRepository;
|
||||
};
|
||||
|
||||
export const initializeServices = async ({
|
||||
@@ -133,13 +134,10 @@ export const initializeServices = async ({
|
||||
envSettings,
|
||||
settingsService,
|
||||
}: {
|
||||
logger: any;
|
||||
envSettings: any;
|
||||
logger: ILogger;
|
||||
envSettings: EnvConfig;
|
||||
settingsService: any;
|
||||
}): Promise<InitializedSerivces> => {
|
||||
const translationService = new TranslationService(logger);
|
||||
await translationService.initialize();
|
||||
|
||||
}): Promise<InitializedServices> => {
|
||||
// Create DB
|
||||
const inviteModule = new InviteModule({ InviteToken, crypto });
|
||||
const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, AppSettings });
|
||||
@@ -176,6 +174,8 @@ export const initializeServices = async ({
|
||||
const settingsRepository = new MongoSettingsRepository();
|
||||
const notificationsRepository = new MongoNotificationsRepository();
|
||||
const incidentsRepository = new MongoIncidentRepository();
|
||||
const teamsRepository = new MongoTeamsRepository();
|
||||
|
||||
const networkService = new NetworkService({
|
||||
axios,
|
||||
got,
|
||||
@@ -206,7 +206,7 @@ export const initializeServices = async ({
|
||||
checksRepository,
|
||||
});
|
||||
|
||||
const bufferService = new BufferService({ logger, checkService });
|
||||
const bufferService = new BufferService({ logger, checkService, settingsService });
|
||||
|
||||
const statusService = new StatusService({ db, logger, buffer: bufferService, monitorsRepository });
|
||||
|
||||
@@ -259,6 +259,7 @@ export const initializeServices = async ({
|
||||
invitesRepository,
|
||||
recoveryTokensRepository,
|
||||
settingsRepository,
|
||||
teamsRepository,
|
||||
});
|
||||
|
||||
const diagnosticService = new DiagnosticService();
|
||||
@@ -276,7 +277,6 @@ export const initializeServices = async ({
|
||||
const monitorService = new MonitorService({
|
||||
jobQueue: superSimpleQueue,
|
||||
emailService,
|
||||
papaparse,
|
||||
logger,
|
||||
errorService,
|
||||
games,
|
||||
@@ -289,7 +289,6 @@ export const initializeServices = async ({
|
||||
const services = {
|
||||
//v1
|
||||
settingsService,
|
||||
translationService,
|
||||
db,
|
||||
networkService,
|
||||
emailService,
|
||||
@@ -318,6 +317,7 @@ export const initializeServices = async ({
|
||||
settingsRepository,
|
||||
notificationsRepository,
|
||||
incidentsRepository,
|
||||
teamsRepository,
|
||||
};
|
||||
|
||||
return services;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { requireTeamId } from "./controllerUtils.js";
|
||||
|
||||
const SERVICE_NAME = "incidentController";
|
||||
|
||||
@@ -17,10 +18,19 @@ class IncidentController {
|
||||
|
||||
getIncidentsByTeam = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const result = await this.incidentService.getIncidentsByTeam({
|
||||
teamId: req?.user?.teamId,
|
||||
query: req?.query,
|
||||
});
|
||||
const { sortOrder, dateRange, page, rowsPerPage, status, monitorId, resolutionType } = req.query || {};
|
||||
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const result = await this.incidentService.getIncidentsByTeam(
|
||||
teamId,
|
||||
sortOrder,
|
||||
dateRange,
|
||||
page,
|
||||
rowsPerPage,
|
||||
status,
|
||||
monitorId,
|
||||
resolutionType
|
||||
);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
|
||||
@@ -174,27 +174,15 @@ class MonitorController {
|
||||
|
||||
createBulkMonitors = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
throw new AppError({ message: "No file uploaded", status: 400 });
|
||||
const userId = req.user?.id;
|
||||
if (!userId) {
|
||||
throw new AppError({ message: "User ID is required", status: 400 });
|
||||
}
|
||||
|
||||
if (!req.file.mimetype.includes("csv")) {
|
||||
throw new AppError({ message: "File is not a CSV", status: 400 });
|
||||
}
|
||||
|
||||
if (req.file.size === 0) {
|
||||
throw new AppError({ message: "File is empty", status: 400 });
|
||||
}
|
||||
|
||||
const userId = requireString(req?.user?.id, "User ID");
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
|
||||
const fileData = req?.file?.buffer?.toString("utf-8");
|
||||
if (!fileData) {
|
||||
throw new AppError({ message: "Cannot get file from buffer", status: 400 });
|
||||
}
|
||||
|
||||
const monitors = await this.monitorService.createBulkMonitors(fileData, userId, teamId);
|
||||
const data = req.body;
|
||||
const monitors = await this.monitorService.createBulkMonitors(data, userId, teamId);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import mongoose from "mongoose";
|
||||
const TeamSchema = mongoose.Schema(
|
||||
{
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
}
|
||||
);
|
||||
export default mongoose.model("Team", TeamSchema);
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
import { Schema, model, type Types } from "mongoose";
|
||||
|
||||
import type { Team } from "@/types/index.js";
|
||||
|
||||
type TeamDocumentBase = Omit<Team, "id" | "createdAt" | "updatedAt"> & {};
|
||||
interface TeamDocument extends TeamDocumentBase {
|
||||
_id: Types.ObjectId;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
const TeamSchema = new Schema<TeamDocument>(
|
||||
{
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
}
|
||||
);
|
||||
const TeamModel = model<TeamDocument>("Team", TeamSchema);
|
||||
|
||||
export type { TeamDocument };
|
||||
export { TeamModel };
|
||||
export default TeamModel;
|
||||
@@ -27,3 +27,6 @@ export { default as NotificationModel } from "@/db/models/Notification.js";
|
||||
|
||||
export * from "@/db/models/Incident.js";
|
||||
export { default as IncidentModel } from "@/db/models/Incident.js";
|
||||
|
||||
export * from "@/db/models/Team.js";
|
||||
export { default as TeamModel } from "@/db/models/Team.js";
|
||||
@@ -7,12 +7,12 @@ import path from "path";
|
||||
import fs from "fs";
|
||||
import { runMigrations } from "./db/migration/index.js";
|
||||
|
||||
import Logger from "./utils/logger.js";
|
||||
import SettingsService from "./service/system/settingsService.js";
|
||||
import Logger, { ILogger } from "@/utils/logger.js";
|
||||
import SettingsService from "@/service/system/settingsService.js";
|
||||
import AppSettings from "./db/models/AppSettings.js";
|
||||
|
||||
const SERVICE_NAME = "Server";
|
||||
let logger;
|
||||
let logger: ILogger;
|
||||
|
||||
const startApp = async () => {
|
||||
// FE path
|
||||
@@ -27,3 +27,6 @@ export { default as MongoNotificationsRepository } from "@/repositories/notifica
|
||||
|
||||
export * from "@/repositories/incidents/IIncidentsRepository.js";
|
||||
export { default as MongoIncidentRepository } from "@/repositories/incidents/MongoIncidentRepository.js";
|
||||
|
||||
export * from "@/repositories/teams/ITeamsRepository.js";
|
||||
export { default as MongoTeamsRepository } from "@/repositories/teams/MongoTeamsRepository.js";
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type MonitorType, type Monitor } from "@/types/index.js";
|
||||
import { type MonitorType, type Monitor, type MonitorsSummary } from "@/types/index.js";
|
||||
|
||||
export interface TeamQueryConfig {
|
||||
limit?: number;
|
||||
@@ -37,6 +37,6 @@ export interface IMonitorsRepository {
|
||||
findMonitorCountByTeamIdAndType(teamId: string, config: TeamQueryConfig): Promise<number>;
|
||||
|
||||
// other
|
||||
findMonitorsSummaryByTeamId(teamId: string, config?: SummaryConfig): Promise<any>;
|
||||
findMonitorsSummaryByTeamId(teamId: string, config?: SummaryConfig): Promise<MonitorsSummary>;
|
||||
findGroupsByTeamId(teamId: string): Promise<string[]>;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MonitorModel } from "@/db/models/index.js";
|
||||
import type { MonitorDocument } from "@/db/models/index.js";
|
||||
import type { Monitor } from "@/types/index.js";
|
||||
import type { Monitor, MonitorsSummary } from "@/types/index.js";
|
||||
import mongoose, { type FilterQuery } from "mongoose";
|
||||
import type { IMonitorsRepository, TeamQueryConfig, SummaryConfig } from "./IMonitorsRepository.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
@@ -146,10 +146,7 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
||||
return { monitors: this.mapDocuments(monitors), deletedCount };
|
||||
};
|
||||
|
||||
findMonitorsSummaryByTeamId = async (
|
||||
teamId: string,
|
||||
config?: SummaryConfig
|
||||
): Promise<{ totalMonitors: number; upMonitors: number; downMonitors: number; pausedMonitors: number }> => {
|
||||
findMonitorsSummaryByTeamId = async (teamId: string, config?: SummaryConfig): Promise<MonitorsSummary> => {
|
||||
const match: FilterQuery<MonitorDocument> = { teamId: new mongoose.Types.ObjectId(teamId) };
|
||||
if (config?.type !== undefined) {
|
||||
match.type = Array.isArray(config.type) ? { $in: config.type } : config.type;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { Team } from "@/types/index.js";
|
||||
export interface ITeamsRepository {
|
||||
// create
|
||||
create(email: string): Promise<Team>;
|
||||
// fetch
|
||||
// update
|
||||
// delete
|
||||
// other
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Team } from "@/types/index.js";
|
||||
import { TeamDocument, TeamModel } from "@/db/models/index.js";
|
||||
import { ITeamsRepository } from "@/repositories/index.js";
|
||||
import mongoose from "mongoose";
|
||||
|
||||
class MongoTeamsRepository implements ITeamsRepository {
|
||||
private toStringId = (value?: mongoose.Types.ObjectId | string | null): string => {
|
||||
if (!value) {
|
||||
return "";
|
||||
}
|
||||
return value instanceof mongoose.Types.ObjectId ? value.toString() : String(value);
|
||||
};
|
||||
|
||||
private toDateString = (value?: Date | string | null): string => {
|
||||
if (!value) {
|
||||
return new Date(0).toISOString();
|
||||
}
|
||||
return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
|
||||
};
|
||||
|
||||
private toEntity = (doc: TeamDocument): Team => {
|
||||
return {
|
||||
id: this.toStringId(doc._id),
|
||||
email: doc.email,
|
||||
createdAt: this.toDateString(doc.createdAt),
|
||||
updatedAt: this.toDateString(doc.updatedAt),
|
||||
};
|
||||
};
|
||||
|
||||
create = async (email: string) => {
|
||||
const team = await TeamModel.create({ email });
|
||||
return this.toEntity(team);
|
||||
};
|
||||
}
|
||||
|
||||
export default MongoTeamsRepository;
|
||||
@@ -1,17 +1,20 @@
|
||||
import { Router } from "express";
|
||||
import { isAllowed } from "../../middleware/isAllowed.js";
|
||||
import { Router, RequestHandler } from "express";
|
||||
import { isAllowed } from "../middleware/isAllowed.js";
|
||||
import multer from "multer";
|
||||
|
||||
const upload = multer();
|
||||
|
||||
class AuthRoutes {
|
||||
constructor(authController, verifyJWT) {
|
||||
private router: Router;
|
||||
private authController: any;
|
||||
|
||||
constructor(authController: any, verifyJWT: RequestHandler) {
|
||||
this.router = Router();
|
||||
this.authController = authController;
|
||||
this.initRoutes(verifyJWT);
|
||||
}
|
||||
|
||||
initRoutes(verifyJWT) {
|
||||
initRoutes(verifyJWT: RequestHandler) {
|
||||
this.router.post("/register", upload.single("profileImage"), this.authController.registerUser);
|
||||
this.router.post("/login", this.authController.loginUser);
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { Router } from "express";
|
||||
|
||||
import { isAllowed } from "../../middleware/isAllowed.js";
|
||||
import { isAllowed } from "../middleware/isAllowed.js";
|
||||
|
||||
class CheckRoutes {
|
||||
constructor(checkController) {
|
||||
private router: Router;
|
||||
private checkController: any;
|
||||
|
||||
constructor(checkController: any) {
|
||||
this.router = Router();
|
||||
this.checkController = checkController;
|
||||
this.initRoutes();
|
||||
@@ -1,14 +1,17 @@
|
||||
import { Router } from "express";
|
||||
import { isAllowed } from "../../middleware/isAllowed.js";
|
||||
import { RequestHandler, Router } from "express";
|
||||
import { isAllowed } from "../middleware/isAllowed.js";
|
||||
|
||||
class DiagnosticRoutes {
|
||||
constructor(diagnosticController, verifyJWT) {
|
||||
private router: Router;
|
||||
private diagnosticController: any;
|
||||
|
||||
constructor(diagnosticController: any, verifyJWT: RequestHandler) {
|
||||
this.router = Router();
|
||||
this.diagnosticController = diagnosticController;
|
||||
this.initRoutes(verifyJWT);
|
||||
}
|
||||
|
||||
initRoutes(verifyJWT) {
|
||||
initRoutes(verifyJWT: RequestHandler) {
|
||||
this.router.get("/system", verifyJWT, isAllowed(["admin", "superadmin"]), this.diagnosticController.getSystemStats);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { Router } from "express";
|
||||
import { isAllowed } from "../../middleware/isAllowed.js";
|
||||
import { isAllowed } from "../middleware/isAllowed.js";
|
||||
|
||||
class IncidentRoutes {
|
||||
constructor(incidentController) {
|
||||
private router: Router;
|
||||
private incidentController: any;
|
||||
|
||||
constructor(incidentController: any) {
|
||||
this.router = Router();
|
||||
this.incidentController = incidentController;
|
||||
this.initRoutes();
|
||||
@@ -1,14 +1,17 @@
|
||||
import { Router } from "express";
|
||||
import { isAllowed } from "../../middleware/isAllowed.js";
|
||||
import { RequestHandler, Router } from "express";
|
||||
import { isAllowed } from "../middleware/isAllowed.js";
|
||||
|
||||
class InviteRoutes {
|
||||
constructor(inviteController, verifyJWT) {
|
||||
private router: Router;
|
||||
private inviteController: any;
|
||||
|
||||
constructor(inviteController: any, verifyJWT: RequestHandler) {
|
||||
this.router = Router();
|
||||
this.inviteController = inviteController;
|
||||
this.initRoutes(verifyJWT);
|
||||
}
|
||||
|
||||
initRoutes(verifyJWT) {
|
||||
initRoutes(verifyJWT: RequestHandler) {
|
||||
this.router.post("/send", verifyJWT, isAllowed(["admin", "superadmin"]), this.inviteController.sendInviteEmail);
|
||||
this.router.post("/verify", this.inviteController.verifyInviteToken);
|
||||
this.router.post("/", verifyJWT, isAllowed(["admin", "superadmin"]), this.inviteController.getInviteToken);
|
||||
@@ -1,7 +1,10 @@
|
||||
import { Router } from "express";
|
||||
import { isAllowed } from "../../middleware/isAllowed.js";
|
||||
import { isAllowed } from "../middleware/isAllowed.js";
|
||||
class LogRoutes {
|
||||
constructor(logController) {
|
||||
private router: Router;
|
||||
private logController: any;
|
||||
|
||||
constructor(logController: any) {
|
||||
this.router = Router();
|
||||
this.logController = logController;
|
||||
this.initRoutes();
|
||||
+4
-1
@@ -1,7 +1,10 @@
|
||||
import { Router } from "express";
|
||||
|
||||
class MaintenanceWindowRoutes {
|
||||
constructor(maintenanceWindowController) {
|
||||
private router: Router;
|
||||
private mwController: any;
|
||||
|
||||
constructor(maintenanceWindowController: any) {
|
||||
this.router = Router();
|
||||
this.mwController = maintenanceWindowController;
|
||||
this.initRoutes();
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Router } from "express";
|
||||
import { isAllowed } from "../../middleware/isAllowed.js";
|
||||
import { isAllowed } from "@/middleware/isAllowed.js";
|
||||
import multer from "multer";
|
||||
import { fetchMonitorCertificate } from "../../controllers/controllerUtils.js";
|
||||
import { fetchMonitorCertificate } from "@/controllers/controllerUtils.js";
|
||||
const upload = multer({
|
||||
storage: multer.memoryStorage(), // Store file in memory as Buffer
|
||||
});
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Router } from "express";
|
||||
class NotificationRoutes {
|
||||
constructor(notificationController) {
|
||||
private router: Router;
|
||||
private notificationController: any;
|
||||
|
||||
constructor(notificationController: any) {
|
||||
this.router = Router();
|
||||
this.notificationController = notificationController;
|
||||
this.initializeRoutes();
|
||||
@@ -1,7 +1,10 @@
|
||||
import { Router } from "express";
|
||||
import { isAllowed } from "../../middleware/isAllowed.js";
|
||||
import { isAllowed } from "../middleware/isAllowed.js";
|
||||
class QueueRoutes {
|
||||
constructor(queueController) {
|
||||
private router: Router;
|
||||
private queueController: any;
|
||||
|
||||
constructor(queueController: any) {
|
||||
this.router = Router();
|
||||
this.queueController = queueController;
|
||||
this.initRoutes();
|
||||
@@ -1,8 +1,11 @@
|
||||
import { Router } from "express";
|
||||
import { isAllowed } from "../../middleware/isAllowed.js";
|
||||
import { isAllowed } from "../middleware/isAllowed.js";
|
||||
|
||||
class SettingsRoutes {
|
||||
constructor(settingsController) {
|
||||
private router: Router;
|
||||
private settingsController: any;
|
||||
|
||||
constructor(settingsController: any) {
|
||||
this.router = Router();
|
||||
this.settingsController = settingsController;
|
||||
this.initRoutes();
|
||||
@@ -1,15 +1,18 @@
|
||||
import { Router } from "express";
|
||||
import { RequestHandler, Router } from "express";
|
||||
import multer from "multer";
|
||||
const upload = multer();
|
||||
|
||||
class StatusPageRoutes {
|
||||
constructor(statusPageController, verifyJWT) {
|
||||
private router: Router;
|
||||
private statusPageController: any;
|
||||
|
||||
constructor(statusPageController: any, verifyJWT: RequestHandler) {
|
||||
this.router = Router();
|
||||
this.statusPageController = statusPageController;
|
||||
this.initRoutes(verifyJWT);
|
||||
}
|
||||
|
||||
initRoutes(verifyJWT) {
|
||||
initRoutes(verifyJWT: RequestHandler) {
|
||||
this.router.get("/", this.statusPageController.getStatusPage);
|
||||
this.router.get("/team", verifyJWT, this.statusPageController.getStatusPagesByTeamId);
|
||||
|
||||
@@ -124,13 +124,21 @@ class IncidentService {
|
||||
}
|
||||
};
|
||||
|
||||
getIncidentsByTeam = async ({ teamId, query }: { teamId: string; query?: any }) => {
|
||||
getIncidentsByTeam = async (
|
||||
teamId: string,
|
||||
sortOrder: string,
|
||||
dateRange: string,
|
||||
page: string,
|
||||
rowsPerPage: string,
|
||||
status: string,
|
||||
monitorId: string,
|
||||
resolutionType: string
|
||||
) => {
|
||||
try {
|
||||
if (!teamId) {
|
||||
throw this.errorService.createBadRequestError("No team ID in request");
|
||||
}
|
||||
|
||||
const { sortOrder, dateRange, page, rowsPerPage, status, monitorId, resolutionType } = query || {};
|
||||
const startDate = dateRangeLookup[dateRange];
|
||||
|
||||
const parsedPage = Number.isFinite(parseInt(page)) ? parseInt(page) : 0;
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
import { createMonitorsBodyValidation } from "@/validation/joi.js";
|
||||
import { NormalizeData, NormalizeDataUptimeDetails } from "@/utils/dataUtils.js";
|
||||
import { type Monitor } from "@/types/index.js";
|
||||
import type { MonitorType } from "@/types/monitor.js";
|
||||
import type {
|
||||
MonitorType,
|
||||
MonitorsWithChecksByTeamIdResult,
|
||||
UptimeDetailsResult,
|
||||
HardwareDetailsResult,
|
||||
PageSpeedDetailsResult,
|
||||
} from "@/types/monitor.js";
|
||||
import type { IChecksRepository, IMonitorsRepository, IMonitorStatsRepository, IStatusPagesRepository } from "@/repositories/index.js";
|
||||
import fs from "fs";
|
||||
import { fileURLToPath } from "url";
|
||||
import path from "path";
|
||||
|
||||
import { AppError } from "../infrastructure/errorService.js";
|
||||
import { ISuperSimpleQueue } from "../infrastructure/SuperSimpleQueue/SuperSimpleQueue.js";
|
||||
|
||||
const SERVICE_NAME = "MonitorService";
|
||||
type DateRangeKey = "recent" | "day" | "week" | "month" | "all";
|
||||
@@ -17,13 +24,13 @@ export interface IMonitorService {
|
||||
|
||||
// create
|
||||
createMonitor(teamId: string, userId: string, body: Monitor): Promise<void>;
|
||||
createBulkMonitors(fileData: string, userId: string, teamId: string): Promise<any>;
|
||||
addDemoMonitors(args: { userId: string; teamId: string }): Promise<any[]>;
|
||||
createBulkMonitors(monitors: Array<Monitor>, userId: string, teamId: string): Promise<Monitor[] | null>;
|
||||
addDemoMonitors(args: { userId: string; teamId: string }): Promise<Monitor[]>;
|
||||
|
||||
// read
|
||||
getUptimeDetailsById(args: { teamId: string; monitorId: string; dateRange: string; normalize?: boolean }): Promise<any>;
|
||||
getHardwareDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise<any>;
|
||||
getPageSpeedDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise<any>;
|
||||
getUptimeDetailsById(args: { teamId: string; monitorId: string; dateRange: string; normalize?: boolean }): Promise<UptimeDetailsResult>;
|
||||
getHardwareDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise<HardwareDetailsResult>;
|
||||
getPageSpeedDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise<PageSpeedDetailsResult>;
|
||||
getMonitorById(args: { teamId: string; monitorId: string }): Promise<Monitor>;
|
||||
getMonitorsByTeamId(args: {
|
||||
teamId: string;
|
||||
@@ -34,7 +41,7 @@ export interface IMonitorService {
|
||||
filter?: string;
|
||||
field?: string;
|
||||
order?: "asc" | "desc";
|
||||
}): Promise<any>;
|
||||
}): Promise<Monitor[] | null>;
|
||||
getMonitorsWithChecksByTeamId(args: {
|
||||
teamId: string;
|
||||
limit?: number;
|
||||
@@ -45,7 +52,7 @@ export interface IMonitorService {
|
||||
field?: string;
|
||||
order?: "asc" | "desc";
|
||||
explain?: boolean;
|
||||
}): Promise<{ summary: any; count: number; monitors: any[] }>;
|
||||
}): Promise<MonitorsWithChecksByTeamIdResult>;
|
||||
getAllGames(): any;
|
||||
getGroupsByTeamId(args: { teamId: string }): Promise<string[]>;
|
||||
|
||||
@@ -59,15 +66,14 @@ export interface IMonitorService {
|
||||
|
||||
// other
|
||||
sendTestEmail(args: { to: string }): Promise<string>;
|
||||
exportMonitorsToJSON(args: { teamId: string }): Promise<any[]>;
|
||||
exportMonitorsToJSON(args: { teamId: string }): Promise<Monitor[]>;
|
||||
}
|
||||
|
||||
export class MonitorService implements IMonitorService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
private jobQueue: any;
|
||||
private jobQueue: ISuperSimpleQueue;
|
||||
private emailService: any;
|
||||
private papaparse: any;
|
||||
private logger: any;
|
||||
private errorService: any;
|
||||
private games: any;
|
||||
@@ -79,7 +85,6 @@ export class MonitorService implements IMonitorService {
|
||||
constructor({
|
||||
jobQueue,
|
||||
emailService,
|
||||
papaparse,
|
||||
logger,
|
||||
errorService,
|
||||
games,
|
||||
@@ -88,9 +93,8 @@ export class MonitorService implements IMonitorService {
|
||||
monitorStatsRepository,
|
||||
statusPagesRepository,
|
||||
}: {
|
||||
jobQueue: any;
|
||||
jobQueue: ISuperSimpleQueue;
|
||||
emailService: any;
|
||||
papaparse: any;
|
||||
logger: any;
|
||||
errorService: any;
|
||||
games: any;
|
||||
@@ -101,7 +105,6 @@ export class MonitorService implements IMonitorService {
|
||||
}) {
|
||||
this.jobQueue = jobQueue;
|
||||
this.emailService = emailService;
|
||||
this.papaparse = papaparse;
|
||||
this.logger = logger;
|
||||
this.errorService = errorService;
|
||||
this.games = games;
|
||||
@@ -149,63 +152,14 @@ export class MonitorService implements IMonitorService {
|
||||
this.jobQueue.addJob(monitor.id, monitor);
|
||||
};
|
||||
|
||||
createBulkMonitors = async (fileData: string, userId: string, teamId: string): Promise<any> => {
|
||||
const { parse } = this.papaparse;
|
||||
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
parse(fileData, {
|
||||
header: true,
|
||||
skipEmptyLines: true,
|
||||
transform: (value: string, header: string): string | number | undefined => {
|
||||
if (value === "") return undefined; // Empty fields become undefined
|
||||
|
||||
// Handle 'port' and 'interval' fields, check if they're valid numbers
|
||||
if (["port", "interval"].includes(header)) {
|
||||
const num = parseInt(value, 10);
|
||||
if (isNaN(num)) {
|
||||
throw this.errorService.createBadRequestError(`${header} should be a valid number, got: ${value}`);
|
||||
}
|
||||
return num;
|
||||
createBulkMonitors = async (monitors: Array<Monitor>, userId: string, teamId: string): Promise<Monitor[] | null> => {
|
||||
const createdMonitors = await this.monitorsRepository.createBulkMonitors(monitors);
|
||||
if (!monitors || monitors.length === 0) {
|
||||
throw new AppError("Failed to create monitors", 500);
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
complete: async ({ data, errors }: { data: any[]; errors: Error[] }): Promise<void> => {
|
||||
try {
|
||||
if (errors.length > 0) {
|
||||
throw this.errorService.createServerError("Error parsing CSV");
|
||||
}
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
throw this.errorService.createServerError("CSV file contains no data rows");
|
||||
}
|
||||
|
||||
const enrichedData = data.map((monitor: Monitor) => ({
|
||||
...monitor,
|
||||
userId,
|
||||
teamId,
|
||||
description: monitor.description || monitor.name || monitor.url,
|
||||
name: monitor.name || monitor.url,
|
||||
type: monitor.type || "http",
|
||||
}));
|
||||
|
||||
await createMonitorsBodyValidation.validateAsync(enrichedData);
|
||||
|
||||
const monitors = await this.monitorsRepository.createBulkMonitors(enrichedData);
|
||||
|
||||
await Promise.all(
|
||||
monitors.map(async (monitor: Monitor) => {
|
||||
this.jobQueue.addJob(monitor.id, monitor);
|
||||
})
|
||||
);
|
||||
|
||||
resolve(monitors);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
await Promise.all(createdMonitors.map((monitor) => this.jobQueue.addJob(monitor.id, monitor)));
|
||||
return createdMonitors;
|
||||
};
|
||||
|
||||
addDemoMonitors = async ({ userId, teamId }: { userId: string; teamId: string }): Promise<any[]> => {
|
||||
@@ -241,7 +195,7 @@ export class MonitorService implements IMonitorService {
|
||||
monitorId: string;
|
||||
dateRange: string;
|
||||
normalize?: boolean;
|
||||
}): Promise<any> => {
|
||||
}): Promise<UptimeDetailsResult> => {
|
||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||
if (!monitor) {
|
||||
throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 });
|
||||
@@ -276,7 +230,15 @@ export class MonitorService implements IMonitorService {
|
||||
};
|
||||
};
|
||||
|
||||
getHardwareDetailsById = async ({ teamId, monitorId, dateRange }: { teamId: string; monitorId: string; dateRange: string }): Promise<any> => {
|
||||
getHardwareDetailsById = async ({
|
||||
teamId,
|
||||
monitorId,
|
||||
dateRange,
|
||||
}: {
|
||||
teamId: string;
|
||||
monitorId: string;
|
||||
dateRange: string;
|
||||
}): Promise<HardwareDetailsResult> => {
|
||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||
if (!monitor) {
|
||||
throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 });
|
||||
@@ -307,7 +269,15 @@ export class MonitorService implements IMonitorService {
|
||||
};
|
||||
};
|
||||
|
||||
getPageSpeedDetailsById = async ({ teamId, monitorId, dateRange }: { teamId: string; monitorId: string; dateRange: string }): Promise<any> => {
|
||||
getPageSpeedDetailsById = async ({
|
||||
teamId,
|
||||
monitorId,
|
||||
dateRange,
|
||||
}: {
|
||||
teamId: string;
|
||||
monitorId: string;
|
||||
dateRange: string;
|
||||
}): Promise<PageSpeedDetailsResult> => {
|
||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||
if (!monitor) {
|
||||
throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 });
|
||||
@@ -336,12 +306,20 @@ export class MonitorService implements IMonitorService {
|
||||
monitorStats,
|
||||
};
|
||||
};
|
||||
getMonitorById = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise<any> => {
|
||||
getMonitorById = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise<Monitor> => {
|
||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||
return monitor;
|
||||
};
|
||||
|
||||
getMonitorsByTeamId = async ({ teamId, type, filter }: { teamId: string; type?: MonitorType | MonitorType[]; filter?: string }): Promise<any> => {
|
||||
getMonitorsByTeamId = async ({
|
||||
teamId,
|
||||
type,
|
||||
filter,
|
||||
}: {
|
||||
teamId: string;
|
||||
type?: MonitorType | MonitorType[];
|
||||
filter?: string;
|
||||
}): Promise<Monitor[] | null> => {
|
||||
const monitors = await this.monitorsRepository.findByTeamId(teamId, {
|
||||
type,
|
||||
filter,
|
||||
@@ -369,7 +347,7 @@ export class MonitorService implements IMonitorService {
|
||||
field?: string;
|
||||
order?: "asc" | "desc";
|
||||
explain?: boolean;
|
||||
}): Promise<{ summary: any; count: number; monitors: any[] }> => {
|
||||
}): Promise<MonitorsWithChecksByTeamIdResult> => {
|
||||
const summary = await this.monitorsRepository.findMonitorsSummaryByTeamId(teamId);
|
||||
const count = await this.monitorsRepository.findMonitorCountByTeamIdAndType(teamId, { type, filter });
|
||||
const monitors = await this.monitorsRepository.findByTeamId(teamId, {
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { IInvitesRepository, IMonitorsRepository, IRecoveryTokensRepository, IUsersRepository, ISettingsRepository } from "@/repositories/index.js";
|
||||
import Team from "@/db/models/Team.js";
|
||||
import {
|
||||
IInvitesRepository,
|
||||
IMonitorsRepository,
|
||||
IRecoveryTokensRepository,
|
||||
IUsersRepository,
|
||||
ISettingsRepository,
|
||||
ITeamsRepository,
|
||||
} from "@/repositories/index.js";
|
||||
import type { User } from "@/types/index.js";
|
||||
import bcrypt from "bcryptjs";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { ISuperSimpleQueue } from "../infrastructure/SuperSimpleQueue/SuperSimpleQueue.js";
|
||||
import { ISuperSimpleQueue } from "@/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js";
|
||||
|
||||
const SERVICE_NAME = "userService";
|
||||
|
||||
@@ -21,6 +27,7 @@ class UserService {
|
||||
private invitesRepository: IInvitesRepository;
|
||||
private recoveryTokensRepository: IRecoveryTokensRepository;
|
||||
private settingsRepository: ISettingsRepository;
|
||||
private teamsRepository: ITeamsRepository;
|
||||
|
||||
constructor({
|
||||
crypto,
|
||||
@@ -34,6 +41,7 @@ class UserService {
|
||||
invitesRepository,
|
||||
recoveryTokensRepository,
|
||||
settingsRepository,
|
||||
teamsRepository,
|
||||
}: {
|
||||
crypto: any;
|
||||
emailService: any;
|
||||
@@ -47,6 +55,7 @@ class UserService {
|
||||
invitesRepository: IInvitesRepository;
|
||||
recoveryTokensRepository: IRecoveryTokensRepository;
|
||||
settingsRepository: ISettingsRepository;
|
||||
teamsRepository: ITeamsRepository;
|
||||
}) {
|
||||
this.emailService = emailService;
|
||||
this.settingsService = settingsService;
|
||||
@@ -59,6 +68,7 @@ class UserService {
|
||||
this.invitesRepository = invitesRepository;
|
||||
this.recoveryTokensRepository = recoveryTokensRepository;
|
||||
this.settingsRepository = settingsRepository;
|
||||
this.teamsRepository = teamsRepository;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
@@ -85,10 +95,11 @@ class UserService {
|
||||
const jwtSecret = this.crypto.randomBytes(64).toString("hex");
|
||||
await this.settingsRepository.update({ jwtSecret });
|
||||
// Create a new team
|
||||
const team = new Team({
|
||||
email: user.email,
|
||||
});
|
||||
user.teamId = team._id;
|
||||
if (!user.email) {
|
||||
throw new AppError({ message: "Email is required for first user", service: SERVICE_NAME, method: "registerUser", status: 400 });
|
||||
}
|
||||
const team = await this.teamsRepository.create(user.email);
|
||||
user.teamId = team.id;
|
||||
}
|
||||
|
||||
const newUser = await this.usersRepository.create({ ...user }, file);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { config } from "@/config/index.js";
|
||||
import type { Check } from "@/types/index.js";
|
||||
|
||||
const SERVICE_NAME = "BufferService";
|
||||
@@ -12,8 +11,9 @@ class BufferService {
|
||||
private bufferTimer: NodeJS.Timeout | null = null;
|
||||
private checksService: any;
|
||||
|
||||
constructor({ logger, checkService }: { logger: any; checkService: any }) {
|
||||
this.BUFFER_TIMEOUT = config.NODE_ENV === "development" ? 10 : 1000 * 60 * 1; // 1 minute
|
||||
constructor({ logger, checkService, settingsService }: { logger: any; checkService: any; settingsService: any }) {
|
||||
this.BUFFER_TIMEOUT = settingsService.getSettings().nodeEnv === "development" ? 10 : 1000 * 60 * 1; // 1 minute
|
||||
console.log(this.BUFFER_TIMEOUT);
|
||||
this.logger = logger;
|
||||
this.checksService = checkService;
|
||||
this.SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
+25
-22
@@ -1,6 +1,17 @@
|
||||
const SERVICE_NAME = "SettingsService";
|
||||
|
||||
const envConfig = {
|
||||
export type EnvConfig = {
|
||||
jwtSecret: string | undefined;
|
||||
jwtTTL: string | undefined;
|
||||
systemEmailHost: string | undefined;
|
||||
nodeEnv: string | undefined;
|
||||
logLevel: string | undefined;
|
||||
clientHost: string | undefined;
|
||||
dbConnectionString: string | undefined;
|
||||
port: string | undefined;
|
||||
};
|
||||
|
||||
const envConfig: EnvConfig = {
|
||||
jwtSecret: process.env.JWT_SECRET,
|
||||
jwtTTL: process.env.TOKEN_TTL,
|
||||
systemEmailHost: process.env.SYSTEM_EMAIL_HOST,
|
||||
@@ -10,19 +21,20 @@ const envConfig = {
|
||||
dbConnectionString: process.env.DB_CONNECTION_STRING,
|
||||
port: process.env.PORT,
|
||||
};
|
||||
/**
|
||||
* SettingsService
|
||||
*
|
||||
* This service is responsible for loading and managing the application settings.
|
||||
*/
|
||||
class SettingsService {
|
||||
static SERVICE_NAME = "SettingsService";
|
||||
|
||||
/**
|
||||
* Constructs a new SettingsService
|
||||
* @constructor
|
||||
* @throws {Error}
|
||||
*/ constructor(AppSettings) {
|
||||
export interface ISettingsService {
|
||||
readonly serviceName: string;
|
||||
loadSettings(): EnvConfig;
|
||||
getSettings(): EnvConfig;
|
||||
getDBSettings(): Promise<Record<string, any>>;
|
||||
}
|
||||
|
||||
class SettingsService implements ISettingsService {
|
||||
static SERVICE_NAME = "SettingsService";
|
||||
private AppSettings: any;
|
||||
private settings: EnvConfig;
|
||||
|
||||
constructor(AppSettings: any) {
|
||||
this.AppSettings = AppSettings;
|
||||
this.settings = { ...envConfig };
|
||||
}
|
||||
@@ -31,19 +43,10 @@ class SettingsService {
|
||||
return SettingsService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load settings from env settings
|
||||
* @returns {Object>} The settings.
|
||||
*/
|
||||
loadSettings() {
|
||||
return this.settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current settings.
|
||||
* @returns {Object} The current settings.
|
||||
* @throws Will throw an error if settings have not been loaded.
|
||||
*/
|
||||
getSettings() {
|
||||
if (!this.settings) {
|
||||
throw new Error("Settings have not been loaded");
|
||||
@@ -1,92 +0,0 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
class TranslationService {
|
||||
static SERVICE_NAME = "TranslationService";
|
||||
constructor(logger) {
|
||||
this.logger = logger;
|
||||
this.translations = {};
|
||||
this._language = "en";
|
||||
this.localesDir = path.join(process.cwd(), "src/locales");
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return TranslationService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
setLanguage(language) {
|
||||
this._language = language;
|
||||
}
|
||||
|
||||
get language() {
|
||||
return this._language;
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
try {
|
||||
await this.loadFromFiles();
|
||||
} catch (error) {
|
||||
this.logger.error({
|
||||
message: error.message,
|
||||
service: "TranslationService",
|
||||
method: "initialize",
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async loadFromFiles() {
|
||||
try {
|
||||
if (!fs.existsSync(this.localesDir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(this.localesDir).filter((file) => file.endsWith(".json"));
|
||||
|
||||
if (files.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
const language = file.replace(".json", "");
|
||||
const filePath = path.join(this.localesDir, file);
|
||||
const content = fs.readFileSync(filePath, "utf8");
|
||||
this.translations[language] = JSON.parse(content);
|
||||
}
|
||||
|
||||
this.logger.info({
|
||||
message: "Translations loaded from files successfully",
|
||||
service: "TranslationService",
|
||||
method: "loadFromFiles",
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.logger.error({
|
||||
message: error.message,
|
||||
service: "TranslationService",
|
||||
method: "loadFromFiles",
|
||||
stack: error.stack,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
getTranslation(key) {
|
||||
let language = this._language;
|
||||
|
||||
try {
|
||||
return this.translations[language]?.[key] || this.translations["en"]?.[key] || key;
|
||||
} catch (error) {
|
||||
this.logger.error({
|
||||
message: error.message,
|
||||
service: "TranslationService",
|
||||
method: "getTranslation",
|
||||
stack: error.stack,
|
||||
});
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default TranslationService;
|
||||
@@ -156,11 +156,17 @@ export interface HardwareChecksResult {
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface GroupedCheck {
|
||||
_id: string;
|
||||
avgResponseTime: number;
|
||||
totalChecks: number;
|
||||
}
|
||||
|
||||
export interface UptimeChecksResult {
|
||||
monitorType: Exclude<MonitorType, "hardware" | "pagespeed">;
|
||||
groupedChecks: Array<{ _id: string; avgResponseTime: number; totalChecks: number }>;
|
||||
groupedUpChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>;
|
||||
groupedDownChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>;
|
||||
groupedChecks: GroupedCheck[];
|
||||
groupedUpChecks: GroupedCheck[];
|
||||
groupedDownChecks: GroupedCheck[];
|
||||
uptimePercentage: number;
|
||||
avgResponseTime: number;
|
||||
}
|
||||
@@ -171,3 +177,11 @@ export interface ChecksSummary {
|
||||
downChecks: number;
|
||||
cannotResolveChecks: number;
|
||||
}
|
||||
|
||||
export type NormalizedCheck<T extends Check = Check> = T & {
|
||||
originalResponseTime: number;
|
||||
};
|
||||
|
||||
export type NormalizedUptimeCheck<T extends GroupedCheck = GroupedCheck> = T & {
|
||||
originalAvgResponseTime: number;
|
||||
};
|
||||
@@ -11,3 +11,4 @@ export * from "@/types/notification.js";
|
||||
export * from "@/types/alert.js";
|
||||
export * from "@/types/incident.js";
|
||||
export * from "@/types/email.js";
|
||||
export * from "@/types/team.js";
|
||||
@@ -44,3 +44,78 @@ export interface Monitor {
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface MonitorsSummary {
|
||||
totalMonitors: number;
|
||||
upMonitors: number;
|
||||
downMonitors: number;
|
||||
pausedMonitors: number;
|
||||
}
|
||||
|
||||
export interface MonitorWithChecks extends Monitor {
|
||||
checks: import("./check.js").Check[];
|
||||
}
|
||||
|
||||
export interface MonitorsWithChecksByTeamIdResult {
|
||||
summary: MonitorsSummary | null;
|
||||
count: number;
|
||||
monitors: MonitorWithChecks[];
|
||||
}
|
||||
|
||||
export interface UptimeDetailsResult {
|
||||
monitorData: {
|
||||
monitor: Monitor;
|
||||
groupedChecks: import("./check.js").GroupedCheck[];
|
||||
groupedUpChecks: import("./check.js").GroupedCheck[];
|
||||
groupedDownChecks: import("./check.js").GroupedCheck[];
|
||||
groupedAvgResponseTime: number;
|
||||
groupedUptimePercentage: number;
|
||||
};
|
||||
monitorStats: import("./monitorStats.js").MonitorStats | null;
|
||||
}
|
||||
|
||||
export interface HardwareDetailsResult extends Monitor {
|
||||
stats: {
|
||||
aggregateData: {
|
||||
latestCheck: import("./check.js").Check | null;
|
||||
totalChecks: number;
|
||||
};
|
||||
upChecks: {
|
||||
totalChecks: number;
|
||||
};
|
||||
checks: Array<{
|
||||
_id: 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 PageSpeedDetailsResult {
|
||||
monitor: Monitor & {
|
||||
checks: import("./check.js").Check[];
|
||||
};
|
||||
monitorStats: import("./monitorStats.js").MonitorStats | null;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface Team {
|
||||
id: string;
|
||||
email: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
@@ -1,35 +1,33 @@
|
||||
const calculatePercentile = (arr, percentile) => {
|
||||
import type { Check, GroupedCheck, NormalizedCheck, NormalizedUptimeCheck } from "@/types/index.js";
|
||||
|
||||
const calculatePercentile = (arr: Check[], percentile: number): number => {
|
||||
const sorted = arr.slice().sort((a, b) => a.responseTime - b.responseTime);
|
||||
const index = (percentile / 100) * (sorted.length - 1);
|
||||
const lower = Math.floor(index);
|
||||
const upper = lower + 1;
|
||||
const weight = index % 1;
|
||||
if (upper >= sorted.length) return sorted[lower].responseTime;
|
||||
return sorted[lower].responseTime * (1 - weight) + sorted[upper].responseTime * weight;
|
||||
if (upper >= sorted.length) return sorted[lower]!.responseTime;
|
||||
return sorted[lower]!.responseTime * (1 - weight) + sorted[upper]!.responseTime * weight;
|
||||
};
|
||||
|
||||
const calculatePercentileUptimeDetails = (arr, percentile) => {
|
||||
const calculatePercentileUptimeDetails = (arr: GroupedCheck[], percentile: number): number => {
|
||||
const sorted = arr.slice().sort((a, b) => a.avgResponseTime - b.avgResponseTime);
|
||||
const index = (percentile / 100) * (sorted.length - 1);
|
||||
const lower = Math.floor(index);
|
||||
const upper = lower + 1;
|
||||
const weight = index % 1;
|
||||
if (upper >= sorted.length) return sorted[lower].avgResponseTime;
|
||||
return sorted[lower].avgResponseTime * (1 - weight) + sorted[upper].avgResponseTime * weight;
|
||||
if (upper >= sorted.length) return sorted[lower]!.avgResponseTime;
|
||||
return sorted[lower]!.avgResponseTime * (1 - weight) + sorted[upper]!.avgResponseTime * weight;
|
||||
};
|
||||
|
||||
const NormalizeData = (checks, rangeMin, rangeMax) => {
|
||||
export const NormalizeData = <T extends Check>(checks: T[], rangeMin: number, rangeMax: number): NormalizedCheck<T>[] => {
|
||||
if (checks.length > 1) {
|
||||
// Get the 5th and 95th percentile
|
||||
const min = calculatePercentile(checks, 0);
|
||||
const max = calculatePercentile(checks, 95);
|
||||
const normalizedChecks = checks.map((check) => {
|
||||
const originalResponseTime = check.responseTime;
|
||||
// Normalize the response time between 1 and 100
|
||||
let normalizedResponseTime = rangeMin + ((check.responseTime - min) * (rangeMax - rangeMin)) / (max - min);
|
||||
|
||||
// Put a floor on the response times so we don't have extreme outliers
|
||||
// Better visuals
|
||||
normalizedResponseTime = Math.max(rangeMin, Math.min(rangeMax, normalizedResponseTime));
|
||||
return {
|
||||
...check,
|
||||
@@ -45,19 +43,16 @@ const NormalizeData = (checks, rangeMin, rangeMax) => {
|
||||
});
|
||||
}
|
||||
};
|
||||
const NormalizeDataUptimeDetails = (checks, rangeMin, rangeMax) => {
|
||||
|
||||
export const NormalizeDataUptimeDetails = <T extends GroupedCheck>(checks: T[], rangeMin: number, rangeMax: number): NormalizedUptimeCheck<T>[] => {
|
||||
if (checks.length > 1) {
|
||||
// Get the 5th and 95th percentile
|
||||
const min = calculatePercentileUptimeDetails(checks, 0);
|
||||
const max = calculatePercentileUptimeDetails(checks, 95);
|
||||
|
||||
const normalizedChecks = checks.map((check) => {
|
||||
const originalResponseTime = check.avgResponseTime;
|
||||
// Normalize the response time between 1 and 100
|
||||
let normalizedResponseTime = rangeMin + ((check.avgResponseTime - min) * (rangeMax - rangeMin)) / (max - min);
|
||||
|
||||
// Put a floor on the response times so we don't have extreme outliers
|
||||
// Better visuals
|
||||
normalizedResponseTime = Math.max(rangeMin, Math.min(rangeMax, normalizedResponseTime));
|
||||
return {
|
||||
...check,
|
||||
@@ -69,32 +64,7 @@ const NormalizeDataUptimeDetails = (checks, rangeMin, rangeMax) => {
|
||||
return normalizedChecks;
|
||||
} else {
|
||||
return checks.map((check) => {
|
||||
return { ...check, originalResponseTime: check.responseTime };
|
||||
return { ...check, originalAvgResponseTime: check.avgResponseTime };
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const safelyParseFloat = (value, defaultValue = 0) => {
|
||||
if (value === null || typeof value === "undefined") {
|
||||
return defaultValue;
|
||||
}
|
||||
const stringValue = String(value).trim();
|
||||
|
||||
if (typeof value === "number" && !isNaN(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (stringValue === "") {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
const parsedValue = parseFloat(stringValue);
|
||||
|
||||
if (isNaN(parsedValue) || !isFinite(parsedValue)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return parsedValue;
|
||||
};
|
||||
|
||||
export { safelyParseFloat, calculatePercentile, NormalizeData, calculatePercentileUptimeDetails, NormalizeDataUptimeDetails };
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,7 @@
|
||||
import { createLogger, format, transports, Logger as WinstonLogger } from "winston";
|
||||
import type { Logform } from "winston";
|
||||
import dotenv from "dotenv";
|
||||
import { EnvConfig } from "@/service/system/settingsService.js";
|
||||
dotenv.config();
|
||||
|
||||
const SERVICE_NAME = "Logger";
|
||||
@@ -18,19 +19,26 @@ interface LogEntry extends LogConfig {
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
interface EnvSettings {
|
||||
logLevel?: string;
|
||||
export interface ILogger {
|
||||
readonly serviceName: string;
|
||||
info(config: LogConfig): void;
|
||||
warn(config: LogConfig): void;
|
||||
error(config: LogConfig): void;
|
||||
debug(config: LogConfig): void;
|
||||
cacheLog(entry: LogEntry): void;
|
||||
getLogs(): LogEntry[];
|
||||
buildLogEntry(level: string, config: LogConfig): LogEntry;
|
||||
}
|
||||
|
||||
class Logger {
|
||||
class Logger implements ILogger {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
private logger: WinstonLogger;
|
||||
private envSettings: EnvSettings;
|
||||
private envSettings: Partial<EnvConfig>;
|
||||
private logCache: LogEntry[];
|
||||
private maxCacheSize: number;
|
||||
|
||||
constructor({ envSettings }: { envSettings: EnvSettings }) {
|
||||
constructor({ envSettings }: { envSettings: Partial<EnvConfig> }) {
|
||||
this.envSettings = envSettings;
|
||||
this.logCache = [];
|
||||
this.maxCacheSize = 1000;
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
export const ROLES = {
|
||||
SUPERADMIN: "superadmin",
|
||||
ADMIN: "admin",
|
||||
USER: "user",
|
||||
DEMO: "demo",
|
||||
};
|
||||
|
||||
export const VALID_ROLES = [ROLES.ADMIN, ROLES.USER, ROLES.DEMO];
|
||||
|
||||
export const EDITABLE_ROLES = [
|
||||
{ role: ROLES.ADMIN, _id: ROLES.ADMIN },
|
||||
{ role: ROLES.USER, _id: ROLES.USER },
|
||||
];
|
||||
@@ -1,5 +1,5 @@
|
||||
import joi from "joi";
|
||||
import { ROLES, VALID_ROLES } from "../utils/roleUtils.js";
|
||||
import { UserRoles } from "@/types/user.js";
|
||||
|
||||
//****************************************
|
||||
// Custom Validators
|
||||
@@ -689,7 +689,7 @@ const editUserByIdBodyValidation = joi.object({
|
||||
email: joi.string().email().required(),
|
||||
role: joi
|
||||
.array()
|
||||
.items(joi.string().valid(...VALID_ROLES))
|
||||
.items(joi.string().valid(...UserRoles))
|
||||
.min(1)
|
||||
.required(),
|
||||
});
|
||||
@@ -700,7 +700,7 @@ const editSuperadminUserByIdBodyValidation = joi.object({
|
||||
email: joi.string().email().required(),
|
||||
role: joi
|
||||
.array()
|
||||
.items(joi.string().valid(...VALID_ROLES, ROLES.SUPERADMIN))
|
||||
.items(joi.string().valid(...UserRoles))
|
||||
.min(1)
|
||||
.required(),
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user