diff --git a/server/private/lib/alerts/events/healthCheckEvents.ts b/server/private/lib/alerts/events/healthCheckEvents.ts index 38dff916b..9ede25fe6 100644 --- a/server/private/lib/alerts/events/healthCheckEvents.ts +++ b/server/private/lib/alerts/events/healthCheckEvents.ts @@ -32,7 +32,7 @@ import { processAlerts } from "../processAlerts"; export async function fireHealthCheckHealthyAlert( orgId: string, healthCheckId: number, - healthCheckName?: string, + healthCheckName?: string | null, extra?: Record ): Promise { try { @@ -68,7 +68,7 @@ export async function fireHealthCheckHealthyAlert( export async function fireHealthCheckNotHealthyAlert( orgId: string, healthCheckId: number, - healthCheckName?: string, + healthCheckName?: string | null, extra?: Record ): Promise { try { @@ -88,4 +88,4 @@ export async function fireHealthCheckNotHealthyAlert( err ); } -} \ No newline at end of file +} diff --git a/server/routers/newt/buildConfiguration.ts b/server/routers/newt/buildConfiguration.ts index 28b6373e0..46729f11d 100644 --- a/server/routers/newt/buildConfiguration.ts +++ b/server/routers/newt/buildConfiguration.ts @@ -212,6 +212,7 @@ export async function buildTargetConfigurationForNewtClient(siteId: number) { hcUnhealthyInterval: targetHealthCheck.hcUnhealthyInterval, hcTimeout: targetHealthCheck.hcTimeout, hcHeaders: targetHealthCheck.hcHeaders, + hcFollowRedirects: targetHealthCheck.hcFollowRedirects, hcMethod: targetHealthCheck.hcMethod, hcTlsServerName: targetHealthCheck.hcTlsServerName, hcStatus: targetHealthCheck.hcStatus, @@ -284,6 +285,7 @@ export async function buildTargetConfigurationForNewtClient(siteId: number) { hcUnhealthyInterval: target.hcUnhealthyInterval, // in seconds hcTimeout: target.hcTimeout, // in seconds hcHeaders: hcHeadersSend, + hcFollowRedirects: target.hcFollowRedirects, hcMethod: target.hcMethod, hcTlsServerName: target.hcTlsServerName, hcStatus: target.hcStatus, diff --git a/server/routers/newt/targets.ts b/server/routers/newt/targets.ts index cd0814bb8..572c63e98 100644 --- a/server/routers/newt/targets.ts +++ b/server/routers/newt/targets.ts @@ -95,6 +95,7 @@ export async function addTargets( hcUnhealthyInterval: hc.hcUnhealthyInterval, // in seconds hcTimeout: hc.hcTimeout, // in seconds hcHeaders: hcHeadersSend, + hcFollowRedirects: hc.hcFollowRedirects, hcMethod: hc.hcMethod, hcStatus: hcStatus, hcTlsServerName: hc.hcTlsServerName, diff --git a/server/routers/target/handleHealthcheckStatusMessage.ts b/server/routers/target/handleHealthcheckStatusMessage.ts index ef2244c39..87f47c17b 100644 --- a/server/routers/target/handleHealthcheckStatusMessage.ts +++ b/server/routers/target/handleHealthcheckStatusMessage.ts @@ -25,6 +25,7 @@ interface TargetHealthStatus { hcUnhealthyInterval?: number; hcTimeout?: number; hcHeaders?: any; + hcFollowRedirects?: boolean; hcMethod?: string; hcTlsServerName?: string; hcHealthyThreshold?: number; diff --git a/src/app/[orgId]/settings/alerting/page.tsx b/src/app/[orgId]/settings/alerting/page.tsx index aeba881a0..cadc83516 100644 --- a/src/app/[orgId]/settings/alerting/page.tsx +++ b/src/app/[orgId]/settings/alerting/page.tsx @@ -1,6 +1,6 @@ import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import AlertingRulesTable from "@app/components/AlertingRulesTable"; -import StandaloneHealthChecksTable from "@app/components/StandaloneHealthChecksTable"; +import HealthChecksTable from "@app/components/HealthChecksTable"; import { HorizontalTabs, TabItem } from "@app/components/HorizontalTabs"; import { getTranslations } from "next-intl/server"; @@ -27,8 +27,8 @@ export default async function AlertingPage(props: AlertingPageProps) { /> - + ); -} \ No newline at end of file +} diff --git a/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx b/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx index a9128b9d3..8c3d4910c 100644 --- a/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx +++ b/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx @@ -640,10 +640,10 @@ function ProxyResourceTargetsForm({ hcInterval: null, hcTimeout: null, hcHeaders: null, + hcFollowRedirects: null, hcScheme: null, hcHostname: null, hcPort: null, - hcFollowRedirects: null, hcHealth: "unknown", hcStatus: null, hcMode: null, diff --git a/src/components/HealthCheckDialog.tsx b/src/components/HealthCheckDialog.tsx index abdc32d4a..d441cdaf3 100644 --- a/src/components/HealthCheckDialog.tsx +++ b/src/components/HealthCheckDialog.tsx @@ -2,28 +2,11 @@ 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 { Form } from "@/components/ui/form"; +import { HealthCheckFormFields } from "@app/components/HealthCheckFormFields"; import { Credenza, CredenzaBody, @@ -59,7 +42,7 @@ type HealthCheckConfig = { type HealthCheckDialogProps = { open: boolean; setOpen: (val: boolean) => void; - targetId: number; + orgId: string; targetAddress: string; targetMethod?: string; initialConfig?: Partial; @@ -69,7 +52,7 @@ type HealthCheckDialogProps = { export default function HealthCheckDialog({ open, setOpen, - targetId, + orgId, targetAddress, targetMethod, initialConfig, @@ -185,9 +168,6 @@ export default function HealthCheckDialog({ }); }, [open]); - const watchedEnabled = form.watch("hcEnabled"); - const watchedMode = form.watch("hcMode"); - const handleFieldChange = async (fieldName: string, value: any) => { try { const currentValues = form.getValues(); @@ -227,659 +207,10 @@ export default function HealthCheckDialog({
- {/* 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" - )} - - - - )} - /> - - )} -
- )}
@@ -889,4 +220,4 @@ export default function HealthCheckDialog({ ); -} \ No newline at end of file +} diff --git a/src/components/HealthCheckFormFields.tsx b/src/components/HealthCheckFormFields.tsx new file mode 100644 index 000000000..9873f9c5d --- /dev/null +++ b/src/components/HealthCheckFormFields.tsx @@ -0,0 +1,548 @@ +"use client"; + +import { UseFormReturn } from "react-hook-form"; +import { useTranslations } from "next-intl"; +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 { + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage +} from "@/components/ui/form"; + +type HealthCheckFormFieldsProps = { + form: UseFormReturn; + onFieldChange?: (fieldName: string, value: any) => void; + showNameField?: boolean; +}; + +export function HealthCheckFormFields({ + form, + onFieldChange, + showNameField +}: HealthCheckFormFieldsProps) { + const t = useTranslations(); + + const watchedEnabled = form.watch("hcEnabled"); + const watchedMode = form.watch("hcMode"); + + const handleChange = (fieldName: string, value: any, fieldOnChange: (v: any) => void) => { + fieldOnChange(value); + if (onFieldChange) { + onFieldChange(fieldName, value); + } + }; + + return ( + <> + {/* Name */} + {showNameField && ( + ( + + {t("standaloneHcNameLabel")} + + + + + + )} + /> + )} + + {/* Enable Health Checks */} + ( + +
+ {t("enableHealthChecks")} + + {t("enableHealthChecksDescription")} + +
+ + + handleChange("hcEnabled", value, field.onChange) + } + /> + +
+ )} + /> + + {watchedEnabled && ( +
+ {/* Mode */} + ( + + {t("healthCheckMode")} + + + {t("healthCheckModeDescription")} + + + + )} + /> + + {/* Connection fields */} + {watchedMode === "tcp" ? ( +
+ ( + + {t("healthHostname")} + + + handleChange( + "hcHostname", + e.target.value, + (v) => field.onChange(e) + ) + } + /> + + + + )} + /> + ( + + {t("healthPort")} + + { + const value = e.target.value; + handleChange("hcPort", value, field.onChange); + }} + /> + + + + )} + /> +
+ ) : ( +
+ ( + + {t("healthScheme")} + + + + )} + /> + ( + + {t("healthHostname")} + + + handleChange( + "hcHostname", + e.target.value, + (v) => field.onChange(e) + ) + } + /> + + + + )} + /> + ( + + {t("healthPort")} + + { + const value = e.target.value; + handleChange("hcPort", value, field.onChange); + }} + /> + + + + )} + /> + ( + + {t("healthCheckPath")} + + + handleChange( + "hcPath", + e.target.value, + (v) => field.onChange(e) + ) + } + /> + + + + )} + /> +
+ )} + + {/* HTTP Method */} + {watchedMode !== "tcp" && ( + ( + + {t("httpMethod")} + + + + )} + /> + )} + + {/* Check Interval, Unhealthy Interval, and Timeout */} +
+ ( + + {t("healthyIntervalSeconds")} + + { + const value = parseInt(e.target.value); + handleChange("hcInterval", value, field.onChange); + }} + /> + + + + )} + /> + + ( + + {t("unhealthyIntervalSeconds")} + + { + const value = parseInt(e.target.value); + handleChange( + "hcUnhealthyInterval", + value, + field.onChange + ); + }} + /> + + + + )} + /> + + ( + + {t("timeoutSeconds")} + + { + const value = parseInt(e.target.value); + handleChange("hcTimeout", value, field.onChange); + }} + /> + + + + )} + /> +
+ + {/* Healthy and Unhealthy Thresholds */} +
+ ( + + {t("healthyThreshold")} + + { + const value = parseInt(e.target.value); + handleChange( + "hcHealthyThreshold", + value, + field.onChange + ); + }} + /> + + + {t("healthyThresholdDescription")} + + + + )} + /> + + ( + + {t("unhealthyThreshold")} + + { + const value = parseInt(e.target.value); + handleChange( + "hcUnhealthyThreshold", + value, + field.onChange + ); + }} + /> + + + {t("unhealthyThresholdDescription")} + + + + )} + /> +
+ + {/* HTTP-only fields */} + {watchedMode !== "tcp" && ( + <> + {/* Expected Response Code */} + ( + + {t("expectedResponseCodes")} + + { + const val = e.target.value; + const value = val ? parseInt(val) : null; + handleChange("hcStatus", value, field.onChange); + }} + /> + + + {t("expectedResponseCodesDescription")} + + + + )} + /> + + {/* TLS Server Name */} + ( + + {t("tlsServerName")} + + + handleChange( + "hcTlsServerName", + e.target.value, + (v) => field.onChange(e) + ) + } + /> + + + {t("tlsServerNameDescription")} + + + + )} + /> + + {/* Custom Headers */} + ( + + {t("customHeaders")} + + + handleChange( + "hcHeaders", + value, + field.onChange + ) + } + rows={4} + /> + + + {t("customHeadersDescription")} + + + + )} + /> + + {/* Follow Redirects */} + ( + +
+ {t("followRedirects")} + + {t("followRedirectsDescription")} + +
+ + + handleChange( + "hcFollowRedirects", + value, + field.onChange + ) + } + /> + +
+ )} + /> + + )} +
+ )} + + ); +} diff --git a/src/components/StandaloneHealthChecksTable.tsx b/src/components/HealthChecksTable.tsx similarity index 99% rename from src/components/StandaloneHealthChecksTable.tsx rename to src/components/HealthChecksTable.tsx index c839b705a..9d68498b5 100644 --- a/src/components/StandaloneHealthChecksTable.tsx +++ b/src/components/HealthChecksTable.tsx @@ -22,6 +22,7 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; import { ArrowUpDown, MoreHorizontal } from "lucide-react"; import { useTranslations } from "next-intl"; import { useState } from "react"; +import HealthCheckDialog from "./HealthCheckDialog"; type StandaloneHealthChecksTableProps = { orgId: string; @@ -62,7 +63,7 @@ function HealthBadge({ health }: { health: HealthCheckRow["hcHealth"] }) { ); } -export default function StandaloneHealthChecksTable({ +export default function HealthChecksTable({ orgId }: StandaloneHealthChecksTableProps) { const t = useTranslations(); diff --git a/src/components/LicenseKeysDataTable.tsx b/src/components/LicenseKeysDataTable.tsx index 1e39c9225..a3e6f3ce5 100644 --- a/src/components/LicenseKeysDataTable.tsx +++ b/src/components/LicenseKeysDataTable.tsx @@ -1,6 +1,5 @@ "use client"; -import { ColumnDef } from "@tanstack/react-table"; import { ExtendedColumnDef } from "@app/components/ui/data-table"; import { DataTable } from "@app/components/ui/data-table"; import { Button } from "@app/components/ui/button"; diff --git a/src/components/StandaloneHealthCheckCredenza.tsx b/src/components/StandaloneHealthCheckCredenza.tsx index dd5a7ab17..99260707d 100644 --- a/src/components/StandaloneHealthCheckCredenza.tsx +++ b/src/components/StandaloneHealthCheckCredenza.tsx @@ -2,28 +2,11 @@ import { useEffect, useState } 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 { Form } from "@/components/ui/form"; +import { HealthCheckFormFields } from "@app/components/HealthCheckFormFields"; import { Credenza, CredenzaBody, @@ -209,9 +192,6 @@ export default function StandaloneHealthCheckCredenza({ } }, [open]); - const watchedEnabled = form.watch("hcEnabled"); - const watchedMode = form.watch("hcMode"); - const onSubmit = async (values: FormValues) => { setLoading(true); try { @@ -286,553 +266,7 @@ export default function StandaloneHealthCheckCredenza({ onSubmit={form.handleSubmit(onSubmit)} className="space-y-6" > - {/* Name */} - ( - - - {t("standaloneHcNameLabel")} - - - - - - - )} - /> - - {/* Enable Health Check */} - ( - -
- - {t("enableHealthChecks")} - - - {t( - "enableHealthChecksDescription" - )} - -
- - - -
- )} - /> - - {watchedEnabled && ( -
- {/* Mode */} - ( - - - {t("healthCheckMode")} - - - - {t( - "healthCheckModeDescription" - )} - - - - )} - /> - - {/* Connection fields */} - {watchedMode === "tcp" ? ( -
- ( - - - {t("healthHostname")} - - - - - - - )} - /> - ( - - - {t("healthPort")} - - - - - - - )} - /> -
- ) : ( -
- ( - - - {t("healthScheme")} - - - - - )} - /> - ( - - - {t("healthHostname")} - - - - - - - )} - /> - ( - - - {t("healthPort")} - - - - - - - )} - /> - ( - - - {t("healthCheckPath")} - - - - - - - )} - /> -
- )} - - {/* HTTP Method */} - {watchedMode !== "tcp" && ( - ( - - - {t("httpMethod")} - - - - - )} - /> - )} - - {/* Check Interval, Unhealthy Interval, and Timeout */} -
- ( - - - {t( - "healthyIntervalSeconds" - )} - - - - field.onChange( - parseInt( - e.target - .value - ) - ) - } - /> - - - - )} - /> - - ( - - - {t( - "unhealthyIntervalSeconds" - )} - - - - field.onChange( - parseInt( - e.target - .value - ) - ) - } - /> - - - - )} - /> - - ( - - - {t("timeoutSeconds")} - - - - field.onChange( - parseInt( - e.target - .value - ) - ) - } - /> - - - - )} - /> -
- - {/* Healthy and Unhealthy Thresholds */} -
- ( - - - {t("healthyThreshold")} - - - - field.onChange( - parseInt( - e.target - .value - ) - ) - } - /> - - - {t( - "healthyThresholdDescription" - )} - - - - )} - /> - - ( - - - {t("unhealthyThreshold")} - - - - field.onChange( - parseInt( - e.target - .value - ) - ) - } - /> - - - {t( - "unhealthyThresholdDescription" - )} - - - - )} - /> -
- - {/* HTTP-only fields */} - {watchedMode !== "tcp" && ( - <> - {/* Expected Response Code */} - ( - - - {t( - "expectedResponseCodes" - )} - - - { - const val = - e.target - .value; - field.onChange( - val - ? parseInt( - val - ) - : null - ); - }} - /> - - - {t( - "expectedResponseCodesDescription" - )} - - - - )} - /> - - {/* TLS Server Name */} - ( - - - {t("tlsServerName")} - - - - - - {t( - "tlsServerNameDescription" - )} - - - - )} - /> - - {/* Custom Headers */} - ( - - - {t("customHeaders")} - - - - - - {t( - "customHeadersDescription" - )} - - - - )} - /> - - {/* Follow Redirects */} - ( - -
- - {t( - "followRedirects" - )} - - - {t( - "followRedirectsDescription" - )} - -
- - - -
- )} - /> - - )} -
- )} +