From e18e2e26da9ad83b14104c55712d5394342366b7 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 14 Oct 2025 12:45:11 -0700 Subject: [PATCH] move v2 utils to v2 dir --- .../v2/DesignElements/StatusLabel.tsx | 2 +- .../v2/Monitors/ChartAvgResponse.tsx | 2 +- .../v2/Monitors/ChartResponseTime.tsx | 4 +- .../v2/Monitors/HistogramResponseTime.tsx | 2 +- .../Monitors/HistogramResponseTimeTooltip.tsx | 2 +- .../v2/Monitors/HistogramStatus.tsx | 6 +- .../Components/v2/Monitors/MonitorStatus.tsx | 2 +- client/src/Hooks/v2/UseApi.tsx | 2 +- client/src/Hooks/v2/useInitMonitorForm.tsx | 17 ++ client/src/Pages/v2/Uptime/CheckTable.tsx | 2 +- client/src/Pages/v2/Uptime/Configure.tsx | 43 +++ client/src/Pages/v2/Uptime/Details.tsx | 2 +- client/src/Pages/v2/Uptime/UptimeForm.tsx | 247 ++++++++++++++++++ client/src/Utils/{ => v2}/ApiClient.ts | 0 client/src/Utils/{ => v2}/DataUtils.ts | 0 client/src/Utils/{ => v2}/MonitorUtils.ts | 2 +- client/src/Utils/{ => v2}/TimeUtils.ts | 0 17 files changed, 321 insertions(+), 14 deletions(-) create mode 100644 client/src/Hooks/v2/useInitMonitorForm.tsx create mode 100644 client/src/Pages/v2/Uptime/Configure.tsx create mode 100644 client/src/Pages/v2/Uptime/UptimeForm.tsx rename client/src/Utils/{ => v2}/ApiClient.ts (100%) rename client/src/Utils/{ => v2}/DataUtils.ts (100%) rename client/src/Utils/{ => v2}/MonitorUtils.ts (94%) rename client/src/Utils/{ => v2}/TimeUtils.ts (100%) diff --git a/client/src/Components/v2/DesignElements/StatusLabel.tsx b/client/src/Components/v2/DesignElements/StatusLabel.tsx index a314c09b0..e4d288e63 100644 --- a/client/src/Components/v2/DesignElements/StatusLabel.tsx +++ b/client/src/Components/v2/DesignElements/StatusLabel.tsx @@ -2,7 +2,7 @@ import Box from "@mui/material/Box"; import { BaseBox } from "@/Components/v2/DesignElements"; import type { MonitorStatus } from "@/Types/Monitor"; -import { getStatusPalette } from "@/Utils/MonitorUtils"; +import { getStatusPalette } from "@/Utils/v2/MonitorUtils"; import { useTheme } from "@mui/material/styles"; export const StatusLabel = ({ diff --git a/client/src/Components/v2/Monitors/ChartAvgResponse.tsx b/client/src/Components/v2/Monitors/ChartAvgResponse.tsx index 10938f359..e9b85cb43 100644 --- a/client/src/Components/v2/Monitors/ChartAvgResponse.tsx +++ b/client/src/Components/v2/Monitors/ChartAvgResponse.tsx @@ -4,7 +4,7 @@ import Typography from "@mui/material/Typography"; import AverageResponseIcon from "@/assets/icons/average-response-icon.svg?react"; import { Cell, RadialBarChart, RadialBar, ResponsiveContainer } from "recharts"; -import { getResponseTimeColor } from "@/Utils/MonitorUtils"; +import { getResponseTimeColor } from "@/Utils/v2/MonitorUtils"; import { useTheme } from "@mui/material/styles"; export const ChartAvgResponse = ({ avg, max }: { avg: number; max: number }) => { diff --git a/client/src/Components/v2/Monitors/ChartResponseTime.tsx b/client/src/Components/v2/Monitors/ChartResponseTime.tsx index 28f40f254..3c1c93f01 100644 --- a/client/src/Components/v2/Monitors/ChartResponseTime.tsx +++ b/client/src/Components/v2/Monitors/ChartResponseTime.tsx @@ -1,7 +1,7 @@ import { BaseChart } from "./HistogramStatus"; import { BaseBox } from "../DesignElements"; import ResponseTimeIcon from "@/assets/icons/response-time-icon.svg?react"; -import { normalizeResponseTimes } from "@/Utils/DataUtils"; +import { normalizeResponseTimes } from "@/Utils/v2/DataUtils"; import { AreaChart, Area, @@ -17,7 +17,7 @@ import { formatDateWithTz, tickDateFormatLookup, tooltipDateFormatLookup, -} from "@/Utils/TimeUtils"; +} from "@/Utils/v2/TimeUtils"; import { useTheme } from "@mui/material/styles"; import type { GroupedCheck } from "@/Types/Check"; import { useSelector } from "react-redux"; diff --git a/client/src/Components/v2/Monitors/HistogramResponseTime.tsx b/client/src/Components/v2/Monitors/HistogramResponseTime.tsx index 1925ac93c..aa1996e2a 100644 --- a/client/src/Components/v2/Monitors/HistogramResponseTime.tsx +++ b/client/src/Components/v2/Monitors/HistogramResponseTime.tsx @@ -3,7 +3,7 @@ import Box from "@mui/material/Box"; import { useTheme } from "@mui/material/styles"; import type { Check } from "@/Types/Check"; import { HistogramResponseTimeTooltip } from "@/Components/v2/Monitors/HistogramResponseTimeTooltip"; -import { normalizeResponseTimes } from "@/Utils/DataUtils"; +import { normalizeResponseTimes } from "@/Utils/v2/DataUtils"; export const HistogramResponseTime = ({ checks }: { checks: Check[] }) => { const normalChecks = normalizeResponseTimes(checks, "responseTime"); diff --git a/client/src/Components/v2/Monitors/HistogramResponseTimeTooltip.tsx b/client/src/Components/v2/Monitors/HistogramResponseTimeTooltip.tsx index d704cf5d4..d8c0263f0 100644 --- a/client/src/Components/v2/Monitors/HistogramResponseTimeTooltip.tsx +++ b/client/src/Components/v2/Monitors/HistogramResponseTimeTooltip.tsx @@ -1,7 +1,7 @@ import Stack from "@mui/material/Stack"; import Tooltip from "@mui/material/Tooltip"; import Typography from "@mui/material/Typography"; -import { formatDateWithTz } from "@/Utils/TimeUtils"; +import { formatDateWithTz } from "@/Utils/v2/TimeUtils"; import { useSelector } from "react-redux"; import type { LatestCheck } from "@/Types/Check"; diff --git a/client/src/Components/v2/Monitors/HistogramStatus.tsx b/client/src/Components/v2/Monitors/HistogramStatus.tsx index 2e10a8042..bc6675bc3 100644 --- a/client/src/Components/v2/Monitors/HistogramStatus.tsx +++ b/client/src/Components/v2/Monitors/HistogramStatus.tsx @@ -9,12 +9,12 @@ import IncidentsIcon from "@/assets/icons/incidents.svg?react"; import type { GroupedCheck } from "@/Types/Check"; import type { MonitorStatus } from "@/Types/Monitor"; -import { normalizeResponseTimes } from "@/Utils/DataUtils"; +import { normalizeResponseTimes } from "@/Utils/v2/DataUtils"; import { useState } from "react"; -import { formatDateWithTz } from "@/Utils/TimeUtils"; +import { formatDateWithTz } from "@/Utils/v2/TimeUtils"; import { useSelector } from "react-redux"; import { useTheme } from "@mui/material/styles"; -import { getResponseTimeColor } from "@/Utils/MonitorUtils"; +import { getResponseTimeColor } from "@/Utils/v2/MonitorUtils"; const XLabel = ({ p1, diff --git a/client/src/Components/v2/Monitors/MonitorStatus.tsx b/client/src/Components/v2/Monitors/MonitorStatus.tsx index ff4d83352..f329e6bb8 100644 --- a/client/src/Components/v2/Monitors/MonitorStatus.tsx +++ b/client/src/Components/v2/Monitors/MonitorStatus.tsx @@ -3,7 +3,7 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; import { PulseDot } from "@/Components/v2/DesignElements/PulseDot"; import { Dot } from "@/Components/v2/DesignElements/Dot"; -import { getStatusColor, formatUrl } from "@/Utils/MonitorUtils"; +import { getStatusColor, formatUrl } from "@/Utils/v2/MonitorUtils"; import { useTheme } from "@mui/material/styles"; import prettyMilliseconds from "pretty-ms"; import { typographyLevels } from "@/Utils/Theme/v2/palette"; diff --git a/client/src/Hooks/v2/UseApi.tsx b/client/src/Hooks/v2/UseApi.tsx index d3001c87b..2ee37c0da 100644 --- a/client/src/Hooks/v2/UseApi.tsx +++ b/client/src/Hooks/v2/UseApi.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import useSWR from "swr"; import type { SWRConfiguration } from "swr"; import type { AxiosRequestConfig } from "axios"; -import { get, post, patch } from "@/Utils/ApiClient"; // your axios wrapper +import { get, post, patch } from "@/Utils/v2/ApiClient"; // your axios wrapper export type ApiResponse = { message: string; diff --git a/client/src/Hooks/v2/useInitMonitorForm.tsx b/client/src/Hooks/v2/useInitMonitorForm.tsx new file mode 100644 index 000000000..2e98b3753 --- /dev/null +++ b/client/src/Hooks/v2/useInitMonitorForm.tsx @@ -0,0 +1,17 @@ +import { monitorSchema } from "@/Validation/v2/zod"; +import { z } from "zod"; +export const useInitForm = ({ + initialData, +}: { + initialData: Partial> | undefined; +}) => { + const defaults: z.infer = { + type: initialData?.type || "http", + url: initialData?.url || "", + n: initialData?.n || 3, + notificationChannels: initialData?.notificationChannels || [], + name: initialData?.name || "", + interval: initialData?.interval || "1 minute", + }; + return { defaults }; +}; diff --git a/client/src/Pages/v2/Uptime/CheckTable.tsx b/client/src/Pages/v2/Uptime/CheckTable.tsx index 42da6da12..c2811c453 100644 --- a/client/src/Pages/v2/Uptime/CheckTable.tsx +++ b/client/src/Pages/v2/Uptime/CheckTable.tsx @@ -10,7 +10,7 @@ import type { MonitorStatus } from "@/Types/Monitor"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { useGet } from "@/Hooks/v2/UseApi"; -import { formatDateWithTz } from "@/Utils/TimeUtils"; +import { formatDateWithTz } from "@/Utils/v2/TimeUtils"; import { useSelector } from "react-redux"; const getHeaders = (t: Function, uiTimezone: string) => { const headers: Header[] = [ diff --git a/client/src/Pages/v2/Uptime/Configure.tsx b/client/src/Pages/v2/Uptime/Configure.tsx new file mode 100644 index 000000000..dc014669b --- /dev/null +++ b/client/src/Pages/v2/Uptime/Configure.tsx @@ -0,0 +1,43 @@ +import { monitorSchema } from "@/Validation/v2/zod"; +import { useGet, usePost } from "@/Hooks/v2/UseApi"; +import { UptimeForm } from "@/Pages/v2/Uptime/UptimeForm"; + +import { useParams } from "react-router"; +import type { ApiResponse } from "@/Hooks/v2/UseApi"; +import humanInterval from "human-interval"; +import { z } from "zod"; + +export const UptimeConfigurePage = () => { + type FormValues = z.infer; + type SubmitValues = Omit & { interval: number | undefined }; + + const { id } = useParams(); + const { response } = useGet("/notification-channels"); + const { response: monitorResponse } = useGet(`/monitors/${id}`); + const monitor = monitorResponse?.data || null; + const notificationOptions = response?.data ?? []; + + 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); + if (result) { + console.log(result); + } else { + console.error(error); + } + }; + return ( + + ); +}; diff --git a/client/src/Pages/v2/Uptime/Details.tsx b/client/src/Pages/v2/Uptime/Details.tsx index c2069c7e4..5515e7b32 100644 --- a/client/src/Pages/v2/Uptime/Details.tsx +++ b/client/src/Pages/v2/Uptime/Details.tsx @@ -14,7 +14,7 @@ import { useTheme } from "@mui/material/styles"; import { useParams } from "react-router"; import { useGet, usePatch, type ApiResponse } from "@/Hooks/v2/UseApi"; import { useState } from "react"; -import { getStatusPalette } from "@/Utils/MonitorUtils"; +import { getStatusPalette } from "@/Utils/v2/MonitorUtils"; import prettyMilliseconds from "pretty-ms"; const UptimeDetailsPage = () => { diff --git a/client/src/Pages/v2/Uptime/UptimeForm.tsx b/client/src/Pages/v2/Uptime/UptimeForm.tsx new file mode 100644 index 000000000..e9636e5dd --- /dev/null +++ b/client/src/Pages/v2/Uptime/UptimeForm.tsx @@ -0,0 +1,247 @@ +import Stack from "@mui/material/Stack"; +import { TextInput } from "@/Components/v2/Inputs/TextInput"; +import { AutoCompleteInput } from "@/Components/v2/Inputs/AutoComplete"; +import { ConfigBox, BasePage } from "@/Components/v2/DesignElements"; +import RadioGroup from "@mui/material/RadioGroup"; +import FormControl from "@mui/material/FormControl"; +import { RadioWithDescription } from "@/Components/v2/Inputs/RadioInput"; +import { Button } from "@/Components/v2/Inputs"; +import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded"; +import { Typography } from "@mui/material"; + +import { useTranslation } from "react-i18next"; +import { monitorSchema } from "@/Validation/v2/zod"; +import { z } from "zod"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm, Controller, useWatch, type SubmitHandler } from "react-hook-form"; +import { useTheme } from "@mui/material/styles"; +import { useInitForm } from "@/Hooks/v2/useInitMonitorForm"; + +type FormValues = z.infer; + +export const UptimeForm = ({ + initialData, + onSubmit, + notificationOptions, + loading, +}: { + initialData?: Partial; + onSubmit: SubmitHandler; + notificationOptions: any[]; + loading: boolean; +}) => { + const { t } = useTranslation(); + const theme = useTheme(); + const { defaults } = useInitForm({ initialData: initialData }); + const { + handleSubmit, + control, + setValue, + formState: { errors }, + } = useForm({ + resolver: zodResolver(monitorSchema) as any, + defaultValues: defaults, + mode: "onChange", + }); + + const selectedType = useWatch({ + control, + name: "type", + }); + const notificationChannels = useWatch({ + control, + name: "notificationChannels", + }); + + return ( + + ( + + + + + + + + )} + /> + } + /> + + ( + + )} + /> + ( + + )} + /> + + } + /> + ( + { + const target = e.target as HTMLInputElement; + field.onChange(target.valueAsNumber); + }} + /> + )} + /> + } + /> + + ( + option.name} + value={notificationOptions.filter((o: any) => + (field.value || []).includes(o._id) + )} + onChange={(_, newValue) => { + field.onChange(newValue.map((o: any) => o._id)); + }} + /> + )} + /> + + {notificationChannels.map((notificationId) => { + const option = notificationOptions.find( + (o: any) => o._id === notificationId + ); + if (!option) return null; + return ( + + {option.name} + { + const updated = notificationChannels.filter( + (id) => id !== notificationId + ); + setValue("notificationChannels", updated); + }} + sx={{ cursor: "pointer" }} + /> + + ); + })} + + + } + /> + ( + + )} + /> + } + /> + + + + + ); +}; diff --git a/client/src/Utils/ApiClient.ts b/client/src/Utils/v2/ApiClient.ts similarity index 100% rename from client/src/Utils/ApiClient.ts rename to client/src/Utils/v2/ApiClient.ts diff --git a/client/src/Utils/DataUtils.ts b/client/src/Utils/v2/DataUtils.ts similarity index 100% rename from client/src/Utils/DataUtils.ts rename to client/src/Utils/v2/DataUtils.ts diff --git a/client/src/Utils/MonitorUtils.ts b/client/src/Utils/v2/MonitorUtils.ts similarity index 94% rename from client/src/Utils/MonitorUtils.ts rename to client/src/Utils/v2/MonitorUtils.ts index dcd202361..c47b7a82e 100644 --- a/client/src/Utils/MonitorUtils.ts +++ b/client/src/Utils/v2/MonitorUtils.ts @@ -1,5 +1,5 @@ import type { MonitorStatus } from "@/Types/Monitor"; -import type { PaletteKey } from "./Theme/v2/theme"; +import type { PaletteKey } from "@/Utils/Theme/v2/theme"; export const getStatusPalette = (status: MonitorStatus): PaletteKey => { const paletteMap: Record = { up: "success", diff --git a/client/src/Utils/TimeUtils.ts b/client/src/Utils/v2/TimeUtils.ts similarity index 100% rename from client/src/Utils/TimeUtils.ts rename to client/src/Utils/v2/TimeUtils.ts