This commit is contained in:
Denozordec
2026-05-03 11:16:07 +07:00
parent ce00c4c671
commit bdb9b72fac
66 changed files with 9553 additions and 1547 deletions
+35 -29
View File
@@ -4,6 +4,7 @@ import { Fragment, useState, useMemo, useEffect } from "react"
import { PageHeader } from "@/components/page-header"
import { Card, CardContent } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { cn } from "@/lib/utils"
import {
RefreshCwIcon, DownloadIcon, SearchIcon,
@@ -257,9 +258,9 @@ function PrefixBar({ rx, tx, active }: { rx: number; tx: number; active: number
return (
<div className="flex flex-col gap-1.5 text-[10px] font-mono">
{[
{ label: "Получено", val: rx, color: "bg-emerald-500", w: rx / max },
{ label: "Активных", val: active, color: "bg-sky-500", w: active / max },
{ label: "Отправлено", val: tx, color: "bg-blue-400", w: Math.min(tx / max, 1) },
{ label: "Получено", val: rx, color: "bg-[var(--chart-rx)]", w: rx / max },
{ label: "Активных", val: active, color: "bg-[var(--chart-1)]", w: active / max },
{ label: "Отправлено", val: tx, color: "bg-[var(--chart-tx)]", w: Math.min(tx / max, 1) },
].map(r => (
<div key={r.label} className="flex items-center gap-2">
<span className="w-20 text-muted-foreground shrink-0">{r.label}</span>
@@ -426,15 +427,15 @@ function SessionsTab({ sessions }: { sessions: BgpSession[] }) {
<div className="flex items-center gap-2 flex-wrap">
{/* search */}
<div className="relative">
<SearchIcon className="absolute left-2.5 top-1/2 -translate-y-1/2 size-3.5 text-muted-foreground pointer-events-none" />
<input
<SearchIcon className="absolute left-2.5 top-1/2 -translate-y-1/2 size-3.5 text-muted-foreground pointer-events-none z-10" />
<Input
value={search} onChange={e => setSearch(e.target.value)}
placeholder="IP, AS, описание…"
className="h-8 pl-8 pr-3 w-52 text-xs bg-muted/50 border border-border rounded-md outline-none focus:border-primary transition-colors"
className="h-8 pl-8 pr-8 w-52 text-xs"
/>
{search && (
<button onClick={() => setSearch("")}
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground">
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground z-10">
<XIcon className="size-3" />
</button>
)}
@@ -522,7 +523,7 @@ function SessionsTab({ sessions }: { sessions: BgpSession[] }) {
</td>
<td className="px-3 py-2.5 font-mono tabular-nums text-right">
{s.prefixesTx > 0
? <span className="text-blue-500">{fmtNum(s.prefixesTx)}</span>
? <span className="text-[var(--chart-tx)]">{fmtNum(s.prefixesTx)}</span>
: <span className="text-muted-foreground"></span>}
</td>
</tr>
@@ -582,12 +583,14 @@ function RoutersTab({ sessions }: { sessions: BgpSession[] }) {
</div>
<div className="flex items-center gap-2 shrink-0">
{estCnt > 0 && (
<span className="text-[10px] px-1.5 py-0.5 rounded-full bg-emerald-500/10 text-emerald-500 border border-emerald-500/25 font-medium">
<span className="text-[10px] px-1.5 py-0.5 rounded-full border border-current/25 font-medium"
style={{ background: "var(--status-online-bg)", color: "var(--status-online-fg)" }}>
{estCnt}
</span>
)}
{downCnt > 0 && (
<span className="text-[10px] px-1.5 py-0.5 rounded-full bg-amber-500/10 text-amber-500 border border-amber-500/25 font-medium">
<span className="text-[10px] px-1.5 py-0.5 rounded-full border border-current/25 font-medium"
style={{ background: "var(--status-degraded-bg)", color: "var(--status-degraded-fg)" }}>
{downCnt}
</span>
)}
@@ -832,26 +835,29 @@ export default function BgpPage() {
const [fetchTick, setFetchTick] = useState(0)
useEffect(() => {
if (!isLive) { setLiveSessions([]); return }
if (!isLive) return
let cancelled = false
setLoading(true)
setLiveError(null)
fetch(`${backendUrl}/api/bgp/sessions`)
.then(r => {
if (!r.ok) throw new Error(`HTTP ${r.status}`)
return r.json() as Promise<BackendBgpSession[]>
})
.then(data => {
if (cancelled) return
setLiveSessions(data.map(backendToFrontend))
setFetchedAt(new Date())
setLoading(false)
})
.catch((err: unknown) => {
if (cancelled) return
setLiveError(err instanceof Error ? err.message : String(err))
setLoading(false)
})
queueMicrotask(() => {
if (cancelled) return
setLoading(true)
setLiveError(null)
fetch(`${backendUrl}/api/bgp/sessions`)
.then(r => {
if (!r.ok) throw new Error(`HTTP ${r.status}`)
return r.json() as Promise<BackendBgpSession[]>
})
.then(data => {
if (cancelled) return
setLiveSessions(data.map(backendToFrontend))
setFetchedAt(new Date())
setLoading(false)
})
.catch((err: unknown) => {
if (cancelled) return
setLiveError(err instanceof Error ? err.message : String(err))
setLoading(false)
})
})
return () => { cancelled = true }
}, [isLive, backendUrl, fetchTick])