mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-16 13:19:52 +00:00
Standardize the healch check form between the two
This commit is contained in:
@@ -3027,5 +3027,7 @@
|
|||||||
"httpDestUpdatedSuccess": "Destination updated successfully",
|
"httpDestUpdatedSuccess": "Destination updated successfully",
|
||||||
"httpDestCreatedSuccess": "Destination created successfully",
|
"httpDestCreatedSuccess": "Destination created successfully",
|
||||||
"httpDestUpdateFailed": "Failed to update destination",
|
"httpDestUpdateFailed": "Failed to update destination",
|
||||||
"httpDestCreateFailed": "Failed to create destination"
|
"httpDestCreateFailed": "Failed to create destination",
|
||||||
|
"followRedirects": "Follow Redirects",
|
||||||
|
"followRedirectsDescription": "Automatically follow HTTP redirects for requests."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import HealthCheckDialog from "@/components/HealthCheckDialog";
|
import HealthCheckCredenza from "@/components/HealthCheckCredenza";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import {
|
import {
|
||||||
@@ -965,10 +965,10 @@ function ProxyResourceTargetsForm({
|
|||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
||||||
{selectedTargetForHealthCheck && (
|
{selectedTargetForHealthCheck && (
|
||||||
<HealthCheckDialog
|
<HealthCheckCredenza
|
||||||
|
mode="autoSave"
|
||||||
open={healthCheckDialogOpen}
|
open={healthCheckDialogOpen}
|
||||||
setOpen={setHealthCheckDialogOpen}
|
setOpen={setHealthCheckDialogOpen}
|
||||||
targetId={selectedTargetForHealthCheck.targetId}
|
|
||||||
targetAddress={`${selectedTargetForHealthCheck.ip}:${selectedTargetForHealthCheck.port}`}
|
targetAddress={`${selectedTargetForHealthCheck.ip}:${selectedTargetForHealthCheck.port}`}
|
||||||
targetMethod={
|
targetMethod={
|
||||||
selectedTargetForHealthCheck.method || undefined
|
selectedTargetForHealthCheck.method || undefined
|
||||||
@@ -993,7 +993,7 @@ function ProxyResourceTargetsForm({
|
|||||||
selectedTargetForHealthCheck.hcPort ||
|
selectedTargetForHealthCheck.hcPort ||
|
||||||
selectedTargetForHealthCheck.port,
|
selectedTargetForHealthCheck.port,
|
||||||
hcFollowRedirects:
|
hcFollowRedirects:
|
||||||
selectedTargetForHealthCheck.hcFollowRedirects ||
|
selectedTargetForHealthCheck.hcFollowRedirects ??
|
||||||
true,
|
true,
|
||||||
hcStatus:
|
hcStatus:
|
||||||
selectedTargetForHealthCheck.hcStatus || undefined,
|
selectedTargetForHealthCheck.hcStatus || undefined,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import CopyTextBox from "@app/components/CopyTextBox";
|
import CopyTextBox from "@app/components/CopyTextBox";
|
||||||
import DomainPicker from "@app/components/DomainPicker";
|
import DomainPicker from "@app/components/DomainPicker";
|
||||||
import HealthCheckDialog from "@app/components/HealthCheckDialog";
|
import HealthCheckCredenza from "@app/components/HealthCheckCredenza";
|
||||||
import {
|
import {
|
||||||
PathMatchDisplay,
|
PathMatchDisplay,
|
||||||
PathMatchModal,
|
PathMatchModal,
|
||||||
@@ -1460,12 +1460,10 @@ export default function Page() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{selectedTargetForHealthCheck && (
|
{selectedTargetForHealthCheck && (
|
||||||
<HealthCheckDialog
|
<HealthCheckCredenza
|
||||||
|
mode="autoSave"
|
||||||
open={healthCheckDialogOpen}
|
open={healthCheckDialogOpen}
|
||||||
setOpen={setHealthCheckDialogOpen}
|
setOpen={setHealthCheckDialogOpen}
|
||||||
targetId={
|
|
||||||
selectedTargetForHealthCheck.targetId
|
|
||||||
}
|
|
||||||
targetAddress={`${selectedTargetForHealthCheck.ip}:${selectedTargetForHealthCheck.port}`}
|
targetAddress={`${selectedTargetForHealthCheck.ip}:${selectedTargetForHealthCheck.port}`}
|
||||||
targetMethod={
|
targetMethod={
|
||||||
selectedTargetForHealthCheck.method ||
|
selectedTargetForHealthCheck.method ||
|
||||||
@@ -1500,7 +1498,7 @@ export default function Page() {
|
|||||||
selectedTargetForHealthCheck.hcPort ||
|
selectedTargetForHealthCheck.hcPort ||
|
||||||
selectedTargetForHealthCheck.port,
|
selectedTargetForHealthCheck.port,
|
||||||
hcFollowRedirects:
|
hcFollowRedirects:
|
||||||
selectedTargetForHealthCheck.hcFollowRedirects ||
|
selectedTargetForHealthCheck.hcFollowRedirects ??
|
||||||
true,
|
true,
|
||||||
hcStatus:
|
hcStatus:
|
||||||
selectedTargetForHealthCheck.hcStatus ||
|
selectedTargetForHealthCheck.hcStatus ||
|
||||||
|
|||||||
417
src/components/HealthCheckCredenza.tsx
Normal file
417
src/components/HealthCheckCredenza.tsx
Normal file
@@ -0,0 +1,417 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { Form } from "@/components/ui/form";
|
||||||
|
import { HealthCheckFormFields } from "@app/components/HealthCheckFormFields";
|
||||||
|
import {
|
||||||
|
Credenza,
|
||||||
|
CredenzaBody,
|
||||||
|
CredenzaClose,
|
||||||
|
CredenzaContent,
|
||||||
|
CredenzaDescription,
|
||||||
|
CredenzaFooter,
|
||||||
|
CredenzaHeader,
|
||||||
|
CredenzaTitle
|
||||||
|
} from "@/components/Credenza";
|
||||||
|
import { toast } from "@app/hooks/useToast";
|
||||||
|
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||||
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
export 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HealthCheckRow = {
|
||||||
|
targetHealthCheckId: number;
|
||||||
|
name: string;
|
||||||
|
hcEnabled: boolean;
|
||||||
|
hcHealth: "unknown" | "healthy" | "unhealthy";
|
||||||
|
hcMode: string | null;
|
||||||
|
hcHostname: string | null;
|
||||||
|
hcPort: number | null;
|
||||||
|
hcPath: string | null;
|
||||||
|
hcScheme: string | null;
|
||||||
|
hcMethod: string | null;
|
||||||
|
hcInterval: number | null;
|
||||||
|
hcUnhealthyInterval: number | null;
|
||||||
|
hcTimeout: number | null;
|
||||||
|
hcHeaders: string | null;
|
||||||
|
hcFollowRedirects: boolean | null;
|
||||||
|
hcStatus: number | null;
|
||||||
|
hcTlsServerName: string | null;
|
||||||
|
hcHealthyThreshold: number | null;
|
||||||
|
hcUnhealthyThreshold: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HealthCheckCredenzaProps =
|
||||||
|
| {
|
||||||
|
mode: "autoSave";
|
||||||
|
open: boolean;
|
||||||
|
setOpen: (v: boolean) => void;
|
||||||
|
orgId?: string;
|
||||||
|
targetAddress: string;
|
||||||
|
targetMethod?: string;
|
||||||
|
initialConfig?: Partial<HealthCheckConfig>;
|
||||||
|
onChanges: (config: HealthCheckConfig) => Promise<void>;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
mode: "submit";
|
||||||
|
open: boolean;
|
||||||
|
setOpen: (v: boolean) => void;
|
||||||
|
orgId: string;
|
||||||
|
initialValues?: HealthCheckRow | null;
|
||||||
|
onSaved: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_VALUES = {
|
||||||
|
name: "",
|
||||||
|
hcEnabled: true,
|
||||||
|
hcMode: "http",
|
||||||
|
hcScheme: "https",
|
||||||
|
hcMethod: "GET",
|
||||||
|
hcHostname: "",
|
||||||
|
hcPort: "",
|
||||||
|
hcPath: "/",
|
||||||
|
hcInterval: 30,
|
||||||
|
hcUnhealthyInterval: 30,
|
||||||
|
hcTimeout: 5,
|
||||||
|
hcHealthyThreshold: 1,
|
||||||
|
hcUnhealthyThreshold: 1,
|
||||||
|
hcFollowRedirects: true,
|
||||||
|
hcTlsServerName: "",
|
||||||
|
hcStatus: null as number | null,
|
||||||
|
hcHeaders: [] as { name: string; value: string }[]
|
||||||
|
};
|
||||||
|
|
||||||
|
export function HealthCheckCredenza(props: HealthCheckCredenzaProps) {
|
||||||
|
const { mode, open, setOpen, orgId } = props;
|
||||||
|
|
||||||
|
const t = useTranslations();
|
||||||
|
const api = createApiClient(useEnvContext());
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const healthCheckSchema = z
|
||||||
|
.object({
|
||||||
|
...(mode === "submit"
|
||||||
|
? {
|
||||||
|
name: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: t("standaloneHcNameLabel") })
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
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"]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormValues = z.infer<typeof healthCheckSchema>;
|
||||||
|
|
||||||
|
const form = useForm<FormValues>({
|
||||||
|
resolver: zodResolver(healthCheckSchema),
|
||||||
|
defaultValues: mode === "submit" ? DEFAULT_VALUES : {}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return;
|
||||||
|
|
||||||
|
if (mode === "autoSave") {
|
||||||
|
const { initialConfig, targetMethod } = props;
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const { initialValues } = props;
|
||||||
|
|
||||||
|
if (initialValues) {
|
||||||
|
let parsedHeaders: { name: string; value: string }[] = [];
|
||||||
|
if (initialValues.hcHeaders) {
|
||||||
|
try {
|
||||||
|
parsedHeaders = JSON.parse(initialValues.hcHeaders);
|
||||||
|
} catch {
|
||||||
|
parsedHeaders = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form.reset({
|
||||||
|
name: initialValues.name,
|
||||||
|
hcEnabled: initialValues.hcEnabled,
|
||||||
|
hcMode: initialValues.hcMode ?? "http",
|
||||||
|
hcScheme: initialValues.hcScheme ?? "https",
|
||||||
|
hcMethod: initialValues.hcMethod ?? "GET",
|
||||||
|
hcHostname: initialValues.hcHostname ?? "",
|
||||||
|
hcPort: initialValues.hcPort
|
||||||
|
? initialValues.hcPort.toString()
|
||||||
|
: "",
|
||||||
|
hcPath: initialValues.hcPath ?? "/",
|
||||||
|
hcInterval: initialValues.hcInterval ?? 30,
|
||||||
|
hcUnhealthyInterval:
|
||||||
|
initialValues.hcUnhealthyInterval ?? 30,
|
||||||
|
hcTimeout: initialValues.hcTimeout ?? 5,
|
||||||
|
hcHealthyThreshold:
|
||||||
|
initialValues.hcHealthyThreshold ?? 1,
|
||||||
|
hcUnhealthyThreshold:
|
||||||
|
initialValues.hcUnhealthyThreshold ?? 1,
|
||||||
|
hcFollowRedirects:
|
||||||
|
initialValues.hcFollowRedirects ?? true,
|
||||||
|
hcTlsServerName: initialValues.hcTlsServerName ?? "",
|
||||||
|
hcStatus: initialValues.hcStatus ?? null,
|
||||||
|
hcHeaders: parsedHeaders
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
form.reset(DEFAULT_VALUES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
const handleFieldChange = async (fieldName: string, value: any) => {
|
||||||
|
if (mode !== "autoSave") return;
|
||||||
|
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 props.onChanges(configToSend);
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: t("healthCheckError"),
|
||||||
|
description: t("healthCheckErrorDescription"),
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (values: FormValues) => {
|
||||||
|
if (mode !== "submit") return;
|
||||||
|
const { initialValues, onSaved } = props;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
name: (values as any).name,
|
||||||
|
hcEnabled: values.hcEnabled,
|
||||||
|
hcMode: values.hcMode,
|
||||||
|
hcScheme: values.hcScheme,
|
||||||
|
hcMethod: values.hcMethod,
|
||||||
|
hcHostname: values.hcHostname,
|
||||||
|
hcPort: parseInt(values.hcPort),
|
||||||
|
hcPath: values.hcPath ?? "",
|
||||||
|
hcInterval: values.hcInterval,
|
||||||
|
hcUnhealthyInterval: values.hcUnhealthyInterval,
|
||||||
|
hcTimeout: values.hcTimeout,
|
||||||
|
hcHealthyThreshold: values.hcHealthyThreshold,
|
||||||
|
hcUnhealthyThreshold: values.hcUnhealthyThreshold,
|
||||||
|
hcFollowRedirects: values.hcFollowRedirects,
|
||||||
|
hcTlsServerName: values.hcTlsServerName,
|
||||||
|
hcStatus: values.hcStatus || null,
|
||||||
|
hcHeaders:
|
||||||
|
values.hcHeaders && values.hcHeaders.length > 0
|
||||||
|
? JSON.stringify(values.hcHeaders)
|
||||||
|
: null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (initialValues) {
|
||||||
|
await api.post(
|
||||||
|
`/org/${orgId}/health-check/${initialValues.targetHealthCheckId}`,
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await api.put(`/org/${orgId}/health-check`, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
toast({ title: t("standaloneHcSaved") });
|
||||||
|
onSaved();
|
||||||
|
setOpen(false);
|
||||||
|
} catch (e) {
|
||||||
|
toast({
|
||||||
|
title: t("error"),
|
||||||
|
description: formatAxiosError(e),
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isEditing = mode === "submit" && !!(props as any).initialValues;
|
||||||
|
|
||||||
|
const title =
|
||||||
|
mode === "autoSave"
|
||||||
|
? t("configureHealthCheck")
|
||||||
|
: isEditing
|
||||||
|
? t("standaloneHcEditTitle")
|
||||||
|
: t("standaloneHcCreateTitle");
|
||||||
|
|
||||||
|
const description =
|
||||||
|
mode === "autoSave"
|
||||||
|
? t("configureHealthCheckDescription", {
|
||||||
|
target: (props as any).targetAddress
|
||||||
|
})
|
||||||
|
: t("standaloneHcDescription");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Credenza open={open} onOpenChange={setOpen}>
|
||||||
|
<CredenzaContent className="max-w-2xl">
|
||||||
|
<CredenzaHeader>
|
||||||
|
<CredenzaTitle>{title}</CredenzaTitle>
|
||||||
|
<CredenzaDescription>{description}</CredenzaDescription>
|
||||||
|
</CredenzaHeader>
|
||||||
|
<CredenzaBody>
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
id="hc-credenza-form"
|
||||||
|
onSubmit={
|
||||||
|
mode === "submit"
|
||||||
|
? form.handleSubmit(onSubmit)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
className="space-y-6"
|
||||||
|
>
|
||||||
|
<HealthCheckFormFields
|
||||||
|
form={form}
|
||||||
|
showNameField={mode === "submit"}
|
||||||
|
hideEnabledField={mode === "submit"}
|
||||||
|
onFieldChange={
|
||||||
|
mode === "autoSave"
|
||||||
|
? handleFieldChange
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</CredenzaBody>
|
||||||
|
<CredenzaFooter>
|
||||||
|
{mode === "autoSave" ? (
|
||||||
|
<Button onClick={() => setOpen(false)}>
|
||||||
|
{t("done")}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<CredenzaClose asChild>
|
||||||
|
<Button variant="outline" type="button">
|
||||||
|
{t("cancel")}
|
||||||
|
</Button>
|
||||||
|
</CredenzaClose>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
form="hc-credenza-form"
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{t("save")}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</CredenzaFooter>
|
||||||
|
</CredenzaContent>
|
||||||
|
</Credenza>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HealthCheckCredenza;
|
||||||
@@ -1,223 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { Form } from "@/components/ui/form";
|
|
||||||
import { HealthCheckFormFields } from "@app/components/HealthCheckFormFields";
|
|
||||||
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;
|
|
||||||
orgId: string;
|
|
||||||
targetAddress: string;
|
|
||||||
targetMethod?: string;
|
|
||||||
initialConfig?: Partial<HealthCheckConfig>;
|
|
||||||
onChanges: (config: HealthCheckConfig) => Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function HealthCheckDialog({
|
|
||||||
open,
|
|
||||||
setOpen,
|
|
||||||
orgId,
|
|
||||||
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<z.infer<typeof healthCheckSchema>>({
|
|
||||||
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 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 (
|
|
||||||
<Credenza open={open} onOpenChange={setOpen}>
|
|
||||||
<CredenzaContent className="max-w-2xl">
|
|
||||||
<CredenzaHeader>
|
|
||||||
<CredenzaTitle>{t("configureHealthCheck")}</CredenzaTitle>
|
|
||||||
<CredenzaDescription>
|
|
||||||
{t("configureHealthCheckDescription", {
|
|
||||||
target: targetAddress
|
|
||||||
})}
|
|
||||||
</CredenzaDescription>
|
|
||||||
</CredenzaHeader>
|
|
||||||
<CredenzaBody>
|
|
||||||
<Form {...form}>
|
|
||||||
<form className="space-y-6">
|
|
||||||
<HealthCheckFormFields
|
|
||||||
form={form}
|
|
||||||
onFieldChange={handleFieldChange}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</CredenzaBody>
|
|
||||||
<CredenzaFooter>
|
|
||||||
<Button onClick={() => setOpen(false)}>{t("done")}</Button>
|
|
||||||
</CredenzaFooter>
|
|
||||||
</CredenzaContent>
|
|
||||||
</Credenza>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -25,16 +25,19 @@ type HealthCheckFormFieldsProps = {
|
|||||||
form: UseFormReturn<any>;
|
form: UseFormReturn<any>;
|
||||||
onFieldChange?: (fieldName: string, value: any) => void;
|
onFieldChange?: (fieldName: string, value: any) => void;
|
||||||
showNameField?: boolean;
|
showNameField?: boolean;
|
||||||
|
hideEnabledField?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function HealthCheckFormFields({
|
export function HealthCheckFormFields({
|
||||||
form,
|
form,
|
||||||
onFieldChange,
|
onFieldChange,
|
||||||
showNameField
|
showNameField,
|
||||||
|
hideEnabledField
|
||||||
}: HealthCheckFormFieldsProps) {
|
}: HealthCheckFormFieldsProps) {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|
||||||
const watchedEnabled = form.watch("hcEnabled");
|
const watchedEnabled = form.watch("hcEnabled");
|
||||||
|
const showFields = hideEnabledField || watchedEnabled;
|
||||||
const watchedMode = form.watch("hcMode");
|
const watchedMode = form.watch("hcMode");
|
||||||
|
|
||||||
const handleChange = (fieldName: string, value: any, fieldOnChange: (v: any) => void) => {
|
const handleChange = (fieldName: string, value: any, fieldOnChange: (v: any) => void) => {
|
||||||
@@ -67,30 +70,32 @@ export function HealthCheckFormFields({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Enable Health Checks */}
|
{/* Enable Health Checks */}
|
||||||
<FormField
|
{!hideEnabledField && (
|
||||||
control={form.control}
|
<FormField
|
||||||
name="hcEnabled"
|
control={form.control}
|
||||||
render={({ field }) => (
|
name="hcEnabled"
|
||||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
render={({ field }) => (
|
||||||
<div className="space-y-0.5">
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
||||||
<FormLabel>{t("enableHealthChecks")}</FormLabel>
|
<div className="space-y-0.5">
|
||||||
<FormDescription>
|
<FormLabel>{t("enableHealthChecks")}</FormLabel>
|
||||||
{t("enableHealthChecksDescription")}
|
<FormDescription>
|
||||||
</FormDescription>
|
{t("enableHealthChecksDescription")}
|
||||||
</div>
|
</FormDescription>
|
||||||
<FormControl>
|
</div>
|
||||||
<Switch
|
<FormControl>
|
||||||
checked={field.value}
|
<Switch
|
||||||
onCheckedChange={(value) =>
|
checked={field.value}
|
||||||
handleChange("hcEnabled", value, field.onChange)
|
onCheckedChange={(value) =>
|
||||||
}
|
handleChange("hcEnabled", value, field.onChange)
|
||||||
/>
|
}
|
||||||
</FormControl>
|
/>
|
||||||
</FormItem>
|
</FormControl>
|
||||||
)}
|
</FormItem>
|
||||||
/>
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{watchedEnabled && (
|
{showFields && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Mode */}
|
{/* Mode */}
|
||||||
<FormField
|
<FormField
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
|
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
|
||||||
import StandaloneHealthCheckCredenza, {
|
import HealthCheckCredenza, {
|
||||||
HealthCheckRow
|
HealthCheckRow
|
||||||
} from "@app/components/StandaloneHealthCheckCredenza";
|
} from "@app/components/HealthCheckCredenza";
|
||||||
import { Badge } from "@app/components/ui/badge";
|
import { Badge } from "@app/components/ui/badge";
|
||||||
import { Button } from "@app/components/ui/button";
|
import { Button } from "@app/components/ui/button";
|
||||||
import { DataTable, ExtendedColumnDef } from "@app/components/ui/data-table";
|
import { DataTable, ExtendedColumnDef } from "@app/components/ui/data-table";
|
||||||
@@ -22,7 +22,7 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|||||||
import { ArrowUpDown, MoreHorizontal } from "lucide-react";
|
import { ArrowUpDown, MoreHorizontal } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import HealthCheckDialog from "./HealthCheckDialog";
|
|
||||||
|
|
||||||
type StandaloneHealthChecksTableProps = {
|
type StandaloneHealthChecksTableProps = {
|
||||||
orgId: string;
|
orgId: string;
|
||||||
@@ -266,7 +266,8 @@ export default function HealthChecksTable({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<StandaloneHealthCheckCredenza
|
<HealthCheckCredenza
|
||||||
|
mode="submit"
|
||||||
open={credenzaOpen}
|
open={credenzaOpen}
|
||||||
setOpen={(val) => {
|
setOpen={(val) => {
|
||||||
setCredenzaOpen(val);
|
setCredenzaOpen(val);
|
||||||
|
|||||||
@@ -1,290 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { Form } from "@/components/ui/form";
|
|
||||||
import { HealthCheckFormFields } from "@app/components/HealthCheckFormFields";
|
|
||||||
import {
|
|
||||||
Credenza,
|
|
||||||
CredenzaBody,
|
|
||||||
CredenzaClose,
|
|
||||||
CredenzaContent,
|
|
||||||
CredenzaDescription,
|
|
||||||
CredenzaFooter,
|
|
||||||
CredenzaHeader,
|
|
||||||
CredenzaTitle
|
|
||||||
} from "@/components/Credenza";
|
|
||||||
import { toast } from "@app/hooks/useToast";
|
|
||||||
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
|
||||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
|
|
||||||
export type HealthCheckRow = {
|
|
||||||
targetHealthCheckId: number;
|
|
||||||
name: string;
|
|
||||||
hcEnabled: boolean;
|
|
||||||
hcHealth: "unknown" | "healthy" | "unhealthy";
|
|
||||||
hcMode: string | null;
|
|
||||||
hcHostname: string | null;
|
|
||||||
hcPort: number | null;
|
|
||||||
hcPath: string | null;
|
|
||||||
hcScheme: string | null;
|
|
||||||
hcMethod: string | null;
|
|
||||||
hcInterval: number | null;
|
|
||||||
hcUnhealthyInterval: number | null;
|
|
||||||
hcTimeout: number | null;
|
|
||||||
hcHeaders: string | null;
|
|
||||||
hcFollowRedirects: boolean | null;
|
|
||||||
hcStatus: number | null;
|
|
||||||
hcTlsServerName: string | null;
|
|
||||||
hcHealthyThreshold: number | null;
|
|
||||||
hcUnhealthyThreshold: number | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StandaloneHealthCheckCredenzaProps = {
|
|
||||||
open: boolean;
|
|
||||||
setOpen: (v: boolean) => void;
|
|
||||||
orgId: string;
|
|
||||||
initialValues?: HealthCheckRow | null;
|
|
||||||
onSaved: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const DEFAULT_VALUES = {
|
|
||||||
name: "",
|
|
||||||
hcEnabled: true,
|
|
||||||
hcMode: "http",
|
|
||||||
hcScheme: "https",
|
|
||||||
hcMethod: "GET",
|
|
||||||
hcHostname: "",
|
|
||||||
hcPort: "",
|
|
||||||
hcPath: "/",
|
|
||||||
hcInterval: 30,
|
|
||||||
hcUnhealthyInterval: 30,
|
|
||||||
hcTimeout: 5,
|
|
||||||
hcHealthyThreshold: 1,
|
|
||||||
hcUnhealthyThreshold: 1,
|
|
||||||
hcFollowRedirects: true,
|
|
||||||
hcTlsServerName: "",
|
|
||||||
hcStatus: null as number | null,
|
|
||||||
hcHeaders: [] as { name: string; value: string }[]
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function StandaloneHealthCheckCredenza({
|
|
||||||
open,
|
|
||||||
setOpen,
|
|
||||||
orgId,
|
|
||||||
initialValues,
|
|
||||||
onSaved
|
|
||||||
}: StandaloneHealthCheckCredenzaProps) {
|
|
||||||
const t = useTranslations();
|
|
||||||
const api = createApiClient(useEnvContext());
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
const healthCheckSchema = z
|
|
||||||
.object({
|
|
||||||
name: z.string().min(1, { message: t("standaloneHcNameLabel") }),
|
|
||||||
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"]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
type FormValues = z.infer<typeof healthCheckSchema>;
|
|
||||||
|
|
||||||
const form = useForm<FormValues>({
|
|
||||||
resolver: zodResolver(healthCheckSchema),
|
|
||||||
defaultValues: DEFAULT_VALUES
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!open) return;
|
|
||||||
|
|
||||||
if (initialValues) {
|
|
||||||
let parsedHeaders: { name: string; value: string }[] = [];
|
|
||||||
if (initialValues.hcHeaders) {
|
|
||||||
try {
|
|
||||||
parsedHeaders = JSON.parse(initialValues.hcHeaders);
|
|
||||||
} catch {
|
|
||||||
parsedHeaders = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
form.reset({
|
|
||||||
name: initialValues.name,
|
|
||||||
hcEnabled: initialValues.hcEnabled,
|
|
||||||
hcMode: initialValues.hcMode ?? "http",
|
|
||||||
hcScheme: initialValues.hcScheme ?? "https",
|
|
||||||
hcMethod: initialValues.hcMethod ?? "GET",
|
|
||||||
hcHostname: initialValues.hcHostname ?? "",
|
|
||||||
hcPort: initialValues.hcPort
|
|
||||||
? initialValues.hcPort.toString()
|
|
||||||
: "",
|
|
||||||
hcPath: initialValues.hcPath ?? "/",
|
|
||||||
hcInterval: initialValues.hcInterval ?? 30,
|
|
||||||
hcUnhealthyInterval: initialValues.hcUnhealthyInterval ?? 30,
|
|
||||||
hcTimeout: initialValues.hcTimeout ?? 5,
|
|
||||||
hcHealthyThreshold: initialValues.hcHealthyThreshold ?? 1,
|
|
||||||
hcUnhealthyThreshold: initialValues.hcUnhealthyThreshold ?? 1,
|
|
||||||
hcFollowRedirects: initialValues.hcFollowRedirects ?? true,
|
|
||||||
hcTlsServerName: initialValues.hcTlsServerName ?? "",
|
|
||||||
hcStatus: initialValues.hcStatus ?? null,
|
|
||||||
hcHeaders: parsedHeaders
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
form.reset(DEFAULT_VALUES);
|
|
||||||
}
|
|
||||||
}, [open]);
|
|
||||||
|
|
||||||
const onSubmit = async (values: FormValues) => {
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const payload = {
|
|
||||||
name: values.name,
|
|
||||||
hcEnabled: values.hcEnabled,
|
|
||||||
hcMode: values.hcMode,
|
|
||||||
hcScheme: values.hcScheme,
|
|
||||||
hcMethod: values.hcMethod,
|
|
||||||
hcHostname: values.hcHostname,
|
|
||||||
hcPort: parseInt(values.hcPort),
|
|
||||||
hcPath: values.hcPath ?? "",
|
|
||||||
hcInterval: values.hcInterval,
|
|
||||||
hcUnhealthyInterval: values.hcUnhealthyInterval,
|
|
||||||
hcTimeout: values.hcTimeout,
|
|
||||||
hcHealthyThreshold: values.hcHealthyThreshold,
|
|
||||||
hcUnhealthyThreshold: values.hcUnhealthyThreshold,
|
|
||||||
hcFollowRedirects: values.hcFollowRedirects,
|
|
||||||
hcTlsServerName: values.hcTlsServerName,
|
|
||||||
hcStatus: values.hcStatus || null,
|
|
||||||
hcHeaders:
|
|
||||||
values.hcHeaders && values.hcHeaders.length > 0
|
|
||||||
? JSON.stringify(values.hcHeaders)
|
|
||||||
: null
|
|
||||||
};
|
|
||||||
|
|
||||||
if (initialValues) {
|
|
||||||
await api.post(
|
|
||||||
`/org/${orgId}/health-check/${initialValues.targetHealthCheckId}`,
|
|
||||||
payload
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
await api.put(
|
|
||||||
`/org/${orgId}/health-check`,
|
|
||||||
payload
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toast({ title: t("standaloneHcSaved") });
|
|
||||||
onSaved();
|
|
||||||
setOpen(false);
|
|
||||||
} catch (e) {
|
|
||||||
toast({
|
|
||||||
title: t("error"),
|
|
||||||
description: formatAxiosError(e),
|
|
||||||
variant: "destructive"
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const isEditing = !!initialValues;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Credenza open={open} onOpenChange={setOpen}>
|
|
||||||
<CredenzaContent className="max-w-2xl">
|
|
||||||
<CredenzaHeader>
|
|
||||||
<CredenzaTitle>
|
|
||||||
{isEditing
|
|
||||||
? t("standaloneHcEditTitle")
|
|
||||||
: t("standaloneHcCreateTitle")}
|
|
||||||
</CredenzaTitle>
|
|
||||||
<CredenzaDescription>
|
|
||||||
{t("standaloneHcDescription")}
|
|
||||||
</CredenzaDescription>
|
|
||||||
</CredenzaHeader>
|
|
||||||
<CredenzaBody>
|
|
||||||
<Form {...form}>
|
|
||||||
<form
|
|
||||||
id="standalone-hc-form"
|
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
|
||||||
className="space-y-6"
|
|
||||||
>
|
|
||||||
<HealthCheckFormFields form={form} showNameField />
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</CredenzaBody>
|
|
||||||
<CredenzaFooter>
|
|
||||||
<CredenzaClose asChild>
|
|
||||||
<Button variant="outline" type="button">
|
|
||||||
{t("cancel")}
|
|
||||||
</Button>
|
|
||||||
</CredenzaClose>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
form="standalone-hc-form"
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
{t("save")}
|
|
||||||
</Button>
|
|
||||||
</CredenzaFooter>
|
|
||||||
</CredenzaContent>
|
|
||||||
</Credenza>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user