diff --git a/client/src/pages/uptime/UptimeDetails.tsx b/client/src/pages/uptime/UptimeDetails.tsx index 79d6a9b70..fb88d4cef 100644 --- a/client/src/pages/uptime/UptimeDetails.tsx +++ b/client/src/pages/uptime/UptimeDetails.tsx @@ -69,6 +69,7 @@ const UptimeDetailsPage = () => { } const stats = response?.data?.stats || null; + console.log(stats); const avgResponseTime = stats?.avgResponseTime || 0; const maxResponseTime = stats?.maxResponseTime || 0; @@ -132,6 +133,14 @@ const UptimeDetailsPage = () => { stats?.lastResponseTime ? `${stats?.lastResponseTime} ms` : "N/A" } /> + ( @@ -70,6 +71,7 @@ const MonitorStatsSchema = new Schema( enum: MonitorStatuses, }, currentStreakStartedAt: { type: Number, required: false }, + certificateExpiry: { type: Date, required: false }, }, { timestamps: true } ); diff --git a/server/src/services/infrastructure/NetworkService.ts b/server/src/services/infrastructure/NetworkService.ts index ba43c5630..a74cc6c91 100644 --- a/server/src/services/infrastructure/NetworkService.ts +++ b/server/src/services/infrastructure/NetworkService.ts @@ -9,6 +9,8 @@ import type { ICaptureInfo, ILighthouseResult, } from "@/db/models/index.js"; +import type { TLSSocket, DetailedPeerCertificate } from "node:tls"; +import type { ClientRequest } from "node:http"; import { MonitorType, MonitorStatus } from "@/db/models/monitors/Monitor.js"; import ApiError from "@/utils/ApiError.js"; import { config } from "@/config/index.js"; @@ -44,6 +46,7 @@ export interface StatusResponse { responseTime: number; timings?: GotTimings; payload?: TPayload; + certificateExpiry?: Date | null; } class NetworkService implements INetworkService { @@ -68,6 +71,7 @@ class NetworkService implements INetworkService { private buildStatusResponse = ( monitor: IMonitor, response: Response | null, + certificateExpiry: Date | null, error: any | null ): StatusResponse => { if (error) { @@ -80,6 +84,7 @@ class NetworkService implements INetworkService { message: error.message || "Network error", responseTime: 0, timings: { phases: {} } as GotTimings, + certificateExpiry, }; if (error instanceof HTTPError) { statusResponse.code = error?.response?.statusCode || this.NETWORK_ERROR; @@ -99,6 +104,7 @@ class NetworkService implements INetworkService { message: response?.statusMessage || "", responseTime: response?.timings?.phases?.total || 0, timings: response?.timings || ({ phases: {} } as GotTimings), + certificateExpiry, }; return statusResponse; @@ -111,11 +117,35 @@ class NetworkService implements INetworkService { throw new Error("No URL provided"); } + let certificateExpiry: Date | null = null; try { - const response: Response = await this.client(url); - return this.buildStatusResponse(monitor, response, null); + const req: any = this.client(url); + req.on("request", (nodeReq: ClientRequest) => { + nodeReq.on("socket", (socket: TLSSocket) => { + const capture = () => { + const cert = socket.getPeerCertificate(true); + if (cert && cert.valid_to) { + certificateExpiry = new Date(cert.valid_to); + } + }; + capture(); + socket.once("secureConnect", capture); + }); + }); + const response: Response = await req; + return this.buildStatusResponse( + monitor, + response, + certificateExpiry, + null + ); } catch (error) { - return this.buildStatusResponse(monitor, null, error); + return this.buildStatusResponse( + monitor, + null, + certificateExpiry, + error + ); } } catch (error) { throw error; @@ -142,7 +172,7 @@ class NetworkService implements INetworkService { } ); - statusResponse = this.buildStatusResponse(monitor, response, null); + statusResponse = this.buildStatusResponse(monitor, response, null, null); if (!response?.body) { throw new ApiError( "No payload received from infrastructure monitor", @@ -152,7 +182,7 @@ class NetworkService implements INetworkService { statusResponse.payload = response?.body; return statusResponse; } catch (error) { - statusResponse = this.buildStatusResponse(monitor, null, error); + statusResponse = this.buildStatusResponse(monitor, null, null, error); } return statusResponse; }; @@ -174,10 +204,11 @@ class NetworkService implements INetworkService { statusResponse = this.buildStatusResponse( monitor, response, + null, null ) as StatusResponse; } catch (error) { - statusResponse = this.buildStatusResponse(monitor, null, error); + statusResponse = this.buildStatusResponse(monitor, null, null, error); } const pagespeedUrl = `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance&key=${apiKey}`; diff --git a/server/src/services/infrastructure/StatusService.ts b/server/src/services/infrastructure/StatusService.ts index 5ff3cb4e4..f42e73c7e 100644 --- a/server/src/services/infrastructure/StatusService.ts +++ b/server/src/services/infrastructure/StatusService.ts @@ -136,6 +136,7 @@ class StatusService implements IStatusService { stats.maxResponseTime, statusResponse.responseTime ); + stats.certificateExpiry = statusResponse.certificateExpiry || null; return await stats.save(); };