Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 196 additions & 0 deletions cable_map/frontend/dev/fixtures/ai-fabric-196-topology.json

Large diffs are not rendered by default.

1,000 changes: 1,000 additions & 0 deletions cable_map/frontend/dev/fixtures/large-1000-topology.json

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions cable_map/frontend/dev/fixtures/mixed-sros-topology.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"platform": "7750 SR-1",
"os": "sros",
"role": "superspine",
"tier": 2,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down Expand Up @@ -80,6 +81,7 @@
"platform": "7220 IXR-H4",
"os": "srl",
"role": "spine",
"tier": 3,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down Expand Up @@ -311,6 +313,7 @@
"platform": "7750 SR-1s",
"os": "sros",
"role": "spine",
"tier": 3,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down Expand Up @@ -565,6 +568,7 @@
"platform": "7750 SR-2s",
"os": "sros",
"role": "borderleaf",
"tier": 4,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down Expand Up @@ -668,6 +672,7 @@
"platform": "7220 IXR-D3L",
"os": "srl",
"role": "leaf",
"tier": 4,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down Expand Up @@ -737,6 +742,7 @@
"platform": "7220 IXR-D2",
"os": "srl",
"role": "leaf",
"tier": 4,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down Expand Up @@ -807,6 +813,7 @@
"platform": "7220 IXR-H3",
"os": "srl",
"role": "leaf",
"tier": 4,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down Expand Up @@ -867,6 +874,7 @@
"platform": "7750 SR-1",
"os": "sros",
"role": "leaf",
"tier": 4,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down Expand Up @@ -950,6 +958,7 @@
"platform": "7750 SR-1s",
"os": "sros",
"role": "leaf",
"tier": 4,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down Expand Up @@ -1017,6 +1026,7 @@
"platform": "7730 SXR-1D-32D",
"os": "srl",
"role": "leaf",
"tier": 4,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down Expand Up @@ -1077,6 +1087,7 @@
"platform": "7750 SR-1",
"os": "sros",
"role": "leaf",
"tier": 4,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down Expand Up @@ -1144,6 +1155,7 @@
"platform": "7750 SR-1",
"os": "sros",
"role": "leaf",
"tier": 4,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down Expand Up @@ -1211,6 +1223,7 @@
"platform": "7750 SR-1",
"os": "sros",
"role": "leaf",
"tier": 4,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down Expand Up @@ -1278,6 +1291,7 @@
"platform": "7750 SR-1",
"os": "sros",
"role": "leaf",
"tier": 4,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down Expand Up @@ -1345,6 +1359,7 @@
"platform": "7750 SR-1",
"os": "sros",
"role": "leaf",
"tier": 4,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down Expand Up @@ -1412,6 +1427,7 @@
"platform": "7750 SR-1",
"os": "sros",
"role": "leaf",
"tier": 4,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down Expand Up @@ -1484,6 +1500,7 @@
"platform": "7750 SR-1",
"os": "sros",
"role": "leaf",
"tier": 4,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down Expand Up @@ -1576,6 +1593,7 @@
"platform": "7750 SR-1se",
"os": "sros",
"role": "leaf",
"tier": 4,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down Expand Up @@ -1636,6 +1654,7 @@
"platform": "7750 SR-1s",
"os": "sros",
"role": "leaf",
"tier": 4,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down Expand Up @@ -1703,6 +1722,7 @@
"platform": "7750 SR-1s",
"os": "sros",
"role": "leaf",
"tier": 4,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down Expand Up @@ -1770,6 +1790,7 @@
"platform": "7750 SR-2s",
"os": "sros",
"role": "leaf",
"tier": 4,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down Expand Up @@ -1842,6 +1863,7 @@
"platform": "7750 SR-2se",
"os": "sros",
"role": "leaf",
"tier": 4,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down Expand Up @@ -1914,6 +1936,7 @@
"platform": "7750 SR-2se",
"os": "sros",
"role": "leaf",
"tier": 4,
"namespace": "dev-mixed-sros",
"version": "dev-mixed-sros",
"labels": {
Expand Down
20 changes: 11 additions & 9 deletions cable_map/frontend/scripts/gen-golden-topology.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -77,19 +77,18 @@ function addEndpointLag(lags, name, members, multihomed) {
};
}

