fix(bgp): отображать только подтверждённые показатели сессий
This commit is contained in:
1 parent
7046808429
commit
8fdc4b917b
13 files changed
+148
-169
No files matched your search
+24
-26
@@ -11,6 +11,7 @@ import { applyFilterQuery } from "@/lib/data-filters/apply-filter-query"
|
||||
import { BGP_FILTER_FIELDS } from "@/lib/data-filters/bgp-filter-fields"
|
||||
import type { BgpSessionRow, BgpState, BgpType } from "@/lib/bgp/types"
|
||||
import { bgpCatalogSchema, bgpServersSchema, type BackendBgpSession, type BgpFailure } from "@/lib/bgp/response"
|
||||
import { fmtBgpNum, sumBgpCounters } from "@/lib/bgp/helpers"
|
||||
import { BGP_AS_NAMES } from "@/lib/bgp/types"
|
||||
import { Frame, FramePanel } from "@/components/reui/frame"
|
||||
import { KpiStatGrid } from "@/components/reui-kit/kpi-stat-grid"
|
||||
@@ -210,11 +211,7 @@ function TypeBadge({ type }: { type: BgpType }) {
|
||||
}
|
||||
|
||||
|
||||
function fmtNum(n: number) {
|
||||
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`
|
||||
if (n >= 1_000) return `${(n / 1_000).toFixed(0)}k`
|
||||
return String(n)
|
||||
}
|
||||
const fmtNum = fmtBgpNum
|
||||
|
||||
// ─── backend mapping ──────────────────────────────────────────────────────────
|
||||
|
||||
@@ -239,13 +236,13 @@ function backendToFrontend(b: BackendBgpSession): BgpSession {
|
||||
description: b.name,
|
||||
state: Object.hasOwn(STATE_STYLE, b.state) ? b.state as BgpState : "Unknown",
|
||||
type: b.type,
|
||||
afi: "IPv4 Unicast",
|
||||
afi: b.afi ?? "Неизвестно",
|
||||
uptime: b.uptime,
|
||||
holdTime: b.holdTime,
|
||||
keepalive: b.keepalive,
|
||||
prefixesRx: b.prefixesRx,
|
||||
prefixesTx: b.prefixesTx,
|
||||
prefixesActive: b.prefixesRx,
|
||||
prefixesActive: null,
|
||||
inputMessages: b.inputMessages,
|
||||
outputMessages: b.outputMessages,
|
||||
capabilities: b.capabilities,
|
||||
@@ -349,8 +346,8 @@ function RoutersTab({ sessions }: { sessions: BgpSession[] }) {
|
||||
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4">
|
||||
{byRouter.map(router => {
|
||||
const estCnt = router.sessions.filter(s => s.state === "Established").length
|
||||
const downCnt = router.sessions.length - estCnt
|
||||
const totalRx = router.sessions.reduce((a, s) => a + s.prefixesRx, 0)
|
||||
const downCnt = router.sessions.filter(s => s.state !== "Established" && s.state !== "Unknown").length
|
||||
const totalRx = sumBgpCounters(router.sessions.map(s => s.prefixesRx))
|
||||
|
||||
return (
|
||||
<Frame key={router.id} dense className="w-full overflow-hidden">
|
||||
@@ -360,7 +357,7 @@ function RoutersTab({ sessions }: { sessions: BgpSession[] }) {
|
||||
<ServerIcon className="size-4 text-muted-foreground shrink-0" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium leading-none">{router.label}</p>
|
||||
<p className="text-[11px] font-mono text-muted-foreground mt-0.5">{router.site} · AS65001</p>
|
||||
<p className="text-[11px] font-mono text-muted-foreground mt-0.5">{router.site} · {Array.from(new Set(router.sessions.map(s => "AS" + s.localAs))).join(", ")}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
{estCnt > 0 && (
|
||||
@@ -398,12 +395,12 @@ function RoutersTab({ sessions }: { sessions: BgpSession[] }) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="shrink-0 text-right">
|
||||
{s.prefixesRx > 0 && (
|
||||
{s.prefixesRx !== null && s.prefixesRx > 0 && (
|
||||
<p className="text-[10px] font-mono text-emerald-500 tabular-nums">
|
||||
↓ {fmtNum(s.prefixesRx)}
|
||||
</p>
|
||||
)}
|
||||
<p className={cn("text-[10px] font-semibold", ss.text)}>{s.state}</p>
|
||||
<p className={cn("text-[10px] font-semibold", ss.text)}>{ss.label}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -411,7 +408,7 @@ function RoutersTab({ sessions }: { sessions: BgpSession[] }) {
|
||||
</div>
|
||||
|
||||
{/* footer summary */}
|
||||
{totalRx > 0 && (
|
||||
{totalRx !== null && totalRx > 0 && (
|
||||
<div className="flex items-center gap-2 px-4 py-2 border-t bg-muted/20">
|
||||
<ArrowDownIcon className="size-3 text-emerald-500" />
|
||||
<span className="text-[11px] font-mono text-muted-foreground">
|
||||
@@ -432,9 +429,9 @@ function RoutersTab({ sessions }: { sessions: BgpSession[] }) {
|
||||
function AnalyticsTab({ sessions }: { sessions: BgpSession[] }) {
|
||||
// top peers by prefixes received (eBGP only)
|
||||
const topPeers = useMemo(() =>
|
||||
[...sessions]
|
||||
.filter(s => s.prefixesRx > 0)
|
||||
.sort((a, b) => b.prefixesRx - a.prefixesRx)
|
||||
(sessions.some(s => s.prefixesRx === null) ? [] : [...sessions])
|
||||
.filter(s => s.prefixesRx !== null && s.prefixesRx > 0)
|
||||
.sort((a, b) => (b.prefixesRx ?? 0) - (a.prefixesRx ?? 0))
|
||||
.slice(0, 8),
|
||||
[sessions]
|
||||
)
|
||||
@@ -448,8 +445,8 @@ function AnalyticsTab({ sessions }: { sessions: BgpSession[] }) {
|
||||
}, [sessions])
|
||||
|
||||
// total prefix stats
|
||||
const totalRx = sessions.reduce((a, s) => a + s.prefixesRx, 0)
|
||||
const totalActive = sessions.reduce((a, s) => a + s.prefixesActive, 0)
|
||||
const totalRx = sumBgpCounters(sessions.map(s => s.prefixesRx))
|
||||
const totalActive = sumBgpCounters(sessions.map(s => s.prefixesActive))
|
||||
const ebgpSessions = sessions.filter(s => s.type === "eBGP").length
|
||||
const ibgpSessions = sessions.filter(s => s.type === "iBGP").length
|
||||
|
||||
@@ -468,6 +465,7 @@ function AnalyticsTab({ sessions }: { sessions: BgpSession[] }) {
|
||||
{
|
||||
id: "active",
|
||||
label: "Активных маршрутов",
|
||||
hint: totalActive === null ? "нет данных API" : undefined,
|
||||
value: fmtNum(totalActive),
|
||||
icon: <GitMergeIcon className="size-4" />,
|
||||
iconClassName: "text-info",
|
||||
@@ -494,7 +492,7 @@ function AnalyticsTab({ sessions }: { sessions: BgpSession[] }) {
|
||||
<OpsPanel title="Топ-8 пиров по полученным префиксам" contentClassName="pt-4 pb-4 px-5">
|
||||
<div className="flex flex-col gap-3">
|
||||
{topPeers.map((s, i) => {
|
||||
const pct = (s.prefixesRx / maxRx) * 100
|
||||
const pct = ((s.prefixesRx ?? 0) / maxRx) * 100
|
||||
const ss = STATE_STYLE[s.state]
|
||||
return (
|
||||
<div key={s.id} className="flex items-center gap-3">
|
||||
@@ -524,7 +522,7 @@ function AnalyticsTab({ sessions }: { sessions: BgpSession[] }) {
|
||||
)
|
||||
})}
|
||||
{topPeers.length === 0 && (
|
||||
<p className="text-sm text-muted-foreground text-center py-4">Нет данных о префиксах</p>
|
||||
<p className="text-sm text-muted-foreground text-center py-4">Недостаточно данных о префиксах для сравнения</p>
|
||||
)}
|
||||
</div>
|
||||
</OpsPanel>
|
||||
@@ -543,7 +541,7 @@ function AnalyticsTab({ sessions }: { sessions: BgpSession[] }) {
|
||||
return (
|
||||
<div key={state} className="flex items-center gap-2">
|
||||
<span className={cn("size-2 rounded-full shrink-0", ss.dot)} />
|
||||
<span className="text-xs w-24">{state}</span>
|
||||
<span className="text-xs w-24">{ss.label}</span>
|
||||
<div className="flex-1 h-2 rounded-full bg-muted overflow-hidden">
|
||||
<div className={cn("h-full rounded-full", ss.dot)} style={{ width: `${pct}%`, opacity: 0.75 }} />
|
||||
</div>
|
||||
@@ -707,8 +705,8 @@ function BgpPageContent() {
|
||||
}, [displayServers, allSessions, hasLoaded, isLive, unknownIds])
|
||||
|
||||
const established = sessions.filter(s => s.state === "Established").length
|
||||
const notEstab = sessions.length - established
|
||||
const totalRx = sessions.reduce((a, s) => a + s.prefixesRx, 0)
|
||||
const notEstab = sessions.filter(s => s.state !== "Established" && s.state !== "Unknown").length
|
||||
const totalRx = sumBgpCounters(sessions.map(s => s.prefixesRx))
|
||||
|
||||
|
||||
return (
|
||||
@@ -796,14 +794,14 @@ function BgpPageContent() {
|
||||
{
|
||||
id: "established",
|
||||
label: "Established",
|
||||
value: incomplete ? "—" : established,
|
||||
value: incomplete || sessions.some(s => s.state === "Unknown") ? "—" : established,
|
||||
icon: <CheckCircleIcon className="size-4" />,
|
||||
iconClassName: "text-success",
|
||||
},
|
||||
{
|
||||
id: "not-estab",
|
||||
label: "Не установлено",
|
||||
value: incomplete ? "—" : notEstab,
|
||||
value: incomplete || sessions.some(s => s.state === "Unknown") ? "—" : notEstab,
|
||||
icon: <AlertCircleIcon className="size-4" />,
|
||||
iconClassName: notEstab > 0 ? "text-warning" : "text-muted-foreground",
|
||||
variant: notEstab > 0 ? "warning" : "default",
|
||||
@@ -826,7 +824,7 @@ function BgpPageContent() {
|
||||
<span className="font-semibold">{notEstab} сессии</span> не в состоянии Established
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
Проверьте {sessions.filter(s => s.state !== "Established").map(s => s.peerIp).join(", ")}
|
||||
Проверьте {sessions.filter(s => s.state !== "Established" && s.state !== "Unknown").map(s => s.peerIp).join(", ")}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user