feat(health-checks): enhance health check configuration and UI components
quality / commitlint (push) Skipped
CD / update-wiki (push) Successful in 5s
quality / changes (push) Successful in 5s
quality / docker-check (push) Skipped
quality / web (push) Successful in 54s
quality / api (push) Successful in 52s
CD / quality (push) Successful in 1m55s
CD / publish (push) Successful in 1m54s

- Introduced a new HealthProviderToggle component to manage health check providers (local/cloudflare) in the UI.
- Updated health check configuration to include additional parameters such as retries and consecutive success/failure counts.
- Improved the service edit and group edit sheets to support the new health check provider options.
- Enhanced documentation to clarify the use of ACTIONS_PAT and GITEA_TOKEN for wiki updates.

This commit improves the health check management experience and expands the configuration options for better service monitoring.
This commit is contained in:
Denozordec
2026-08-19 12:39:33 +07:00
parent 3f6f402872
commit b2dbb4ad98
7 changed files with 154 additions and 47 deletions
@@ -17,11 +17,15 @@ import {
SelectValue,
} from '@cfdm/ui/components/select'
import { Switch } from '@cfdm/ui/components/switch'
import { Button } from '@cfdm/ui/components/button'
import { ButtonGroup } from '@cfdm/ui/components/button-group'
import { FieldGroup } from '@cfdm/ui/components/field'
import { Alert, AlertDescription, AlertTitle } from '@/components/reui/alert'
import { cn } from '@cfdm/ui/lib/utils'
export type LbMode = 'round_robin' | 'failover' | 'weighted'
export type HealthCheckType = 'tcp' | 'http'
export type HealthProvider = 'local' | 'cloudflare'
export interface HealthCheckConfig {
enabled: boolean
@@ -32,7 +36,11 @@ export interface HealthCheckConfig {
interval_sec: number
timeout_ms: number
verify_tls: boolean
provider?: 'local' | 'cloudflare'
provider: HealthProvider
method?: string | null
retries?: number
consecutive_fails?: number
consecutive_successes?: number
}
export interface LbAndHealthConfig extends HealthCheckConfig {
@@ -50,6 +58,47 @@ const healthCheckTypes = [
{ value: 'http', label: 'HTTP' },
] as const
const cloudflareTypes = [
{ value: 'tcp', label: 'TCP' },
{ value: 'http', label: 'HTTP' },
] as const
export function HealthProviderToggle({
value,
onChange,
id,
}: {
value: HealthProvider
onChange: (next: HealthProvider) => void
id?: string
}) {
const provider = value || 'local'
return (
<ButtonGroup className="w-full" id={id}>
<Button
type="button"
size="sm"
className="flex-1"
variant={provider === 'local' ? 'secondary' : 'outline'}
aria-pressed={provider === 'local'}
onClick={() => onChange('local')}
>
Local
</Button>
<Button
type="button"
size="sm"
className="flex-1"
variant={provider === 'cloudflare' ? 'secondary' : 'outline'}
aria-pressed={provider === 'cloudflare'}
onClick={() => onChange('cloudflare')}
>
Cloudflare
</Button>
</ButtonGroup>
)
}
interface HealthCheckConfigFieldsProps {
value: LbAndHealthConfig
onChange: (next: LbAndHealthConfig) => void
@@ -120,6 +169,7 @@ export function HealthCheckConfigFields({
className={rowClass}
>
<Select
modal={false}
value={value.lb_mode}
onValueChange={(v) => patch({ lb_mode: (v ?? 'round_robin') as LbMode })}
>
@@ -144,21 +194,26 @@ export function HealthCheckConfigFields({
compact
className={rowClass}
>
<Select
<HealthProviderToggle
id={`${idPrefix}-provider`}
value={value.provider ?? 'local'}
onValueChange={(v) =>
patch({ provider: (v ?? 'local') as 'local' | 'cloudflare' })
onChange={(provider) =>
patch({
provider,
enabled: provider === 'cloudflare' ? true : value.enabled,
})
}
>
<SelectTrigger id={`${idPrefix}-provider`} className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="local">Local</SelectItem>
<SelectItem value="cloudflare">Cloudflare</SelectItem>
</SelectContent>
</Select>
/>
</SettingRow>
{value.provider === 'cloudflare' ? (
<Alert>
<AlertTitle>Cloudflare Health Checks</AlertTitle>
<AlertDescription>
Поля соответствуют официальному API зоны. Если план не позволяет Health
Checks, API вернёт ошибку останется Local. Workers не используются.
</AlertDescription>
</Alert>
) : null}
<SettingRow
title="Health-check"
@@ -188,6 +243,7 @@ export function HealthCheckConfigFields({
<div className="grid grid-cols-2 gap-3">
<FormFieldSimple label="Тип" htmlFor={`${idPrefix}-type`}>
<Select
modal={false}
value={value.type}
onValueChange={(v) => patch({ type: (v ?? 'tcp') as HealthCheckType })}
>
@@ -195,7 +251,10 @@ export function HealthCheckConfigFields({
<SelectValue placeholder="Тип" />
</SelectTrigger>
<SelectContent>
{healthCheckTypes.map((item) => (
{(value.provider === 'cloudflare'
? cloudflareTypes
: healthCheckTypes
).map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
@@ -289,6 +348,57 @@ export function HealthCheckConfigFields({
/>
</FormFieldSimple>
</div>
{value.provider === 'cloudflare' ? (
<div className="grid grid-cols-2 gap-3">
<FormFieldSimple
label="Retries"
htmlFor={`${idPrefix}-retries`}
hint="Cloudflare retries"
>
<CompactNumberField
id={`${idPrefix}-retries`}
value={value.retries ?? 2}
min={0}
max={10}
placeholder="2"
onValueChange={(retries) => patch({ retries: retries ?? 2 })}
/>
</FormFieldSimple>
<FormFieldSimple
label="Successes"
htmlFor={`${idPrefix}-successes`}
hint="consecutive_successes"
>
<CompactNumberField
id={`${idPrefix}-successes`}
value={value.consecutive_successes ?? 2}
min={1}
max={20}
placeholder="2"
onValueChange={(consecutive_successes) =>
patch({ consecutive_successes: consecutive_successes ?? 2 })
}
/>
</FormFieldSimple>
</div>
) : null}
{value.provider === 'cloudflare' && isHttp ? (
<FormFieldSimple label="HTTP method" htmlFor={`${idPrefix}-method`}>
<Select
modal={false}
value={value.method ?? 'GET'}
onValueChange={(v) => patch({ method: v ?? 'GET' })}
>
<SelectTrigger id={`${idPrefix}-method`} className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="GET">GET</SelectItem>
<SelectItem value="HEAD">HEAD</SelectItem>
</SelectContent>
</Select>
</FormFieldSimple>
) : null}
</div>
) : null}
</FieldGroup>
@@ -57,6 +57,7 @@ interface BindingHealthConfig {
interval_sec: number
timeout_ms: number
verify_tls: boolean
provider: 'local' | 'cloudflare'
}
export interface ServiceBindingDraft {
@@ -79,6 +80,7 @@ const defaultHealth: BindingHealthConfig = {
interval_sec: 30,
timeout_ms: 3000,
verify_tls: false,
provider: 'local',
}
interface ServiceEditSheetProps {
@@ -112,6 +114,7 @@ function toBindingDrafts(service: ServiceView): ServiceBindingDraft[] {
interval_sec: binding.health_check_interval_sec,
timeout_ms: binding.health_check_timeout_ms,
verify_tls: binding.health_check_verify_tls ?? false,
provider: 'local',
},
target_ip_weights: binding.target_ip_weights ?? {},
target_ip_priorities: binding.target_ip_priorities ?? {},
@@ -328,6 +331,7 @@ export function ServiceEditSheet({
interval_sec: next.interval_sec,
timeout_ms: next.timeout_ms,
verify_tls: next.verify_tls,
provider: next.provider ?? 'local',
},
}
: item,
@@ -650,6 +654,7 @@ export function ServiceEditSheet({
interval_sec: binding.health.interval_sec,
timeout_ms: binding.health.timeout_ms,
verify_tls: binding.health.verify_tls,
provider: binding.health.provider ?? 'local',
}}
onChange={(next) => handleBindingHealthChange(index, next)}
lbModeLabel="Режим балансировки"
@@ -54,6 +54,7 @@ const defaultLbHealth: LbAndHealthConfig = {
interval_sec: 30,
timeout_ms: 3000,
verify_tls: false,
provider: 'local',
}
export function ServiceGroupEditSheet({
@@ -93,6 +94,7 @@ export function ServiceGroupEditSheet({
interval_sec: group.health_check_interval_sec,
timeout_ms: group.health_check_timeout_ms,
verify_tls: group.health_check_verify_tls,
provider: 'local',
})
} else {
form.reset({ name: '', type: 'custom', domain: null })