fix(ui): проверять ревизии и сохранять ошибки истории

This commit is contained in:
Denozordec committed 2026-09-29 19:04:00 +07:00
1 parent 46d62c98b8
commit 4a8d696e4e
11 files changed
+183 -98

No files matched your search

+18 -9
View File
@@ -1,6 +1,6 @@
"use client"
import { useMemo, useState, useCallback, useEffect } from "react"
import { useMemo, useState, useCallback, useEffect, useRef } from "react"
import { PageHeader } from "@/components/page-header"
import { EmptyState } from "@/components/empty-state"
import { StatusDot } from "@/components/status-dot"
@@ -37,7 +37,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip
import { toast } from "sonner"
import { CodeExportSheet } from "@/components/reui-kit/code-export-sheet"
import { ConfigHistorySheet } from "@/components/config-history-sheet"
import type { ConfigRevisionDto } from "@/lib/config-revisions"
import { parseConfigRevisions, type ConfigRevisionDto } from "@/lib/config-revisions"
import { ServerRailLayout, ServerRailMobileButton } from "@/components/server-rail-layout"
import { type ServerTileItem } from "@/components/server-tile-rail"
@@ -1334,6 +1334,8 @@ export default function FiltersPage() {
const [liveStale, setLiveStale] = useState(false)
const [historyOpen, setHistoryOpen] = useState(false)
const [historyLoading, setHistoryLoading] = useState(false)
const [historyError, setHistoryError] = useState<string | null>(null)
const historyEpoch = useRef(0)
const [historyRestoring, setHistoryRestoring] = useState(false)
const [revisions, setRevisions] = useState<ConfigRevisionDto[]>([])
@@ -1508,19 +1510,24 @@ export default function FiltersPage() {
}
}, [isLive, selectedServerId, applyBusy, loadLiveRules, ensureGreTunnels, ensureRecursiveRoutes])
useEffect(() => () => { historyEpoch.current += 1 }, [selectedServerId, backendUrl, isLive])
const loadRevisions = useCallback(async () => {
if (!isLive || !selectedServerId) return
const epoch = ++historyEpoch.current
setHistoryLoading(true)
setHistoryError(null)
try {
const res = await apiFetch<{ revisions: ConfigRevisionDto[] }>(
const res = await apiFetch<unknown>(
`/api/filters/revisions?serverId=${encodeURIComponent(selectedServerId)}`,
)
setRevisions(res.revisions)
} catch (err) {
toast.error("Не удалось загрузить историю", { description: String(err) })
setRevisions([])
if (epoch !== historyEpoch.current) return
setRevisions(parseConfigRevisions(res, selectedServerId, "filters"))
} catch (error) {
if (epoch !== historyEpoch.current) return
setHistoryError(error instanceof Error ? error.message : "Не удалось загрузить историю")
} finally {
setHistoryLoading(false)
if (epoch === historyEpoch.current) setHistoryLoading(false)
}
}, [isLive, selectedServerId, apiFetch])
@@ -1881,7 +1888,9 @@ export default function FiltersPage() {
<ConfigHistorySheet
open={historyOpen}
onOpenChange={setHistoryOpen}
onOpenChange={v => { if (!v) historyEpoch.current += 1; setHistoryOpen(v) }}
loadError={historyError}
onRetry={() => { void loadRevisions() }}
title="История фильтров"
itemLabel="правил"
revisions={revisions}
+17 -8
View File
@@ -56,7 +56,7 @@ import { ServerRailLayout, ServerRailMobileButton } from "@/components/server-ra
import { ALL_SERVERS_ID, type ServerTileItem } from "@/components/server-tile-rail"
import { toast } from "sonner"
import { ConfigHistorySheet } from "@/components/config-history-sheet"
import type { ConfigRevisionDto } from "@/lib/config-revisions"
import { parseConfigRevisions, type ConfigRevisionDto } from "@/lib/config-revisions"
// ─── Types ────────────────────────────────────────────────────────────────────
@@ -1952,6 +1952,8 @@ function FirewallPageInner() {
const [historyOpen, setHistoryOpen] = useState(false)
const [revisions, setRevisions] = useState<ConfigRevisionDto[]>([])
const [historyLoading, setHistoryLoading] = useState(false)
const [historyError, setHistoryError] = useState<string | null>(null)
const historyEpoch = useRef(0)
const [historyRestoring, setHistoryRestoring] = useState(false)
const loadLive = useCallback(async () => {
@@ -1978,19 +1980,24 @@ function FirewallPageInner() {
const historyServerId = selectedServerId === ALL_SERVERS_ID ? null : selectedServerId
useEffect(() => () => { historyEpoch.current += 1 }, [historyServerId, backendUrl, isLive])
const loadRevisions = useCallback(async () => {
if (!isLive || !historyServerId) return
const epoch = ++historyEpoch.current
setHistoryLoading(true)
setHistoryError(null)
try {
const res = await apiFetch<{ revisions: ConfigRevisionDto[] }>(
const res = await apiFetch<unknown>(
`/api/firewall/revisions?serverId=${encodeURIComponent(historyServerId)}`,
)
setRevisions(res.revisions)
} catch (err) {
toast.error("Не удалось загрузить историю", { description: String(err) })
setRevisions([])
if (epoch !== historyEpoch.current) return
setRevisions(parseConfigRevisions(res, historyServerId, "firewall"))
} catch (error) {
if (epoch !== historyEpoch.current) return
setHistoryError(error instanceof Error ? error.message : "Не удалось загрузить историю")
} finally {
setHistoryLoading(false)
if (epoch === historyEpoch.current) setHistoryLoading(false)
}
}, [isLive, historyServerId, apiFetch])
@@ -2645,7 +2652,9 @@ function FirewallPageInner() {
<ConfigHistorySheet
open={historyOpen}
onOpenChange={setHistoryOpen}
onOpenChange={v => { if (!v) historyEpoch.current += 1; setHistoryOpen(v) }}
loadError={historyError}
onRetry={() => { void loadRevisions() }}
title="История Firewall"
itemLabel="объектов"
revisions={revisions}
+18 -9
View File
@@ -1,6 +1,6 @@
"use client"
import { Suspense, useCallback, useEffect, useMemo, useState } from "react"
import { Suspense, useCallback, useEffect, useRef, useMemo, useState } from "react"
import Link from "next/link"
import { useRouter, useSearchParams } from "next/navigation"
import { Alert, AlertTitle, AlertDescription } from "@/components/reui/alert"
@@ -21,7 +21,7 @@ import { OpsPanel } from "@/components/ops-panel"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { ConfigHistorySheet } from "@/components/config-history-sheet"
import type { ConfigRevisionDto } from "@/lib/config-revisions"
import { parseConfigRevisions, type ConfigRevisionDto } from "@/lib/config-revisions"
import {
AlertDialog,
AlertDialogAction,
@@ -280,6 +280,8 @@ function GrePageContent({ selectedServerId, selectedTunnelId, allTunnelsHref, on
const [historyOpen, setHistoryOpen] = useState(false)
const [revisions, setRevisions] = useState<ConfigRevisionDto[]>([])
const [historyLoading, setHistoryLoading] = useState(false)
const [historyError, setHistoryError] = useState<string | null>(null)
const historyEpoch = useRef(0)
const [historyRestoring, setHistoryRestoring] = useState(false)
const [poolOpen, setPoolOpen] = useState(false)
const [codePreviewTunnel, setCodePreviewTunnel] = useState<GreTunnel | null>(null)
@@ -373,19 +375,24 @@ function GrePageContent({ selectedServerId, selectedTunnelId, allTunnelsHref, on
const historyServerId = selectedServerId === ALL_SERVERS_ID ? null : selectedServerId
const mutationsLocked = isLive && (mutateBusy || liveStale || selectedTunnelError || historyRestoring)
useEffect(() => () => { historyEpoch.current += 1 }, [historyServerId, backendUrl, isLive])
const loadRevisions = useCallback(async () => {
if (!isLive || !historyServerId) return
const epoch = ++historyEpoch.current
setHistoryLoading(true)
setHistoryError(null)
try {
const res = await apiFetch<{ revisions: ConfigRevisionDto[] }>(
const res = await apiFetch<unknown>(
`/api/gre/revisions?serverId=${encodeURIComponent(historyServerId)}`,
)
setRevisions(res.revisions)
} catch (err) {
toast.error("Не удалось загрузить историю", { description: String(err) })
setRevisions([])
if (epoch !== historyEpoch.current) return
setRevisions(parseConfigRevisions(res, historyServerId, "gre"))
} catch (error) {
if (epoch !== historyEpoch.current) return
setHistoryError(error instanceof Error ? error.message : "Не удалось загрузить историю")
} finally {
setHistoryLoading(false)
if (epoch === historyEpoch.current) setHistoryLoading(false)
}
}, [isLive, historyServerId, apiFetch])
@@ -1073,7 +1080,9 @@ function GrePageContent({ selectedServerId, selectedTunnelId, allTunnelsHref, on
<ConfigHistorySheet
open={historyOpen}
onOpenChange={setHistoryOpen}
onOpenChange={v => { if (!v) historyEpoch.current += 1; setHistoryOpen(v) }}
loadError={historyError}
onRetry={() => { void loadRevisions() }}
title="История GRE"
itemLabel="туннелей"
revisions={revisions}
+3 -4
View File
@@ -61,7 +61,7 @@ import {
type ServerTileItem,
} from "@/components/server-tile-rail"
import { ConfigHistorySheet } from "@/components/config-history-sheet"
import type { ConfigRevisionDto } from "@/lib/config-revisions"
import { parseConfigRevisions, type ConfigRevisionDto } from "@/lib/config-revisions"
import { toast } from "sonner"
import { findFreePoolIp } from "@/lib/ipsec-client"
import {
@@ -255,13 +255,12 @@ export default function IpsecPage() {
setHistoryLoading(true)
setHistoryError(null)
try {
const res = await requestJson<{ revisions: ConfigRevisionDto[] }>(
const res = await requestJson<unknown>(
backendUrl,
`/api/ipsec/revisions?serverId=${encodeURIComponent(historyServerId)}`,
)
if (epoch !== historyEpoch.current) return
if (!Array.isArray(res?.revisions)) throw new Error("Некорректный ответ истории IPsec")
setRevisions(res.revisions)
setRevisions(parseConfigRevisions(res, historyServerId, "ipsec"))
} catch (err) {
if (epoch !== historyEpoch.current) return
setHistoryError(err instanceof Error ? err.message : "Не удалось загрузить историю")
+18 -9
View File
@@ -1,6 +1,6 @@
"use client"
import { useCallback, useEffect, useMemo, useState } from "react"
import { useCallback, useEffect, useRef, useMemo, useState } from "react"
import { PageHeader } from "@/components/page-header"
import {
RecursiveRoutesDataGrid,
@@ -23,7 +23,7 @@ import { toast } from "sonner"
import { requestJson } from "@/shared/api/http-client"
import { ServerRailLayout, ServerRailMobileButton } from "@/components/server-rail-layout"
import { ConfigHistorySheet } from "@/components/config-history-sheet"
import type { ConfigRevisionDto } from "@/lib/config-revisions"
import { parseConfigRevisions, type ConfigRevisionDto } from "@/lib/config-revisions"
import { type ServerTileItem } from "@/components/server-tile-rail"
interface BackendServer {
@@ -379,6 +379,8 @@ export default function RecursiveRoutesPage() {
const [liveStale, setLiveStale] = useState(false)
const [historyOpen, setHistoryOpen] = useState(false)
const [historyLoading, setHistoryLoading] = useState(false)
const [historyError, setHistoryError] = useState<string | null>(null)
const historyEpoch = useRef(0)
const [historyRestoring, setHistoryRestoring] = useState(false)
const [revisions, setRevisions] = useState<ConfigRevisionDto[]>([])
/** В live не дергаем API с id мока (srv1…) пока не подтянули /api/servers */
@@ -472,19 +474,24 @@ export default function RecursiveRoutesPage() {
}
}, [isLive, selectedServerId, liveStale, rows, apiFetch])
useEffect(() => () => { historyEpoch.current += 1 }, [selectedServerId, backendUrl, isLive])
const loadRevisions = useCallback(async () => {
if (!isLive || !selectedServerId) return
const epoch = ++historyEpoch.current
setHistoryLoading(true)
setHistoryError(null)
try {
const res = await apiFetch<{ revisions: ConfigRevisionDto[] }>(
const res = await apiFetch<unknown>(
`/api/recursive-routes/revisions?serverId=${encodeURIComponent(selectedServerId)}`,
)
setRevisions(res.revisions)
} catch (e) {
toast.error(e instanceof Error ? e.message : "Не удалось загрузить историю")
setRevisions([])
if (epoch !== historyEpoch.current) return
setRevisions(parseConfigRevisions(res, selectedServerId, "recursive-routes"))
} catch (error) {
if (epoch !== historyEpoch.current) return
setHistoryError(error instanceof Error ? error.message : "Не удалось загрузить историю")
} finally {
setHistoryLoading(false)
if (epoch === historyEpoch.current) setHistoryLoading(false)
}
}, [isLive, selectedServerId, apiFetch])
@@ -752,7 +759,9 @@ export default function RecursiveRoutesPage() {
<ConfigHistorySheet
open={historyOpen}
onOpenChange={setHistoryOpen}
onOpenChange={v => { if (!v) historyEpoch.current += 1; setHistoryOpen(v) }}
loadError={historyError}
onRetry={() => { void loadRevisions() }}
title="История маршрутов"
itemLabel="маршрутов"
revisions={revisions}