Skip to content

Commit eb30887

Browse files
committed
web: reintroduce subnet nodes for editing
1 parent 6be841f commit eb30887

3 files changed

Lines changed: 59 additions & 7 deletions

File tree

web/src/App.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,11 @@ g polygon {
204204
background: rgba(255, 234, 198, 0.30);
205205
}
206206

207+
.node-container-subnet {
208+
border: 2px solid #87c493;
209+
background: rgba(199, 240, 208, 0.35);
210+
}
211+
207212
.node-container-title {
208213
position: absolute;
209214
top: -12px;

web/src/components/NodeCanvas.js

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ function computeContainerBounds(containers, visibleItems, positions) {
106106

107107
function buildAutoPositions(items, edges, containers, hiddenIds, basePositions) {
108108
const visibleItems = items.filter((item) => !hiddenIds.has(item.id));
109+
const subnetContainer = containers.find((c) => c.type === 'subnet');
110+
const subnetNodeIds = new Set(subnetContainer ? subnetContainer.members : []);
111+
const graphItems = visibleItems.filter((item) => !subnetNodeIds.has(item.id));
112+
const subnetItems = visibleItems.filter((item) => subnetNodeIds.has(item.id));
109113
const g = new dagre.graphlib.Graph({ compound: true });
110114
g.setGraph({
111115
rankdir: 'TB',
@@ -116,12 +120,14 @@ function buildAutoPositions(items, edges, containers, hiddenIds, basePositions)
116120
});
117121
g.setDefaultEdgeLabel(() => ({}));
118122

119-
visibleItems.forEach((item) => {
123+
graphItems.forEach((item) => {
120124
g.setNode(item.id, { width: CARD_W, height: CARD_H });
121125
});
122126

123127
containers.forEach((c) => {
124-
const members = c.members.filter((id) => visibleItems.some((n) => n.id === id));
128+
if (c.type === 'subnet')
129+
return;
130+
const members = c.members.filter((id) => graphItems.some((n) => n.id === id));
125131
if (members.length === 0) {
126132
const cid = `empty_cluster_${c.id}`;
127133
g.setNode(cid, { width: 220, height: 80 });
@@ -131,7 +137,7 @@ function buildAutoPositions(items, edges, containers, hiddenIds, basePositions)
131137
members.forEach((id) => g.setParent(id, c.id));
132138
});
133139

134-
const visibleNodeIds = new Set(visibleItems.map((n) => n.id));
140+
const visibleNodeIds = new Set(graphItems.map((n) => n.id));
135141
edges.forEach((e) => {
136142
if (!visibleNodeIds.has(e.from) || !visibleNodeIds.has(e.to))
137143
return;
@@ -141,12 +147,20 @@ function buildAutoPositions(items, edges, containers, hiddenIds, basePositions)
141147
dagre.layout(g);
142148

143149
const positions = {};
144-
visibleItems.forEach((item) => {
150+
graphItems.forEach((item) => {
145151
const n = g.node(item.id);
146152
if (n) {
147153
positions[item.id] = { x: n.x - CARD_W / 2, y: n.y - CARD_H / 2 };
148154
}
149155
});
156+
if (subnetItems.length > 0) {
157+
const graphBottom = Object.values(positions)
158+
.reduce((acc, p) => Math.max(acc, p.y + CARD_H), 0);
159+
const rowY = graphBottom + 120;
160+
subnetItems.forEach((item, idx) => {
161+
positions[item.id] = { x: 20 + idx * (CARD_W + 24), y: rowY };
162+
});
163+
}
150164
return { ...positions, ...(basePositions || {}) };
151165
}
152166

@@ -287,8 +301,15 @@ export default function NodeCanvas(props) {
287301
<div className="node-canvas" ref={canvasRef} onClick={() => onSelect(null)}>
288302
{renderedContainers.map((c) => {
289303
const b = c.bounds;
290-
let cls = c.type === 'netns' ? 'node-container node-container-netns' :
291-
'node-container node-container-vrf';
304+
let cls;
305+
if (c.type === 'netns')
306+
cls = 'node-container node-container-netns';
307+
else if (c.type === 'vrf')
308+
cls = 'node-container node-container-vrf';
309+
else if (c.type === 'subnet')
310+
cls = 'node-container node-container-subnet';
311+
else
312+
cls = 'node-container node-container-vrf';
292313
if (selectedItemId === c.id)
293314
cls += ' node-container-selected';
294315

web/src/components/Pen.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ export default function Pen(props) {
149149
const parsed = parseDot(dot);
150150
const cidrMap = subnetIdsByCidr();
151151

152-
const nodes = Object.values(parsed.nodes).map((n) => {
152+
let nodes = Object.values(parsed.nodes).map((n) => {
153153
const ownerId = nodeOwnerFromGraphId(n.id);
154154
const owner = ownerId ? props.objlist[ownerId] : null;
155155
let subnetRefs = [];
@@ -168,6 +168,21 @@ export default function Pen(props) {
168168
};
169169
});
170170

171+
const existingSubnetOwnerIds = new Set(nodes
172+
.filter((n) => n.type === 'subnet' && n.ownerId)
173+
.map((n) => n.ownerId));
174+
const syntheticSubnetNodes = Object.values(props.objlist)
175+
.filter((o) => o.type === 'subnet' && !existingSubnetOwnerIds.has(o.id))
176+
.map((s) => ({
177+
id: `subnet_${s.id}`,
178+
ownerId: s.id,
179+
type: 'subnet',
180+
label: s.name,
181+
cidrText: s.cidr || '',
182+
subnetRefs: [],
183+
}));
184+
nodes = nodes.concat(syntheticSubnetNodes);
185+
171186
const rawToContainerId = {};
172187
Object.values(parsed.clusters).forEach((c) => {
173188
const vrfLabel = c.label.split(' ')[0];
@@ -192,6 +207,17 @@ export default function Pen(props) {
192207
};
193208
}).filter((c) => !(c.id === 'cluster_ebpfprog' && c.members.length === 0));
194209

210+
const subnetNodeIds = nodes.filter((n) => n.type === 'subnet').map((n) => n.id);
211+
if (subnetNodeIds.length > 0 && !containers.find((c) => c.id === 'cluster_subnet')) {
212+
containers.push({
213+
id: 'cluster_subnet',
214+
type: 'subnet',
215+
label: 'Subnets',
216+
parentId: null,
217+
members: subnetNodeIds,
218+
});
219+
}
220+
195221
setGraph({ nodes, edges: parsed.edges, containers });
196222
}
197223

0 commit comments

Comments
 (0)