Add switch port labels to nodes with location-aware external detection
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -130,8 +130,7 @@ locations:
|
|||||||
- name: Booth
|
- name: Booth
|
||||||
direction: vertical
|
direction: vertical
|
||||||
children:
|
children:
|
||||||
- name: SATELLITE-1 Rack
|
- nodes:
|
||||||
nodes:
|
|
||||||
- satellite-1
|
- satellite-1
|
||||||
|
|
||||||
- direction: horizontal
|
- direction: horizontal
|
||||||
|
|||||||
@@ -66,8 +66,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.node {
|
.node {
|
||||||
|
position: relative;
|
||||||
width: 120px;
|
width: 120px;
|
||||||
height: 50px;
|
min-height: 50px;
|
||||||
background: #a6d;
|
background: #a6d;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -77,10 +78,29 @@
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
overflow: hidden;
|
overflow: visible;
|
||||||
word-break: normal;
|
word-break: normal;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node .switch-port {
|
||||||
|
position: absolute;
|
||||||
|
top: -8px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
font-size: 9px;
|
||||||
|
background: #444;
|
||||||
|
color: #ccc;
|
||||||
|
padding: 1px 6px;
|
||||||
|
border-radius: 8px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node .switch-port.external {
|
||||||
|
background: #633;
|
||||||
|
color: #f99;
|
||||||
}
|
}
|
||||||
|
|
||||||
.node:hover {
|
.node:hover {
|
||||||
@@ -162,7 +182,7 @@
|
|||||||
|
|
||||||
let anonCounter = 0;
|
let anonCounter = 0;
|
||||||
|
|
||||||
function buildLocationTree(locations, parentId) {
|
function buildLocationTree(locations, parent) {
|
||||||
if (!locations) return [];
|
if (!locations) return [];
|
||||||
return locations.map((loc, idx) => {
|
return locations.map((loc, idx) => {
|
||||||
let locId;
|
let locId;
|
||||||
@@ -173,17 +193,46 @@
|
|||||||
locId = 'loc_anon_' + (anonCounter++);
|
locId = 'loc_anon_' + (anonCounter++);
|
||||||
anonymous = true;
|
anonymous = true;
|
||||||
}
|
}
|
||||||
return {
|
const locObj = {
|
||||||
id: locId,
|
id: locId,
|
||||||
name: loc.name || '',
|
name: loc.name || '',
|
||||||
anonymous: anonymous,
|
anonymous: anonymous,
|
||||||
direction: loc.direction || 'horizontal',
|
direction: loc.direction || 'horizontal',
|
||||||
nodeRefs: (loc.nodes || []).map(n => n.toLowerCase()),
|
nodeRefs: (loc.nodes || []).map(n => n.toLowerCase()),
|
||||||
children: buildLocationTree(loc.children, locId)
|
parent: parent,
|
||||||
|
children: []
|
||||||
};
|
};
|
||||||
|
locObj.children = buildLocationTree(loc.children, locObj);
|
||||||
|
return locObj;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSwitchesInLocation(loc, assignedNodes) {
|
||||||
|
const switches = [];
|
||||||
|
const nodes = assignedNodes.get(loc) || [];
|
||||||
|
nodes.forEach(n => {
|
||||||
|
if (isSwitch(n)) switches.push(n);
|
||||||
|
});
|
||||||
|
loc.children.forEach(child => {
|
||||||
|
if (child.anonymous) {
|
||||||
|
switches.push(...getSwitchesInLocation(child, assignedNodes));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return switches;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findEffectiveSwitch(loc, assignedNodes) {
|
||||||
|
if (!loc) return null;
|
||||||
|
const switches = getSwitchesInLocation(loc, assignedNodes);
|
||||||
|
if (switches.length === 1) {
|
||||||
|
return switches[0];
|
||||||
|
}
|
||||||
|
if (loc.parent) {
|
||||||
|
return findEffectiveSwitch(loc.parent, assignedNodes);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function buildNodeIndex(locations, index) {
|
function buildNodeIndex(locations, index) {
|
||||||
locations.forEach(loc => {
|
locations.forEach(loc => {
|
||||||
loc.nodeRefs.forEach(ref => {
|
loc.nodeRefs.forEach(ref => {
|
||||||
@@ -203,10 +252,26 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNodeElement(node) {
|
function createNodeElement(node, switchConnection, nodeLocation) {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.className = 'node' + (isSwitch(node) ? ' switch' : '');
|
div.className = 'node' + (isSwitch(node) ? ' switch' : '');
|
||||||
div.textContent = getLabel(node);
|
|
||||||
|
if (!isSwitch(node) && switchConnection) {
|
||||||
|
const portEl = document.createElement('div');
|
||||||
|
portEl.className = 'switch-port';
|
||||||
|
if (switchConnection.external) {
|
||||||
|
portEl.classList.add('external');
|
||||||
|
portEl.textContent = switchConnection.switchName + ':' + switchConnection.port;
|
||||||
|
} else {
|
||||||
|
portEl.textContent = switchConnection.port;
|
||||||
|
}
|
||||||
|
div.appendChild(portEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
const labelEl = document.createElement('span');
|
||||||
|
labelEl.textContent = getLabel(node);
|
||||||
|
div.appendChild(labelEl);
|
||||||
|
|
||||||
div.addEventListener('click', () => {
|
div.addEventListener('click', () => {
|
||||||
const json = JSON.stringify(node, null, 2);
|
const json = JSON.stringify(node, null, 2);
|
||||||
navigator.clipboard.writeText(json).then(() => {
|
navigator.clipboard.writeText(json).then(() => {
|
||||||
@@ -217,12 +282,12 @@
|
|||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderLocation(loc, assignedNodes, isTopLevel) {
|
function renderLocation(loc, assignedNodes, isTopLevel, switchConnections) {
|
||||||
const nodes = assignedNodes.get(loc) || [];
|
const nodes = assignedNodes.get(loc) || [];
|
||||||
const hasNodes = nodes.length > 0;
|
const hasNodes = nodes.length > 0;
|
||||||
|
|
||||||
const childElements = loc.children
|
const childElements = loc.children
|
||||||
.map(child => renderLocation(child, assignedNodes, false))
|
.map(child => renderLocation(child, assignedNodes, false, switchConnections))
|
||||||
.filter(el => el !== null);
|
.filter(el => el !== null);
|
||||||
|
|
||||||
if (!hasNodes && childElements.length === 0) {
|
if (!hasNodes && childElements.length === 0) {
|
||||||
@@ -248,7 +313,7 @@
|
|||||||
const switchRow = document.createElement('div');
|
const switchRow = document.createElement('div');
|
||||||
switchRow.className = 'node-row';
|
switchRow.className = 'node-row';
|
||||||
switches.forEach(node => {
|
switches.forEach(node => {
|
||||||
switchRow.appendChild(createNodeElement(node));
|
switchRow.appendChild(createNodeElement(node, null, loc));
|
||||||
});
|
});
|
||||||
container.appendChild(switchRow);
|
container.appendChild(switchRow);
|
||||||
}
|
}
|
||||||
@@ -257,7 +322,8 @@
|
|||||||
const nodeRow = document.createElement('div');
|
const nodeRow = document.createElement('div');
|
||||||
nodeRow.className = 'node-row';
|
nodeRow.className = 'node-row';
|
||||||
nonSwitches.forEach(node => {
|
nonSwitches.forEach(node => {
|
||||||
nodeRow.appendChild(createNodeElement(node));
|
const conn = switchConnections.get(node.typeid);
|
||||||
|
nodeRow.appendChild(createNodeElement(node, conn, loc));
|
||||||
});
|
});
|
||||||
container.appendChild(nodeRow);
|
container.appendChild(nodeRow);
|
||||||
}
|
}
|
||||||
@@ -292,12 +358,19 @@
|
|||||||
const nodeIndex = new Map();
|
const nodeIndex = new Map();
|
||||||
buildNodeIndex(locationTree, nodeIndex);
|
buildNodeIndex(locationTree, nodeIndex);
|
||||||
|
|
||||||
|
const nodesByTypeId = new Map();
|
||||||
|
nodes.forEach(node => {
|
||||||
|
nodesByTypeId.set(node.typeid, node);
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeLocations = new Map();
|
||||||
const assignedNodes = new Map();
|
const assignedNodes = new Map();
|
||||||
const unassignedNodes = [];
|
const unassignedNodes = [];
|
||||||
|
|
||||||
nodes.forEach(node => {
|
nodes.forEach(node => {
|
||||||
const loc = findLocationForNode(node, nodeIndex);
|
const loc = findLocationForNode(node, nodeIndex);
|
||||||
if (loc) {
|
if (loc) {
|
||||||
|
nodeLocations.set(node.typeid, loc);
|
||||||
if (!assignedNodes.has(loc)) {
|
if (!assignedNodes.has(loc)) {
|
||||||
assignedNodes.set(loc, []);
|
assignedNodes.set(loc, []);
|
||||||
}
|
}
|
||||||
@@ -307,11 +380,39 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const switchConnections = new Map();
|
||||||
|
links.forEach(link => {
|
||||||
|
const nodeA = nodesByTypeId.get(link.node_a?.typeid);
|
||||||
|
const nodeB = nodesByTypeId.get(link.node_b?.typeid);
|
||||||
|
if (!nodeA || !nodeB) return;
|
||||||
|
|
||||||
|
const aIsSwitch = isSwitch(nodeA);
|
||||||
|
const bIsSwitch = isSwitch(nodeB);
|
||||||
|
|
||||||
|
if (aIsSwitch && !bIsSwitch) {
|
||||||
|
const nodeLoc = nodeLocations.get(nodeB.typeid);
|
||||||
|
const effectiveSwitch = findEffectiveSwitch(nodeLoc, assignedNodes);
|
||||||
|
switchConnections.set(nodeB.typeid, {
|
||||||
|
port: link.interface_a || '?',
|
||||||
|
switchName: getLabel(nodeA),
|
||||||
|
external: !effectiveSwitch || effectiveSwitch.typeid !== nodeA.typeid
|
||||||
|
});
|
||||||
|
} else if (bIsSwitch && !aIsSwitch) {
|
||||||
|
const nodeLoc = nodeLocations.get(nodeA.typeid);
|
||||||
|
const effectiveSwitch = findEffectiveSwitch(nodeLoc, assignedNodes);
|
||||||
|
switchConnections.set(nodeA.typeid, {
|
||||||
|
port: link.interface_b || '?',
|
||||||
|
switchName: getLabel(nodeB),
|
||||||
|
external: !effectiveSwitch || effectiveSwitch.typeid !== nodeB.typeid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const container = document.getElementById('container');
|
const container = document.getElementById('container');
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
|
|
||||||
locationTree.forEach(loc => {
|
locationTree.forEach(loc => {
|
||||||
const el = renderLocation(loc, assignedNodes, true);
|
const el = renderLocation(loc, assignedNodes, true, switchConnections);
|
||||||
if (el) container.appendChild(el);
|
if (el) container.appendChild(el);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -331,7 +432,7 @@
|
|||||||
const switchRow = document.createElement('div');
|
const switchRow = document.createElement('div');
|
||||||
switchRow.className = 'node-row';
|
switchRow.className = 'node-row';
|
||||||
switches.forEach(node => {
|
switches.forEach(node => {
|
||||||
switchRow.appendChild(createNodeElement(node));
|
switchRow.appendChild(createNodeElement(node, null, null));
|
||||||
});
|
});
|
||||||
unassignedLoc.appendChild(switchRow);
|
unassignedLoc.appendChild(switchRow);
|
||||||
}
|
}
|
||||||
@@ -340,7 +441,8 @@
|
|||||||
const nodeRow = document.createElement('div');
|
const nodeRow = document.createElement('div');
|
||||||
nodeRow.className = 'node-row';
|
nodeRow.className = 'node-row';
|
||||||
nonSwitches.forEach(node => {
|
nonSwitches.forEach(node => {
|
||||||
nodeRow.appendChild(createNodeElement(node));
|
const conn = switchConnections.get(node.typeid);
|
||||||
|
nodeRow.appendChild(createNodeElement(node, conn, null));
|
||||||
});
|
});
|
||||||
unassignedLoc.appendChild(nodeRow);
|
unassignedLoc.appendChild(nodeRow);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user