Init 2
This commit is contained in:
+42
-28
@@ -345,10 +345,10 @@ function moveInArray<T>(arr: T[], from: number, to: number): T[] {
|
||||
|
||||
function stateClass(state: OspfNeighbor["state"] | BfdSession["state"]) {
|
||||
if (state === "Full" || state === "Up")
|
||||
return "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/25"
|
||||
return "bg-[var(--status-online-bg)] text-[var(--status-online-fg)] border-current/25"
|
||||
if (state === "2-Way" || state === "Init")
|
||||
return "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25"
|
||||
return "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/25"
|
||||
return "bg-[var(--status-degraded-bg)] text-[var(--status-degraded-fg)] border-current/25"
|
||||
return "bg-[var(--status-offline-bg)] text-[var(--status-offline-fg)] border-current/25"
|
||||
}
|
||||
|
||||
function routeTypeClass(type: OspfRoute["type"]) {
|
||||
@@ -573,8 +573,10 @@ function NodeDetailPanel({
|
||||
<span className="size-2 rounded-full shrink-0" style={{ background: theme.stroke }} />
|
||||
<span className="text-xs font-mono font-semibold" style={{ color: theme.stroke }}>{panelState}</span>
|
||||
<div className="flex gap-1.5 ml-auto">
|
||||
{fullCnt > 0 && <span className="text-[10px] px-1.5 py-0.5 rounded-full border border-emerald-500/25 bg-emerald-500/10 text-emerald-400 font-medium">Full ×{fullCnt}</span>}
|
||||
{partCnt > 0 && <span className="text-[10px] px-1.5 py-0.5 rounded-full border border-amber-500/25 bg-amber-500/10 text-amber-400 font-medium">2-Way ×{partCnt}</span>}
|
||||
{fullCnt > 0 && <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)" }}>Full ×{fullCnt}</span>}
|
||||
{partCnt > 0 && <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)" }}>2-Way ×{partCnt}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -630,7 +632,9 @@ function InterfacesTab({ items: initialItems, isLive }: { items: OspfItem[]; isL
|
||||
const [toast, setToast] = useState<string | null>(null)
|
||||
|
||||
// Sync with live data when it changes
|
||||
useEffect(() => { setItems(initialItems) }, [initialItems])
|
||||
useEffect(() => {
|
||||
queueMicrotask(() => setItems(initialItems))
|
||||
}, [initialItems])
|
||||
|
||||
function showToast(msg: string) { setToast(msg); setTimeout(() => setToast(null), 2500) }
|
||||
|
||||
@@ -708,7 +712,8 @@ function InterfacesTab({ items: initialItems, isLive }: { items: OspfItem[]; isL
|
||||
</div>
|
||||
|
||||
{toast && (
|
||||
<div className="flex items-center gap-2 rounded-lg border border-emerald-500/30 bg-emerald-500/10 px-4 py-2.5 text-sm text-emerald-600 dark:text-emerald-400">
|
||||
<div className="flex items-center gap-2 rounded-lg border border-current/20 px-4 py-2.5 text-sm"
|
||||
style={{ background: "var(--status-online-bg)", color: "var(--status-online-fg)" }}>
|
||||
<CheckIcon className="size-4 shrink-0" />{toast}
|
||||
</div>
|
||||
)}
|
||||
@@ -760,7 +765,7 @@ function InterfacesTab({ items: initialItems, isLive }: { items: OspfItem[]; isL
|
||||
dragging !== item.key && dragOver !== item.key && "hover:bg-muted/40",
|
||||
)}>
|
||||
<GripVerticalIcon className={cn("size-3.5 shrink-0", isLive ? "text-muted-foreground/10" : "text-muted-foreground/30")} />
|
||||
<span className={cn("inline-block size-1.5 rounded-full shrink-0", item.active ? "bg-emerald-500" : "bg-muted-foreground/40")} />
|
||||
<span className={cn("inline-block size-1.5 rounded-full shrink-0", item.active ? "bg-[var(--status-online)]" : "bg-muted-foreground/40")} />
|
||||
<code className="text-xs font-mono flex-1 min-w-0 truncate">{item.interfaceName}</code>
|
||||
{item.type && item.type !== "broadcast" && (
|
||||
<Chip color="bg-violet-500/10 text-violet-600 dark:text-violet-400 border-violet-500/20">{item.type}</Chip>
|
||||
@@ -828,8 +833,8 @@ function NeighborsTab({
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{[
|
||||
{ label: "Всего соседей", value: neighbors.length, color: "" },
|
||||
{ label: "Full", value: fullCount, color: "text-emerald-600 dark:text-emerald-400" },
|
||||
{ label: "Не Full", value: neighbors.length - fullCount, color: neighbors.length - fullCount > 0 ? "text-amber-500" : "text-muted-foreground" },
|
||||
{ label: "Full", value: fullCount, color: "text-[var(--status-online-fg)]" },
|
||||
{ label: "Не Full", value: neighbors.length - fullCount, color: neighbors.length - fullCount > 0 ? "text-[var(--status-degraded-fg)]" : "text-muted-foreground" },
|
||||
].map(s => (
|
||||
<Card key={s.label}>
|
||||
<CardContent className="pt-4 pb-3 px-4">
|
||||
@@ -861,7 +866,7 @@ function NeighborsTab({
|
||||
? "bg-black/60 border-white/15 text-white/70 hover:text-white"
|
||||
: "bg-black/60 border-white/10 text-white/30 hover:text-white/60",
|
||||
)}>
|
||||
<span className={cn("size-1.5 rounded-full", showDots ? "bg-emerald-400" : "bg-white/20")} />
|
||||
<span className={cn("size-1.5 rounded-full", showDots ? "bg-[var(--status-online)]" : "bg-white/20")} />
|
||||
Анимация
|
||||
</button>
|
||||
{!selectedId && (
|
||||
@@ -1024,9 +1029,9 @@ function BfdTab({ sessions }: { sessions: BfdSession[] }) {
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
||||
{[
|
||||
{ label: "Сессий BFD", value: sessions.length, color: "" },
|
||||
{ label: "Up", value: upCount, color: "text-emerald-600 dark:text-emerald-400" },
|
||||
{ label: "Down / Admin", value: downCount, color: downCount > 0 ? "text-red-500" : "text-muted-foreground" },
|
||||
{ label: "Init / другие", value: initCount, color: initCount > 0 ? "text-amber-500" : "text-muted-foreground" },
|
||||
{ label: "Up", value: upCount, color: "text-[var(--status-online-fg)]" },
|
||||
{ label: "Down / Admin", value: downCount, color: downCount > 0 ? "text-[var(--status-offline-fg)]" : "text-muted-foreground" },
|
||||
{ label: "Init / другие", value: initCount, color: initCount > 0 ? "text-[var(--status-degraded-fg)]" : "text-muted-foreground" },
|
||||
].map(s => (
|
||||
<Card key={s.label}>
|
||||
<CardContent className="pt-4 pb-3 px-4">
|
||||
@@ -1094,7 +1099,7 @@ function BfdTab({ sessions }: { sessions: BfdSession[] }) {
|
||||
{b.packetsTx.toLocaleString()}
|
||||
</td>
|
||||
<td className="px-3 py-2.5 font-mono tabular-nums text-center">
|
||||
<span className={cn(b.stateChanges > 3 ? "text-amber-500" : "text-muted-foreground")}>
|
||||
<span className={cn(b.stateChanges > 3 ? "text-[var(--status-degraded-fg)]" : "text-muted-foreground")}>
|
||||
{b.stateChanges}
|
||||
</span>
|
||||
</td>
|
||||
@@ -1142,22 +1147,31 @@ export default function OspfPage() {
|
||||
const [fetchTick, setFetchTick] = useState(0)
|
||||
|
||||
// Reset server filter when switching live ↔ mock
|
||||
useEffect(() => { setFilterServerId("all") }, [isLive])
|
||||
useEffect(() => {
|
||||
queueMicrotask(() => setFilterServerId("all"))
|
||||
}, [isLive])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLive) { setLiveData(null); return }
|
||||
if (!isLive) {
|
||||
queueMicrotask(() => setLiveData(null))
|
||||
return
|
||||
}
|
||||
let cancelled = false
|
||||
setLoading(true); setLiveError(null)
|
||||
fetch(`${backendUrl}/api/ospf/all`)
|
||||
.then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json() as Promise<BackendOspfAll> })
|
||||
.then(data => {
|
||||
if (cancelled) return
|
||||
setLiveData(data); 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/ospf/all`)
|
||||
.then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json() as Promise<BackendOspfAll> })
|
||||
.then(data => {
|
||||
if (cancelled) return
|
||||
setLiveData(data); 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])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user