fix(ui): показывать свежесть срезов Firewall
This commit is contained in:
1 parent
c2159508d5
commit
4b01f20558
6 files changed
+118
-4
No files matched your search
@@ -22,6 +22,7 @@ import {
|
||||
} from "@/lib/data"
|
||||
import { useDataSource } from "@/lib/data-source"
|
||||
import { firewallResponseSchema, firewallServersSchema, mergeFirewallRows, type FirewallFailure, type FirewallBackendServer } from "@/lib/firewall-response"
|
||||
import { updateFirewallSliceTimes, firewallTimeRange, type FirewallSliceTime } from "@/lib/firewall-freshness"
|
||||
import { requestJson } from "@/shared/api/http-client"
|
||||
import { Frame, FramePanel } from "@/components/reui/frame"
|
||||
import { KpiStatGrid } from "@/components/reui-kit/kpi-stat-grid"
|
||||
@@ -1935,6 +1936,7 @@ function FirewallPageInner() {
|
||||
const [dataLoading, setDataLoading] = useState(false)
|
||||
const [dataError, setDataError] = useState<string | null>(null)
|
||||
const [hasLoaded, setHasLoaded] = useState(false)
|
||||
const [sliceTimes, setSliceTimes] = useState<FirewallSliceTime[]>([])
|
||||
const [failures, setFailures] = useState<FirewallFailure[]>([])
|
||||
const loadEpoch = useRef(0)
|
||||
useEffect(() => () => { loadEpoch.current += 1 }, [backendUrl, isLive])
|
||||
@@ -1990,6 +1992,8 @@ function FirewallPageInner() {
|
||||
setLiveRules(previous => mergeFirewallRows(previous, firewall.data.rules, firewall.data.failures, enabledIds))
|
||||
setLiveAddrLists(previous => mergeFirewallRows(previous, firewall.data.addressLists, firewall.data.failures, enabledIds, true))
|
||||
setFailures(firewall.data.failures)
|
||||
const receivedAt = new Date().toISOString()
|
||||
setSliceTimes(previous => updateFirewallSliceTimes(previous, [...enabledIds], firewall.data.failures, receivedAt))
|
||||
setHasLoaded(true)
|
||||
} catch (e) {
|
||||
if (epoch !== loadEpoch.current) return
|
||||
@@ -2054,6 +2058,7 @@ function FirewallPageInner() {
|
||||
setLiveRules([])
|
||||
setLiveAddrLists([])
|
||||
setHasLoaded(false)
|
||||
setSliceTimes([])
|
||||
setFailures([])
|
||||
setDataError(null)
|
||||
setDataLoading(false)
|
||||
@@ -2151,6 +2156,8 @@ function FirewallPageInner() {
|
||||
|
||||
const scopedFailures = isLive ? failures.filter(f => (selectedServerId === ALL_SERVERS_ID || f.serverId === selectedServerId) && (!f.family || f.family === ipFamily)) : []
|
||||
const firewallIncomplete = scopedFailures.length > 0
|
||||
const scopedSliceTimes = sliceTimes.filter(slice => (selectedServerId === ALL_SERVERS_ID || slice.serverId === selectedServerId) && slice.family === ipFamily)
|
||||
const freshness = firewallTimeRange(scopedSliceTimes)
|
||||
const totalEnabled = familyRules.filter((r) => r.enabled).length
|
||||
const totalHits = familyRules.reduce((s, r) => s + r.hits, 0)
|
||||
const dropRules = familyRules.filter((r) => r.action === "drop" || r.action === "reject").length
|
||||
@@ -2572,10 +2579,25 @@ function FirewallPageInner() {
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{isLive && <div aria-label="Свежесть данных Firewall" className="text-xs text-muted-foreground">
|
||||
{dataLoading && <p role="status">{hasLoaded ? "Обновление данных Firewall…" : "Загрузка данных Firewall…"}</p>}
|
||||
<p title="Время получения данных в приложении; не время измерения на RouterOS">
|
||||
{freshness.oldest ? <>Получено в приложении: <time dateTime={freshness.oldest}>{new Date(freshness.oldest).toLocaleString("ru-RU")}</time>
|
||||
{freshness.newest !== freshness.oldest && <> — <time dateTime={freshness.newest}>{new Date(freshness.newest!).toLocaleString("ru-RU")}</time></>}
|
||||
</> : hasLoaded && liveServers.every(server => !server.enabled) ? "Нет включённых устройств для опроса" : "Время получения срезов неизвестно"}
|
||||
{freshness.unknown > 0 && <> · Не получено срезов: {freshness.unknown}</>}
|
||||
</p>
|
||||
</div>}
|
||||
{firewallIncomplete && <div role="alert" className="rounded-lg border border-warning/30 bg-warning/5 px-4 py-3 text-sm">
|
||||
<p className="font-medium">Не все срезы Firewall получены</p>
|
||||
<p>Сохранены последние данные недоступных срезов; их состояние неизвестно. Обновите данные перед изменениями.</p>
|
||||
{scopedFailures.map((failure, index) => <p key={index}>{[failure.serverName || failure.serverId, failure.family, failure.table, failure.error].filter(Boolean).join(" · ")}</p>)}
|
||||
{scopedFailures.map((failure, index) => {
|
||||
const range = firewallTimeRange(scopedSliceTimes.filter(slice => slice.serverId === failure.serverId && (!failure.family || slice.family === failure.family) && (!failure.table || slice.table === failure.table)))
|
||||
return <p key={index}>{[failure.serverName || failure.serverId, failure.family, failure.table, failure.error].filter(Boolean).join(" · ")} · Последние данные: {range.oldest
|
||||
? <><time dateTime={range.oldest}>{new Date(range.oldest).toLocaleString("ru-RU")}</time>
|
||||
{range.newest !== range.oldest && <> — <time dateTime={range.newest}>{new Date(range.newest!).toLocaleString("ru-RU")}</time></>}
|
||||
</> : "ещё не получены"}{range.unknown > 0 && <> · Не получено срезов: {range.unknown}</>}</p>
|
||||
})}
|
||||
</div>}
|
||||
{moveStatus && <p role="status" className="text-sm text-muted-foreground">{moveStatus}</p>}
|
||||
{moveError && <p role="alert" className="text-sm text-destructive">{moveError}</p>}
|
||||
|
||||
Reference in new issue
Block a user