fix(ui): подтверждать создание интерфейса и пира WireGuard
This commit is contained in:
1 parent
ec6ec52965
commit
8fff3d7fff
8 files changed
+166
-52
No files matched your search
@@ -1,9 +1,10 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { FormField, FormToggle, SectionTitle } from "@/components/form-kit"
|
||||
import { useMemo, useState } from "react"
|
||||
import { FormField, SectionTitle } from "@/components/form-kit"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import {
|
||||
Sheet, SheetContent, SheetHeader, SheetTitle,
|
||||
SheetDescription, SheetFooter, SheetClose,
|
||||
@@ -51,6 +52,7 @@ function WgCreateSheet({
|
||||
onOpenChange,
|
||||
servers,
|
||||
busy,
|
||||
error,
|
||||
defaultServerId,
|
||||
onSubmit,
|
||||
}: {
|
||||
@@ -58,6 +60,7 @@ function WgCreateSheet({
|
||||
onOpenChange: (v: boolean) => void
|
||||
servers: ServerOption[]
|
||||
busy?: boolean
|
||||
error?: string | null
|
||||
defaultServerId?: string
|
||||
onSubmit: (form: WgCreateFormState) => void | Promise<void>
|
||||
}) {
|
||||
@@ -65,18 +68,19 @@ function WgCreateSheet({
|
||||
const set = <K extends keyof WgCreateFormState>(k: K, v: WgCreateFormState[K]) =>
|
||||
setForm((f) => ({ ...f, [k]: v }))
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
setForm({ ...defaultWgCreateForm(), serverId: defaultServerId ?? "" })
|
||||
}, [open, defaultServerId])
|
||||
const [context, setContext] = useState({ open: false, defaultServerId })
|
||||
if (context.open !== open || context.defaultServerId !== defaultServerId) {
|
||||
setContext({ open, defaultServerId })
|
||||
if (open) setForm({ ...defaultWgCreateForm(), serverId: defaultServerId ?? "" })
|
||||
}
|
||||
|
||||
const canSubmit = useMemo(() => {
|
||||
return Boolean(form.serverId && form.name.trim() && form.listenPort)
|
||||
}, [form.serverId, form.name, form.listenPort])
|
||||
|
||||
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={v => { if (!busy) onOpenChange(v) }}>
|
||||
<SheetContent showCloseButton={!busy} 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>Быстрый туннель WireGuard</SheetTitle>
|
||||
<SheetDescription>
|
||||
@@ -84,12 +88,13 @@ function WgCreateSheet({
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
|
||||
<div className="flex-1 overflow-y-auto px-6 py-5 flex flex-col gap-5">
|
||||
<fieldset disabled={busy} aria-busy={busy} className="min-h-0 min-w-0 flex-1 overflow-y-auto px-6 py-5 flex flex-col gap-5">
|
||||
<div className="flex flex-col gap-4">
|
||||
<SectionTitle>Основные</SectionTitle>
|
||||
<FormField label="Сервер" required>
|
||||
<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]"
|
||||
aria-label="Сервер"
|
||||
value={form.serverId}
|
||||
onChange={(e) => set("serverId", e.target.value)}
|
||||
>
|
||||
@@ -105,7 +110,7 @@ function WgCreateSheet({
|
||||
<Input
|
||||
className="font-mono"
|
||||
placeholder="wg0"
|
||||
value={form.name}
|
||||
aria-label="Имя интерфейса" value={form.name}
|
||||
onChange={(e) => set("name", e.target.value)}
|
||||
/>
|
||||
</FormField>
|
||||
@@ -113,21 +118,21 @@ function WgCreateSheet({
|
||||
<FormField label="Listen port" required>
|
||||
<Input
|
||||
className="font-mono"
|
||||
value={form.listenPort}
|
||||
aria-label="Listen port" value={form.listenPort}
|
||||
onChange={(e) => set("listenPort", e.target.value)}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="MTU">
|
||||
<Input
|
||||
className="font-mono"
|
||||
value={form.mtu}
|
||||
aria-label="MTU" value={form.mtu}
|
||||
onChange={(e) => set("mtu", e.target.value)}
|
||||
/>
|
||||
</FormField>
|
||||
</div>
|
||||
<FormField label="Комментарий">
|
||||
<Input
|
||||
value={form.comment}
|
||||
aria-label="Комментарий" value={form.comment}
|
||||
onChange={(e) => set("comment", e.target.value)}
|
||||
placeholder="MSK → SPB overlay"
|
||||
/>
|
||||
@@ -137,7 +142,7 @@ function WgCreateSheet({
|
||||
<p className="text-sm font-medium">Включён</p>
|
||||
<p className="text-xs text-muted-foreground">disabled=no на роутере</p>
|
||||
</div>
|
||||
<FormToggle checked={form.enabled} onChange={(v) => set("enabled", v)} />
|
||||
<Switch aria-label="Интерфейс включён" disabled={busy} checked={form.enabled} onCheckedChange={(v) => set("enabled", v)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -156,7 +161,7 @@ function WgCreateSheet({
|
||||
<Input
|
||||
className="font-mono"
|
||||
placeholder="10.210.0.1/30"
|
||||
value={form.address}
|
||||
aria-label="IP на интерфейсе" value={form.address}
|
||||
onChange={(e) => set("address", e.target.value)}
|
||||
/>
|
||||
</FormField>
|
||||
@@ -166,7 +171,7 @@ function WgCreateSheet({
|
||||
<p className="text-sm font-medium">Добавить первого пира</p>
|
||||
<p className="text-xs text-muted-foreground">Сразу после создания интерфейса</p>
|
||||
</div>
|
||||
<FormToggle checked={form.peerEnabled} onChange={(v) => set("peerEnabled", v)} />
|
||||
<Switch aria-label="Добавить первого пира" disabled={busy} checked={form.peerEnabled} onCheckedChange={(v) => set("peerEnabled", v)} />
|
||||
</div>
|
||||
|
||||
{form.peerEnabled && (
|
||||
@@ -174,7 +179,7 @@ function WgCreateSheet({
|
||||
<FormField label="Public key пира" required>
|
||||
<Input
|
||||
className="font-mono text-xs"
|
||||
value={form.peerPublicKey}
|
||||
aria-label="Public key пира" value={form.peerPublicKey}
|
||||
onChange={(e) => set("peerPublicKey", e.target.value)}
|
||||
/>
|
||||
</FormField>
|
||||
@@ -182,7 +187,7 @@ function WgCreateSheet({
|
||||
<Input
|
||||
className="font-mono"
|
||||
placeholder="10.210.0.2/32"
|
||||
value={form.peerAllowedIps}
|
||||
aria-label="Allowed IPs пира" value={form.peerAllowedIps}
|
||||
onChange={(e) => set("peerAllowedIps", e.target.value)}
|
||||
/>
|
||||
</FormField>
|
||||
@@ -190,20 +195,20 @@ function WgCreateSheet({
|
||||
<Input
|
||||
className="font-mono"
|
||||
placeholder="1.2.3.4:13231"
|
||||
value={form.peerEndpoint}
|
||||
aria-label="Endpoint пира" value={form.peerEndpoint}
|
||||
onChange={(e) => set("peerEndpoint", e.target.value)}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="Keepalive (сек)">
|
||||
<Input
|
||||
className="font-mono"
|
||||
value={form.peerKeepalive}
|
||||
aria-label="Keepalive пира" value={form.peerKeepalive}
|
||||
onChange={(e) => set("peerKeepalive", e.target.value)}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="Комментарий пира">
|
||||
<Input
|
||||
value={form.peerComment}
|
||||
aria-label="Комментарий пира" value={form.peerComment}
|
||||
onChange={(e) => set("peerComment", e.target.value)}
|
||||
/>
|
||||
</FormField>
|
||||
@@ -211,13 +216,15 @@ function WgCreateSheet({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
{busy && <p role="status" className="px-6 text-sm text-muted-foreground">Ожидание подтверждения создания…</p>}
|
||||
{error && <p role="alert" className="px-6 text-sm text-destructive">{error}</p>}
|
||||
<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>
|
||||
<Button
|
||||
type="button"
|
||||
className="flex-1"
|
||||
disabled={!canSubmit || busy}
|
||||
onClick={() => void onSubmit(form)}
|
||||
|
||||
@@ -30,12 +30,14 @@ function WgPeerSheet({
|
||||
open,
|
||||
iface,
|
||||
busy,
|
||||
error,
|
||||
onOpenChange,
|
||||
onSubmit,
|
||||
}: {
|
||||
open: boolean
|
||||
iface: WgIfaceWithServer | null
|
||||
busy?: boolean
|
||||
error?: string | null
|
||||
onOpenChange: (v: boolean) => void
|
||||
onSubmit: (form: WgPeerFormState) => void | Promise<void>
|
||||
}) {
|
||||
@@ -43,27 +45,33 @@ function WgPeerSheet({
|
||||
const set = <K extends keyof WgPeerFormState>(k: K, v: WgPeerFormState[K]) =>
|
||||
setForm((f) => ({ ...f, [k]: v }))
|
||||
|
||||
const [context, setContext] = useState({ open: false, iface })
|
||||
if (context.open !== open || context.iface !== iface) {
|
||||
setContext({ open, iface })
|
||||
if (open) setForm(emptyPeerForm())
|
||||
}
|
||||
|
||||
return (
|
||||
<Sheet
|
||||
open={open}
|
||||
onOpenChange={(v) => {
|
||||
if (v) setForm(emptyPeerForm())
|
||||
if (busy) return
|
||||
onOpenChange(v)
|
||||
}}
|
||||
>
|
||||
<SheetContent side="right" className="w-full sm:max-w-md flex flex-col gap-0 p-0">
|
||||
<SheetContent showCloseButton={!busy} 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>Добавить пира</SheetTitle>
|
||||
<SheetDescription>
|
||||
{iface ? `${iface.name} · ${iface.serverName}` : "WireGuard peer"}
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
<div className="flex-1 overflow-y-auto px-6 py-5 flex flex-col gap-4">
|
||||
<fieldset disabled={busy} aria-busy={busy} className="min-h-0 min-w-0 flex-1 overflow-y-auto px-6 py-5 flex flex-col gap-4">
|
||||
<SectionTitle>Параметры пира</SectionTitle>
|
||||
<FormField label="Public key" required>
|
||||
<Input
|
||||
className="font-mono text-xs"
|
||||
value={form.publicKey}
|
||||
aria-label="Public key" value={form.publicKey}
|
||||
onChange={(e) => set("publicKey", e.target.value)}
|
||||
/>
|
||||
</FormField>
|
||||
@@ -71,7 +79,7 @@ function WgPeerSheet({
|
||||
<Input
|
||||
className="font-mono"
|
||||
placeholder="10.210.0.2/32"
|
||||
value={form.allowedIps}
|
||||
aria-label="Allowed IPs" value={form.allowedIps}
|
||||
onChange={(e) => set("allowedIps", e.target.value)}
|
||||
/>
|
||||
</FormField>
|
||||
@@ -79,26 +87,29 @@ function WgPeerSheet({
|
||||
<Input
|
||||
className="font-mono"
|
||||
placeholder="1.2.3.4:13231"
|
||||
value={form.endpoint}
|
||||
aria-label="Endpoint" value={form.endpoint}
|
||||
onChange={(e) => set("endpoint", e.target.value)}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="Keepalive (сек)">
|
||||
<Input
|
||||
className="font-mono"
|
||||
value={form.keepalive}
|
||||
aria-label="Keepalive (сек)" value={form.keepalive}
|
||||
onChange={(e) => set("keepalive", e.target.value)}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="Комментарий">
|
||||
<Input value={form.comment} onChange={(e) => set("comment", e.target.value)} />
|
||||
<Input aria-label="Комментарий" value={form.comment} onChange={(e) => set("comment", e.target.value)} />
|
||||
</FormField>
|
||||
</div>
|
||||
</fieldset>
|
||||
{busy && <p role="status" className="px-6 text-sm text-muted-foreground">Ожидание подтверждения создания…</p>}
|
||||
{error && <p role="alert" className="px-6 text-sm text-destructive">{error}</p>}
|
||||
<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>
|
||||
<Button
|
||||
type="button"
|
||||
className="flex-1"
|
||||
disabled={busy || !form.publicKey.trim() || !form.allowedIps.trim()}
|
||||
onClick={() => void onSubmit(form)}
|
||||
|
||||
Reference in new issue
Block a user