Files
MikrotikManager/components/ipsec/ipsec-server-grid.tsx
T
Denozordec 7755d77340
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
feat(ipsec): управление IKEv2/IPsec VPN и клиентами из одного окна
- мастер инициализации сервера: 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
2026-09-12 20:20:56 +07:00

100 lines
4.2 KiB
TypeScript

"use client"
import type { IpsecServerSummaryDto } from "@mmapp/contracts/ipsec"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"
import {
GlobeIcon,
NetworkIcon,
ShieldCheckIcon,
Trash2Icon,
} from "lucide-react"
function Row({ label, value, mono = true }: { label: string; value: string; mono?: boolean }) {
return (
<div className="flex items-baseline justify-between gap-3 py-1">
<span className="text-xs text-muted-foreground">{label}</span>
<span className={cn("truncate text-xs", mono ? "font-mono" : "")} title={value}>
{value || "—"}
</span>
</div>
)
}
export interface IpsecServerGridProps {
servers: IpsecServerSummaryDto[]
onInit?: (row: IpsecServerSummaryDto) => void
onRemove?: (row: IpsecServerSummaryDto) => void
}
function IpsecServerGrid({ servers, onInit, onRemove }: IpsecServerGridProps) {
return (
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
{servers.map((s) => (
<div key={s.serverId} className="rounded-lg border border-border bg-card p-4 flex flex-col gap-3">
<div className="flex items-start justify-between gap-2">
<div className="min-w-0">
<p className="truncate text-sm font-medium">{s.serverName}</p>
<p className="truncate font-mono text-[11px] text-muted-foreground">
{s.serverEndpoint ?? "не инициализирован"}
</p>
</div>
{s.initialized ? (
<Badge className="gap-1">
<ShieldCheckIcon className="size-3" />
IKEv2
</Badge>
) : (
<Badge variant="outline">нет</Badge>
)}
</div>
<div className="flex flex-col divide-y divide-border/60">
<Row label="Клиенты" value={`${s.clientsOnline} онлайн / ${s.clientsTotal}`} />
<Row label="Пул адресов" value={s.pool ? `${s.pool.name}: ${s.pool.ranges}` : "—"} />
<Row label="Peer" value={s.peer ? `${s.peer.name} (${s.peer.exchangeMode ?? "ike2"}${s.peer.passive ? ", passive" : ""})` : "—"} />
<Row label="CA-сертификат" value={s.caCert ? `${s.caCert.commonName ?? s.caCert.name}${s.caCert.expiresAt ? ` · до ${s.caCert.expiresAt}` : ""}` : "—"} />
<Row label="Серверный серт." value={s.serverCert ? `${s.serverCert.commonName ?? s.serverCert.name}${s.serverCert.expiresAt ? ` · до ${s.serverCert.expiresAt}` : ""}` : "—"} />
<Row label="NAT (интернет)" value={s.natRuleManaged ? "managed masquerade" : "нет правила"} mono={false} />
</div>
<div className="mt-auto flex justify-end gap-1.5">
<Button
type="button"
size="sm"
variant="outline"
onClick={() => onInit?.(s)}
title={s.initialized ? "Обновить managed-конфиг сервера" : "Инициализировать IKEv2-сервер"}
>
<GlobeIcon className="size-3.5" />
{s.initialized ? "Переинициализировать" : "Инициализировать"}
</Button>
{s.initialized && onRemove ? (
<Button
type="button"
size="icon"
variant="ghost"
className="size-8 text-destructive"
aria-label="Удалить IKEv2-сервер"
title="Удалить managed-объекты IKEv2 (identity/mode-config/peer/pool/NAT + сертификаты)"
onClick={() => onRemove(s)}
>
<Trash2Icon className="size-3.5" />
</Button>
) : null}
</div>
</div>
))}
{servers.length === 0 ? (
<div className="col-span-full flex items-center justify-center gap-2 py-16 text-sm text-muted-foreground">
<NetworkIcon className="size-4" />
Серверы не найдены
</div>
) : null}
</div>
)
}
export { IpsecServerGrid }