diff --git a/app/(main)/wireguard/page.tsx b/app/(main)/wireguard/page.tsx index 60e8a3ec..e6a4c2f9 100644 --- a/app/(main)/wireguard/page.tsx +++ b/app/(main)/wireguard/page.tsx @@ -192,7 +192,9 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf const [search, setSearch] = useState("") const [createOpen, setCreateOpen] = useState(false) const [importOpen, setImportOpen] = useState(false) - const importLock = useRef(false) + const formLock = useRef(false) + const [createError, setCreateError] = useState(null) + const [peerError, setPeerError] = useState(null) const [importError, setImportError] = useState(null) const [exportIface, setExportIface] = useState(null) const [exportInitialTab, setExportInitialTab] = useState<"rsc" | "conf" | "peer">("rsc") @@ -400,10 +402,13 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf }, [displayServers, displayIfaces]) async function handleCreate(form: WgCreateFormState) { + if (formLock.current) return if (!isLive) { toast.info("Создание на роутер доступно только в live-режиме") return } + formLock.current = true + setCreateError(null) setBusy(true) try { const ep = parseEndpoint(form.peerEndpoint) @@ -433,8 +438,9 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf setCreateOpen(false) await loadLive() } catch (e) { - toast.error(e instanceof Error ? e.message : "Ошибка создания") + setCreateError(e instanceof Error ? e.message : "Ошибка создания") } finally { + formLock.current = false setBusy(false) } } @@ -445,12 +451,12 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf format: "auto" | "rsc" | "conf" dryRun: boolean }) { - if (importLock.current) return + if (formLock.current) return if (!isLive) { toast.info("Импорт на роутер доступен только в live-режиме") return } - importLock.current = true + formLock.current = true setImportError(null) setBusy(true) try { @@ -474,7 +480,7 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf } catch (e) { setImportError(e instanceof Error ? e.message : "Ошибка импорта") } finally { - importLock.current = false + formLock.current = false setBusy(false) } } @@ -539,11 +545,14 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf } async function handleAddPeer(form: WgPeerFormState) { + if (formLock.current) return if (sourceUnavailable(peerIface?.serverId)) return if (!isLive || !peerIface) { toast.info("Доступно только в live-режиме") return } + formLock.current = true + setPeerError(null) setBusy(true) try { const ep = parseEndpoint(form.endpoint) @@ -564,8 +573,9 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf setPeerIface(null) await loadLive() } catch (e) { - toast.error(e instanceof Error ? e.message : "Ошибка") + setPeerError(e instanceof Error ? e.message : "Ошибка") } finally { + formLock.current = false setBusy(false) } } @@ -605,7 +615,7 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf } const emptyCreateAction = ( - @@ -657,7 +667,7 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf Импорт - @@ -776,7 +786,7 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf compactServer={compactServer} emptyAction={emptyCreateAction} onExport={(iface) => openExport(iface, "rsc")} - onAddPeer={selectedSourceError ? undefined : setPeerIface} + onAddPeer={selectedSourceError ? undefined : iface => { setPeerError(null); setPeerIface(iface) }} onToggle={selectedSourceError ? undefined : handleToggle} onDelete={selectedSourceError ? undefined : (iface) => setPendingDelete({ kind: "iface", iface })} onDeletePeer={selectedSourceError ? undefined : (iface, peerId) => setPendingDelete({ kind: "peer", iface, peerId })} @@ -798,7 +808,7 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf compactServer={compactServer} emptyAction={ scopedIfaces.length === 1 ? ( - @@ -872,7 +882,8 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf { if (!formLock.current) setCreateOpen(v) }} + error={createError} servers={serverOptions} defaultServerId={sheetServerId} busy={busy} @@ -880,7 +891,7 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf /> { if (!importLock.current) setImportOpen(v) }} + onOpenChange={v => { if (!formLock.current) setImportOpen(v) }} error={importError} servers={serverOptions} defaultServerId={sheetServerId} @@ -891,7 +902,8 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf open={!!peerIface} iface={peerIface} busy={busy} - onOpenChange={(v) => { if (!v) setPeerIface(null) }} + onOpenChange={(v) => { if (!v && !formLock.current) setPeerIface(null) }} + error={peerError} onSubmit={handleAddPeer} /> void servers: ServerOption[] busy?: boolean + error?: string | null defaultServerId?: string onSubmit: (form: WgCreateFormState) => void | Promise }) { @@ -65,18 +68,19 @@ function WgCreateSheet({ const set = (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 ( - - + { if (!busy) onOpenChange(v) }}> + Быстрый туннель WireGuard @@ -84,12 +88,13 @@ function WgCreateSheet({ -
+
Основные set("name", e.target.value)} /> @@ -113,21 +118,21 @@ function WgCreateSheet({ set("listenPort", e.target.value)} /> set("mtu", e.target.value)} />
set("comment", e.target.value)} placeholder="MSK → SPB overlay" /> @@ -137,7 +142,7 @@ function WgCreateSheet({

Включён

disabled=no на роутере

- set("enabled", v)} /> + set("enabled", v)} /> @@ -156,7 +161,7 @@ function WgCreateSheet({ set("address", e.target.value)} /> @@ -166,7 +171,7 @@ function WgCreateSheet({

Добавить первого пира

Сразу после создания интерфейса

- set("peerEnabled", v)} /> + set("peerEnabled", v)} /> {form.peerEnabled && ( @@ -174,7 +179,7 @@ function WgCreateSheet({ set("peerPublicKey", e.target.value)} /> @@ -182,7 +187,7 @@ function WgCreateSheet({ set("peerAllowedIps", e.target.value)} /> @@ -190,20 +195,20 @@ function WgCreateSheet({ set("peerEndpoint", e.target.value)} /> set("peerKeepalive", e.target.value)} /> set("peerComment", e.target.value)} /> @@ -211,13 +216,15 @@ function WgCreateSheet({ )} )} - - + + {busy &&

Ожидание подтверждения создания…

} + {error &&

{error}

} }> Отмена