Files
cloudflare-domain-manager/apps/web/src/components/change-ip-sheet.tsx
T
Denozordec 3f6f402872
quality / commitlint (push) Skipped
CD / update-wiki (push) Failing after 8s
quality / changes (push) Successful in 5s
quality / docker-check (push) Skipped
quality / web (push) Successful in 50s
quality / api (push) Successful in 41s
CD / quality (push) Successful in 1m40s
CD / publish (push) Successful in 1m33s
feat(health-checks): enhance health check functionality and add new routes
- Introduced origin health check routes and integrated them into the application.
- Updated health check configuration to include success recovery thresholds.
- Expanded error handling with new error codes for health check failures.
- Added new service routes for managing health checks, including creation and listing.
- Improved health check service logic to track consecutive successes and failures.

This commit enhances the health check capabilities, providing better monitoring and management of service health.
2026-08-19 12:26:12 +07:00

153 lines
4.7 KiB
TypeScript

import { useEffect, useMemo, useState } from 'react'
import { useForm } from 'react-hook-form'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { FormSheet } from '@/components/form-sheet'
import { FormFieldSimple } from '@/components/form-field'
import { LoadingButton } from '@/components/loading-button'
import { Button } from '@cfdm/ui/components/button'
import { Input } from '@cfdm/ui/components/input'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@cfdm/ui/components/select'
import { Alert, AlertDescription, AlertTitle } from '@/components/reui/alert'
import { changeBindingIp, serviceNodesQueryOptions } from '@/queries'
interface ChangeIpSheetProps {
open: boolean
onOpenChange: (open: boolean) => void
bindingId: number | null
serviceId?: number | null
currentIp?: string | null
}
interface FormValues {
from_ip: string
to_ip: string
node_id: string
}
export function ChangeIpSheet({
open,
onOpenChange,
bindingId,
serviceId,
currentIp,
}: ChangeIpSheetProps) {
const queryClient = useQueryClient()
const form = useForm<FormValues>({
defaultValues: { from_ip: currentIp ?? '', to_ip: '', node_id: '' },
})
const [preview, setPreview] = useState<string | null>(null)
const nodesQuery = useQuery({
...serviceNodesQueryOptions(serviceId ?? 0),
enabled: open && serviceId != null,
})
useEffect(() => {
if (open) {
form.reset({ from_ip: currentIp ?? '', to_ip: '', node_id: '' })
setPreview(null)
}
}, [open, currentIp, form])
const fromIp = form.watch('from_ip')
const toIp = form.watch('to_ip')
const nodeId = form.watch('node_id')
const nodes = (nodesQuery.data ?? []) as Array<{ id: number; address: string }>
const previewText = useMemo(() => {
const next = nodeId
? nodes.find((n) => String(n.id) === nodeId)?.address
: toIp
if (!fromIp || !next) return null
return `${fromIp}${next}`
}, [fromIp, toIp, nodeId, nodes])
const mutate = useMutation({
mutationFn: async () => {
if (bindingId == null) throw new Error('нет привязки')
const selectedNode = nodeId ? Number(nodeId) : undefined
return changeBindingIp(bindingId, {
from_ip: fromIp || undefined,
to_ip: selectedNode ? undefined : toIp || undefined,
node_id: selectedNode,
dry_run: false,
})
},
onSuccess: async (result) => {
setPreview(result.message)
toast.success(result.message)
await queryClient.invalidateQueries()
onOpenChange(false)
},
onError: (e: unknown) =>
toast.error(e instanceof Error ? e.message : 'Не удалось сменить IP'),
})
return (
<FormSheet
open={open}
onOpenChange={onOpenChange}
title="Сменить IP"
description="Обновить A-запись в Cloudflare без перехода на страницу DNS."
form={form}
onSubmit={() => mutate.mutate()}
footer={
<>
<Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
Отмена
</Button>
<LoadingButton
type="submit"
isLoading={mutate.isPending}
loadingLabel="Updating…"
>
Сменить IP
</LoadingButton>
</>
}
>
<FormFieldSimple label="Текущий IP" htmlFor="from_ip">
<Input id="from_ip" {...form.register('from_ip')} />
</FormFieldSimple>
{nodes.length > 0 ? (
<FormFieldSimple label="Нода" htmlFor="node_id">
<Select
value={nodeId || null}
onValueChange={(value) => {
form.setValue('node_id', value ?? '')
const node = nodes.find((n) => String(n.id) === value)
if (node) form.setValue('to_ip', node.address)
}}
>
<SelectTrigger id="node_id">
<SelectValue placeholder="Выберите ноду" />
</SelectTrigger>
<SelectContent>
{nodes.map((node) => (
<SelectItem key={node.id} value={String(node.id)}>
{node.address}
</SelectItem>
))}
</SelectContent>
</Select>
</FormFieldSimple>
) : null}
<FormFieldSimple label="Новый IP" htmlFor="to_ip" hint="IPv4">
<Input id="to_ip" {...form.register('to_ip')} placeholder="10.0.0.20" />
</FormFieldSimple>
{previewText ? (
<Alert>
<AlertTitle>Предпросмотр</AlertTitle>
<AlertDescription>{preview ?? previewText}</AlertDescription>
</Alert>
) : null}
</FormSheet>
)
}