2026-03-02 17:46:33 +00:00
|
|
|
import { z } from "zod";
|
2026-07-06 09:39:28 -07:00
|
|
|
import RE2 from "re2";
|
2026-05-11 10:12:27 -07:00
|
|
|
import { booleanCoercion, dnsHostnameRegex, dnsServerValidation } from "./shared.js";
|
2026-06-09 12:34:15 -07:00
|
|
|
import { GeoContinents } from "@/domain/geo-checks/geo-check.type.js";
|
|
|
|
|
import {
|
|
|
|
|
DnsRecordTypes,
|
2026-06-22 10:15:14 -07:00
|
|
|
HttpMethods,
|
2026-06-09 12:34:15 -07:00
|
|
|
HttpStatusCodeSet,
|
|
|
|
|
MonitorMatchMethods,
|
|
|
|
|
MonitorStatuses,
|
|
|
|
|
MonitorTypes,
|
|
|
|
|
PageSpeedStrategies,
|
2026-08-20 10:59:40 -07:00
|
|
|
ProxyModes,
|
2026-07-24 11:32:00 -07:00
|
|
|
} from "@/domain/monitors/monitor.type.js";
|
2026-07-21 10:48:18 -07:00
|
|
|
import { DateRanges, SortOrders } from "@/types/query.js";
|
2026-08-28 13:59:24 -07:00
|
|
|
import { DockerContainerStates, DockerHealthStatuses } from "@/types/network.js";
|
2026-06-01 13:21:30 +05:30
|
|
|
|
2026-06-03 23:27:10 +05:30
|
|
|
const httpStatusCode = z.number().refine((code) => HttpStatusCodeSet.has(code), { message: "Must be a valid HTTP status code" });
|
2026-03-01 17:35:00 +00:00
|
|
|
|
2026-08-26 11:19:08 -07:00
|
|
|
// The client form submits proxyId: "" when no proxy is selected, set it to undefined
|
|
|
|
|
const proxyIdValidation = z
|
|
|
|
|
.string()
|
|
|
|
|
.optional()
|
|
|
|
|
.transform((value) => (value === "" ? undefined : value))
|
|
|
|
|
.refine((value) => value === undefined || /^[0-9a-f]{24}$/i.test(value), { message: "Invalid proxy ID" });
|
|
|
|
|
|
2026-03-02 17:46:33 +00:00
|
|
|
export const getMonitorByIdParamValidation = z.object({
|
|
|
|
|
monitorId: z.string().min(1, "Monitor ID is required"),
|
2026-03-01 17:35:00 +00:00
|
|
|
});
|
|
|
|
|
|
2026-03-02 17:46:33 +00:00
|
|
|
export const getMonitorByIdQueryValidation = z.object({
|
2026-03-02 19:25:34 +00:00
|
|
|
status: booleanCoercion.optional(),
|
2026-07-21 10:48:18 -07:00
|
|
|
sortOrder: z.enum(SortOrders).optional(),
|
2026-03-02 17:46:33 +00:00
|
|
|
limit: z.coerce.number().optional(),
|
2026-07-21 10:48:18 -07:00
|
|
|
dateRange: z.enum(DateRanges).optional(),
|
2026-03-02 17:46:33 +00:00
|
|
|
numToDisplay: z.coerce.number().optional(),
|
2026-03-11 12:57:27 -07:00
|
|
|
continent: z.union([z.enum(GeoContinents), z.array(z.enum(GeoContinents))]).optional(),
|
2026-03-01 17:35:00 +00:00
|
|
|
});
|
|
|
|
|
|
2026-03-02 17:46:33 +00:00
|
|
|
export const getMonitorsByTeamIdParamValidation = z.object({});
|
2026-03-01 17:35:00 +00:00
|
|
|
|
2026-03-02 17:46:33 +00:00
|
|
|
export const getMonitorsByTeamIdQueryValidation = z.object({
|
2026-05-08 12:56:56 -07:00
|
|
|
type: z.union([z.enum(MonitorTypes), z.array(z.enum(MonitorTypes))]).optional(),
|
2026-03-11 12:41:27 -07:00
|
|
|
filter: z.union([z.string(), z.literal("")]).optional(),
|
2026-05-19 14:45:42 -07:00
|
|
|
tags: z.union([z.string(), z.array(z.string())]).optional(),
|
2026-03-01 17:35:00 +00:00
|
|
|
});
|
|
|
|
|
|
2026-03-02 17:46:33 +00:00
|
|
|
export const getMonitorsWithChecksQueryValidation = z.object({
|
|
|
|
|
limit: z.coerce.number().int().min(1).max(100).optional(),
|
|
|
|
|
page: z.coerce.number().int().min(0).optional(),
|
|
|
|
|
rowsPerPage: z.coerce.number().int().min(1).max(100).optional(),
|
2026-03-11 12:41:27 -07:00
|
|
|
filter: z.union([z.string(), z.literal("")]).optional(),
|
2026-03-02 17:46:33 +00:00
|
|
|
field: z.string().optional(),
|
2026-07-21 10:48:18 -07:00
|
|
|
order: z.enum(SortOrders).optional(),
|
2026-05-08 12:56:56 -07:00
|
|
|
type: z.union([z.enum(MonitorTypes), z.array(z.enum(MonitorTypes))]).optional(),
|
2026-05-19 14:45:42 -07:00
|
|
|
tags: z.union([z.string(), z.array(z.string())]).optional(),
|
2026-03-02 19:25:34 +00:00
|
|
|
explain: booleanCoercion.optional(),
|
2026-03-01 17:35:00 +00:00
|
|
|
});
|
|
|
|
|
|
2026-03-02 17:46:33 +00:00
|
|
|
export const getCertificateParamValidation = z.object({
|
|
|
|
|
monitorId: z.string().min(1, "Monitor ID is required"),
|
2026-03-01 17:35:00 +00:00
|
|
|
});
|
|
|
|
|
|
2026-08-11 00:34:21 +08:00
|
|
|
export const getDomainParamValidation = z.object({
|
|
|
|
|
monitorId: z.string().min(1, "Monitor ID is required"),
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-11 10:12:27 -07:00
|
|
|
const refineDnsHostname = (body: { type?: string; url?: string }, ctx: z.RefinementCtx) => {
|
|
|
|
|
if (body.type === "dns" && body.url && !dnsHostnameRegex.test(body.url)) {
|
|
|
|
|
ctx.addIssue({
|
|
|
|
|
code: "custom",
|
|
|
|
|
path: ["url"],
|
|
|
|
|
message: "Enter a valid domain (e.g. www.example.com)",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-11 11:28:39 -07:00
|
|
|
const refineStrategyType = (body: { type?: string; strategy?: string }, ctx: z.RefinementCtx) => {
|
|
|
|
|
if (body.strategy !== undefined && body.type !== undefined && body.type !== "pagespeed") {
|
|
|
|
|
ctx.addIssue({
|
|
|
|
|
code: "custom",
|
|
|
|
|
path: ["strategy"],
|
|
|
|
|
message: "Strategy is only valid for pagespeed monitors",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-07-06 09:39:28 -07:00
|
|
|
// The regex is executed at check time with RE2, reject patterns RE2 won't accept
|
|
|
|
|
const refineRegexPattern = (body: { matchMethod?: string; expectedValue?: string }, ctx: z.RefinementCtx) => {
|
|
|
|
|
if (body.matchMethod !== "regex" || !body.expectedValue) return;
|
|
|
|
|
try {
|
|
|
|
|
new RE2(body.expectedValue);
|
|
|
|
|
} catch {
|
|
|
|
|
ctx.addIssue({
|
|
|
|
|
code: "custom",
|
|
|
|
|
path: ["expectedValue"],
|
|
|
|
|
message: "Invalid regex pattern. Backreferences and lookahead/lookbehind are not supported.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-06-22 10:31:13 -07:00
|
|
|
const refineHeadMatching = (body: { method?: string; useAdvancedMatching?: boolean; jsonPath?: string }, ctx: z.RefinementCtx) => {
|
|
|
|
|
if (body.method === "HEAD" && (body.useAdvancedMatching === true || (body.jsonPath ?? "") !== "")) {
|
|
|
|
|
ctx.addIssue({
|
|
|
|
|
code: "custom",
|
|
|
|
|
path: ["method"],
|
|
|
|
|
message: "HEAD requests have no response body, so they cannot use advanced matching or a JSON path",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-20 13:34:56 -07:00
|
|
|
const refineProxySelection = (body: { proxyMode?: string; proxyId?: string }, ctx: z.RefinementCtx) => {
|
|
|
|
|
if (body.proxyMode === "custom" && !body.proxyId) {
|
|
|
|
|
ctx.addIssue({
|
|
|
|
|
code: "custom",
|
|
|
|
|
path: ["proxyId"],
|
|
|
|
|
message: "A proxy must be selected when proxy mode is custom",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-28 13:59:24 -07:00
|
|
|
const dockerUrlRegex = /^(?:ssh:\/\/[^@\s]+@[^\s/:@]+(?::\d{1,5})?\/?|unix:\/\/\/\S+|\/\S+)$/;
|
|
|
|
|
|
|
|
|
|
const refineDockerUrl = (data: { type?: string; url?: string; sshPrivateKey?: string }, ctx: z.RefinementCtx) => {
|
|
|
|
|
if (data.type !== "docker" || data.url === undefined) return;
|
|
|
|
|
if (!dockerUrlRegex.test(data.url)) {
|
|
|
|
|
ctx.addIssue({
|
|
|
|
|
code: "custom",
|
|
|
|
|
path: ["url"],
|
|
|
|
|
message: "Docker host must be ssh://user@host[:port], unix:///path, or an absolute socket path",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (data.url.startsWith("ssh://") && !data.sshPrivateKey) {
|
|
|
|
|
ctx.addIssue({
|
|
|
|
|
code: "custom",
|
|
|
|
|
path: ["sshPrivateKey"],
|
|
|
|
|
message: "SSH Docker hosts require a private key",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-11 10:12:27 -07:00
|
|
|
export const createMonitorBodyValidation = z
|
|
|
|
|
.object({
|
|
|
|
|
_id: z.string().optional(),
|
|
|
|
|
name: z.string().min(1, "Name is required"),
|
|
|
|
|
description: z.union([z.string(), z.literal("")]).optional(),
|
|
|
|
|
type: z.enum(MonitorTypes, "Invalid monitor type"),
|
|
|
|
|
statusWindowSize: z.number().min(1).max(20).default(5),
|
|
|
|
|
statusWindowThreshold: z.number().min(1).max(100).default(60),
|
|
|
|
|
url: z.string().min(1, "URL is required"),
|
|
|
|
|
ignoreTlsErrors: z.boolean().default(false),
|
2026-08-20 13:34:56 -07:00
|
|
|
proxyMode: z.enum(ProxyModes).default("inherit"),
|
2026-08-26 11:19:08 -07:00
|
|
|
proxyId: proxyIdValidation,
|
2026-05-11 10:12:27 -07:00
|
|
|
useAdvancedMatching: z.boolean().default(false),
|
|
|
|
|
port: z.number().optional(),
|
|
|
|
|
isActive: z.boolean().optional(),
|
|
|
|
|
interval: z.number().optional(),
|
|
|
|
|
cpuAlertThreshold: z.number().optional(),
|
|
|
|
|
memoryAlertThreshold: z.number().optional(),
|
|
|
|
|
diskAlertThreshold: z.number().optional(),
|
|
|
|
|
tempAlertThreshold: z.number().optional(),
|
|
|
|
|
notifications: z.array(z.string()).optional(),
|
2026-05-19 11:16:03 -07:00
|
|
|
tags: z.array(z.string()).optional(),
|
2026-06-01 13:21:30 +05:30
|
|
|
customUpCodes: z.array(httpStatusCode).default([]),
|
2026-05-11 10:12:27 -07:00
|
|
|
secret: z.string().optional(),
|
2026-08-28 13:59:24 -07:00
|
|
|
sshPrivateKey: z.string().optional(),
|
2026-05-11 10:12:27 -07:00
|
|
|
jsonPath: z.union([z.string(), z.literal("")]).optional(),
|
|
|
|
|
expectedValue: z.union([z.string(), z.literal("")]).optional(),
|
|
|
|
|
matchMethod: z.union([z.enum(MonitorMatchMethods), z.literal("")]).optional(),
|
2026-06-22 10:15:14 -07:00
|
|
|
method: z.enum(HttpMethods).optional(),
|
2026-05-11 10:12:27 -07:00
|
|
|
gameId: z.union([z.string(), z.literal("")]).optional(),
|
|
|
|
|
grpcServiceName: z.union([z.string(), z.literal("")]).default(""),
|
2026-05-11 11:11:13 -07:00
|
|
|
strategy: z.enum(PageSpeedStrategies).optional(),
|
2026-05-11 10:12:27 -07:00
|
|
|
selectedDisks: z.array(z.string()).optional(),
|
|
|
|
|
group: z.union([z.string().max(50).trim(), z.null(), z.literal("")]).optional(),
|
|
|
|
|
geoCheckEnabled: z.boolean().optional(),
|
|
|
|
|
geoCheckLocations: z.array(z.enum(GeoContinents)).optional(),
|
|
|
|
|
geoCheckInterval: z.number().min(300000).optional(),
|
|
|
|
|
dnsServer: dnsServerValidation.optional(),
|
|
|
|
|
dnsRecordType: z.enum(DnsRecordTypes).optional(),
|
|
|
|
|
})
|
2026-05-11 11:28:39 -07:00
|
|
|
.superRefine(refineDnsHostname)
|
2026-06-22 10:31:13 -07:00
|
|
|
.superRefine(refineStrategyType)
|
2026-07-06 09:39:28 -07:00
|
|
|
.superRefine(refineHeadMatching)
|
2026-08-20 13:34:56 -07:00
|
|
|
.superRefine(refineRegexPattern)
|
2026-08-28 13:59:24 -07:00
|
|
|
.superRefine(refineProxySelection)
|
|
|
|
|
.superRefine(refineDockerUrl);
|
2026-05-11 10:12:27 -07:00
|
|
|
|
|
|
|
|
export const editMonitorBodyValidation = z
|
|
|
|
|
.object({
|
|
|
|
|
name: z.string().optional(),
|
|
|
|
|
type: z.enum(MonitorTypes).optional(),
|
|
|
|
|
url: z.string().optional(),
|
|
|
|
|
statusWindowSize: z.number().min(1).max(20).default(5),
|
|
|
|
|
statusWindowThreshold: z.number().min(1).max(100).default(60),
|
|
|
|
|
description: z.union([z.string(), z.literal("")]).optional(),
|
|
|
|
|
interval: z.number().optional(),
|
|
|
|
|
notifications: z.array(z.string()).optional(),
|
2026-05-19 11:16:03 -07:00
|
|
|
tags: z.array(z.string()).optional(),
|
2026-06-01 13:21:30 +05:30
|
|
|
customUpCodes: z.array(httpStatusCode).optional(),
|
2026-05-11 10:12:27 -07:00
|
|
|
secret: z.string().optional(),
|
2026-08-28 13:59:24 -07:00
|
|
|
sshPrivateKey: z.string().optional(),
|
2026-05-11 10:12:27 -07:00
|
|
|
ignoreTlsErrors: z.boolean().optional(),
|
2026-08-20 13:34:56 -07:00
|
|
|
proxyMode: z.enum(ProxyModes).optional(),
|
2026-08-26 11:19:08 -07:00
|
|
|
proxyId: proxyIdValidation,
|
2026-05-11 10:12:27 -07:00
|
|
|
useAdvancedMatching: z.boolean().optional(),
|
|
|
|
|
jsonPath: z.union([z.string(), z.literal("")]).optional(),
|
|
|
|
|
expectedValue: z.union([z.string(), z.literal("")]).optional(),
|
|
|
|
|
matchMethod: z.union([z.enum(MonitorMatchMethods), z.literal("")]).optional(),
|
2026-06-22 10:15:14 -07:00
|
|
|
method: z.enum(HttpMethods).optional(),
|
2026-05-11 10:12:27 -07:00
|
|
|
port: z.number().min(1).max(65535).optional(),
|
|
|
|
|
cpuAlertThreshold: z.number().optional(),
|
|
|
|
|
memoryAlertThreshold: z.number().optional(),
|
|
|
|
|
diskAlertThreshold: z.number().optional(),
|
|
|
|
|
tempAlertThreshold: z.number().optional(),
|
|
|
|
|
gameId: z.union([z.string(), z.literal("")]).optional(),
|
|
|
|
|
grpcServiceName: z.union([z.string(), z.literal("")]).optional(),
|
2026-05-11 11:11:13 -07:00
|
|
|
strategy: z.enum(PageSpeedStrategies).optional(),
|
2026-05-11 10:12:27 -07:00
|
|
|
selectedDisks: z.array(z.string()).optional(),
|
|
|
|
|
group: z.union([z.string().max(50).trim(), z.null(), z.literal("")]).optional(),
|
|
|
|
|
geoCheckEnabled: z.boolean().optional(),
|
|
|
|
|
geoCheckLocations: z.array(z.enum(GeoContinents)).optional(),
|
|
|
|
|
geoCheckInterval: z.number().min(300000).optional(),
|
|
|
|
|
dnsServer: dnsServerValidation.optional(),
|
|
|
|
|
dnsRecordType: z.enum(DnsRecordTypes).optional(),
|
|
|
|
|
})
|
2026-05-11 11:28:39 -07:00
|
|
|
.superRefine(refineDnsHostname)
|
2026-06-22 10:31:13 -07:00
|
|
|
.superRefine(refineStrategyType)
|
2026-07-06 09:39:28 -07:00
|
|
|
.superRefine(refineHeadMatching)
|
2026-08-20 13:34:56 -07:00
|
|
|
.superRefine(refineRegexPattern)
|
2026-08-28 13:59:24 -07:00
|
|
|
.superRefine(refineProxySelection)
|
|
|
|
|
.superRefine(refineDockerUrl);
|
2026-03-01 17:35:00 +00:00
|
|
|
|
2026-03-02 17:46:33 +00:00
|
|
|
export const pauseMonitorParamValidation = z.object({
|
|
|
|
|
monitorId: z.string().min(1, "Monitor ID is required"),
|
2026-03-01 17:35:00 +00:00
|
|
|
});
|
|
|
|
|
|
2026-05-01 19:42:51 +05:30
|
|
|
export const bulkPauseMonitorBodyValidation = z.object({
|
|
|
|
|
monitorIds: z
|
|
|
|
|
.array(z.string().min(1, "Monitor ID must not be empty"))
|
|
|
|
|
.min(1, "At least one monitor ID is required")
|
|
|
|
|
.max(100, "Cannot bulk update more than 100 monitors at once"),
|
|
|
|
|
pause: z.boolean(),
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-11 12:41:27 -07:00
|
|
|
export const getUptimeDetailsByIdParamValidation = z.object({
|
|
|
|
|
monitorId: z.string().min(1, "Monitor ID is required"),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const getUptimeDetailsByIdQueryValidation = z.object({
|
2026-07-21 10:48:18 -07:00
|
|
|
dateRange: z.enum(DateRanges),
|
2026-03-11 12:41:27 -07:00
|
|
|
});
|
|
|
|
|
|
2026-05-11 10:12:27 -07:00
|
|
|
const importedMonitorSchema = z
|
|
|
|
|
.object({
|
|
|
|
|
id: z.string().optional(),
|
|
|
|
|
userId: z.string().optional(),
|
|
|
|
|
teamId: z.string().optional(),
|
|
|
|
|
name: z.string().min(1, "Name is required"),
|
|
|
|
|
description: z.union([z.string(), z.literal("")]).optional(),
|
2026-07-21 10:48:18 -07:00
|
|
|
status: z.enum(MonitorStatuses).default("initializing"),
|
2026-05-11 10:12:27 -07:00
|
|
|
statusWindow: z.array(z.boolean()).default([]),
|
|
|
|
|
statusWindowSize: z.number().min(1).max(20).default(5),
|
|
|
|
|
statusWindowThreshold: z.number().min(1).max(100).default(60),
|
|
|
|
|
type: z.enum(MonitorTypes, "Invalid monitor type"),
|
|
|
|
|
ignoreTlsErrors: z.boolean().default(false),
|
2026-08-20 10:59:40 -07:00
|
|
|
proxyMode: z.enum(ProxyModes).default("inherit"),
|
2026-08-26 11:19:08 -07:00
|
|
|
proxyId: proxyIdValidation,
|
2026-05-11 10:12:27 -07:00
|
|
|
useAdvancedMatching: z.boolean().default(false),
|
|
|
|
|
jsonPath: z.union([z.string(), z.literal("")]).optional(),
|
|
|
|
|
expectedValue: z.union([z.string(), z.literal("")]).optional(),
|
|
|
|
|
matchMethod: z.union([z.enum(MonitorMatchMethods), z.literal("")]).optional(),
|
2026-06-22 10:25:27 -07:00
|
|
|
method: z.enum(HttpMethods).optional().default("GET"),
|
2026-05-11 10:12:27 -07:00
|
|
|
url: z.string().min(1, "URL is required"),
|
|
|
|
|
port: z.number().optional(),
|
|
|
|
|
isActive: z.boolean().default(true),
|
|
|
|
|
interval: z.number().default(60000),
|
|
|
|
|
uptimePercentage: z.number().optional(),
|
|
|
|
|
notifications: z.array(z.string()).default([]),
|
2026-05-19 11:16:03 -07:00
|
|
|
tags: z.array(z.string()).default([]),
|
2026-06-01 13:21:30 +05:30
|
|
|
customUpCodes: z.array(httpStatusCode).default([]),
|
2026-05-11 10:12:27 -07:00
|
|
|
secret: z.string().optional(),
|
2026-08-28 13:59:24 -07:00
|
|
|
sshPrivateKey: z.string().optional(),
|
2026-05-11 10:12:27 -07:00
|
|
|
cpuAlertThreshold: z.number().default(100),
|
|
|
|
|
cpuAlertCounter: z.number().default(5),
|
|
|
|
|
memoryAlertThreshold: z.number().default(100),
|
|
|
|
|
memoryAlertCounter: z.number().default(5),
|
|
|
|
|
diskAlertThreshold: z.number().default(100),
|
|
|
|
|
diskAlertCounter: z.number().default(5),
|
|
|
|
|
tempAlertThreshold: z.number().default(100),
|
|
|
|
|
tempAlertCounter: z.number().default(5),
|
|
|
|
|
selectedDisks: z.array(z.string()).default([]),
|
|
|
|
|
gameId: z.union([z.string(), z.literal("")]).optional(),
|
|
|
|
|
grpcServiceName: z.union([z.string(), z.literal("")]).default(""),
|
2026-05-11 11:28:39 -07:00
|
|
|
strategy: z.enum(PageSpeedStrategies).optional(),
|
2026-05-11 10:12:27 -07:00
|
|
|
group: z.union([z.string().max(50).trim(), z.null()]).default(null),
|
|
|
|
|
geoCheckEnabled: z.boolean().default(false),
|
|
|
|
|
geoCheckLocations: z.array(z.enum(GeoContinents)).default([]),
|
|
|
|
|
geoCheckInterval: z.number().min(300000).default(300000),
|
|
|
|
|
dnsServer: dnsServerValidation.optional(),
|
|
|
|
|
dnsRecordType: z.enum(DnsRecordTypes).optional(),
|
|
|
|
|
createdAt: z.string().optional(),
|
|
|
|
|
updatedAt: z.string().optional(),
|
|
|
|
|
})
|
2026-05-11 11:28:39 -07:00
|
|
|
.superRefine(refineDnsHostname)
|
2026-06-22 10:31:13 -07:00
|
|
|
.superRefine(refineStrategyType)
|
2026-07-06 09:39:28 -07:00
|
|
|
.superRefine(refineHeadMatching)
|
2026-08-20 13:34:56 -07:00
|
|
|
.superRefine(refineRegexPattern)
|
2026-08-28 13:59:24 -07:00
|
|
|
.superRefine(refineProxySelection)
|
|
|
|
|
.superRefine(refineDockerUrl);
|
2026-03-11 12:41:27 -07:00
|
|
|
|
|
|
|
|
export const importMonitorsBodyValidation = z.object({
|
|
|
|
|
monitors: z.array(importedMonitorSchema).min(1, "At least one monitor is required"),
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-20 10:37:29 -07:00
|
|
|
export type ImportedMonitor = z.output<typeof importedMonitorSchema>;
|
|
|
|
|
|
2026-03-02 17:46:33 +00:00
|
|
|
export const getHardwareDetailsByIdParamValidation = z.object({
|
|
|
|
|
monitorId: z.string().min(1, "Monitor ID is required"),
|
2026-03-01 17:35:00 +00:00
|
|
|
});
|
|
|
|
|
|
2026-03-02 17:46:33 +00:00
|
|
|
export const getHardwareDetailsByIdQueryValidation = z.object({
|
2026-07-21 10:48:18 -07:00
|
|
|
dateRange: z.enum(DateRanges).optional(),
|
2026-03-01 17:35:00 +00:00
|
|
|
});
|
2026-05-01 11:24:19 -07:00
|
|
|
|
2026-08-28 13:59:24 -07:00
|
|
|
export const getDockerDetailsByIdParamValidation = z.object({ monitorId: z.string().min(1, "Monitor ID is required") });
|
|
|
|
|
export const getDockerDetailsByIdQueryValidation = z.object({ dateRange: z.enum(DateRanges).optional() });
|
|
|
|
|
|
2026-09-01 10:05:34 -07:00
|
|
|
export const getDockerContainerNameParamValidation = z.object({
|
|
|
|
|
monitorId: z.string().min(1, "Monitor ID is required"),
|
|
|
|
|
containerName: z.string().min(1, "Container name is required"),
|
|
|
|
|
});
|
|
|
|
|
export const getDockerContainerByNameQueryValidation = z.object({ dateRange: z.enum(DateRanges).optional() });
|
|
|
|
|
|
2026-05-01 11:24:19 -07:00
|
|
|
// Canonical monitor shape returned by /monitors endpoints. Keep aligned with
|
|
|
|
|
// what the controllers actually serialize.
|
|
|
|
|
export const monitorResponseSchema = z
|
|
|
|
|
.object({
|
|
|
|
|
_id: z.string(),
|
|
|
|
|
name: z.string(),
|
|
|
|
|
description: z.string().optional(),
|
|
|
|
|
type: z.enum(MonitorTypes),
|
|
|
|
|
url: z.string(),
|
|
|
|
|
port: z.number().optional(),
|
|
|
|
|
isActive: z.boolean(),
|
|
|
|
|
interval: z.number(),
|
|
|
|
|
status: z.enum(MonitorStatuses),
|
|
|
|
|
statusWindowSize: z.number(),
|
|
|
|
|
statusWindowThreshold: z.number(),
|
|
|
|
|
ignoreTlsErrors: z.boolean(),
|
2026-08-20 13:34:56 -07:00
|
|
|
proxyMode: z.enum(ProxyModes),
|
2026-08-26 11:19:08 -07:00
|
|
|
proxyId: proxyIdValidation,
|
2026-05-01 11:24:19 -07:00
|
|
|
useAdvancedMatching: z.boolean(),
|
|
|
|
|
jsonPath: z.string().optional(),
|
|
|
|
|
expectedValue: z.string().optional(),
|
|
|
|
|
matchMethod: z.enum(MonitorMatchMethods).optional(),
|
2026-06-22 10:15:14 -07:00
|
|
|
method: z.enum(HttpMethods),
|
2026-05-01 11:24:19 -07:00
|
|
|
notifications: z.array(z.string()),
|
2026-05-19 14:45:42 -07:00
|
|
|
tags: z.array(z.string()),
|
2026-06-01 13:21:30 +05:30
|
|
|
customUpCodes: z.array(httpStatusCode).optional(),
|
2026-05-01 11:24:19 -07:00
|
|
|
secret: z.string().optional(),
|
2026-08-28 13:59:24 -07:00
|
|
|
sshPrivateKey: z.string().optional(),
|
2026-05-01 11:24:19 -07:00
|
|
|
cpuAlertThreshold: z.number(),
|
|
|
|
|
memoryAlertThreshold: z.number(),
|
|
|
|
|
diskAlertThreshold: z.number(),
|
|
|
|
|
tempAlertThreshold: z.number(),
|
|
|
|
|
selectedDisks: z.array(z.string()),
|
|
|
|
|
gameId: z.string().optional(),
|
|
|
|
|
grpcServiceName: z.string().optional(),
|
|
|
|
|
group: z.string().nullable().optional(),
|
|
|
|
|
geoCheckEnabled: z.boolean(),
|
|
|
|
|
geoCheckLocations: z.array(z.enum(GeoContinents)),
|
|
|
|
|
geoCheckInterval: z.number(),
|
2026-05-08 12:43:33 -07:00
|
|
|
dnsServer: z.string().optional(),
|
|
|
|
|
dnsRecordType: z.enum(DnsRecordTypes).optional(),
|
2026-05-01 11:24:19 -07:00
|
|
|
teamId: z.string(),
|
|
|
|
|
userId: z.string(),
|
|
|
|
|
createdAt: z.string(),
|
|
|
|
|
updatedAt: z.string(),
|
2026-06-11 14:18:56 -07:00
|
|
|
lastEvaluatedAt: z.number(),
|
2026-05-01 11:24:19 -07:00
|
|
|
})
|
|
|
|
|
.passthrough();
|
2026-07-30 10:35:01 -07:00
|
|
|
|
|
|
|
|
// Grouped-check buckets returned by GET /monitors/uptime/details/{monitorId}. Keep
|
|
|
|
|
// aligned with GroupedCheck / GroupedUptimeCheck in domain/checks/check.type.ts.
|
|
|
|
|
export const groupedCheckResponseSchema = z.object({
|
|
|
|
|
bucketDate: z.string(),
|
|
|
|
|
avgResponseTime: z.number(),
|
|
|
|
|
totalChecks: z.number(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const groupedUptimeCheckResponseSchema = groupedCheckResponseSchema.extend({
|
|
|
|
|
avgDns: z.number(),
|
|
|
|
|
avgTcp: z.number(),
|
|
|
|
|
avgTls: z.number(),
|
|
|
|
|
avgRequest: z.number(),
|
|
|
|
|
avgFirstByte: z.number(),
|
|
|
|
|
avgDownload: z.number(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Keep aligned with MonitorStats in domain/monitor-stats/monitor-stats.type.ts.
|
|
|
|
|
export const monitorStatsResponseSchema = z.object({
|
|
|
|
|
id: z.string(),
|
|
|
|
|
monitorId: z.string(),
|
|
|
|
|
avgResponseTime: z.number(),
|
|
|
|
|
maxResponseTime: z.number(),
|
|
|
|
|
totalChecks: z.number(),
|
|
|
|
|
totalUpChecks: z.number(),
|
|
|
|
|
totalDownChecks: z.number(),
|
|
|
|
|
uptimePercentage: z.number(),
|
|
|
|
|
lastCheckTimestamp: z.number(),
|
|
|
|
|
lastResponseTime: z.number(),
|
|
|
|
|
timeOfLastFailure: z.number().optional(),
|
|
|
|
|
createdAt: z.string(),
|
|
|
|
|
updatedAt: z.string(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Response of GET /monitors/uptime/details/{monitorId}. Keep aligned with
|
|
|
|
|
// UptimeDetailsResult in domain/monitors/monitor.type.ts; the monitor here is the
|
|
|
|
|
// repository's domain entity, which serializes `id` rather than `_id`.
|
|
|
|
|
export const uptimeDetailsResponseSchema = z.object({
|
|
|
|
|
monitorData: z.object({
|
|
|
|
|
monitor: monitorResponseSchema.omit({ _id: true }).extend({ id: z.string() }),
|
|
|
|
|
groupedChecks: z.array(groupedUptimeCheckResponseSchema),
|
|
|
|
|
groupedUpChecks: z.array(groupedCheckResponseSchema),
|
|
|
|
|
groupedDownChecks: z.array(groupedCheckResponseSchema),
|
|
|
|
|
groupedAvgResponseTime: z.number(),
|
|
|
|
|
groupedUptimePercentage: z.number(),
|
|
|
|
|
}),
|
|
|
|
|
monitorStats: monitorStatsResponseSchema.nullable(),
|
|
|
|
|
});
|
2026-08-28 13:59:24 -07:00
|
|
|
|
|
|
|
|
// Keep aligned with DockerContainerInfo / DockerContainerSummary in types/network.ts.
|
|
|
|
|
export const dockerContainerResponseSchema = z.object({
|
|
|
|
|
id: z.string(),
|
|
|
|
|
name: z.string(),
|
|
|
|
|
image: z.string(),
|
|
|
|
|
state: z.enum(DockerContainerStates),
|
|
|
|
|
status: z.string(),
|
|
|
|
|
health: z.enum(DockerHealthStatuses),
|
|
|
|
|
cpuPct: z.number().optional(),
|
|
|
|
|
memoryUsedBytes: z.number().optional(),
|
|
|
|
|
memoryLimitBytes: z.number().optional(),
|
|
|
|
|
memoryPct: z.number().optional(),
|
|
|
|
|
restartCount: z.number().optional(),
|
|
|
|
|
startedAt: z.string().optional(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const containerSummaryResponseSchema = z.object({
|
|
|
|
|
total: z.number(),
|
|
|
|
|
running: z.number(),
|
|
|
|
|
stopped: z.number(),
|
|
|
|
|
unhealthy: z.number(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Keep aligned with DockerStatsBucket in domain/checks/check.type.ts. The avg fields are
|
|
|
|
|
// null for buckets with no values ($avg skips missing; down checks store no containerSummary).
|
|
|
|
|
export const dockerStatsBucketResponseSchema = z.object({
|
|
|
|
|
_id: z.string(),
|
|
|
|
|
avgResponseTime: z.number().nullable(),
|
|
|
|
|
upCount: z.number(),
|
|
|
|
|
totalCount: z.number(),
|
|
|
|
|
avgRunning: z.number().nullable(),
|
|
|
|
|
avgTotal: z.number().nullable(),
|
|
|
|
|
avgUnhealthy: z.number().nullable(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Response of GET /monitors/docker/details/{monitorId}. Keep aligned with
|
|
|
|
|
// DockerDetailsResult in domain/monitors/monitor.type.ts; the monitor here is the
|
|
|
|
|
// repository's domain entity, which serializes `id` rather than `_id`.
|
|
|
|
|
export const dockerDetailsResponseSchema = z.object({
|
|
|
|
|
monitor: monitorResponseSchema.omit({ _id: true }).extend({ id: z.string() }),
|
|
|
|
|
stats: z.object({
|
|
|
|
|
aggregateData: z.object({ totalChecks: z.number() }),
|
|
|
|
|
upChecks: z.object({ totalChecks: z.number() }),
|
|
|
|
|
aggregate: z.array(dockerStatsBucketResponseSchema),
|
|
|
|
|
latest: z
|
|
|
|
|
.object({
|
|
|
|
|
containers: z.array(dockerContainerResponseSchema),
|
|
|
|
|
summary: containerSummaryResponseSchema.optional(),
|
|
|
|
|
checkedAt: z.string(),
|
|
|
|
|
})
|
|
|
|
|
.nullable(),
|
|
|
|
|
}),
|
|
|
|
|
monitorStats: monitorStatsResponseSchema.nullable(),
|
|
|
|
|
});
|