fix(ui): показывать свежесть опроса и изменений IPsec

This commit is contained in:
Denozordec committed 2026-09-30 14:58:55 +07:00
1 parent 921aba3054
commit ce43b2d762
5 files changed
+75 -1

No files matched your search

+24
View File
@@ -183,6 +183,9 @@ function IpsecPage() {
const [dataError, setDataError] = useState<string | null>(null)
const [inventoryError, setInventoryError] = useState<string | null>(null)
const [hasLoaded, setHasLoaded] = useState(false)
const [readReceipt, setReadReceipt] = useState<Record<string, string>>({})
const [responseReceipt, setResponseReceipt] = useState<string | null>(null)
const [writeReceipt, setWriteReceipt] = useState<{ serverId: string; at: string } | null>(null)
const mounted = useRef(true)
const operationLock = useRef(false)
useEffect(() => { mounted.current = true; return () => { mounted.current = false } }, [])
@@ -239,6 +242,10 @@ function IpsecPage() {
setSummaries(previous => [...ipsec.servers, ...previous.filter(s => failedIds.has(s.serverId))])
setClients(previous => [...ipsec.clients, ...previous.filter(c => failedIds.has(c.serverId))])
setFailures(ipsec.failures ?? [])
const at = new Date().toISOString()
setResponseReceipt(at)
const successfulIds = new Set([...ipsec.servers.map(s => s.serverId), ...ipsec.clients.map(c => c.serverId)])
setReadReceipt(previous => ({ ...previous, ...Object.fromEntries([...successfulIds].map(id => [id, at])) }))
setHasLoaded(true)
setDataError(null)
} else {
@@ -292,6 +299,7 @@ function IpsecPage() {
const res = await restoreIpsecRevision(backendUrl, id, historyServerId)
if (!mounted.current) return
if (res?.ok !== true) throw new Error("Сервер не подтвердил восстановление")
confirmOperation(historyServerId)
toast.success("Версия применена на роутер")
await loadLive(true)
await loadRevisions()
@@ -311,6 +319,10 @@ function IpsecPage() {
const assertDeviceReady = (serverId: string) => {
if (operationLock.current || !canManage(serverId)) throw new Error("Сначала подтвердите актуальное состояние устройства: повторите опрос IPsec")
}
const confirmOperation = (serverId: string) => setWriteReceipt({ serverId, at: new Date().toISOString() })
const formatReceipt = (at: string) => new Date(at).toLocaleString("ru-RU", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit" })
const scopeReceipt = effectiveServerId === ALL_SERVERS_ID ? responseReceipt : readReceipt[effectiveServerId]
const scopeWriteReceipt = writeReceipt && (effectiveServerId === ALL_SERVERS_ID || writeReceipt.serverId === effectiveServerId) ? writeReceipt : null
const beginOperation = (serverId: string) => {
assertDeviceReady(serverId)
operationLock.current = true
@@ -407,6 +419,7 @@ function IpsecPage() {
createNatRule: form.createNatRule,
})
if (!mounted.current) return
confirmOperation(form.serverId)
toast.success("IKEv2-сервер инициализирован")
setInitOpen(false)
await loadLive(true)
@@ -429,6 +442,7 @@ function IpsecPage() {
passphrase: form.passphrase.trim() || undefined,
})
if (!mounted.current) return
confirmOperation(form.serverId)
toast.success(`Клиент «${form.name.trim()}» создан`)
setUserOpen(false)
setEditing(null)
@@ -459,6 +473,7 @@ function IpsecPage() {
psk: form.authMethod === "pre-shared-key" && form.psk.trim() ? form.psk : undefined,
})
if (!mounted.current) return
confirmOperation(editing.serverId)
toast.success("Клиент обновлён")
setUserOpen(false)
setEditing(null)
@@ -535,6 +550,7 @@ function IpsecPage() {
await deleteIpsecCert(backendUrl, target.serverId, target.cert.name, { force })
}
if (!mounted.current) return
confirmOperation(serverId)
toast.success("Удалено")
setPendingDelete(null)
setForceAllowed(false)
@@ -568,6 +584,7 @@ function IpsecPage() {
disabled: form.disabled,
})
if (!mounted.current) return
confirmOperation(editingPeer.serverId)
toast.success("Peer обновлён")
setPeerOpen(false)
setEditingPeer(null)
@@ -688,6 +705,13 @@ function IpsecPage() {
]}
/>
{isLive && <div role="group" aria-label="Свежесть IPsec" className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground">
<span data-source-time="read">{effectiveServerId === ALL_SERVERS_ID ? "Последний ответ IPsec получен в приложении: " : "Устройство опрошено, ответ получен в приложении: "}
{scopeReceipt ? <time dateTime={scopeReceipt} title="Клиентское время получения ответа; не время измерения RouterOS">{formatReceipt(scopeReceipt)}</time> : "неизвестно"}
</span>
{scopeWriteReceipt && <span data-source-time="write">Изменение подтверждено API: <time dateTime={scopeWriteReceipt.at} title={`Устройство ${scopeWriteReceipt.serverId}; подтверждение API не заменяет опрос`}>{formatReceipt(scopeWriteReceipt.at)}</time></span>}
</div>}
{isLive && (dataError || inventoryError) ? (
<Alert variant="destructive">
<AlertCircleIcon />