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
- 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.
153 lines
4.7 KiB
TypeScript
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>
|
|
)
|
|
}
|