fix(ui): ограничить граф OSPF с сохранением полного контекста
This commit is contained in:
1 parent
1a82c1b41f
commit
8d86c5cbb0
10 files changed
+194
-13
No files matched your search
@@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { OspfTopologyScope } from "@/components/ospf-topology-scope"
|
||||
import { usePrefersReducedMotion } from "@/hooks/use-prefers-reduced-motion"
|
||||
import { deriveOspfRouterIdentity } from "@/lib/ospf-router-identity"
|
||||
import { buildOspfCostIndex, ospfCostKey } from "@/lib/ospf-cost"
|
||||
@@ -377,6 +378,7 @@ function Chip({ children, color }: { children: React.ReactNode; color?: string }
|
||||
// ─── topology SVG ─────────────────────────────────────────────────────────────
|
||||
|
||||
interface TopologyGraphProps {
|
||||
nodeStates?: ReadonlyMap<string, ReturnType<typeof ospfNodeState>>
|
||||
graphNodes: GraphNode[]
|
||||
graphEdges: GraphEdge[]
|
||||
routerIds: Record<string, string>
|
||||
@@ -387,7 +389,7 @@ interface TopologyGraphProps {
|
||||
onNodeClick: (id: string | null) => void
|
||||
}
|
||||
|
||||
function TopologyGraph({ graphNodes, graphEdges, highlightId, selectedId, showDots, onNodeClick }: TopologyGraphProps) {
|
||||
function TopologyGraph({ graphNodes, graphEdges, nodeStates, highlightId, selectedId, showDots, onNodeClick }: TopologyGraphProps) {
|
||||
const svgRef = useRef<SVGSVGElement>(null)
|
||||
useEffect(() => {
|
||||
const svg = svgRef.current, viewport = svg?.parentElement
|
||||
@@ -405,6 +407,7 @@ function TopologyGraph({ graphNodes, graphEdges, highlightId, selectedId, showDo
|
||||
const nodeMap = Object.fromEntries(graphNodes.map(n => [n.id, n]))
|
||||
|
||||
function nodeState(id: string): keyof typeof NODE_THEME {
|
||||
if (nodeStates?.has(id)) return nodeStates.get(id)!
|
||||
const involved = graphEdges.filter(e => e.from === id || e.to === id)
|
||||
return ospfNodeState(involved.map(edge => edge.state))
|
||||
}
|
||||
@@ -469,7 +472,7 @@ function TopologyGraph({ graphNodes, graphEdges, highlightId, selectedId, showDo
|
||||
(e.from === highlightId && e.to === node.id) ||
|
||||
(e.to === highlightId && e.from === node.id))
|
||||
return (
|
||||
<g role="button" tabIndex={0} aria-label={`Открыть узел ${node.label}`} aria-expanded={isSel} className="focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring" data-ospf-node={node.id} key={node.id} transform={`translate(${node.x},${node.y})`}
|
||||
<g role="button" tabIndex={0} aria-label={`Открыть узел ${node.label}`} aria-expanded={isSel} className="focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring" data-ospf-node={node.id} data-ospf-state={state} key={node.id} transform={`translate(${node.x},${node.y})`}
|
||||
style={{ cursor: "pointer", transition: showDots ? "opacity 0.2s" : "none" }}
|
||||
opacity={dimmed ? 0.25 : 1}
|
||||
onClick={e => { e.stopPropagation(); onNodeClick(isSel ? null : node.id) }}
|
||||
@@ -896,7 +899,7 @@ function NeighborsTab({
|
||||
const reducedMotion = usePrefersReducedMotion()
|
||||
function closeNode() {
|
||||
const previous = selectedId; onSelect(null)
|
||||
if (previous) requestAnimationFrame(() => document.querySelector<SVGGElement>(`[data-ospf-node="${CSS.escape(previous)}"]`)?.focus())
|
||||
if (previous) requestAnimationFrame(() => (document.querySelector<SVGGElement>(`[data-ospf-node="${CSS.escape(previous)}"]`) ?? document.querySelector<HTMLInputElement>('[aria-label="Найти узел топологии"]'))?.focus())
|
||||
}
|
||||
const [showDots, setShowDots] = useState(true)
|
||||
|
||||
@@ -906,7 +909,7 @@ function NeighborsTab({
|
||||
return (
|
||||
<div className="flex flex-col gap-5">
|
||||
<KpiStatGrid
|
||||
gridClassName="grid-cols-2 md:grid-cols-3"
|
||||
gridClassName="grid-cols-1 sm:grid-cols-3"
|
||||
aria-label="Сводка соседей OSPF"
|
||||
items={[
|
||||
{
|
||||
@@ -937,11 +940,13 @@ function NeighborsTab({
|
||||
{selectedId && !graphUnknown && !graphNodes.some(node => node.id === selectedId) && <Alert variant="warning"><AlertTitle>Узел OSPF недоступен</AlertTitle><AlertDescription>Узел из ссылки отсутствует в текущем срезе.<Button type="button" size="sm" variant="outline" onClick={() => onSelect(null)}>Закрыть узел OSPF</Button></AlertDescription></Alert>}
|
||||
{graphUnknown && <p role="status" className="text-sm text-muted-foreground">Топология OSPF неизвестна до успешного опроса всех устройств.</p>}
|
||||
{!graphUnknown && graphNodes.length > 0 && (
|
||||
<div className="flex min-w-0 flex-col rounded-xl overflow-hidden border border-border lg:h-[360px] lg:flex-row">
|
||||
<OspfTopologyScope nodes={graphNodes} edges={graphEdges} selectedId={selectedId} onSelect={onSelect} width={TOPO_W} height={TOPO_H}>
|
||||
{view => <div className="flex min-w-0 flex-col rounded-xl overflow-hidden border border-border lg:h-[360px] lg:flex-row">
|
||||
<div className="relative min-w-0 overflow-x-auto lg:flex-1" style={{ background: "var(--background)", minWidth: 0 }}>
|
||||
<TopologyGraph
|
||||
graphNodes={graphNodes}
|
||||
graphEdges={graphEdges}
|
||||
graphNodes={view.nodes}
|
||||
graphEdges={view.edges}
|
||||
nodeStates={view.nodeStates}
|
||||
routerIds={routerIds}
|
||||
neighbors={neighbors}
|
||||
highlightId={highlightId}
|
||||
@@ -952,7 +957,7 @@ function NeighborsTab({
|
||||
<button type="button" aria-pressed={showDots && !reducedMotion} disabled={reducedMotion} title={reducedMotion ? "Анимация отключена настройками устройства" : undefined}
|
||||
onClick={() => setShowDots(v => !v)}
|
||||
className={cn(
|
||||
"absolute top-3 right-3 flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg border text-[11px] font-medium transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring disabled:opacity-50",
|
||||
"absolute top-3 right-3 flex min-h-11 items-center gap-1.5 px-2.5 py-1.5 rounded-lg border text-[11px] font-medium transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring disabled:opacity-50",
|
||||
showDots
|
||||
? "bg-background border-border text-foreground hover:bg-muted"
|
||||
: "bg-background border-border text-muted-foreground hover:bg-muted",
|
||||
@@ -976,7 +981,8 @@ function NeighborsTab({
|
||||
onClose={closeNode}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>}
|
||||
</OspfTopologyScope>
|
||||
)}
|
||||
|
||||
{!graphUnknown && graphNodes.length > 0 && <div className="flex flex-wrap items-center gap-x-4 gap-y-2 text-xs text-muted-foreground" aria-label="Легенда OSPF">
|
||||
|
||||
Reference in new issue
Block a user