uptime
This commit is contained in:
1 parent
7f15811fdf
commit
8e6a8e5a06
7 files changed
+113
-114
No files matched your search
@@ -88,6 +88,7 @@ const UptimeMonitors = () => {
|
||||
);
|
||||
|
||||
const monitors: IMonitor[] = response?.data?.monitors ?? ([] as IMonitor[]);
|
||||
const checksMap = response?.data?.checksMap || {};
|
||||
const count = response?.data?.count || 0;
|
||||
const upCount = response?.data?.upCount || 0;
|
||||
const downCount = response?.data?.downCount || 0;
|
||||
@@ -156,6 +157,7 @@ const UptimeMonitors = () => {
|
||||
/>
|
||||
<MonitorTable
|
||||
monitors={monitors}
|
||||
checksMap={checksMap}
|
||||
refetch={refetch}
|
||||
setSelectedMonitor={setSelectedMonitor}
|
||||
sortField={sortField}
|
||||
|
||||
@@ -14,12 +14,14 @@ import { useTheme } from "@mui/material/styles";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { usePatch } from "@/hooks/UseApi";
|
||||
|
||||
import type { ICheck } from "@/types/check";
|
||||
import type { IMonitor } from "@/types/monitor";
|
||||
import type { ActionMenuItem } from "@/components/actions-menu";
|
||||
import type { ChartType } from "@/features/uiSlice";
|
||||
|
||||
export const MonitorTable = ({
|
||||
monitors,
|
||||
checksMap,
|
||||
refetch,
|
||||
setSelectedMonitor,
|
||||
sortField,
|
||||
@@ -33,6 +35,7 @@ export const MonitorTable = ({
|
||||
setRowsPerPage,
|
||||
}: {
|
||||
monitors: IMonitor[];
|
||||
checksMap: Record<string, ICheck[]>;
|
||||
refetch: Function;
|
||||
setSelectedMonitor: Function;
|
||||
sortField: string;
|
||||
@@ -206,9 +209,9 @@ export const MonitorTable = ({
|
||||
content: t("monitors.uptime.table.headers.responseTime"),
|
||||
render: (row) => {
|
||||
if (chartType === "histogram") {
|
||||
return <HistogramResponseTime checks={row.latestChecks ?? []} />;
|
||||
return <HistogramResponseTime checks={checksMap[row._id] ?? []} />;
|
||||
} else {
|
||||
return <HeatmapResponseTime checks={row.latestChecks ?? []} />;
|
||||
return <HeatmapResponseTime checks={checksMap[row._id] ?? []} />;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -29,7 +29,7 @@ export interface IMonitor {
|
||||
createdBy: string;
|
||||
interval: number;
|
||||
rejectUnauthorized: boolean;
|
||||
latestChecks: ICheck[];
|
||||
latestStatuses: MonitorStatus[];
|
||||
n: number;
|
||||
name: string;
|
||||
port: number;
|
||||
@@ -56,6 +56,7 @@ export interface IMonitorWithStats {
|
||||
upCount: number;
|
||||
pausedCount: number;
|
||||
monitors: IMonitor[];
|
||||
checksMap: Record<string, ICheck[]>;
|
||||
}
|
||||
|
||||
export interface IMonitorWithMonitorStats {
|
||||
|
||||
@@ -24,6 +24,7 @@ export const MonitorStatuses = [
|
||||
"down",
|
||||
"paused",
|
||||
"initializing",
|
||||
"resuming",
|
||||
] as const;
|
||||
export type MonitorStatus = (typeof MonitorStatuses)[number];
|
||||
|
||||
@@ -56,7 +57,7 @@ export interface IMonitor extends Document {
|
||||
n: number; // Number of consecutive successes required to change status
|
||||
thresholds?: IMonitorThresholds;
|
||||
lastCheckedAt?: Date;
|
||||
latestChecks: LatestCheck[];
|
||||
latestStatuses?: MonitorStatus[];
|
||||
notificationChannels?: Types.ObjectId[];
|
||||
createdBy: Types.ObjectId;
|
||||
updatedBy: Types.ObjectId;
|
||||
@@ -98,46 +99,7 @@ const MonitorSchema = new Schema<IMonitor>(
|
||||
n: { type: Number, required: true, default: 1 },
|
||||
thresholds: { type: ThresholdsSchema, required: false, default: undefined },
|
||||
lastCheckedAt: { type: Date },
|
||||
latestChecks: {
|
||||
type: [
|
||||
{
|
||||
status: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: MonitorStatuses,
|
||||
},
|
||||
responseTime: { type: Number, required: true },
|
||||
checkedAt: { type: Date, required: true },
|
||||
dockerContainers: {
|
||||
type: [
|
||||
{
|
||||
container_id: { type: String },
|
||||
container_name: { type: String },
|
||||
status: { type: String },
|
||||
health: {
|
||||
healthy: { type: Boolean },
|
||||
source: { type: String },
|
||||
message: { type: String },
|
||||
},
|
||||
running: { type: Boolean },
|
||||
base_image: { type: String },
|
||||
exposed_ports: [
|
||||
{
|
||||
port: { type: String },
|
||||
protocol: { type: String },
|
||||
},
|
||||
],
|
||||
started_at: { type: Number },
|
||||
finished_at: { type: Number },
|
||||
},
|
||||
],
|
||||
required: false,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
],
|
||||
default: [],
|
||||
},
|
||||
latestStatuses: { type: [String], enum: MonitorStatuses, default: [] },
|
||||
notificationChannels: {
|
||||
type: [{ type: Schema.Types.ObjectId, ref: "NotificationChannel" }],
|
||||
default: [],
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Check } from "@/db/models/index.js";
|
||||
import type { ICheck } from "@/db/models/index.js";
|
||||
import type { IChecksRepository } from "@/repositories/checks/IChecksRepository.js";
|
||||
import { CheckEntity } from "@/types/domain/check.js";
|
||||
import mongoose from "mongoose";
|
||||
|
||||
export class MongoChecksRepository implements IChecksRepository {
|
||||
private toEntity = (check: ICheck): CheckEntity => {
|
||||
return {
|
||||
id: check._id.toString(),
|
||||
metadata: {
|
||||
monitorId: check.metadata.monitorId.toString(),
|
||||
teamId: check.metadata.teamId.toString(),
|
||||
type: check.metadata.type,
|
||||
},
|
||||
status: check.status,
|
||||
httpStatusCode: check.httpStatusCode,
|
||||
message: check.message,
|
||||
timings: check.timings,
|
||||
errorMessage: check.errorMessage,
|
||||
ackAt: check.ackAt,
|
||||
ackBy: check.ackBy?.toString(),
|
||||
system: check.system,
|
||||
capture: check.capture,
|
||||
lighthouse: check.lighthouse,
|
||||
dockerContainers: check.dockerContainers,
|
||||
createdAt: check.createdAt,
|
||||
updatedAt: check.updatedAt,
|
||||
expiry: check.expiry,
|
||||
};
|
||||
};
|
||||
|
||||
findLatestByMonitorIds = async (monitorIds: string[]) => {
|
||||
const checks = await Check.aggregate([
|
||||
{
|
||||
$match: {
|
||||
"metadata.monitorId": {
|
||||
$in: monitorIds.map((id) => new mongoose.Types.ObjectId(id)),
|
||||
},
|
||||
},
|
||||
},
|
||||
{ $sort: { createdAt: -1 } },
|
||||
{
|
||||
$group: {
|
||||
_id: "$metadata.monitorId",
|
||||
latestChecks: { $push: "$$ROOT" },
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
latestChecks: { $slice: [{ $ifNull: ["$latestChecks", []] }, 25] },
|
||||
},
|
||||
},
|
||||
]);
|
||||
console.log(checks);
|
||||
return checks;
|
||||
};
|
||||
}
|
||||
@@ -189,48 +189,39 @@ class MonitorService implements IMonitorService {
|
||||
.skip(skip)
|
||||
.limit(rowsPerPage);
|
||||
|
||||
if (
|
||||
type.length === 1 &&
|
||||
(type[0] === "pagespeed" ||
|
||||
type[0] === "infrastructure" ||
|
||||
type[0] === "docker")
|
||||
) {
|
||||
const monitorIds = monitors.map((m) => m._id);
|
||||
const monitorIds = monitors.map((m) => m._id);
|
||||
|
||||
const checks = await Check.aggregate([
|
||||
{
|
||||
$match: {
|
||||
"metadata.monitorId": { $in: monitorIds },
|
||||
},
|
||||
const checks = await Check.aggregate([
|
||||
{
|
||||
$match: {
|
||||
"metadata.monitorId": { $in: monitorIds },
|
||||
},
|
||||
{ $sort: { createdAt: -1 } },
|
||||
{
|
||||
$group: {
|
||||
_id: "$metadata.monitorId",
|
||||
latestChecks: { $push: "$$ROOT" },
|
||||
},
|
||||
},
|
||||
{ $sort: { createdAt: -1 } },
|
||||
{
|
||||
$group: {
|
||||
_id: "$metadata.monitorId",
|
||||
latestChecks: { $push: "$$ROOT" },
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
latestChecks: { $slice: [{ $ifNull: ["$latestChecks", []] }, 25] },
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
latestChecks: { $slice: [{ $ifNull: ["$latestChecks", []] }, 25] },
|
||||
},
|
||||
]);
|
||||
},
|
||||
]);
|
||||
|
||||
const checksMap = new Map(
|
||||
checks.map((c: any) => [c._id.toString(), c.latestChecks])
|
||||
);
|
||||
const checksMap = new Map(
|
||||
checks.map((c: any) => [c._id.toString(), c.latestChecks])
|
||||
);
|
||||
|
||||
monitors.forEach((monitor) => {
|
||||
monitor.latestChecks = checksMap.get(monitor._id.toString()) || [];
|
||||
});
|
||||
}
|
||||
return {
|
||||
count: counts.total,
|
||||
upCount: counts.upCount,
|
||||
downCount: counts.downCount,
|
||||
pausedCount: counts.pausedCount,
|
||||
monitors,
|
||||
checksMap: Object.fromEntries(checksMap),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -786,7 +777,7 @@ class MonitorService implements IMonitorService {
|
||||
status: {
|
||||
$cond: {
|
||||
if: { $eq: ["$status", "paused"] },
|
||||
then: "initializing",
|
||||
then: "resuming",
|
||||
else: "paused",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
ICheck,
|
||||
} from "@/db/models/index.js";
|
||||
import { StatusResponse } from "./NetworkService.js";
|
||||
import { LatestCheck } from "@/db/models/monitors/Monitor.js";
|
||||
// Using compact latestStatuses on Monitor for transition checks
|
||||
|
||||
const emptyMetrics = (): ThresholdEvaluationResult["metrics"] => ({
|
||||
cpu: { breached: false },
|
||||
@@ -73,49 +73,31 @@ class StatusService implements IStatusService {
|
||||
const newStatus = statusResponse.status;
|
||||
monitor.lastCheckedAt = new Date();
|
||||
|
||||
// Store latest checks for display
|
||||
monitor.latestChecks = monitor.latestChecks || [];
|
||||
const nextCheck: LatestCheck = {
|
||||
status: newStatus,
|
||||
responseTime: statusResponse.responseTime,
|
||||
checkedAt: monitor.lastCheckedAt,
|
||||
};
|
||||
// If this is a docker type, add the snapshot
|
||||
if (monitor.type === "docker") {
|
||||
const payload: any = statusResponse.payload;
|
||||
nextCheck.dockerContainers = payload.data;
|
||||
}
|
||||
monitor.latestChecks.push(nextCheck);
|
||||
|
||||
if (monitor.type === "docker") {
|
||||
monitor.latestStatuses = monitor.latestStatuses || [];
|
||||
const prevLen = monitor.latestStatuses.length;
|
||||
monitor.latestStatuses.push(newStatus);
|
||||
while (monitor.latestStatuses.length > MAX_LATEST_CHECKS) {
|
||||
monitor.latestStatuses.shift();
|
||||
}
|
||||
|
||||
while (monitor.latestChecks.length > MAX_LATEST_CHECKS) {
|
||||
monitor.latestChecks.shift();
|
||||
}
|
||||
|
||||
// Update monitor status
|
||||
if (monitor.status === "initializing") {
|
||||
monitor.status = newStatus;
|
||||
return [await monitor.save(), monitor.latestChecks.length === 0];
|
||||
} else {
|
||||
const { n } = monitor;
|
||||
const latestChecks = monitor.latestChecks.slice(-n);
|
||||
// Return early if not enough statuses to evaluate
|
||||
if (latestChecks.length < n) {
|
||||
return [await monitor.save(), false];
|
||||
}
|
||||
|
||||
// If all different than current status, update status
|
||||
const allDifferent = latestChecks.every(
|
||||
(check) => check.status !== monitor.status
|
||||
);
|
||||
if (allDifferent && monitor.status !== newStatus) {
|
||||
monitor.status = newStatus;
|
||||
}
|
||||
|
||||
return [await monitor.save(), allDifferent];
|
||||
const lenAfter = monitor.latestStatuses.length;
|
||||
const thresholdReachedNow = lenAfter >= monitor.n && prevLen < monitor.n;
|
||||
return [await monitor.save(), thresholdReachedNow];
|
||||
}
|
||||
|
||||
const { n } = monitor;
|
||||
const latestN = monitor.latestStatuses.slice(-n);
|
||||
if (latestN.length < n) {
|
||||
return [await monitor.save(), false];
|
||||
}
|
||||
|
||||
const allDifferent = latestN.every((s) => s !== monitor.status);
|
||||
if (allDifferent && monitor.status !== newStatus) {
|
||||
monitor.status = newStatus;
|
||||
}
|
||||
return [await monitor.save(), allDifferent];
|
||||
};
|
||||
|
||||
calculateAvgResponseTime = (
|
||||
|
||||
Reference in new issue
Block a user