126 lines
4.3 KiB
TypeScript
126 lines
4.3 KiB
TypeScript
"use client"
|
|
|
|
import { useState } from "react"
|
|
import { FormField, SectionTitle } from "@/components/form-kit"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Input } from "@/components/ui/input"
|
|
import {
|
|
Sheet, SheetContent, SheetHeader, SheetTitle,
|
|
SheetDescription, SheetFooter, SheetClose,
|
|
} from "@/components/ui/sheet"
|
|
import type { WgIfaceWithServer } from "@/components/data-grids/wireguard-data-grid"
|
|
|
|
export type WgPeerFormState = {
|
|
publicKey: string
|
|
allowedIps: string
|
|
endpoint: string
|
|
keepalive: string
|
|
comment: string
|
|
}
|
|
|
|
const emptyPeerForm = (): WgPeerFormState => ({
|
|
publicKey: "",
|
|
allowedIps: "",
|
|
endpoint: "",
|
|
keepalive: "25",
|
|
comment: "",
|
|
})
|
|
|
|
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>
|
|
}) {
|
|
const [form, setForm] = useState<WgPeerFormState>(emptyPeerForm)
|
|
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 (busy) return
|
|
onOpenChange(v)
|
|
}}
|
|
>
|
|
<SheetContent showCloseButton={!busy} side="right" className="data-[side=right]:w-full data-[side=right]: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>
|
|
<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"
|
|
aria-label="Public key" value={form.publicKey}
|
|
onChange={(e) => set("publicKey", e.target.value)}
|
|
/>
|
|
</FormField>
|
|
<FormField label="Allowed IPs" required hint="Через запятую">
|
|
<Input
|
|
className="font-mono"
|
|
placeholder="10.210.0.2/32"
|
|
aria-label="Allowed IPs" value={form.allowedIps}
|
|
onChange={(e) => set("allowedIps", e.target.value)}
|
|
/>
|
|
</FormField>
|
|
<FormField label="Endpoint" hint="host:port">
|
|
<Input
|
|
className="font-mono"
|
|
placeholder="1.2.3.4:13231"
|
|
aria-label="Endpoint" value={form.endpoint}
|
|
onChange={(e) => set("endpoint", e.target.value)}
|
|
/>
|
|
</FormField>
|
|
<FormField label="Keepalive (сек)">
|
|
<Input
|
|
className="font-mono"
|
|
aria-label="Keepalive (сек)" value={form.keepalive}
|
|
onChange={(e) => set("keepalive", e.target.value)}
|
|
/>
|
|
</FormField>
|
|
<FormField label="Комментарий">
|
|
<Input aria-label="Комментарий" value={form.comment} onChange={(e) => set("comment", e.target.value)} />
|
|
</FormField>
|
|
</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)}
|
|
>
|
|
{busy ? "Сохранение…" : "Добавить"}
|
|
</Button>
|
|
</SheetFooter>
|
|
</SheetContent>
|
|
</Sheet>
|
|
)
|
|
}
|
|
|
|
export { WgPeerSheet }
|