Docker images / prepare-release (push) Successful in 15s
Docker images / backend-test (push) Successful in 2m32s
Docker images / frontend-image (push) Successful in 4m19s
Docker images / updater-image (push) Successful in 50s
Docker images / backend-image (push) Successful in 2m40s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 8s
- мастер инициализации сервера: CA и серверный сертификаты, peer/profile/proposal, пул, mode-config, policy-template, managed NAT masquerade - клиенты по сертификату (RSA) и PSK: статический IP или из пула, онлайн-статус по active-peers - скачивание .p12 и strongSwan .sswan с инструкцией, перекачка с новой passphrase - история изменений (config_revisions, секция ipsec) и restore только managed-объектов - привязка IPsec-клиентов к пользователям приложения по Common Name - страница /ipsec с KPI и вкладками Клиенты/Сервер/CLI, сайдбар, command palette
163 lines
6.1 KiB
TypeScript
163 lines
6.1 KiB
TypeScript
"use client"
|
|
|
|
import { useEffect, useMemo, useState } from "react"
|
|
import { FormField, FormToggle, 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 { Alert, AlertDescription, AlertTitle } from "@/components/reui/alert"
|
|
import { InfoIcon } from "lucide-react"
|
|
|
|
export type IpsecInitFormState = {
|
|
serverId: string
|
|
serverEndpoint: string
|
|
poolCidr: string
|
|
dns: string
|
|
createNatRule: boolean
|
|
}
|
|
|
|
export const defaultIpsecInitForm = (): IpsecInitFormState => ({
|
|
serverId: "",
|
|
serverEndpoint: "",
|
|
poolCidr: "10.77.0.0/24",
|
|
dns: "",
|
|
createNatRule: true,
|
|
})
|
|
|
|
type ServerOption = { id: string; name: string; host: string }
|
|
|
|
function IpsecInitSheet({
|
|
open,
|
|
onOpenChange,
|
|
servers,
|
|
busy,
|
|
defaultServerId,
|
|
onSubmit,
|
|
}: {
|
|
open: boolean
|
|
onOpenChange: (v: boolean) => void
|
|
servers: ServerOption[]
|
|
busy?: boolean
|
|
defaultServerId?: string
|
|
onSubmit: (form: IpsecInitFormState) => void | Promise<void>
|
|
}) {
|
|
const [form, setForm] = useState<IpsecInitFormState>(defaultIpsecInitForm)
|
|
const set = <K extends keyof IpsecInitFormState>(k: K, v: IpsecInitFormState[K]) =>
|
|
setForm((f) => ({ ...f, [k]: v }))
|
|
|
|
useEffect(() => {
|
|
if (!open) return
|
|
const server = servers.find((s) => s.id === (defaultServerId ?? ""))
|
|
queueMicrotask(() => 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}(?:\.\d{1,3}){3}\/\d{1,2}$/.test(form.poolCidr.trim()),
|
|
)
|
|
}, [form])
|
|
|
|
return (
|
|
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
<SheetContent 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>
|
|
CA + серверный сертификат, peer (ike2/passive), пул адресов и mode-config
|
|
</SheetDescription>
|
|
</SheetHeader>
|
|
|
|
<div className="flex-1 overflow-y-auto px-6 py-5 flex flex-col gap-5">
|
|
<Alert variant="info">
|
|
<InfoIcon />
|
|
<AlertTitle>Выпуск CA и сертификатов занимает до минуты</AlertTitle>
|
|
<AlertDescription>
|
|
Повторный запуск безопасен: существующие managed-объекты обновятся, сертификаты
|
|
не перевыпускаются.
|
|
</AlertDescription>
|
|
</Alert>
|
|
|
|
<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]"
|
|
value={form.serverId}
|
|
onChange={(e) => {
|
|
const id = e.target.value
|
|
const server = servers.find((s) => s.id === id)
|
|
setForm((f) => ({ ...f, serverId: id, serverEndpoint: server?.host ?? f.serverEndpoint }))
|
|
}}
|
|
>
|
|
<option value="">Выберите сервер…</option>
|
|
{servers.map((s) => (
|
|
<option key={s.id} value={s.id}>
|
|
{s.name} ({s.host})
|
|
</option>
|
|
))}
|
|
</select>
|
|
</FormField>
|
|
<FormField label="Адрес сервера (CN/SAN)" required hint="Домен или IP — по нему подключаются клиенты">
|
|
<Input
|
|
className="font-mono"
|
|
placeholder="vpn.example.com"
|
|
value={form.serverEndpoint}
|
|
onChange={(e) => set("serverEndpoint", e.target.value)}
|
|
/>
|
|
</FormField>
|
|
<FormField label="Подсеть клиентов" required hint="Из неё пул .2–.254 и статические IP">
|
|
<Input
|
|
className="font-mono"
|
|
placeholder="10.77.0.0/24"
|
|
value={form.poolCidr}
|
|
onChange={(e) => set("poolCidr", e.target.value)}
|
|
/>
|
|
</FormField>
|
|
<FormField label="DNS для клиентов" hint="Например 10.77.0.1 или 1.1.1.1">
|
|
<Input
|
|
className="font-mono"
|
|
value={form.dns}
|
|
onChange={(e) => set("dns", e.target.value)}
|
|
/>
|
|
</FormField>
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm font-medium">Интернет клиентам (NAT)</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
Managed srcnat masquerade для подсети клиентов (out-interface-list WAN)
|
|
</p>
|
|
</div>
|
|
<FormToggle checked={form.createNatRule} onChange={(v) => set("createNatRule", v)} />
|
|
</div>
|
|
</div>
|
|
</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>
|
|
<Button
|
|
className="flex-1"
|
|
disabled={!canSubmit || busy}
|
|
onClick={() => void onSubmit(form)}
|
|
>
|
|
{busy ? "Инициализация…" : "Инициализировать"}
|
|
</Button>
|
|
</SheetFooter>
|
|
</SheetContent>
|
|
</Sheet>
|
|
)
|
|
}
|
|
|
|
export { IpsecInitSheet }
|