fix(ui): подтверждать сохранение peer IPsec
This commit is contained in:
1 parent
611f31875e
commit
f5b64ea154
7 files changed
+115
-23
No files matched your search
@@ -1,9 +1,10 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useEffect, useMemo, useRef, useState } from "react"
|
||||
import type { IpsecPeerDto } from "@mmapp/contracts/ipsec"
|
||||
import { FormField, FormToggle, SectionTitle } from "@/components/form-kit"
|
||||
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,
|
||||
@@ -49,11 +50,19 @@ function IpsecPeerSheet({
|
||||
onSubmit: (form: IpsecPeerFormState) => void | Promise<void>
|
||||
}) {
|
||||
const [form, setForm] = useState<IpsecPeerFormState>(defaultIpsecPeerForm)
|
||||
const lock = useRef(false)
|
||||
const opened = useRef(false)
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const pending = busy || submitting
|
||||
const set = <K extends keyof IpsecPeerFormState>(k: K, v: IpsecPeerFormState[K]) =>
|
||||
setForm((f) => ({ ...f, [k]: v }))
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || !editing) return
|
||||
if (!open) { opened.current = false; return }
|
||||
if (!editing || opened.current) return
|
||||
opened.current = true
|
||||
queueMicrotask(() => setError(null))
|
||||
queueMicrotask(() => setForm({
|
||||
name: editing.name,
|
||||
address: editing.address ?? "",
|
||||
@@ -67,9 +76,19 @@ function IpsecPeerSheet({
|
||||
|
||||
const canSubmit = useMemo(() => form.name.trim().length > 0, [form.name])
|
||||
|
||||
const submit = async () => {
|
||||
if (!canSubmit || busy || lock.current) return
|
||||
lock.current = true
|
||||
setSubmitting(true)
|
||||
setError(null)
|
||||
try { await onSubmit(form) }
|
||||
catch (error) { setError(error instanceof Error ? error.message : "Не удалось сохранить peer") }
|
||||
finally { lock.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 (!lock.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>{editing ? `Peer «${editing.name}»` : "Peer"}</SheetTitle>
|
||||
<SheetDescription>
|
||||
@@ -80,18 +99,19 @@ function IpsecPeerSheet({
|
||||
</SheetHeader>
|
||||
|
||||
<div className="flex-1 overflow-y-auto px-6 py-5 flex flex-col gap-5">
|
||||
<fieldset disabled={pending} className="flex flex-col gap-5">
|
||||
<div className="flex flex-col gap-4">
|
||||
<SectionTitle>Основные</SectionTitle>
|
||||
<FormField label="Имя" required>
|
||||
<Input value={form.name} onChange={(e) => set("name", e.target.value)} />
|
||||
<Input aria-label="Имя" value={form.name} onChange={(e) => set("name", e.target.value)} />
|
||||
</FormField>
|
||||
<FormField label="Адрес" hint="Обычно 0.0.0.0/0 для road-warrior">
|
||||
<Input className="font-mono" value={form.address} onChange={(e) => set("address", e.target.value)} />
|
||||
<Input className="font-mono" aria-label="Адрес" value={form.address} onChange={(e) => set("address", e.target.value)} />
|
||||
</FormField>
|
||||
<FormField label="Exchange mode">
|
||||
<select
|
||||
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.exchangeMode}
|
||||
aria-label="Exchange mode" value={form.exchangeMode}
|
||||
onChange={(e) => set("exchangeMode", e.target.value)}
|
||||
>
|
||||
{EXCHANGE_MODES.map((m) => (
|
||||
@@ -104,7 +124,7 @@ function IpsecPeerSheet({
|
||||
className="font-mono"
|
||||
list="ipsec-peer-certs"
|
||||
placeholder="ipsec-server"
|
||||
value={form.certificate}
|
||||
aria-label="Сертификат" value={form.certificate}
|
||||
onChange={(e) => set("certificate", e.target.value)}
|
||||
/>
|
||||
<datalist id="ipsec-peer-certs">
|
||||
@@ -112,7 +132,7 @@ function IpsecPeerSheet({
|
||||
</datalist>
|
||||
</FormField>
|
||||
<FormField label="Profile" hint="Пусто — не менять">
|
||||
<Input className="font-mono" value={form.profile} onChange={(e) => set("profile", e.target.value)} />
|
||||
<Input className="font-mono" aria-label="Profile" value={form.profile} onChange={(e) => set("profile", e.target.value)} />
|
||||
</FormField>
|
||||
</div>
|
||||
|
||||
@@ -120,25 +140,28 @@ function IpsecPeerSheet({
|
||||
<SectionTitle>Состояние</SectionTitle>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-medium">Passive</p>
|
||||
<FormToggle checked={form.passive} onChange={(v) => set("passive", v)} />
|
||||
<Switch aria-label="Passive" disabled={pending} checked={form.passive} onCheckedChange={(v) => set("passive", v)} />
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-medium">Отключён</p>
|
||||
<FormToggle checked={form.disabled} onChange={(v) => set("disabled", v)} />
|
||||
<Switch aria-label="Отключён" disabled={pending} checked={form.disabled} onCheckedChange={(v) => set("disabled", 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