"use client"; import { useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { HeadersInput } from "@app/components/HeadersInput"; import { z } from "zod"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Credenza, CredenzaBody, CredenzaClose, CredenzaContent, CredenzaDescription, CredenzaFooter, CredenzaHeader, CredenzaTitle } from "@/components/Credenza"; import { toast } from "@/hooks/useToast"; import { useTranslations } from "next-intl"; type HealthCheckConfig = { hcEnabled: boolean; hcPath: string; hcMethod: string; hcInterval: number; hcTimeout: number; hcStatus: number | null; hcHeaders?: { name: string; value: string }[] | null; hcScheme?: string; hcHostname: string; hcPort: number; hcFollowRedirects: boolean; hcMode: string; hcUnhealthyInterval: number; hcTlsServerName: string; hcHealthyThreshold: number; hcUnhealthyThreshold: number; }; type HealthCheckDialogProps = { open: boolean; setOpen: (val: boolean) => void; targetId: number; targetAddress: string; targetMethod?: string; initialConfig?: Partial; onChanges: (config: HealthCheckConfig) => Promise; }; export default function HealthCheckDialog({ open, setOpen, targetId, targetAddress, targetMethod, initialConfig, onChanges }: HealthCheckDialogProps) { const t = useTranslations(); const healthCheckSchema = z .object({ hcEnabled: z.boolean(), hcPath: z.string().optional(), hcMethod: z.string().optional(), hcInterval: z .int() .positive() .min(5, { message: t("healthCheckIntervalMin") }), hcTimeout: z .int() .positive() .min(1, { message: t("healthCheckTimeoutMin") }), hcStatus: z.int().positive().min(100).optional().nullable(), hcHeaders: z .array(z.object({ name: z.string(), value: z.string() })) .nullable() .optional(), hcScheme: z.string().optional(), hcHostname: z.string(), hcPort: z .string() .min(1, { message: t("healthCheckPortInvalid") }) .refine( (val) => { const port = parseInt(val); return port > 0 && port <= 65535; }, { message: t("healthCheckPortInvalid") } ), hcFollowRedirects: z.boolean(), hcMode: z.string(), hcUnhealthyInterval: z.int().positive().min(5), hcTlsServerName: z.string(), hcHealthyThreshold: z .int() .positive() .min(1, { message: t("healthCheckHealthyThresholdMin") }), hcUnhealthyThreshold: z .int() .positive() .min(1, { message: t("healthCheckUnhealthyThresholdMin") }) }) .superRefine((data, ctx) => { if (data.hcMode !== "tcp") { if (!data.hcPath || data.hcPath.length < 1) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: t("healthCheckPathRequired"), path: ["hcPath"] }); } if (!data.hcMethod || data.hcMethod.length < 1) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: t("healthCheckMethodRequired"), path: ["hcMethod"] }); } } }); const form = useForm>({ resolver: zodResolver(healthCheckSchema), defaultValues: {} }); useEffect(() => { if (!open) return; const getDefaultScheme = () => { if (initialConfig?.hcScheme) { return initialConfig.hcScheme; } if (targetMethod === "https") { return "https"; } return "http"; }; form.reset({ hcEnabled: initialConfig?.hcEnabled, hcPath: initialConfig?.hcPath, hcMethod: initialConfig?.hcMethod, hcInterval: initialConfig?.hcInterval, hcTimeout: initialConfig?.hcTimeout, hcStatus: initialConfig?.hcStatus, hcHeaders: initialConfig?.hcHeaders, hcScheme: getDefaultScheme(), hcHostname: initialConfig?.hcHostname, hcPort: initialConfig?.hcPort ? initialConfig.hcPort.toString() : "", hcFollowRedirects: initialConfig?.hcFollowRedirects, hcMode: initialConfig?.hcMode ?? "http", hcUnhealthyInterval: initialConfig?.hcUnhealthyInterval, hcTlsServerName: initialConfig?.hcTlsServerName ?? "", hcHealthyThreshold: initialConfig?.hcHealthyThreshold ?? 1, hcUnhealthyThreshold: initialConfig?.hcUnhealthyThreshold ?? 1 }); }, [open]); const watchedEnabled = form.watch("hcEnabled"); const watchedMode = form.watch("hcMode"); const handleFieldChange = async (fieldName: string, value: any) => { try { const currentValues = form.getValues(); const updatedValues = { ...currentValues, [fieldName]: value }; const configToSend: HealthCheckConfig = { ...updatedValues, hcPath: updatedValues.hcPath ?? "", hcMethod: updatedValues.hcMethod ?? "", hcPort: parseInt(updatedValues.hcPort), hcStatus: updatedValues.hcStatus || null, hcHealthyThreshold: updatedValues.hcHealthyThreshold, hcUnhealthyThreshold: updatedValues.hcUnhealthyThreshold }; await onChanges(configToSend); } catch (error) { toast({ title: t("healthCheckError"), description: t("healthCheckErrorDescription"), variant: "destructive" }); } }; return ( {t("configureHealthCheck")} {t("configureHealthCheckDescription", { target: targetAddress })}
{/* Enable Health Checks */} (
{t("enableHealthChecks")} {t( "enableHealthChecksDescription" )}
{ field.onChange(value); handleFieldChange( "hcEnabled", value ); }} />
)} /> {watchedEnabled && (
{/* Mode */} ( {t("healthCheckMode")} {t( "healthCheckModeDescription" )} )} /> {/* Connection fields */} {watchedMode === "tcp" ? (
( {t("healthHostname")} { field.onChange( e ); handleFieldChange( "hcHostname", e.target .value ); }} /> )} /> ( {t("healthPort")} { const value = e.target .value; field.onChange( value ); handleFieldChange( "hcPort", value ); }} /> )} />
) : (
( {t("healthScheme")} )} /> ( {t("healthHostname")} { field.onChange( e ); handleFieldChange( "hcHostname", e.target .value ); }} /> )} /> ( {t("healthPort")} { const value = e.target .value; field.onChange( value ); handleFieldChange( "hcPort", value ); }} /> )} /> ( {t("healthCheckPath")} { field.onChange( e ); handleFieldChange( "hcPath", e.target .value ); }} /> )} />
)} {/* HTTP Method */} {watchedMode !== "tcp" && ( ( {t("httpMethod")} )} /> )} {/* Check Interval, Unhealthy Interval, and Timeout */}
( {t( "healthyIntervalSeconds" )} { const value = parseInt( e.target .value ); field.onChange( value ); handleFieldChange( "hcInterval", value ); }} /> )} /> ( {t( "unhealthyIntervalSeconds" )} { const value = parseInt( e.target .value ); field.onChange( value ); handleFieldChange( "hcUnhealthyInterval", value ); }} /> )} /> ( {t("timeoutSeconds")} { const value = parseInt( e.target .value ); field.onChange( value ); handleFieldChange( "hcTimeout", value ); }} /> )} />
{/* Healthy and Unhealthy Thresholds */}
( {t("healthyThreshold")} { const value = parseInt( e.target .value ); field.onChange( value ); handleFieldChange( "hcHealthyThreshold", value ); }} /> {t( "healthyThresholdDescription" )} )} /> ( {t("unhealthyThreshold")} { const value = parseInt( e.target .value ); field.onChange( value ); handleFieldChange( "hcUnhealthyThreshold", value ); }} /> {t( "unhealthyThresholdDescription" )} )} />
{/* HTTP-only fields */} {watchedMode !== "tcp" && ( <> {/* Expected Response Codes */} ( {t( "expectedResponseCodes" )} { const value = parseInt( e .target .value ); field.onChange( value ); handleFieldChange( "hcStatus", value ); }} /> {t( "expectedResponseCodesDescription" )} )} /> {/* TLS Server Name (SNI) */} ( {t("tlsServerName")} { field.onChange( e ); handleFieldChange( "hcTlsServerName", e.target .value ); }} /> {t( "tlsServerNameDescription" )} )} /> {/* Custom Headers */} ( {t("customHeaders")} { field.onChange( value ); handleFieldChange( "hcHeaders", value ); }} rows={4} /> {t( "customHeadersDescription" )} )} /> )}
)}
); }