diff --git a/web/gui/src/main/webapp/topo2.css b/web/gui/src/main/webapp/topo2.css index aeaad2d99a..2dd2a0543f 100644 --- a/web/gui/src/main/webapp/topo2.css +++ b/web/gui/src/main/webapp/topo2.css @@ -24,14 +24,33 @@ opacity: 0.5; } + /* NODES */ -#topo svg .node.device { - stroke: none; - stroke-width: 1.5px; +#topo svg .node { cursor: pointer; } +#topo svg .node.selected rect, +#topo svg .node.selected circle { + filter: url(#blue-glow); +} + +/* for debugging */ +#topo svg .node circle.debug { + fill: white; + stroke: red; +} + +#topo svg .node text { + pointer-events: none; +} + +/* Device Nodes */ + +#topo svg .node.device { +} + #topo svg .node.device rect { stroke-width: 1.5px; } @@ -54,31 +73,28 @@ fill: #03c; } -#topo svg .node.host { - fill: #846; -} - /* note: device is offline without the 'online' class */ #topo svg .node.device text { fill: #aaa; font: 10pt sans-serif; - pointer-events: none; } #topo svg .node.device.online text { fill: white; } + +/* Host Nodes */ + +#topo svg .node.host { + fill: #846; +} + #topo svg .node.host text { fill: #846; font: 9pt sans-serif; - pointer-events: none; } -#topo svg .node.selected rect, -#topo svg .node.selected circle { - filter: url(#blue-glow); -} /* LINKS */ @@ -91,20 +107,13 @@ stroke-width: 6px; } -/* for debugging */ -#topo svg .node circle.debug { - fill: white; - stroke: red; -} - -/* detail topo-detail pane */ +/* Fly-in details pane */ #topo-detail { /* gets base CSS from .fpanel in floatPanel.css */ } - #topo-detail h2 { margin: 8px 4px; color: black; @@ -128,7 +137,6 @@ } #topo-detail td.value { - } #topo-detail hr { diff --git a/web/gui/src/main/webapp/topo2.js b/web/gui/src/main/webapp/topo2.js index a23f48dd73..681562ebd9 100644 --- a/web/gui/src/main/webapp/topo2.js +++ b/web/gui/src/main/webapp/topo2.js @@ -127,7 +127,8 @@ P: togglePorts, U: unpin, - X: requestPath + Z: requestPath, + X: cancelMonitor }; // state variables @@ -518,6 +519,13 @@ sendMessage('requestPath', payload); } + function cancelMonitor() { + var payload = { + id: "need_the_intent_id" // FIXME: where are we storing this? + }; + sendMessage('cancelMonitor', payload); + } + // request details for the selected element function requestDetails() { var data = getSel(0).obj, @@ -701,18 +709,57 @@ function positionNode(node) { var meta = node.metaUi, - x = 0, - y = 0; + x = meta && meta.x, + y = meta && meta.y, + xy; - if (meta) { - x = meta.x; - y = meta.y; - } + // If we have [x,y] already, use that... if (x && y) { node.fixed = true; + node.x = x; + node.y = y; + return; } - node.x = x || network.view.width() / 2; - node.y = y || network.view.height() / 2; + + // Note: Placing incoming unpinned nodes at exactly the same point + // (center of the view) causes them to explode outwards when + // the force layout kicks in. So, we spread them out a bit + // initially, to provide a more serene layout convergence. + // Additionally, if the node is a host, we place it near + // the device it is connected to. + + function spread(s) { + return Math.floor((Math.random() * s) - s/2); + } + + function randDim(dim) { + return dim / 2 + spread(dim * 0.7071); + } + + function rand() { + return { + x: randDim(network.view.width()), + y: randDim(network.view.height()) + }; + } + + function near(node) { + var min = 12, + dx = spread(12), + dy = spread(12); + return { + x: node.x + min + dx, + y: node.y + min + dy + }; + } + + function getDevice(cp) { + var d = network.lookup[cp.device]; + return d || rand(); + } + + xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand(); + $.extend(node, xy); } function iconUrl(d) {