infra refactor

This commit is contained in:
Alex Holliday committed 2025-12-28 18:35:14 +00:00
1 parent df707ab194
commit 4dfeb71a9d
14 files changed
+195 -187

No files matched your search

+2
View File
@@ -3,3 +3,5 @@ export * from "./login";
export * from "./register";
export * from "./monitor";
export * from "./pagespeed";
export * from "./infra";
export * from "./utils";
+74
View File
@@ -0,0 +1,74 @@
import { z } from "zod";
import { urlRegex, durationSchema } from "@/validation/utils";
export const monitorSchemaInfra = z.object({
type: z.string().min(1, "You must select an option"),
url: z.string().min(1, "URL is required").regex(urlRegex, "Invalid URL"),
secret: z.string().min(1, "Secret is required"),
n: z.coerce
.number({ message: "Number required" })
.min(1, "Minimum value is 1")
.max(25, "Maximum value is 25"),
notificationChannels: z.array(z.string()).optional().default([]),
name: z.string().min(1, "Display name is required"),
interval: durationSchema(60000),
rejectUnauthorized: z.boolean().default(true),
thresholds: z.object({
cpu: z.preprocess(
(val) => (typeof val === "number" ? String(val) : val),
z
.string()
.trim()
.min(1, { message: "CPU threshold is required" })
.refine((v) => /^-?\d+(\.\d+)?$/.test(v), {
message: "CPU must be a number",
})
.transform((v) => Number(v))
.refine((v) => v >= 0 && v <= 100, {
message: "CPU must be between 0 and 100",
})
),
memory: z.preprocess(
(val) => (typeof val === "number" ? String(val) : val),
z
.string()
.trim()
.min(1, { message: "Memory threshold is required" })
.refine((v) => /^-?\d+(\.\d+)?$/.test(v), {
message: "Memory must be a number",
})
.transform((v) => Number(v))
.refine((v) => v >= 0 && v <= 100, {
message: "Memory must be between 0 and 100",
})
),
disk: z.preprocess(
(val) => (typeof val === "number" ? String(val) : val),
z
.string()
.trim()
.min(1, { message: "Disk threshold is required" })
.refine((v) => /^-?\d+(\.\d+)?$/.test(v), {
message: "Disk must be a number",
})
.transform((v) => Number(v))
.refine((v) => v >= 0 && v <= 100, {
message: "Disk must be between 0 and 100",
})
),
temperature: z.preprocess(
(val) => (typeof val === "number" ? String(val) : val),
z
.string()
.trim()
.min(1, { message: "Temperature threshold is required" })
.refine((v) => /^-?\d+(\.\d+)?$/.test(v), {
message: "Temperature must be a number",
})
.transform((v) => Number(v))
.refine((v) => v >= -50 && v <= 150, {
message: "Temperature must be between -50 and 150",
})
),
}),
});
+1 -21
View File
@@ -1,25 +1,5 @@
import { z } from "zod";
import humanInterval from "human-interval";
import ms from "ms";
export const urlRegex =
/^(https?:\/\/)?(localhost|([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}|[a-zA-Z0-9-]+|(\d{1,3}\.){3}\d{1,3})(:\d{1,5})?(\/.*)?$/;
export const durationSchema = (minInterval: number) =>
z.string().superRefine((val, ctx) => {
const milis = humanInterval(val);
if (!milis || isNaN(milis)) {
ctx.addIssue({
code: "custom",
message: "Invalid duration format",
});
} else if (milis < minInterval) {
ctx.addIssue({
code: "custom",
message: `Minimum duration is ${ms(minInterval, { long: true })}`,
});
}
});
import { urlRegex, durationSchema } from "@/validation/utils";
export const monitorSchema = z
.object({
+1 -1
View File
@@ -1,5 +1,5 @@
import { z } from "zod";
import { urlRegex, durationSchema } from "@/validation";
import { urlRegex, durationSchema } from "@/validation/utils";
export const monitorSchemaPageSpeed = z.object({
type: z.string().min(1, "You must select an option"),
+23
View File
@@ -0,0 +1,23 @@
import { z } from "zod";
import humanInterval from "human-interval";
import ms from "ms";
export const urlRegex =
/^(https?:\/\/)?(localhost|([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}|[a-zA-Z0-9-]+|(\d{1,3}\.){3}\d{1,3})(:\d{1,5})?(\/.*)?$/;
export const durationSchema = (minInterval: number) =>
z.string().superRefine((val, ctx) => {
const milis = humanInterval(val);
if (!milis || isNaN(milis)) {
ctx.addIssue({
code: "custom",
message: "Invalid duration format",
});
} else if (milis < minInterval) {
ctx.addIssue({
code: "custom",
message: `Minimum duration is ${ms(minInterval, { long: true })}`,
});
}
});
+3 -92
View File
@@ -1,5 +1,5 @@
import { z } from "zod";
import humanInterval from "human-interval";
// humanInterval moved to split schema files
import { ChannelTypes } from "@/types/notification-channel";
import { MaintenanceRepeats } from "@/types/maintenance";
@@ -8,25 +8,7 @@ const urlRegex =
// monitor duration schema moved to client/src/validation/monitor.ts
// pagespeed duration schema moved to client/src/validation/pagespeed.ts
const durationSchemaInfra = z
.string()
.optional()
.superRefine((val, ctx) => {
if (!val || val.trim() === "") return;
const ms = humanInterval(val);
if (!ms || isNaN(ms)) {
ctx.addIssue({
code: "custom",
message: "Invalid duration format",
});
} else if (ms < 60000) {
ctx.addIssue({
code: "custom",
message: "Minimum duration is 1 minute",
});
}
});
// infra duration schema moved to client/src/validation/infra.ts
const optionalString = <T extends z.ZodTypeAny>(schema: T) =>
z.preprocess(
@@ -38,78 +20,7 @@ const optionalString = <T extends z.ZodTypeAny>(schema: T) =>
// moved to client/src/validation/monitor.ts
// moved to client/src/validation/pagespeed.ts
export const monitorSchemaInfra = z.object({
type: z.string().min(1, "You must select an option"),
url: z.string().min(1, "URL is required").regex(urlRegex, "Invalid URL"),
secret: z.string().min(1, "Secret is required"),
n: z.coerce
.number({ message: "Number required" })
.min(1, "Minimum value is 1")
.max(25, "Maximum value is 25"),
notificationChannels: z.array(z.string()).optional().default([]),
name: z.string().min(1, "Display name is required"),
interval: durationSchemaInfra,
rejectUnauthorized: z.boolean().default(true),
thresholds: z.object({
// Accept both strings and numbers from the form. Enforce required and ranges with clear messages.
cpu: z.preprocess(
(val) => (typeof val === "number" ? String(val) : val),
z
.string()
.trim()
.min(1, { message: "CPU threshold is required" })
.refine((v) => /^-?\d+(\.\d+)?$/.test(v), {
message: "CPU must be a number",
})
.transform((v) => Number(v))
.refine((v) => v >= 0 && v <= 100, {
message: "CPU must be between 0 and 100",
})
),
memory: z.preprocess(
(val) => (typeof val === "number" ? String(val) : val),
z
.string()
.trim()
.min(1, { message: "Memory threshold is required" })
.refine((v) => /^-?\d+(\.\d+)?$/.test(v), {
message: "Memory must be a number",
})
.transform((v) => Number(v))
.refine((v) => v >= 0 && v <= 100, {
message: "Memory must be between 0 and 100",
})
),
disk: z.preprocess(
(val) => (typeof val === "number" ? String(val) : val),
z
.string()
.trim()
.min(1, { message: "Disk threshold is required" })
.refine((v) => /^-?\d+(\.\d+)?$/.test(v), {
message: "Disk must be a number",
})
.transform((v) => Number(v))
.refine((v) => v >= 0 && v <= 100, {
message: "Disk must be between 0 and 100",
})
),
temperature: z.preprocess(
(val) => (typeof val === "number" ? String(val) : val),
z
.string()
.trim()
.min(1, { message: "Temperature threshold is required" })
.refine((v) => /^-?\d+(\.\d+)?$/.test(v), {
message: "Temperature must be a number",
})
.transform((v) => Number(v))
.refine((v) => v >= -50 && v <= 150, {
message: "Temperature must be between -50 and 150",
})
),
}),
});
// moved to client/src/validation/infra.ts
export const teamSchema = z.object({
name: z.string().min(1, "Team name is required"),