fix(ui): подтверждать инициализацию IPsec и сохранять форму
This commit is contained in:
1 parent
4a8d696e4e
commit
f9413d5100
7 files changed
+144
-27
No files matched your search
@@ -1,8 +1,9 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { FormField, FormToggle, SectionTitle } from "@/components/form-kit"
|
||||
import { useEffect, useMemo, useRef, useState } from "react"
|
||||
import { FormField, SectionTitle } from "@/components/form-kit"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import {
|
||||
Sheet, SheetContent, SheetHeader, SheetTitle,
|
||||
@@ -45,31 +46,58 @@ function IpsecInitSheet({
|
||||
onSubmit: (form: IpsecInitFormState) => void | Promise<void>
|
||||
}) {
|
||||
const [form, setForm] = useState<IpsecInitFormState>(defaultIpsecInitForm)
|
||||
const submittingRef = useRef(false)
|
||||
const openedRef = useRef(false)
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const pending = busy || submitting
|
||||
const set = <K extends keyof IpsecInitFormState>(k: K, v: IpsecInitFormState[K]) =>
|
||||
setForm((f) => ({ ...f, [k]: v }))
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
if (!open) { openedRef.current = false; return }
|
||||
if (openedRef.current) return
|
||||
openedRef.current = true
|
||||
const server = servers.find((s) => s.id === (defaultServerId ?? ""))
|
||||
queueMicrotask(() => setForm({
|
||||
...defaultIpsecInitForm(),
|
||||
serverId: defaultServerId ?? "",
|
||||
serverEndpoint: server?.host ?? "",
|
||||
}))
|
||||
queueMicrotask(() => {
|
||||
setError(null)
|
||||
setForm({
|
||||
...defaultIpsecInitForm(),
|
||||
serverId: defaultServerId ?? "",
|
||||
serverEndpoint: server?.host ?? "",
|
||||
})
|
||||
})
|
||||
}, [open, defaultServerId, servers])
|
||||
|
||||
const canSubmit = useMemo(() => {
|
||||
return Boolean(
|
||||
form.serverId &&
|
||||
form.serverEndpoint.trim() &&
|
||||
/^(\d{1,3}(?:\.\d{1,3}){3}\/\d{1,2}|[\w.-]+\.[a-z]{2,})$/i.test(form.serverEndpoint.trim()) &&
|
||||
(/^(?:\d{1,3}\.){3}\d{1,3}$/.test(form.serverEndpoint.trim())
|
||||
? form.serverEndpoint.trim().split(".").every(part => Number(part) <= 255)
|
||||
: /^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/i.test(form.serverEndpoint.trim())) &&
|
||||
/^\d{1,3}(?:\.\d{1,3}){3}\/\d{1,2}$/.test(form.poolCidr.trim()),
|
||||
)
|
||||
}, [form])
|
||||
|
||||
const submit = async () => {
|
||||
if (!canSubmit || busy || submittingRef.current) return
|
||||
submittingRef.current = true
|
||||
setSubmitting(true)
|
||||
setError(null)
|
||||
try {
|
||||
await onSubmit(form)
|
||||
} catch (error) {
|
||||
setError(error instanceof Error ? error.message : "Не удалось инициализировать сервер")
|
||||
} finally {
|
||||
submittingRef.current = false
|
||||
setSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={onOpenChange}>
|
||||
<SheetContent side="right" className="w-full sm:max-w-md flex flex-col gap-0 p-0">
|
||||
<Sheet open={open} onOpenChange={value => { if (!submittingRef.current && !busy) onOpenChange(value) }}>
|
||||
<SheetContent showCloseButton={!pending} side="right" className="w-full sm:max-w-md flex flex-col gap-0 p-0">
|
||||
<SheetHeader className="px-6 pt-6 pb-4 border-b shrink-0">
|
||||
<SheetTitle>Инициализация IKEv2-сервера</SheetTitle>
|
||||
<SheetDescription>
|
||||
@@ -87,10 +115,11 @@ function IpsecInitSheet({
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<fieldset disabled={pending} className="flex flex-col gap-4">
|
||||
<SectionTitle>Основные</SectionTitle>
|
||||
<FormField label="Сервер" required>
|
||||
<select
|
||||
aria-label="Сервер"
|
||||
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]"
|
||||
value={form.serverId}
|
||||
onChange={(e) => {
|
||||
@@ -111,6 +140,7 @@ function IpsecInitSheet({
|
||||
<Input
|
||||
className="font-mono"
|
||||
placeholder="vpn.example.com"
|
||||
aria-label="Адрес сервера (CN/SAN)"
|
||||
value={form.serverEndpoint}
|
||||
onChange={(e) => set("serverEndpoint", e.target.value)}
|
||||
/>
|
||||
@@ -119,6 +149,7 @@ function IpsecInitSheet({
|
||||
<Input
|
||||
className="font-mono"
|
||||
placeholder="10.77.0.0/24"
|
||||
aria-label="Подсеть клиентов"
|
||||
value={form.poolCidr}
|
||||
onChange={(e) => set("poolCidr", e.target.value)}
|
||||
/>
|
||||
@@ -126,6 +157,7 @@ function IpsecInitSheet({
|
||||
<FormField label="DNS для клиентов" hint="Например 10.77.0.1 или 1.1.1.1">
|
||||
<Input
|
||||
className="font-mono"
|
||||
aria-label="DNS для клиентов"
|
||||
value={form.dns}
|
||||
onChange={(e) => set("dns", e.target.value)}
|
||||
/>
|
||||
@@ -137,21 +169,23 @@ function IpsecInitSheet({
|
||||
Managed srcnat masquerade для подсети клиентов (out-interface-list WAN)
|
||||
</p>
|
||||
</div>
|
||||
<FormToggle checked={form.createNatRule} onChange={(v) => set("createNatRule", v)} />
|
||||
<Switch aria-label="Интернет клиентам (NAT)" disabled={pending} checked={form.createNatRule} onCheckedChange={(v) => set("createNatRule", v)} />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
{pending && <p role="status" className="text-sm text-muted-foreground">Ожидание результата инициализации…</p>}
|
||||
{error && <p role="alert" className="text-sm text-destructive">{error}</p>}
|
||||
</div>
|
||||
|
||||
<SheetFooter className="px-6 py-4 border-t shrink-0 flex-row gap-2">
|
||||
<SheetClose render={<Button variant="outline" className="flex-1" disabled={busy} />}>
|
||||
<SheetClose render={<Button variant="outline" className="flex-1" disabled={pending} />}>
|
||||
Отмена
|
||||
</SheetClose>
|
||||
<Button
|
||||
className="flex-1"
|
||||
disabled={!canSubmit || busy}
|
||||
onClick={() => void onSubmit(form)}
|
||||
disabled={!canSubmit || pending}
|
||||
onClick={() => void submit()}
|
||||
>
|
||||
{busy ? "Инициализация…" : "Инициализировать"}
|
||||
{pending ? "Инициализация…" : "Инициализировать"}
|
||||
</Button>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
|
||||
Reference in new issue
Block a user