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
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:
@@ -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 })
|
||||
|
||||
Reference in New Issue
Block a user