fix(ipsec): подтвердить достоверность операций и приёмку раздела

This commit is contained in:
Denozordec committed 2026-10-09 12:46:22 +07:00
1 parent e2d81f9657
commit f80d9b7637
28 files changed
+969 -170

No files matched your search

+30 -24
View File
@@ -1,7 +1,8 @@
"use client"
import { z } from "zod"
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react"
import { useSearchParams } from "next/navigation"
import { PageHeader } from "@/components/page-header"
import { servers as mockServers } from "@/lib/data"
import type { Server } from "@/lib/data"
@@ -174,7 +175,7 @@ const ipsecInventorySchema = z.array(z.object({
latency: z.number().nullable().optional(), asn: z.string().optional(),
})).refine(rows => new Set(rows.map(row => row.id)).size === rows.length)
function IpsecPage() {
function IpsecPage({ selectedServerId, setSelectedServerId, workspaceTab, setWorkspaceTab }: { selectedServerId: string; setSelectedServerId: (id: string) => void; workspaceTab: IpsecWorkspaceTab; setWorkspaceTab: (tab: IpsecWorkspaceTab) => void }) {
const { mode, backendUrl } = useDataSource()
const isLive = mode === "live"
@@ -201,10 +202,9 @@ function IpsecPage() {
const certPending = useRef(false)
useEffect(() => () => { certEpoch.current += 1 }, [])
const [selectedServerId, setSelectedServerId] = useState(ALL_SERVERS_ID)
const [workspaceTab, setWorkspaceTab] = useState<IpsecWorkspaceTab>("clients")
const [search, setSearch] = useState("")
const [initOpen, setInitOpen] = useState(false)
const [initTargetId, setInitTargetId] = useState<string | undefined>()
const [userOpen, setUserOpen] = useState(false)
const [editing, setEditing] = useState<IpsecClientDto | null>(null)
const [certOpen, setCertOpen] = useState(false)
@@ -265,10 +265,8 @@ function IpsecPage() {
const displaySummaries = isLive ? summaries : MOCK_IPSEC_SERVERS
const displayClients = isLive ? clients : MOCK_IPSEC_CLIENTS
const effectiveServerId =
selectedServerId === ALL_SERVERS_ID || displayServers.some((s) => s.id === selectedServerId)
? selectedServerId
: ALL_SERVERS_ID
const effectiveServerId = selectedServerId
const missingSelectedServer = selectedServerId !== ALL_SERVERS_ID && hasLoaded && !displayServers.some(s => s.id === selectedServerId)
const historyServerId = effectiveServerId === ALL_SERVERS_ID ? null : effectiveServerId
@@ -315,7 +313,7 @@ function IpsecPage() {
}
const failedServerIds = new Set(failures.map(f => f.serverId))
const scopeUnknown = isLive && (!hasLoaded || !!dataError || (effectiveServerId === ALL_SERVERS_ID ? failures.length > 0 : failedServerIds.has(effectiveServerId)))
const scopeUnknown = missingSelectedServer || isLive && (!hasLoaded || !!dataError || (effectiveServerId === ALL_SERVERS_ID ? failures.length > 0 : failedServerIds.has(effectiveServerId)))
const canManage = (serverId: string) => isLive && hasLoaded && !dataError && !inventoryError &&
!loading && !busy && !historyRestoring && !failedServerIds.has(serverId) && liveServers.some(s => s.id === serverId)
@@ -337,7 +335,7 @@ function IpsecPage() {
operationLock.current = false
if (mounted.current) setBusy(false)
}
const hasReadyDevice = displayServers.some(s => canManage(s.id))
const hasReadyDevice = !missingSelectedServer && displayServers.some(s => (effectiveServerId === ALL_SERVERS_ID || s.id === effectiveServerId) && canManage(s.id))
const scopedClients = useMemo(() => {
if (effectiveServerId === ALL_SERVERS_ID) return displayClients
@@ -366,10 +364,6 @@ function IpsecPage() {
const caOk = scopedSummaries.some((s) => s.caCert)
const compactServer = effectiveServerId !== ALL_SERVERS_ID
const sheetServerId = compactServer ? effectiveServerId : undefined
const certNames = useMemo(
() => Array.from(new Set(scopedSummaries.flatMap((s) => (s.certs ?? []).map((c) => c.name)))).sort(),
[scopedSummaries],
)
const peerOptions = useMemo(
() => scopedSummaries.flatMap((s) => s.peers.map((p) => ({ serverId: s.serverId, name: p.name, managed: p.managed }))),
[scopedSummaries],
@@ -583,7 +577,6 @@ function IpsecPage() {
address: form.address.trim() || undefined,
exchangeMode: form.exchangeMode.trim() || undefined,
passive: form.passive,
certificate: form.certificate.trim() || undefined,
profile: form.profile.trim() || undefined,
disabled: form.disabled,
})
@@ -626,6 +619,7 @@ function IpsecPage() {
<>
<ServerRailLayout
items={railItems}
preserveSelection
selectedId={effectiveServerId}
onSelect={setSelectedServerId}
showAll
@@ -675,6 +669,7 @@ function IpsecPage() {
}
>
<div className="flex flex-col gap-4">
{missingSelectedServer && <Alert variant="destructive" role="alert"><AlertTitle>Устройство из ссылки не найдено</AlertTitle><AlertDescription>Выберите доступный сервер. Данные других устройств не подставляются в этот срез.</AlertDescription></Alert>}
<KpiStatGrid
aria-label="Сводка IPsec"
items={[
@@ -799,7 +794,7 @@ function IpsecPage() {
{isLive && !hasLoaded ? <p role="status" className="p-5 text-sm text-muted-foreground">Состояние серверов IPsec пока неизвестно</p> : <IpsecServerGrid
servers={scopedSummaries}
canManage={canManage}
onInit={isLive ? (s) => { setSelectedServerId(s.serverId); setInitOpen(true) } : undefined}
onInit={isLive ? (s) => { setInitTargetId(s.serverId); setSelectedServerId(s.serverId); setInitOpen(true) } : undefined}
onRemove={isLive ? (s) => setPendingDelete({ kind: "server", summary: s }) : undefined}
onEditPeer={isLive ? openEditPeer : undefined}
onDeletePeer={isLive ? (serverId, peer) => setPendingDelete({ kind: "peer", serverId, peer }) : undefined}
@@ -831,12 +826,10 @@ function IpsecPage() {
lines: [
"/ip ipsec peer add \\",
" name=ipsec-vpn address=0.0.0.0/0 \\",
" exchange-mode=ike2 passive=yes \\",
" certificate=ipsec-server send-cert=always",
" exchange-mode=ike2 passive=yes profile=ipsec-vpn",
"/ip ipsec identity add \\",
" peer=ipsec-vpn auth-method=rsa-key \\",
" peer=ipsec-vpn auth-method=digital-signature \\",
" certificate=ipsec-server \\",
" match-by=certificate \\",
" generate-policy=port-strict \\",
" mode-config=ipsec-vpn",
],
@@ -892,7 +885,6 @@ function IpsecPage() {
onOpenChange={(v) => { setPeerOpen(v); if (!v) setEditingPeer(null) }}
busy={busy}
editing={editingPeer?.peer ?? null}
certificates={certNames}
onSubmit={handleEditPeer}
/>
@@ -901,7 +893,7 @@ function IpsecPage() {
onOpenChange={setInitOpen}
servers={serverOptions.filter(s => canManage(s.id) || busy)}
busy={busy}
defaultServerId={sheetServerId}
defaultServerId={initTargetId ?? sheetServerId}
onSubmit={handleInit}
/>
@@ -959,7 +951,21 @@ function IpsecPage() {
)
}
export default function IpsecSource() {
function IpsecRoute() {
const { mode, backendUrl } = useDataSource()
return <IpsecPage key={JSON.stringify([mode, backendUrl])} />
const params = useSearchParams()
const selectedServerId = params.get("server") ?? ALL_SERVERS_ID
const rawTab = params.get("tab")
const workspaceTab: IpsecWorkspaceTab = rawTab === "server" || rawTab === "cli" ? rawTab : "clients"
const navigate = (key: string, value: string, defaultValue: string) => {
const next = new URLSearchParams(params.toString())
if (value === defaultValue) next.delete(key)
else next.set(key, value)
window.history.pushState(null, "", "/ipsec" + (next.size ? "?" + next.toString() : ""))
}
return <IpsecPage key={JSON.stringify([mode, backendUrl])} selectedServerId={selectedServerId} setSelectedServerId={id => navigate("server", id, ALL_SERVERS_ID)} workspaceTab={workspaceTab} setWorkspaceTab={tab => navigate("tab", tab, "clients")} />
}
export default function IpsecSource() {
return <Suspense fallback={<div role="status">Загрузка IPsec…</div>}><IpsecRoute /></Suspense>
}