diff --git a/apps/api/src/services/health-check-service.ts b/apps/api/src/services/health-check-service.ts index 7a6dddf..ec39c16 100644 --- a/apps/api/src/services/health-check-service.ts +++ b/apps/api/src/services/health-check-service.ts @@ -1,5 +1,5 @@ import { connect } from "node:net"; -import { Agent } from "undici"; +import { Agent, fetch as undiciFetch } from "undici"; import type { Db } from "@cfdm/db"; import { repos } from "@cfdm/db"; import type { HealthCheckTarget, IpHealthState } from "@cfdm/shared"; @@ -67,14 +67,13 @@ async function httpProbe( const path = target.path?.trim() || "/"; const pathWithSlash = path.startsWith("/") ? path : `/${path}`; const port = target.port ?? 80; - const hostHeader = target.hostname || ip; const useTls = port === 443; - // For HTTPS probing against a raw IP, set SNI/Host so TLS handshake and vhost routing work. - const authorityHost = target.hostname || ip; - const url = useTls - ? `https://${authorityHost}${pathWithSlash}` - : `http://${ip}${pathWithSlash}`; - // Custom dispatcher for HTTPS-to-IP: connect to the literal IP, but present hostname via SNI. + // For HTTPS on 443, probe via the hostname (URL host = hostname) so TLS SNI, Host header + // and any edge/vhost protection (e.g. Cloudflare origin 421 on direct-IP) all line up. + // For multi-A records this loses strict per-IP HTTPS granularity — use TCP probe for that. + // For plain HTTP we still hit the literal IP (per-record target). + const urlHost = useTls ? target.hostname || ip : ip; + const url = `${useTls ? "https" : "http"}://${urlHost}${pathWithSlash}`; const dispatcher = useTls && target.hostname ? new Agent({ @@ -85,12 +84,11 @@ async function httpProbe( }) : undefined; try { - const response = await fetch(url, { + const response = await undiciFetch(url, { method: "GET", - headers: { Host: hostHeader }, + headers: { Host: target.hostname || ip }, signal: AbortSignal.timeout(timeoutMs), redirect: "manual", - // @ts-expect-error undici dispatcher option is not in fetch types dispatcher, }); const latency = Date.now() - started; diff --git a/apps/web/src/components/form-field.tsx b/apps/web/src/components/form-field.tsx index 7fb6dc7..061a075 100644 --- a/apps/web/src/components/form-field.tsx +++ b/apps/web/src/components/form-field.tsx @@ -63,6 +63,7 @@ interface FormFieldSimpleProps { label: string htmlFor: string error?: { message?: string } + hint?: string className?: string children: ReactNode } @@ -71,6 +72,7 @@ export function FormFieldSimple({ label, htmlFor, error, + hint, className, children, }: FormFieldSimpleProps) { @@ -78,6 +80,9 @@ export function FormFieldSimple({ {label} {children} + {hint && !error && ( +

{hint}

+ )}
) diff --git a/apps/web/src/components/health-check-config-fields.tsx b/apps/web/src/components/health-check-config-fields.tsx index 5a48d43..4b504ea 100644 --- a/apps/web/src/components/health-check-config-fields.tsx +++ b/apps/web/src/components/health-check-config-fields.tsx @@ -141,10 +141,11 @@ export function HealthCheckConfigFields({ patch({ path: e.target.value === '' ? null : e.target.value })