function portNumber(label) {
const matches = label.match(/\d+/g);
if (!matches) return 0;
if (matches.length >= 2) return Number(matches[1]) * 100 + Number(matches[2] ?? 0);
return Number(matches[0]) * 100;
}

// natural port order, mirroring the backend's naturalLess (topology.go)
function sortTables(nodes) {
for (const node of nodes) {
node.table = node.table.toSorted((left, right) => portNumber(left.label) - portNumber(right.label) || left.remoteNode.localeCompare(right.remoteNode));
node.table = node.table.toSorted((left, right) =>
left.label.localeCompare(right.label, undefined, { numeric: true })
|| left.remoteNode.localeCompare(right.remoteNode));
}
}

// mirrors the backend's role-derived tier fallback (roleTier in topology.go)
const ROLE_TIER = { backbone: 1, superspine: 2, spine: 3, borderleaf: 4, leaf: 4 };

function collectLabels(nodes) {
const out = {};
for (const node of nodes) {
Expand All @@ -106,7 +105,10 @@ function collectLabels(nodes) {

function topology(nodes, links, lags) {
sortTables(nodes);
return { nodes, links, lags, labels: collectLabels(nodes) };
const tiered = nodes.map(({ name, platform, os, role, ...rest }) => (
{ name, platform, os, role, tier: ROLE_TIER[role] ?? 4, ...rest }
));
return { nodes: tiered, links, lags, labels: collectLabels(nodes) };
}

function buildGoldenTopology() {
Expand Down
5 changes: 3 additions & 2 deletions cable_map/frontend/src/NodeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import TableCell from '@mui/material/TableCell';
import Link from '@mui/material/Link';
import FrontPanel from './FrontPanel';
import RemoteCell from './RemoteCell';
import { cageKey, portIdx } from './domain/ports/interfaceNames';
import { cageKey } from './domain/ports/interfaceNames';
import { slottedOf } from './domain/ports/frontPanelMeta';
import type { Node, Row } from './domain/topology/contract';
import { stencilFor, componentsOf } from './stencil';
Expand Down Expand Up @@ -43,7 +43,8 @@ export default function NodeView({ node, rows, showHeader = true, interactive =
const stencil = stencilFor(node.platform, componentsOf(node.raw));
const slotted = slottedOf(stencil);
const occupied = rows.map((r) => cageKey(r.label, slotted)).filter((c): c is string => c != null);
const sorted = rows.toSorted((a, b) => (portIdx(a.label) ?? 0) - (portIdx(b.label) ?? 0));
// natural order: digit runs compare numerically (ethernet-1-2 before ethernet-1-10), slot-aware
const sorted = rows.toSorted((a, b) => a.label.localeCompare(b.label, undefined, { numeric: true }));
// print leaves empty cells blank (room to write); the drawer shows a dash
const blank = interactive ? <Box component="span" color="text.disabled">—</Box> : null;

Expand Down
1 change: 1 addition & 0 deletions cable_map/frontend/src/generated/topology.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface Node {
platform: string;
os: string;
role: string;
tier?: number /* int */; // canvas row from the cluster's TopologyGrouping (1 = top); role-derived fallback
namespace?: string;
version?: string;
labels?: { [key: string]: string};
Expand Down
10 changes: 6 additions & 4 deletions cable_map/frontend/src/graph/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ export function buildGraph(topo: Topology, options: BuildGraphOptions = {}): Bui
const tiered = options.tiered ?? compact;
const byName = new Map(topo.nodes.map((n) => [n.name, n]));
const role = (name: string) => byName.get(name)?.role ?? 'leaf';
const tierByName = new Map(topo.nodes.map((n) => [n.name, tierOf(n)]));
const tier = (name: string) => tierByName.get(name) ?? 4;
// the stencil each node renders (its platform, or card-derived for modular SROS chassis); drives all
// sizing + FP_META lookups, while node.platform stays the displayed model name.
const stencilByName = new Map(topo.nodes.map((n) => [n.name, stencilFor(n.platform, componentsOf(n.raw))]));
const stencilOf = (name: string) => stencilByName.get(name) ?? '';
const lagOf = (name?: string): LagInfo | undefined => (name ? topo.lags[name] : undefined);
const sideBetween = (node: string, remote: string): 'top' | 'bottom' =>
tierOf(role(remote)) <= tierOf(role(node)) ? 'top' : 'bottom';
tier(remote) <= tier(node) ? 'top' : 'bottom';
const switchHandle = (node: string, remote: string, target = false) =>
compact ? compactSwitchHandle(sideBetween(node, remote), target) : null;

Expand Down Expand Up @@ -71,10 +73,10 @@ export function buildGraph(topo: Topology, options: BuildGraphOptions = {}): Bui

if (r.type === 'interSwitch' && r.remoteNode) {
hay(sw.name).push(r.label);
const remoteTier = tierOf(role(r.remoteNode));
const remoteTier = tier(r.remoteNode);
// uplinks leave the top; a same-tier peer link (leaf↔leaf MLAG ISL) also routes over
// the top, into the open space above — routing it below would dive through the host band.
const side: 'top' | 'bottom' = remoteTier <= tierOf(sw.role) ? 'top' : 'bottom';
const side: 'top' | 'bottom' = remoteTier <= tier(sw.name) ? 'top' : 'bottom';
const rp = cageKey(r.remotePort, slottedOf(stencilOf(r.remoteNode))) ?? p;
// canonical per-cable id, stable across the mirrored far-side row
const cableId = canonicalIslCableId(sw.name, r);
Expand All @@ -86,7 +88,7 @@ export function buildGraph(topo: Topology, options: BuildGraphOptions = {}): Bui
portsOf(sw.name).push({ port: p, kind: 'isl', side, remote: r.remoteNode, remotePort: r.remotePort, desc: r.desc, cableId, lldp: r.lldpStatus });
if (!islSeen.has(cableId)) {
islSeen.add(cableId);
const child = tierOf(sw.role) >= tierOf(role(r.remoteNode)) ? sw.name : r.remoteNode;
const child = tier(sw.name) >= tier(r.remoteNode) ? sw.name : r.remoteNode;
const parent = child === sw.name ? r.remoteNode : sw.name;
const childPort = child === sw.name ? p : rp;
const parentPort = child === sw.name ? rp : p;
Expand Down
8 changes: 5 additions & 3 deletions cable_map/frontend/src/graph/constants.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { LldpStatus } from '../domain/topology/contract';
import type { CableKind } from './types';

// role tier: smaller = closer to the fabric core (drawn higher up).
const TIER: Record<string, number> = { superspine: 0, spine: 1, borderleaf: 2, leaf: 3 };
export const tierOf = (role: string): number => TIER[role] ?? 3;
// Tier: smaller = closer to the fabric core (drawn higher up). The backend computes
// node.tier from the cluster's TopologyGrouping; the role map mirrors EDA's bootstrap
// "role" grouping as a fallback for data that predates the tier field (dev fixtures).
const ROLE_TIER: Record<string, number> = { backbone: 1, superspine: 2, spine: 3, borderleaf: 4, leaf: 4 };
export const tierOf = (n: { tier?: number; role?: string }): number => n.tier ?? ROLE_TIER[n.role ?? ''] ?? 4;

export const LARGE_TOPOLOGY_NODE_THRESHOLD = 300;
export const LARGE_TOPOLOGY_ROW_THRESHOLD = 5000;
Expand Down
Loading
Loading