From df707ab194c487a15f73769824a171eea0ecf194 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sun, 28 Dec 2025 07:05:09 +0000 Subject: [PATCH] pagespeed cleanup --- .../src/hooks/forms/UseInitPageSpeedForm.tsx | 62 +++++++++++++------ .../src/pages/pagespeed/PageSpeedConfig.tsx | 25 +++----- .../src/pages/pagespeed/PageSpeedCreate.tsx | 15 +---- client/src/pages/pagespeed/PageSpeedForm.tsx | 27 +++++--- client/src/validation/index.ts | 1 + client/src/validation/monitor.ts | 35 ++++++----- client/src/validation/pagespeed.ts | 15 +++++ client/src/validation/zod.ts | 53 +--------------- 8 files changed, 106 insertions(+), 127 deletions(-) create mode 100644 client/src/validation/pagespeed.ts diff --git a/client/src/hooks/forms/UseInitPageSpeedForm.tsx b/client/src/hooks/forms/UseInitPageSpeedForm.tsx index 7c080c608..a26cc67d0 100644 --- a/client/src/hooks/forms/UseInitPageSpeedForm.tsx +++ b/client/src/hooks/forms/UseInitPageSpeedForm.tsx @@ -1,31 +1,53 @@ -import { monitorSchemaPageSpeed } from "@/validation/zod"; +import humanInterval from "human-interval"; +import type { IMonitor } from "@/types/monitor"; import ms from "ms"; import { useMemo } from "react"; +import type { FormValues, SubmitValues } from "@/pages/pagespeed/PageSpeedForm"; -import { z } from "zod"; export const useInitForm = ({ initialData, }: { - initialData: Partial> | undefined; + initialData: IMonitor | undefined; }) => { return useMemo(() => { - let humanInterval = "3 minutes"; - if (initialData?.interval) { - const parsed = Number(initialData.interval); - if (!isNaN(parsed)) { - humanInterval = ms(parsed, { long: true }); - } - } - - const defaults: z.infer = { - type: "pagespeed", - url: initialData?.url || "", - n: initialData?.n || 3, - notificationChannels: initialData?.notificationChannels || [], - name: initialData?.name || "", - interval: humanInterval, - rejectUnauthorized: true, + const apiToForm = (apiData: IMonitor): FormValues => { + console.log(JSON.stringify(apiData)); + return { + type: apiData.type, + url: apiData.url, + n: apiData.n, + notificationChannels: apiData.notificationChannels, + name: apiData.name, + interval: ms(apiData.interval, { long: true }), + rejectUnauthorized: apiData.rejectUnauthorized, + }; }; - return { defaults }; + + const formToApi = (form: FormValues): SubmitValues => { + const submitData: SubmitValues = { + type: form.type, + url: form.url, + n: form.n, + notificationChannels: form.notificationChannels, + name: form.name, + interval: humanInterval(form.interval), + rejectUnauthorized: form.rejectUnauthorized, + }; + return submitData; + }; + + const defaults: FormValues = initialData + ? apiToForm(initialData) + : { + type: "pagespeed", + url: "", + n: 3, + notificationChannels: [], + name: "", + interval: "3 minutes", + rejectUnauthorized: true, + }; + + return { defaults, formToApi }; }, [initialData]); }; diff --git a/client/src/pages/pagespeed/PageSpeedConfig.tsx b/client/src/pages/pagespeed/PageSpeedConfig.tsx index 5a291406b..7f6cb91bc 100644 --- a/client/src/pages/pagespeed/PageSpeedConfig.tsx +++ b/client/src/pages/pagespeed/PageSpeedConfig.tsx @@ -1,21 +1,14 @@ -import { monitorSchemaPageSpeed } from "@/validation/zod"; import { useGet, usePatch } from "@/hooks/UseApi"; import { PageSpeedForm } from "@/pages/pagespeed/PageSpeedForm"; import { useParams } from "react-router"; import type { ApiResponse } from "@/types/api"; -import humanInterval from "human-interval"; -import { z } from "zod"; import { useNavigate } from "react-router"; import type { INotificationChannel } from "@/types/notification-channel"; import type { IMonitor } from "@/types/monitor"; +import type { SubmitValues } from "@/pages/pagespeed/PageSpeedForm"; const PageSpeedConfigurePage = () => { - type FormValues = z.infer; - type SubmitValues = Omit & { - interval: number | undefined; - }; - const { id } = useParams(); const navigate = useNavigate(); const { response } = useGet>( @@ -29,16 +22,12 @@ const PageSpeedConfigurePage = () => { const { patch, loading, error } = usePatch, IMonitor>(); - const onSubmit = async (data: FormValues) => { - let interval = humanInterval(data.interval); - if (!interval) interval = 180000; - const submitData = { - type: data.type, - name: data.name, - notificationChannels: data.notificationChannels, - interval, - }; - const result = await patch(`/monitors/${id}`, submitData); + if (!monitor) { + return null; + } + + const onSubmit = async (data: SubmitValues) => { + const result = await patch(`/monitors/${id}`, data); if (result) { navigate(`/pagespeed/${id}`); } else { diff --git a/client/src/pages/pagespeed/PageSpeedCreate.tsx b/client/src/pages/pagespeed/PageSpeedCreate.tsx index 744daae7a..bc490edac 100644 --- a/client/src/pages/pagespeed/PageSpeedCreate.tsx +++ b/client/src/pages/pagespeed/PageSpeedCreate.tsx @@ -1,28 +1,19 @@ import { PageSpeedForm } from "@/pages/pagespeed/PageSpeedForm"; -import humanInterval from "human-interval"; -import { z } from "zod"; -import { monitorSchema } from "@/validation"; import { useGet, usePost } from "@/hooks/UseApi"; import type { ApiResponse } from "@/types/api"; import { useNavigate } from "react-router"; import type { INotificationChannel } from "@/types/notification-channel"; +import type { SubmitValues } from "@/pages/pagespeed/PageSpeedForm"; const UptimeCreatePage = () => { - type FormValues = z.infer; - type SubmitValues = Omit & { - interval: number | undefined; - }; const navigate = useNavigate(); const { response } = useGet>( "/notification-channels" ); const { post, loading, error } = usePost(); - const onSubmit = async (data: FormValues) => { - let interval = humanInterval(data.interval); - if (!interval) interval = 60000; - const submitData = { ...data, interval }; - const result = await post("/monitors", submitData); + const onSubmit = async (data: SubmitValues) => { + const result = await post("/monitors", data); if (result) { navigate("/pagespeed"); } else { diff --git a/client/src/pages/pagespeed/PageSpeedForm.tsx b/client/src/pages/pagespeed/PageSpeedForm.tsx index 865c8b65f..9a0c92979 100644 --- a/client/src/pages/pagespeed/PageSpeedForm.tsx +++ b/client/src/pages/pagespeed/PageSpeedForm.tsx @@ -13,7 +13,7 @@ import { Typography } from "@mui/material"; import { useEffect, useState, useRef } from "react"; import { useTranslation } from "react-i18next"; -import { monitorSchemaPageSpeed } from "@/validation/zod"; +import { monitorSchemaPageSpeed } from "@/validation"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; import { @@ -24,8 +24,15 @@ import { } from "react-hook-form"; import { useTheme } from "@mui/material/styles"; import { useInitForm } from "@/hooks/forms/UseInitPageSpeedForm"; +import type { IMonitor } from "@/types/monitor"; -type FormValues = z.infer; +export type FormValues = z.infer; +export type SubmitValues = Omit< + z.infer, + "interval" +> & { + interval: number | undefined; +}; export const PageSpeedForm = ({ mode = "create", @@ -35,14 +42,14 @@ export const PageSpeedForm = ({ loading, }: { mode?: "create" | "configure"; - initialData?: Partial; - onSubmit: SubmitHandler; + initialData?: IMonitor; + onSubmit: SubmitHandler; notificationOptions: any[]; loading: boolean; }) => { const { t } = useTranslation(); const theme = useTheme(); - const { defaults } = useInitForm({ initialData: initialData }); + const { defaults, formToApi } = useInitForm({ initialData: initialData }); const urlInputRef = useRef(null); const [selectedProtocol, setSelectedProtocol] = useState("https"); @@ -92,13 +99,13 @@ export const PageSpeedForm = ({ } }, [selectedProtocol]); - const notificationChannels = useWatch({ - control, - name: "notificationChannels", - }); + const notificationChannels = + useWatch({ control, name: "notificationChannels" }) || []; + + const submitForm = (data: FormValues) => onSubmit(formToApi(data) as any); return ( - + { - // if (!val || val.trim() === "") return; - const ms = humanInterval(val); +export const durationSchema = (minInterval: number) => + z.string().superRefine((val, ctx) => { + const milis = humanInterval(val); - if (!ms || isNaN(ms)) { - ctx.addIssue({ - code: "custom", - message: "Invalid duration format", - }); - } else if (ms < 10000) { - ctx.addIssue({ - code: "custom", - message: "Minimum duration is 10 seconds", - }); - } -}); + 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 })}`, + }); + } + }); export const monitorSchema = z .object({ @@ -31,7 +32,7 @@ export const monitorSchema = z port: z.coerce.number().optional(), notificationChannels: z.array(z.string()).optional().default([]), name: z.string().min(1, "Display name is required"), - interval: durationSchema, + interval: durationSchema(10000), rejectUnauthorized: z.boolean().default(true), }) .superRefine((data, ctx) => { diff --git a/client/src/validation/pagespeed.ts b/client/src/validation/pagespeed.ts new file mode 100644 index 000000000..bfb3e6df2 --- /dev/null +++ b/client/src/validation/pagespeed.ts @@ -0,0 +1,15 @@ +import { z } from "zod"; +import { urlRegex, durationSchema } from "@/validation"; + +export const monitorSchemaPageSpeed = z.object({ + type: z.string().min(1, "You must select an option"), + url: z.string().min(1, "URL is required").regex(urlRegex, "Invalid URL"), + 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(180000), + rejectUnauthorized: z.boolean().default(true), +}); diff --git a/client/src/validation/zod.ts b/client/src/validation/zod.ts index 2a5cd5afb..8f8976990 100644 --- a/client/src/validation/zod.ts +++ b/client/src/validation/zod.ts @@ -6,44 +6,8 @@ import { MaintenanceRepeats } from "@/types/maintenance"; 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})?(\/.*)?$/; -const durationSchema = 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 < 10000) { - ctx.addIssue({ - code: "custom", - message: "Minimum duration is 10 seconds", - }); - } - }); -const durationSchemaPageSpeed = 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 < 180000) { - ctx.addIssue({ - code: "custom", - message: "Minimum duration is 3 minutes", - }); - } - }); +// 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() @@ -73,18 +37,7 @@ const optionalString = (schema: T) => // moved to client/src/validation/monitor.ts -export const monitorSchemaPageSpeed = z.object({ - type: z.string().min(1, "You must select an option"), - url: z.string().min(1, "URL is required").regex(urlRegex, "Invalid URL"), - 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: durationSchemaPageSpeed, - rejectUnauthorized: z.boolean().default(true), -}); +// 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"),