fix(ui): заменить таблицы на карточки данных и улучшить функциональность поиска
Docker images / prepare-release (push) Successful in 6s
Docker images / backend-image (push) Successful in 1m37s
Docker images / frontend-image (push) Successful in 1m50s
Docker images / updater-image (push) Successful in 44s
Docker images / notify-webhook (push) Has been skipped
Docker images / publish-release (push) Successful in 7s
Docker images / prepare-release (push) Successful in 6s
Docker images / backend-image (push) Successful in 1m37s
Docker images / frontend-image (push) Successful in 1m50s
Docker images / updater-image (push) Successful in 44s
Docker images / notify-webhook (push) Has been skipped
Docker images / publish-release (push) Successful in 7s
This commit is contained in:
+18
-151
@@ -2,6 +2,10 @@
|
||||
|
||||
import { useMemo, useState, useEffect } from "react"
|
||||
import { PageHeader } from "@/components/page-header"
|
||||
import { DataPageCard } from "@/components/data-page-card"
|
||||
import { OspfNeighborsDataGrid } from "@/components/data-grids/ospf-neighbors-data-grid"
|
||||
import { OspfRoutesDataGrid, routeTypeClass } from "@/components/data-grids/ospf-routes-data-grid"
|
||||
import { OspfBfdDataGrid } from "@/components/data-grids/ospf-bfd-data-grid"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
@@ -375,13 +379,6 @@ function stateClass(state: OspfNeighbor["state"] | BfdSession["state"]) {
|
||||
return "bg-[var(--status-offline-bg)] text-[var(--status-offline-fg)] border-current/25"
|
||||
}
|
||||
|
||||
function routeTypeClass(type: OspfRoute["type"]) {
|
||||
if (type === "O") return "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/25"
|
||||
if (type === "O IA") return "bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/25"
|
||||
if (type === "O E1") return "bg-purple-500/10 text-purple-600 dark:text-purple-400 border-purple-500/25"
|
||||
return "bg-orange-500/10 text-orange-600 dark:text-orange-400 border-orange-500/25"
|
||||
}
|
||||
|
||||
function Chip({ children, color }: { children: React.ReactNode; color?: string }) {
|
||||
return (
|
||||
<span className={cn(
|
||||
@@ -978,60 +975,14 @@ function NeighborsTab({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Card className="overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-xs">
|
||||
<thead>
|
||||
<tr className="border-b bg-muted/40">
|
||||
{["Роутер", "Интерфейс", "Сосед (Router ID)", "Область", "Состояние", "Cost", "Uptime", "Prio"].map(h => (
|
||||
<th key={h} className="text-left px-3 py-2.5 font-medium text-muted-foreground whitespace-nowrap">{h}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border/60">
|
||||
{neighbors.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={8} className="px-4 py-8 text-center text-sm text-muted-foreground">
|
||||
Нет OSPF-соседей
|
||||
</td>
|
||||
</tr>
|
||||
) : neighbors.map(n => {
|
||||
const isHighlighted = selectedId === n.localRouter || selectedId === n.remoteRouter
|
||||
return (
|
||||
<tr key={n.id}
|
||||
onMouseEnter={() => setHighlightId(n.localRouter)}
|
||||
onMouseLeave={() => setHighlightId(null)}
|
||||
onClick={() => setSelectedId(prev => prev === n.localRouter ? null : n.localRouter)}
|
||||
className={cn(
|
||||
"transition-colors cursor-pointer",
|
||||
isHighlighted ? "bg-primary/5 hover:bg-primary/8" : "hover:bg-muted/30",
|
||||
)}>
|
||||
<td className="px-3 py-2.5 font-mono whitespace-nowrap">{n.localLabel}</td>
|
||||
<td className="px-3 py-2.5 font-mono text-muted-foreground whitespace-nowrap">{n.localIface}</td>
|
||||
<td className="px-3 py-2.5">
|
||||
<div className="flex flex-col">
|
||||
<span className="font-mono">{n.remoteLabel !== n.remoteRouterId ? n.remoteLabel : n.remoteRouterId}</span>
|
||||
{n.remoteLabel !== n.remoteRouterId && (
|
||||
<span className="text-[10px] font-mono text-muted-foreground">{n.remoteRouterId}</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-3 py-2.5 font-mono text-muted-foreground">{n.area}</td>
|
||||
<td className="px-3 py-2.5">
|
||||
<span className={cn("inline-flex items-center rounded border px-1.5 py-0.5 text-[11px] font-medium", stateClass(n.state))}>
|
||||
{n.state}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-3 py-2.5 font-mono tabular-nums text-center">{n.cost}</td>
|
||||
<td className="px-3 py-2.5 text-muted-foreground whitespace-nowrap tabular-nums">{n.uptime}</td>
|
||||
<td className="px-3 py-2.5 text-center font-mono">{n.priority}</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
<DataPageCard>
|
||||
<OspfNeighborsDataGrid
|
||||
neighbors={neighbors}
|
||||
selectedRouterId={selectedId}
|
||||
onSelect={setSelectedId}
|
||||
onHighlight={setHighlightId}
|
||||
/>
|
||||
</DataPageCard>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1052,36 +1003,9 @@ function RoutesTab({ routes }: { routes: OspfRoute[] }) {
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<Card className="overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-xs">
|
||||
<thead>
|
||||
<tr className="border-b bg-muted/40">
|
||||
{["Назначение", "Тип", "Cost", "Следующий хоп", "Интерфейс", "Роутер", "Область"].map(h => (
|
||||
<th key={h} className="text-left px-3 py-2.5 font-medium text-muted-foreground whitespace-nowrap">{h}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border/60">
|
||||
{routes.map(r => (
|
||||
<tr key={r.id} className="hover:bg-muted/30 transition-colors">
|
||||
<td className="px-3 py-2.5 font-mono">{r.destination}</td>
|
||||
<td className="px-3 py-2.5">
|
||||
<span className={cn("inline-flex items-center rounded border px-1.5 py-0.5 text-[11px] font-medium", routeTypeClass(r.type))}>
|
||||
{r.type}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-3 py-2.5 font-mono tabular-nums text-center">{r.cost}</td>
|
||||
<td className="px-3 py-2.5 font-mono text-muted-foreground">{r.nextHop}</td>
|
||||
<td className="px-3 py-2.5 font-mono text-muted-foreground">{r.via}</td>
|
||||
<td className="px-3 py-2.5 font-mono">{r.serverLabel}</td>
|
||||
<td className="px-3 py-2.5 font-mono text-muted-foreground">{r.area}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
<DataPageCard>
|
||||
<OspfRoutesDataGrid routes={routes} />
|
||||
</DataPageCard>
|
||||
<div className="flex items-center gap-5 flex-wrap px-1">
|
||||
<span className="text-xs text-muted-foreground">Типы:</span>
|
||||
{([
|
||||
@@ -1139,66 +1063,9 @@ function BfdTab({ sessions }: { sessions: BfdSession[] }) {
|
||||
)}
|
||||
|
||||
{sessions.length > 0 && (
|
||||
<Card className="overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-xs">
|
||||
<thead>
|
||||
<tr className="border-b bg-muted/40">
|
||||
{[
|
||||
"Роутер", "Интерфейс", "Локальный", "Удалённый",
|
||||
"Состояние", "Uptime", "Tx / Rx", "Hold", "Mult",
|
||||
"Пакеты Rx", "Пакеты Tx", "Переходы",
|
||||
].map(h => (
|
||||
<th key={h} className="text-left px-3 py-2.5 font-medium text-muted-foreground whitespace-nowrap">{h}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border/60">
|
||||
{sessions.map(b => (
|
||||
<tr key={b.id} className="hover:bg-muted/30 transition-colors">
|
||||
<td className="px-3 py-2.5 font-mono whitespace-nowrap">
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span>{b.serverLabel}</span>
|
||||
{b.multihop && (
|
||||
<Chip color="bg-violet-500/10 text-violet-600 dark:text-violet-400 border-violet-500/20">multihop</Chip>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-3 py-2.5 font-mono text-muted-foreground whitespace-nowrap">{b.iface || "—"}</td>
|
||||
<td className="px-3 py-2.5 font-mono whitespace-nowrap">{b.localAddr}</td>
|
||||
<td className="px-3 py-2.5 font-mono whitespace-nowrap">{b.remoteAddr}</td>
|
||||
<td className="px-3 py-2.5">
|
||||
<span className={cn("inline-flex items-center rounded border px-1.5 py-0.5 text-[11px] font-medium", stateClass(b.state))}>
|
||||
{b.state}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-3 py-2.5 text-muted-foreground whitespace-nowrap tabular-nums">
|
||||
{b.uptime ?? "—"}
|
||||
</td>
|
||||
<td className="px-3 py-2.5 font-mono tabular-nums text-muted-foreground whitespace-nowrap">
|
||||
{fmtMs(b.interval)} / {fmtMs(b.rxInterval)}
|
||||
</td>
|
||||
<td className="px-3 py-2.5 font-mono tabular-nums text-muted-foreground whitespace-nowrap">
|
||||
{fmtMs(b.holdTime)}
|
||||
</td>
|
||||
<td className="px-3 py-2.5 font-mono tabular-nums text-center">{b.multiplier}</td>
|
||||
<td className="px-3 py-2.5 font-mono tabular-nums text-right text-muted-foreground">
|
||||
{b.packetsRx.toLocaleString()}
|
||||
</td>
|
||||
<td className="px-3 py-2.5 font-mono tabular-nums text-right text-muted-foreground">
|
||||
{b.packetsTx.toLocaleString()}
|
||||
</td>
|
||||
<td className="px-3 py-2.5 font-mono tabular-nums text-center">
|
||||
<span className={cn(b.stateChanges > 3 ? "text-[var(--status-degraded-fg)]" : "text-muted-foreground")}>
|
||||
{b.stateChanges}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
<DataPageCard>
|
||||
<OspfBfdDataGrid sessions={sessions} />
|
||||
</DataPageCard>
|
||||
)}
|
||||
|
||||
{sessions.length > 0 && (
|
||||
|
||||
Reference in New Issue
Block a user