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

This commit is contained in:
Denozordec
2026-06-30 22:22:51 +07:00
parent a1a9124f3d
commit d3a2d38b37
63 changed files with 8509 additions and 3652 deletions
+18 -151
View File
@@ -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 && (