mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-18 07:28:31 -05:00
pagespeed details
This commit is contained in:
@@ -6,7 +6,7 @@ import { useMonitorUtils } from "./useMonitorUtils.js";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useFetchMonitorsWithSummary = ({ types, monitorUpdateTrigger }) => {
|
||||
export const useFetchMonitorsWithSummary = ({ types, monitorUpdateTrigger }) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [monitors, setMonitors] = useState(undefined);
|
||||
const [monitorsSummary, setMonitorsSummary] = useState(undefined);
|
||||
@@ -37,7 +37,7 @@ const useFetchMonitorsWithSummary = ({ types, monitorUpdateTrigger }) => {
|
||||
return [monitors, monitorsSummary, isLoading, networkError];
|
||||
};
|
||||
|
||||
const useFetchMonitorsWithChecks = ({
|
||||
export const useFetchMonitorsWithChecks = ({
|
||||
types,
|
||||
limit,
|
||||
page,
|
||||
@@ -100,7 +100,7 @@ const useFetchMonitorsWithChecks = ({
|
||||
return [monitors, count, isLoading, networkError];
|
||||
};
|
||||
|
||||
const useFetchMonitorsByTeamId = ({ types, filter, updateTrigger }) => {
|
||||
export const useFetchMonitorsByTeamId = ({ types, filter, updateTrigger }) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [monitors, setMonitors] = useState(undefined);
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
@@ -126,15 +126,11 @@ const useFetchMonitorsByTeamId = ({ types, filter, updateTrigger }) => {
|
||||
}
|
||||
};
|
||||
fetchMonitors();
|
||||
}, [
|
||||
types,
|
||||
filter,
|
||||
updateTrigger,
|
||||
]);
|
||||
}, [types, filter, updateTrigger]);
|
||||
return [monitors, isLoading, networkError];
|
||||
};
|
||||
|
||||
const useFetchStatsByMonitorId = ({
|
||||
export const useFetchStatsByMonitorId = ({
|
||||
monitorId,
|
||||
sortOrder,
|
||||
limit,
|
||||
@@ -173,7 +169,7 @@ const useFetchStatsByMonitorId = ({
|
||||
return [monitor, audits, isLoading, networkError];
|
||||
};
|
||||
|
||||
const useFetchMonitorGames = ({ setGames, updateTrigger }) => {
|
||||
export const useFetchMonitorGames = ({ setGames, updateTrigger }) => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
useEffect(() => {
|
||||
const fetchGames = async () => {
|
||||
@@ -192,7 +188,7 @@ const useFetchMonitorGames = ({ setGames, updateTrigger }) => {
|
||||
return [isLoading];
|
||||
};
|
||||
|
||||
const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => {
|
||||
export const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
useEffect(() => {
|
||||
if (typeof monitorId === "undefined") {
|
||||
@@ -215,7 +211,7 @@ const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => {
|
||||
return [isLoading];
|
||||
};
|
||||
|
||||
const useFetchHardwareMonitorById = ({ monitorId, dateRange, updateTrigger }) => {
|
||||
export const useFetchHardwareMonitorById = ({ monitorId, dateRange, updateTrigger }) => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
const [monitor, setMonitor] = useState(undefined);
|
||||
@@ -241,8 +237,34 @@ const useFetchHardwareMonitorById = ({ monitorId, dateRange, updateTrigger }) =>
|
||||
}, [monitorId, dateRange, updateTrigger]);
|
||||
return [monitor, isLoading, networkError];
|
||||
};
|
||||
export const useFetchPageSpeedMonitorById = ({ monitorId, dateRange, updateTrigger }) => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
const [monitor, setMonitor] = useState(undefined);
|
||||
|
||||
const useFetchUptimeMonitorById = ({ monitorId, dateRange, trigger }) => {
|
||||
useEffect(() => {
|
||||
const fetchMonitor = async () => {
|
||||
try {
|
||||
if (!monitorId) {
|
||||
return { monitor: undefined, isLoading: false, networkError: undefined };
|
||||
}
|
||||
const response = await networkService.getPageSpeedDetailsByMonitorId({
|
||||
monitorId: monitorId,
|
||||
dateRange: dateRange,
|
||||
});
|
||||
setMonitor(response.data.data);
|
||||
} catch (error) {
|
||||
setNetworkError(true);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
fetchMonitor();
|
||||
}, [monitorId, dateRange, updateTrigger]);
|
||||
return [monitor, isLoading, networkError];
|
||||
};
|
||||
|
||||
export const useFetchUptimeMonitorById = ({ monitorId, dateRange, trigger }) => {
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [monitor, setMonitor] = useState(undefined);
|
||||
@@ -270,7 +292,7 @@ const useFetchUptimeMonitorById = ({ monitorId, dateRange, trigger }) => {
|
||||
return [monitor, monitorStats, isLoading, networkError];
|
||||
};
|
||||
|
||||
const useCreateMonitor = () => {
|
||||
export const useCreateMonitor = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const createMonitor = async ({ monitor, redirect }) => {
|
||||
@@ -290,7 +312,7 @@ const useCreateMonitor = () => {
|
||||
return [createMonitor, isLoading];
|
||||
};
|
||||
|
||||
const useFetchGlobalSettings = () => {
|
||||
export const useFetchGlobalSettings = () => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [globalSettings, setGlobalSettings] = useState(undefined);
|
||||
useEffect(() => {
|
||||
@@ -312,7 +334,7 @@ const useFetchGlobalSettings = () => {
|
||||
return [globalSettings, isLoading];
|
||||
};
|
||||
|
||||
const useDeleteMonitor = () => {
|
||||
export const useDeleteMonitor = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const deleteMonitor = async ({ monitor, redirect }) => {
|
||||
@@ -333,7 +355,7 @@ const useDeleteMonitor = () => {
|
||||
return [deleteMonitor, isLoading];
|
||||
};
|
||||
|
||||
const useUpdateMonitor = () => {
|
||||
export const useUpdateMonitor = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const updateMonitor = async ({ monitor, redirect }) => {
|
||||
@@ -380,7 +402,7 @@ const useUpdateMonitor = () => {
|
||||
return [updateMonitor, isLoading];
|
||||
};
|
||||
|
||||
const usePauseMonitor = () => {
|
||||
export const usePauseMonitor = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState(undefined);
|
||||
const pauseMonitor = async ({ monitorId, triggerUpdate }) => {
|
||||
@@ -403,7 +425,7 @@ const usePauseMonitor = () => {
|
||||
return [pauseMonitor, isLoading, error];
|
||||
};
|
||||
|
||||
const useAddDemoMonitors = () => {
|
||||
export const useAddDemoMonitors = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const addDemoMonitors = async () => {
|
||||
@@ -420,7 +442,7 @@ const useAddDemoMonitors = () => {
|
||||
return [addDemoMonitors, isLoading];
|
||||
};
|
||||
|
||||
const useDeleteAllMonitors = () => {
|
||||
export const useDeleteAllMonitors = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const deleteAllMonitors = async () => {
|
||||
@@ -437,7 +459,7 @@ const useDeleteAllMonitors = () => {
|
||||
return [deleteAllMonitors, isLoading];
|
||||
};
|
||||
|
||||
const useDeleteMonitorStats = () => {
|
||||
export const useDeleteMonitorStats = () => {
|
||||
const { t } = useTranslation();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const deleteMonitorStats = async () => {
|
||||
@@ -455,7 +477,7 @@ const useDeleteMonitorStats = () => {
|
||||
return [deleteMonitorStats, isLoading];
|
||||
};
|
||||
|
||||
const useCreateBulkMonitors = () => {
|
||||
export const useCreateBulkMonitors = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const createBulkMonitors = async (file, user) => {
|
||||
@@ -478,7 +500,7 @@ const useCreateBulkMonitors = () => {
|
||||
return [createBulkMonitors, isLoading];
|
||||
};
|
||||
|
||||
const useExportMonitors = () => {
|
||||
export const useExportMonitors = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -511,7 +533,7 @@ const useExportMonitors = () => {
|
||||
return [exportMonitors, isLoading];
|
||||
};
|
||||
|
||||
const useFetchJson = () => {
|
||||
export const useFetchJson = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const fetchJson = async () => {
|
||||
try {
|
||||
@@ -527,25 +549,3 @@ const useFetchJson = () => {
|
||||
};
|
||||
return [fetchJson, isLoading];
|
||||
};
|
||||
|
||||
export {
|
||||
useFetchMonitorsWithSummary,
|
||||
useFetchMonitorsWithChecks,
|
||||
useFetchMonitorsByTeamId,
|
||||
useFetchStatsByMonitorId,
|
||||
useFetchMonitorById,
|
||||
useFetchUptimeMonitorById,
|
||||
useFetchHardwareMonitorById,
|
||||
useCreateMonitor,
|
||||
useFetchGlobalSettings,
|
||||
useDeleteMonitor,
|
||||
useUpdateMonitor,
|
||||
usePauseMonitor,
|
||||
useAddDemoMonitors,
|
||||
useDeleteAllMonitors,
|
||||
useDeleteMonitorStats,
|
||||
useCreateBulkMonitors,
|
||||
useExportMonitors,
|
||||
useFetchMonitorGames,
|
||||
useFetchJson,
|
||||
};
|
||||
|
||||
@@ -11,14 +11,13 @@ import GenericFallback from "@/Components/v1/GenericFallback/index.jsx";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useFetchStatsByMonitorId } from "../../../Hooks/monitorHooks.js";
|
||||
import { useFetchPageSpeedMonitorById } from "../../../Hooks/monitorHooks.js";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
// Constants
|
||||
const BREADCRUMBS = [
|
||||
{ name: "pagespeed", path: "/pagespeed" },
|
||||
{ name: "details", path: `` },
|
||||
// { name: "details", path: `/pagespeed/${monitorId}` }, // Not needed?
|
||||
];
|
||||
|
||||
const PageSpeedDetails = () => {
|
||||
@@ -36,13 +35,9 @@ const PageSpeedDetails = () => {
|
||||
});
|
||||
const [trigger, setTrigger] = useState(false);
|
||||
// Network
|
||||
const [monitor, audits, isLoading, networkError] = useFetchStatsByMonitorId({
|
||||
const [monitor, isLoading, networkError] = useFetchPageSpeedMonitorById({
|
||||
monitorId,
|
||||
sortOrder: "desc",
|
||||
limit: 50,
|
||||
dateRange: "day",
|
||||
numToDisplay: null,
|
||||
normalize: null,
|
||||
updateTrigger: trigger,
|
||||
});
|
||||
|
||||
@@ -116,7 +111,7 @@ const PageSpeedDetails = () => {
|
||||
/>
|
||||
<PerformanceReport
|
||||
shouldRender={!isLoading}
|
||||
audits={audits}
|
||||
audits={monitor?.checks[0]?.audits}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -152,7 +152,8 @@ class NetworkService {
|
||||
params.append("type", type);
|
||||
});
|
||||
}
|
||||
if (filter !== undefined && filter !== null && filter !== "") params.append("filter", filter);
|
||||
if (filter !== undefined && filter !== null && filter !== "")
|
||||
params.append("filter", filter);
|
||||
|
||||
return this.axiosInstance.get(`/monitors/team?${params.toString()}`, {
|
||||
headers: {
|
||||
@@ -197,6 +198,14 @@ class NetworkService {
|
||||
`/monitors/hardware/details/${config.monitorId}?${params.toString()}`
|
||||
);
|
||||
}
|
||||
async getPageSpeedDetailsByMonitorId(config) {
|
||||
const params = new URLSearchParams();
|
||||
if (config.dateRange) params.append("dateRange", config.dateRange);
|
||||
|
||||
return this.axiosInstance.get(
|
||||
`/monitors/pagespeed/details/${config.monitorId}?${params.toString()}`
|
||||
);
|
||||
}
|
||||
async getUptimeDetailsById(config) {
|
||||
const params = new URLSearchParams();
|
||||
if (config.dateRange) params.append("dateRange", config.dateRange);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { type MonitorType, MonitorTypes } from "@/types/index.js";
|
||||
|
||||
const fetchMonitorCertificate = async (sslChecker: any, monitor: any): Promise<any> => {
|
||||
const monitorUrl = new URL(monitor.url);
|
||||
const hostname = monitorUrl.hostname;
|
||||
@@ -8,5 +11,101 @@ const fetchMonitorCertificate = async (sslChecker: any, monitor: any): Promise<a
|
||||
}
|
||||
return cert;
|
||||
};
|
||||
const requireString = (value: unknown, fieldName: string): string => {
|
||||
if (typeof value === "string" && value.trim().length > 0) {
|
||||
return value;
|
||||
}
|
||||
throw new AppError({ message: `${fieldName} is required`, status: 400 });
|
||||
};
|
||||
|
||||
export { fetchMonitorCertificate };
|
||||
const optionalString = (value: unknown, fieldName: string): string | undefined => {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return value;
|
||||
}
|
||||
throw new AppError({ message: `${fieldName} must be a string`, status: 400 });
|
||||
};
|
||||
|
||||
const optionalNumber = (value: unknown, fieldName: string): number | undefined => {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string" && value.trim() !== "") {
|
||||
const parsed = Number(value);
|
||||
if (!Number.isNaN(parsed)) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
throw new AppError({ message: `${fieldName} must be a number`, status: 400 });
|
||||
};
|
||||
|
||||
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 });
|
||||
};
|
||||
|
||||
const parseMonitorTypeFilter = (value: unknown): MonitorType | MonitorType[] | undefined => {
|
||||
const parseSingle = (input: unknown): MonitorType => {
|
||||
if (typeof input !== "string") {
|
||||
throw new AppError({ message: "Monitor type must be a string", status: 400 });
|
||||
}
|
||||
if (!MonitorTypes.includes(input as MonitorType)) {
|
||||
throw new AppError({ message: `Invalid monitor type: ${input}`, status: 400 });
|
||||
}
|
||||
return input as MonitorType;
|
||||
};
|
||||
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((entry) => parseSingle(entry));
|
||||
}
|
||||
return parseSingle(value);
|
||||
};
|
||||
|
||||
const parseSortOrder = (value: unknown): "asc" | "desc" | undefined => {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (value === "asc" || value === "desc") {
|
||||
return value;
|
||||
}
|
||||
throw new AppError({ message: "order must be either 'asc' or 'desc'", status: 400 });
|
||||
};
|
||||
|
||||
const requireTeamId = (teamId?: string): string => {
|
||||
if (!teamId) {
|
||||
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||
}
|
||||
return teamId;
|
||||
};
|
||||
|
||||
export {
|
||||
fetchMonitorCertificate,
|
||||
requireString,
|
||||
optionalString,
|
||||
optionalNumber,
|
||||
optionalBoolean,
|
||||
parseMonitorTypeFilter,
|
||||
parseSortOrder,
|
||||
requireTeamId,
|
||||
};
|
||||
|
||||
@@ -14,16 +14,26 @@ import {
|
||||
getHardwareDetailsByIdQueryValidation,
|
||||
} from "@/validation/joi.js";
|
||||
import sslChecker from "ssl-checker";
|
||||
import { fetchMonitorCertificate } from "./controllerUtils.js";
|
||||
import {
|
||||
fetchMonitorCertificate,
|
||||
requireString,
|
||||
optionalString,
|
||||
optionalNumber,
|
||||
optionalBoolean,
|
||||
parseMonitorTypeFilter,
|
||||
parseSortOrder,
|
||||
requireTeamId,
|
||||
} from "./controllerUtils.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { IMonitorService } from "@/service/index.js";
|
||||
|
||||
const SERVICE_NAME = "monitorController";
|
||||
class MonitorController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
private monitorService: any;
|
||||
private monitorService: IMonitorService;
|
||||
|
||||
constructor(monitorService: any) {
|
||||
constructor(monitorService: IMonitorService) {
|
||||
this.monitorService = monitorService;
|
||||
}
|
||||
|
||||
@@ -32,8 +42,8 @@ class MonitorController {
|
||||
}
|
||||
|
||||
async verifyTeamAccess(teamId: string, monitorId: string) {
|
||||
const monitor = await this.monitorService.getMonitorById(monitorId);
|
||||
if (!monitor.teamId.equals(teamId)) {
|
||||
const monitor = await this.monitorService.getMonitorById({ teamId, monitorId });
|
||||
if (monitor.teamId !== teamId) {
|
||||
throw new AppError({ message: "Access denied", status: 403 });
|
||||
}
|
||||
}
|
||||
@@ -41,11 +51,8 @@ class MonitorController {
|
||||
getMonitorCertificate = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await getCertificateParamValidation.validateAsync(req.params);
|
||||
const teamId = req?.user?.teamId;
|
||||
if (!teamId) {
|
||||
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||
}
|
||||
const { monitorId } = req.params;
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
const monitorId = requireString(req.params?.monitorId, "Monitor ID");
|
||||
const monitor = await this.monitorService.getMonitorById({ teamId, monitorId });
|
||||
const certificate = await fetchMonitorCertificate(sslChecker, monitor);
|
||||
|
||||
@@ -63,15 +70,10 @@ class MonitorController {
|
||||
|
||||
getUptimeDetailsById = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const monitorId = req?.params?.monitorId;
|
||||
const dateRange = req?.query?.dateRange;
|
||||
const normalize = req?.query?.normalize;
|
||||
|
||||
const teamId = req?.user?.teamId;
|
||||
|
||||
if (!teamId) {
|
||||
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||
}
|
||||
const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
|
||||
const dateRange = requireString(req?.query?.dateRange, "dateRange");
|
||||
const normalize = optionalBoolean(req?.query?.normalize, "normalize");
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
|
||||
const data = await this.monitorService.getUptimeDetailsById({
|
||||
teamId,
|
||||
@@ -94,12 +96,9 @@ class MonitorController {
|
||||
await getHardwareDetailsByIdParamValidation.validateAsync(req.params);
|
||||
await getHardwareDetailsByIdQueryValidation.validateAsync(req.query);
|
||||
|
||||
const monitorId = req?.params?.monitorId;
|
||||
const dateRange = req?.query?.dateRange;
|
||||
const teamId = req?.user?.teamId;
|
||||
if (!teamId) {
|
||||
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||
}
|
||||
const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
|
||||
const dateRange = requireString(req?.query?.dateRange, "dateRange");
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
|
||||
const monitor = await this.monitorService.getHardwareDetailsById({
|
||||
teamId,
|
||||
@@ -116,18 +115,40 @@ class MonitorController {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
getPageSpeedDetailsById = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await getHardwareDetailsByIdParamValidation.validateAsync(req.params);
|
||||
await getHardwareDetailsByIdQueryValidation.validateAsync(req.query);
|
||||
|
||||
const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
|
||||
const dateRange = requireString(req?.query?.dateRange, "dateRange");
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
|
||||
const monitor = await this.monitorService.getPageSpeedDetailsById({
|
||||
teamId,
|
||||
monitorId,
|
||||
dateRange,
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Page speed details retrieved successfully",
|
||||
data: monitor,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getMonitorById = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await getMonitorByIdParamValidation.validateAsync(req.params);
|
||||
await getMonitorByIdQueryValidation.validateAsync(req.query);
|
||||
|
||||
const teamId = req?.user?.teamId;
|
||||
if (!teamId) {
|
||||
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||
}
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
|
||||
|
||||
const monitor = await this.monitorService.getMonitorById({ teamId, monitorId: req?.params?.monitorId });
|
||||
const monitor = await this.monitorService.getMonitorById({ teamId, monitorId });
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
@@ -143,8 +164,8 @@ class MonitorController {
|
||||
try {
|
||||
await createMonitorBodyValidation.validateAsync(req.body);
|
||||
|
||||
const userId = req?.user?._id;
|
||||
const teamId = req?.user?.teamId;
|
||||
const userId = requireString(req?.user?._id, "User ID");
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
|
||||
const monitor = await this.monitorService.createMonitor(teamId, userId, req.body);
|
||||
|
||||
@@ -172,12 +193,8 @@ class MonitorController {
|
||||
throw new AppError({ message: "File is empty", status: 400 });
|
||||
}
|
||||
|
||||
const userId = req?.user?._id;
|
||||
const teamId = req?.user?.teamId;
|
||||
|
||||
if (!userId || !teamId) {
|
||||
throw new AppError({ message: "Missing userId or teamId", 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) {
|
||||
@@ -199,11 +216,8 @@ class MonitorController {
|
||||
deleteMonitor = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await getMonitorByIdParamValidation.validateAsync(req.params);
|
||||
const monitorId = req.params.monitorId;
|
||||
const teamId = req?.user?.teamId;
|
||||
if (!teamId) {
|
||||
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||
}
|
||||
const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
|
||||
const deletedMonitor = await this.monitorService.deleteMonitor({ teamId, monitorId });
|
||||
|
||||
@@ -219,10 +233,7 @@ class MonitorController {
|
||||
|
||||
deleteAllMonitors = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const teamId = req?.user?.teamId;
|
||||
if (!teamId) {
|
||||
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||
}
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
|
||||
const deletedCount = await this.monitorService.deleteAllMonitors({ teamId });
|
||||
|
||||
@@ -239,12 +250,8 @@ class MonitorController {
|
||||
try {
|
||||
await getMonitorByIdParamValidation.validateAsync(req.params);
|
||||
await editMonitorBodyValidation.validateAsync(req.body);
|
||||
const monitorId = req?.params?.monitorId;
|
||||
|
||||
const teamId = req?.user?.teamId;
|
||||
if (!teamId) {
|
||||
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||
}
|
||||
const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
|
||||
const editedMonitor = await this.monitorService.editMonitor({ teamId, monitorId, body: req.body });
|
||||
|
||||
@@ -262,11 +269,8 @@ class MonitorController {
|
||||
try {
|
||||
await pauseMonitorParamValidation.validateAsync(req.params);
|
||||
|
||||
const monitorId = req.params.monitorId;
|
||||
const teamId = req?.user?.teamId;
|
||||
if (!teamId) {
|
||||
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||
}
|
||||
const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
|
||||
const monitor = await this.monitorService.pauseMonitor({ teamId, monitorId });
|
||||
|
||||
@@ -282,7 +286,8 @@ class MonitorController {
|
||||
|
||||
addDemoMonitors = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { _id, teamId } = req.user;
|
||||
const _id = requireString(req?.user?._id, "User ID");
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
const demoMonitors = await this.monitorService.addDemoMonitors({ userId: _id, teamId });
|
||||
|
||||
return res.status(200).json({
|
||||
@@ -318,8 +323,9 @@ class MonitorController {
|
||||
await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
|
||||
await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
|
||||
|
||||
const { type, filter } = req.query;
|
||||
const teamId = req?.user?.teamId;
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
const type = parseMonitorTypeFilter(req.query?.type);
|
||||
const filter = optionalString(req.query?.filter, "filter");
|
||||
|
||||
const monitors = await this.monitorService.getMonitorsByTeamId({ teamId, type, filter });
|
||||
|
||||
@@ -338,12 +344,9 @@ class MonitorController {
|
||||
await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
|
||||
await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
|
||||
|
||||
const explain = req?.query?.explain;
|
||||
const type = req?.query?.type;
|
||||
const teamId = req?.user?.teamId;
|
||||
if (!teamId) {
|
||||
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||
}
|
||||
const explain = optionalBoolean(req?.query?.explain, "explain");
|
||||
const type = parseMonitorTypeFilter(req?.query?.type);
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
|
||||
const result = await this.monitorService.getMonitorsAndSummaryByTeamId({ teamId, type, explain });
|
||||
|
||||
@@ -361,12 +364,15 @@ class MonitorController {
|
||||
await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
|
||||
await getMonitorsWithChecksQueryValidation.validateAsync(req.query);
|
||||
|
||||
const explain = req?.query?.explain;
|
||||
let { limit, type, page, rowsPerPage, filter, field, order } = req.query;
|
||||
const teamId = req?.user?.teamId;
|
||||
if (!teamId) {
|
||||
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||
}
|
||||
const explain = optionalBoolean(req?.query?.explain, "explain");
|
||||
const limit = optionalNumber(req?.query?.limit, "limit");
|
||||
const page = optionalNumber(req?.query?.page, "page");
|
||||
const rowsPerPage = optionalNumber(req?.query?.rowsPerPage, "rowsPerPage");
|
||||
const filter = optionalString(req?.query?.filter, "filter");
|
||||
const field = optionalString(req?.query?.field, "field");
|
||||
const order = parseSortOrder(req?.query?.order);
|
||||
const type = parseMonitorTypeFilter(req?.query?.type);
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
|
||||
const monitors = await this.monitorService.getMonitorsWithChecksByTeamId({
|
||||
teamId,
|
||||
|
||||
@@ -1,6 +1,58 @@
|
||||
import type { Check, MonitorType } from "@/types/index.js";
|
||||
import type { Check, CheckAudits, MonitorType } from "@/types/index.js";
|
||||
import type { LatestChecksMap } from "@/repositories/checks/MongoChecksRepistory.js";
|
||||
|
||||
export interface PageSpeedChecksResult {
|
||||
monitorType: "pagespeed";
|
||||
checks: Check[];
|
||||
}
|
||||
|
||||
export interface HardwareChecksResult {
|
||||
monitorType: "hardware";
|
||||
aggregateData: {
|
||||
latestCheck: 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 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 }>;
|
||||
uptimePercentage: number;
|
||||
avgResponseTime: number;
|
||||
}
|
||||
|
||||
export interface IChecksRepository {
|
||||
findLatestChecksByMonitorIds(monitorIds: string[], options?: { limitPerMonitor?: number }): Promise<LatestChecksMap>;
|
||||
findDateRangeChecksByMonitor(
|
||||
@@ -9,51 +61,5 @@ export interface IChecksRepository {
|
||||
endDate: Date,
|
||||
dateString: string,
|
||||
options?: { type?: MonitorType }
|
||||
): Promise<
|
||||
| {
|
||||
monitorType: "uptime";
|
||||
groupedChecks: Array<{ _id: string; avgResponseTime: number; totalChecks: number }>;
|
||||
groupedUpChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>;
|
||||
groupedDownChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>;
|
||||
uptimePercentage: number;
|
||||
avgResponseTime: number;
|
||||
}
|
||||
| {
|
||||
monitorType: "hardware";
|
||||
aggregateData: {
|
||||
latestCheck: 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;
|
||||
}>;
|
||||
}>;
|
||||
}
|
||||
>;
|
||||
): Promise<UptimeChecksResult | HardwareChecksResult | PageSpeedChecksResult>;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import type {
|
||||
CheckMetadata,
|
||||
CheckNetworkInterfaceInfo,
|
||||
CheckTimings,
|
||||
MonitorType,
|
||||
} from "@/types/index.js";
|
||||
import { CheckModel, type CheckDocument } from "@/db/models/index.js";
|
||||
import mongoose from "mongoose";
|
||||
@@ -177,10 +178,7 @@ class MongoChecksRepistory implements IChecksRepository {
|
||||
};
|
||||
};
|
||||
|
||||
findLatestChecksByMonitorIds = async (
|
||||
monitorIds: string[],
|
||||
options?: { limitPerMonitor?: number }
|
||||
): Promise<LatestChecksMap> => {
|
||||
findLatestChecksByMonitorIds = async (monitorIds: string[], options?: { limitPerMonitor?: number }): Promise<LatestChecksMap> => {
|
||||
if (monitorIds.length === 0) {
|
||||
return {};
|
||||
}
|
||||
@@ -218,15 +216,24 @@ class MongoChecksRepistory implements IChecksRepository {
|
||||
}, {});
|
||||
};
|
||||
|
||||
findDateRangeChecksByMonitor = async (monitorId: string, startDate: Date, endDate: Date, dateString: string, options?: { type?: string }) => {
|
||||
findDateRangeChecksByMonitor = async (monitorId: string, startDate: Date, endDate: Date, dateString: string, options?: { type?: MonitorType }) => {
|
||||
const monitorObjectId = new mongoose.Types.ObjectId(monitorId);
|
||||
if (options?.type === "hardware") {
|
||||
return this.findHardwareDateRangeChecks(monitorObjectId, startDate, endDate, dateString);
|
||||
}
|
||||
return this.findUptimeDateRangeChecks(monitorObjectId, startDate, endDate, dateString);
|
||||
if (options?.type === "pagespeed") {
|
||||
return this.findPageSpeedDateRangeChecks(monitorObjectId, startDate, endDate);
|
||||
}
|
||||
return this.findUptimeDateRangeChecks(options?.type ?? "http", monitorObjectId, startDate, endDate, dateString);
|
||||
};
|
||||
|
||||
private findUptimeDateRangeChecks = async (monitorObjectId: mongoose.Types.ObjectId, startDate: Date, endDate: Date, dateString: string) => {
|
||||
private findUptimeDateRangeChecks = async (
|
||||
monitorType: Exclude<MonitorType, "hardware" | "pagespeed">,
|
||||
monitorObjectId: mongoose.Types.ObjectId,
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
dateString: string
|
||||
) => {
|
||||
const matchStage = {
|
||||
"metadata.monitorId": monitorObjectId,
|
||||
updatedAt: { $gte: startDate, $lte: endDate },
|
||||
@@ -307,7 +314,7 @@ class MongoChecksRepistory implements IChecksRepository {
|
||||
const avgResponseTime = result?.groupedAvgResponseTime?.[0]?.avgResponseTime ?? 0;
|
||||
|
||||
return {
|
||||
monitorType: "uptime" as const,
|
||||
monitorType,
|
||||
groupedChecks: result?.groupedChecks ?? [],
|
||||
groupedUpChecks: result?.groupedUpChecks ?? [],
|
||||
groupedDownChecks: result?.groupedDownChecks ?? [],
|
||||
@@ -369,6 +376,19 @@ class MongoChecksRepistory implements IChecksRepository {
|
||||
checks,
|
||||
};
|
||||
};
|
||||
|
||||
private findPageSpeedDateRangeChecks = async (monitorObjectId: mongoose.Types.ObjectId, startDate: Date, endDate: Date) => {
|
||||
const matchStage = {
|
||||
"metadata.monitorId": monitorObjectId,
|
||||
createdAt: { $gte: startDate, $lte: endDate },
|
||||
};
|
||||
|
||||
const checks = await CheckModel.find(matchStage).sort({ createdAt: -1 }).limit(25).lean();
|
||||
return {
|
||||
monitorType: "pagespeed" as const,
|
||||
checks: checks.map((doc) => this.toEntity(doc)),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export default MongoChecksRepistory;
|
||||
|
||||
@@ -27,6 +27,8 @@ class MonitorRoutes {
|
||||
|
||||
// Hardware routes
|
||||
this.router.get("/hardware/details/:monitorId", this.monitorController.getHardwareDetailsById);
|
||||
// PageSpeed routes
|
||||
this.router.get("/pagespeed/details/:monitorId", this.monitorController.getPageSpeedDetailsById);
|
||||
|
||||
// General monitor routes
|
||||
this.router.post("/pause/:monitorId", isAllowed(["admin", "superadmin"]), this.monitorController.pauseMonitor);
|
||||
|
||||
@@ -24,6 +24,7 @@ export interface IMonitorService {
|
||||
// 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>;
|
||||
getMonitorById(args: { teamId: string; monitorId: string }): Promise<Monitor>;
|
||||
getMonitorsByTeamId(args: {
|
||||
teamId: string;
|
||||
@@ -268,7 +269,13 @@ export class MonitorService implements IMonitorService {
|
||||
});
|
||||
const monitorStats = await this.monitorStatsRepository.findByMonitorId(monitor.id);
|
||||
|
||||
if (checksData.monitorType !== "uptime") {
|
||||
if (
|
||||
checksData.monitorType !== "http" &&
|
||||
checksData.monitorType !== "ping" &&
|
||||
checksData.monitorType !== "docker" &&
|
||||
checksData.monitorType !== "port" &&
|
||||
checksData.monitorType !== "game"
|
||||
) {
|
||||
throw new AppError({ message: `${monitor.type} monitors are not supported for uptime details`, status: 400 });
|
||||
}
|
||||
|
||||
@@ -317,6 +324,30 @@ export class MonitorService implements IMonitorService {
|
||||
};
|
||||
};
|
||||
|
||||
getPageSpeedDetailsById = async ({ teamId, monitorId, dateRange }: { teamId: string; monitorId: string; dateRange: string }): Promise<any> => {
|
||||
await this.verifyTeamAccess({ teamId, monitorId });
|
||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||
if (!monitor) {
|
||||
throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 });
|
||||
}
|
||||
if (monitor.type !== "pagespeed") {
|
||||
throw new AppError({ message: `${monitor.type} monitors are not supported for pagespeed details`, status: 400 });
|
||||
}
|
||||
|
||||
const rangeKey = (dateRange as DateRangeKey) ?? "recent";
|
||||
const { start, end } = this.getDateRange(rangeKey);
|
||||
const checksData = await this.checksRepository.findDateRangeChecksByMonitor(monitor.id, start, end, this.getDateFormat(rangeKey), {
|
||||
type: monitor.type,
|
||||
});
|
||||
|
||||
if (checksData.monitorType !== "pagespeed") {
|
||||
throw new AppError({ message: "Unable to load pagespeed stats for this monitor", status: 500 });
|
||||
}
|
||||
return {
|
||||
...monitor,
|
||||
checks: checksData.checks,
|
||||
};
|
||||
};
|
||||
getMonitorById = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise<any> => {
|
||||
await this.verifyTeamAccess({ teamId, monitorId });
|
||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||
|
||||
Reference in New Issue
Block a user