You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ea...@apache.org on 2018/11/26 15:22:50 UTC

qpid-dispatch git commit: DISPATCH-1190 Fix console's display of edge router connected to multiple interior routers

Repository: qpid-dispatch
Updated Branches:
  refs/heads/master f4192a303 -> dc6b9a709


DISPATCH-1190 Fix console's display of edge router connected to multiple interior routers


Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/dc6b9a70
Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/dc6b9a70
Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/dc6b9a70

Branch: refs/heads/master
Commit: dc6b9a70936c26f291cb3546d2bd48fc14f9f2fa
Parents: f4192a3
Author: Ernest Allen <ea...@redhat.com>
Authored: Mon Nov 26 10:22:35 2018 -0500
Committer: Ernest Allen <ea...@redhat.com>
Committed: Mon Nov 26 10:22:35 2018 -0500

----------------------------------------------------------------------
 console/stand-alone/plugin/css/dispatch.css     |   4 +
 console/stand-alone/plugin/js/topology/links.js | 199 ++++++++++++++++---
 console/stand-alone/plugin/js/topology/nodes.js |  19 +-
 .../plugin/js/topology/qdrTopology.js           |  85 ++++----
 .../stand-alone/plugin/js/topology/traffic.js   |  16 +-
 5 files changed, 242 insertions(+), 81 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dc6b9a70/console/stand-alone/plugin/css/dispatch.css
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/css/dispatch.css b/console/stand-alone/plugin/css/dispatch.css
index 82bb8a6..000b961 100644
--- a/console/stand-alone/plugin/css/dispatch.css
+++ b/console/stand-alone/plugin/css/dispatch.css
@@ -1006,6 +1006,10 @@ svg {
     cursor: default;
   }
   
+  path.link.unknown {
+    stroke-dasharray: 4 3;
+    stroke: #888888;
+  }
   path:not(.traffic) {
       stroke: #000;
   }

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dc6b9a70/console/stand-alone/plugin/js/topology/links.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/topology/links.js b/console/stand-alone/plugin/js/topology/links.js
index 9ce12ce..264cb8c 100644
--- a/console/stand-alone/plugin/js/topology/links.js
+++ b/console/stand-alone/plugin/js/topology/links.js
@@ -23,13 +23,15 @@ class Link {
   constructor(source, target, dir, cls, uid) {
     this.source = source;
     this.target = target;
-    this.left = dir != 'out';
+    this.left = (dir == 'in' || dir == 'both');
     this.right = (dir == 'out' || dir == 'both');
     this.cls = cls;
     this.uid = uid;
   }
   markerId (end) {
     let selhigh = this.highlighted ? 'highlighted' : this.selected ? 'selected' : '';
+    if (selhigh === '' && (!this.left && !this.right))
+      selhigh = 'unknown';
     return `-${selhigh}-${end === 'end' ? this.target.radius() : this.source.radius()}`;
   }
 }
@@ -78,45 +80,158 @@ export class Links {
     return null;
   }
 
+  getPosition (name, nodes, source, client, localStorage, height) {
+    let position = localStorage[name] ? JSON.parse(localStorage[name]) : undefined;
+    if ((typeof position == 'undefined')) {
+      position = {
+        x: Math.round(nodes.get(source).x + 40 * Math.sin(client / (Math.PI * 2.0))),
+        y: Math.round(nodes.get(source).y + 40 * Math.cos(client / (Math.PI * 2.0))),
+        fixed: false,
+        animate: true
+      };
+    } else 
+      position.animate = false;
+    if (position.y > height) {
+      position.y = Math.round(nodes.get(source).y + 40 + Math.cos(client / (Math.PI * 2.0)));
+    }
+    return position;
+  }
+
+  initialize (nodeInfo, nodes, unknowns, localStorage, height) {
+    let connectionsPerContainer = {};
+    let nodeIds = Object.keys(nodeInfo);
+    // collect connection info for each router
+    for (let source=0; source<nodeIds.length; source++) {
+      let onode = nodeInfo[nodeIds[source]];
+      // skip any routers without connections
+      if (!onode.connection || !onode.connection.results || onode.connection.results.length === 0)
+        continue;
+
+      const suid = nodes.get(source).uid();
+      for (let c=0; c<onode.connection.results.length; c++) {
+        let connection = utils.flatten(onode.connection.attributeNames, onode.connection.results[c]);
+
+        // this is a connection to another interior router
+        if (connection.role === 'inter-router') {
+          const target = getContainerIndex(connection.container, nodeInfo);
+          if (target >= 0) {
+            const tuid = nodes.get(target).uid();
+            this.getLink(source, target, connection.dir, '', `${suid}-${tuid}`);
+          }
+          continue;
+        }
+        if (!connectionsPerContainer[connection.container])
+          connectionsPerContainer[connection.container] = [];
+        let linksDir = getLinkDir(connection , onode);
+        if (linksDir === 'unknown')
+          unknowns.push(nodeIds[source]);
+        connectionsPerContainer[connection.container].push({
+          source: source, 
+          linksDir: linksDir,
+          connection: connection,
+          resultsIndex: c});
+      }
+    }
+    let unique = {};
+    // create map of type:id:dir to [containers]
+    for (let container in connectionsPerContainer) {
+      let key = getKey(connectionsPerContainer[container]);
+      if (!unique[key])
+        unique[key] = {c: [], nodes: []};
+      unique[key].c.push(container);
+    }
+    for (let key in unique) {
+      let containers = unique[key].c;
+      for (let i=0; i<containers.length; i++) {
+        let containerId = containers[i];
+        let connections = connectionsPerContainer[containerId];
+        let container = connections[0];
+        let name = utils.nameFromId(nodeIds[container.source]) + '.' + container.connection.identity;
+        let position = this.getPosition (name, 
+          nodes, 
+          container.source, 
+          container.resultsIndex, 
+          localStorage, 
+          height);
+
+        let node = nodes.getOrCreateNode (nodeIds[container.source], 
+          name, 
+          container.connection.role, 
+          nodes.getLength(), 
+          position.x, position.y, 
+          container.connection.container, 
+          container.resultsIndex, 
+          position.fixed, 
+          container.connection.properties);
+        node.host = container.connection.host;
+        node.cdir = container.linksDir;
+        node.user = container.connection.user;
+        node.isEncrypted = container.connection.isEncrypted;
+        node.connectionId = container.connection.identity;
+        node.uuid = `${node.routerId}${node.nodeType}${node.cdir}`;
+        // in case a created node (or group) is connected to multiple
+        // routers, we need to remember all the routers for traffic animations
+        for (let c=1; c<connections.length; c++) {
+          if (!node.alsoConnectsTo)
+            node.alsoConnectsTo = [];
+          node.alsoConnectsTo.push({
+            key: nodeIds[connections[c].source],
+            dir: connections[c].linksDir,
+            connectionId: connections[c].connection.identity});
+        }
+        unique[key].nodes.push(node);
+      }
+    }
+    for (let key in unique) {
+      nodes.add(unique[key].nodes[0]);
+      let target = nodes.nodes.length - 1;
+      unique[key].nodes[0].normals = [unique[key].nodes[0]];
+      for (let n=1; n<unique[key].nodes.length; n++) {
+        unique[key].nodes[0].normals.push(unique[key].nodes[n]);
+      }
+      let containerId = unique[key].c[0];
+      let links = connectionsPerContainer[containerId];
+      for (let l=0; l<links.length; l++) {
+        let source = links[l].source;
+        const suid = nodes.get(source).uid();
+        const tuid = nodes.get(target).uid();
+        this.getLink(links[l].source, target, links[l].linksDir, 'small', `${suid}-${tuid}`);
+      }
+    }
+  }
+
   initializeLinks (nodeInfo, nodes, unknowns, localStorage, height) {
     let animate = false;
     let client = 1.0;
     let nodeIds = Object.keys(nodeInfo);
+    // loop through all the routers
     for (let source=0; source<nodeIds.length; source++) {
       let id = nodeIds[source];
       const suid = nodes.get(source).uid();
-      let parts = id.split('/');
-      let routerType = parts[1]; // _topo || _edge
       let onode = nodeInfo[id];
       if (!onode['connection']) {
         continue;
       }
-      let conns = onode['connection'].results;
-      let attrs = onode['connection'].attributeNames;
       let normalsParent = {}; // 1st normal node for this parent
-
-      for (let j = 0; j < conns.length; j++) {
-        let connection = utils.flatten(attrs, conns[j]);
+      // loop through each connection for this router
+      for (let j = 0; j < onode['connection'].results.length; j++) {
+        let connection = utils.flatten(onode['connection'].attributeNames, 
+          onode['connection'].results[j]);
         let role = connection.role;
-        let properties = connection.properties || {};
         let dir = connection.dir;
+        // connection to another interior router, just create a link between them
         if (role == 'inter-router') {
-          // there are already 2 router nodes, just link them
           let connId = connection.container;
           let target = getContainerIndex(connId, nodeInfo);
           if (target >= 0) {
             const tuid = nodes.get(target).uid();
             this.getLink(source, target, dir, '', suid + '-' + tuid);
           }
+          continue;
         }
+        let properties = connection.properties || {};
         // handle external connections
         let name = utils.nameFromId(id) + '.' + connection.identity;
-        // is this connection for a router connected to an edge router
-        if (role == 'edge' && routerType === '_edge') {
-          name = connection.container;
-          role = 'inter-router';
-        }
-
         // if we have any new clients, animate the force graph to position them
         let position = localStorage[name] ? JSON.parse(localStorage[name]) : undefined;
         if ((typeof position == 'undefined')) {
@@ -134,7 +249,7 @@ export class Links {
         let normalInfo = nodes.normalExists(connection.container);
         let node = nodes.getOrCreateNode(id, name, role, nodeInfo, nodes.getLength(), position.x, position.y, connection.container, j, position.fixed, properties);
         let nodeType = utils.isAConsole(properties, connection.identity, role, node.key) ? 'console' : 'client';
-        let cdir = getLinkDir(connection, onode);
+        let linksDir = getLinkDir(connection, onode);
         if (existingNodeIndex >= 0) {
           // make a link between the current router (source) and the existing node
           const tuid = nodes.get(existingNodeIndex).uid();
@@ -143,38 +258,39 @@ export class Links {
           // get node index of node that contained this connection in its normals array
           let normalSource = this.getLinkSource(normalInfo.nodesIndex);
           if (normalSource >= 0) {
-            if (cdir === 'unknown')
-              cdir = dir;
-            node.cdir = cdir;
+            if (linksDir === 'unknown')
+              linksDir = dir;
+            node.cdir = linksDir;
             nodes.add(node);
             const suidn = nodes.get(this.links[normalSource].source).uid();
             const tuid = node.uid();
             // create link from original node to the new node
-            this.getLink(this.links[normalSource].source, nodes.getLength()-1, cdir, 'small', suidn + '-' + tuid);
+            this.getLink(this.links[normalSource].source, nodes.getLength()-1, linksDir, 'small', suidn + '-' + tuid);
             // create link from this router to the new node
-            this.getLink(source, nodes.getLength()-1, cdir, 'small', suid + '-' + tuid);
+            this.getLink(source, nodes.getLength()-1, linksDir, 'small', suid + '-' + tuid);
             // remove the old node from the normals list
             nodes.get(normalInfo.nodesIndex).normals.splice(normalInfo.normalsIndex, 1);
           }
         } else if (role === 'normal' || role === 'edge') {
         // normal nodes can be collapsed into a single node if they are all the same dir
-          if (cdir !== 'unknown') {
+          if (linksDir !== 'unknown') {
             node.user = connection.user;
             node.isEncrypted = connection.isEncrypted;
             node.host = connection.host;
             node.connectionId = connection.identity;
-            node.cdir = cdir;
+            node.cdir = linksDir;
             node.uuid = `${node.routerId}${node.nodeType}${node.cdir}`;
             // determine arrow direction by using the link directions
-            if (!normalsParent[nodeType+cdir]) {
-              normalsParent[nodeType+cdir] = node;
+            if (!normalsParent[nodeType+linksDir]) {
+              normalsParent[nodeType+linksDir] = node;
               nodes.add(node);
               node.normals = [node];
+              node.connectsTo = {id: linksDir};
               // now add a link
-              this.getLink(source, nodes.getLength() - 1, cdir, 'small', suid + '-' + node.uid());
+              this.getLink(source, nodes.getLength() - 1, linksDir, 'small', suid + '-' + node.uid());
               client++;
             } else {
-              normalsParent[nodeType+cdir].normals.push(node);
+              normalsParent[nodeType+linksDir].normals.push(node);
             }
           } else {
             node.id = nodes.getLength() - 1 + unknowns.length;
@@ -213,10 +329,12 @@ var getLinkDir = function (connection, onode) {
     return 'unknown';
   }
   let inCount = 0, outCount = 0;
+  let typeIndex = links.attributeNames.indexOf('linkType');
+  let connectionIdIndex = links.attributeNames.indexOf('connectionId');
+  let dirIndex = links.attributeNames.indexOf('linkDir');
   links.results.forEach( function (linkResult) {
-    let link = utils.flatten(links.attributeNames, linkResult);
-    if (link.linkType === 'endpoint' && link.connectionId === connection.identity)
-      if (link.linkDir === 'in')
+    if (linkResult[typeIndex] === 'endpoint' && linkResult[connectionIdIndex] === connection.identity)
+      if (linkResult[dirIndex] === 'in')
         ++inCount;
       else
         ++outCount;
@@ -229,4 +347,23 @@ var getLinkDir = function (connection, onode) {
     return 'out';
   return 'unknown';
 };
+var getKey = function (containers) {
+  let parts = [];
+  let connection = containers[0].connection;
+  let d = {nodeType: connection.role, properties: connection.properties || {}};
+  let connectionType = 'client';
+  if (utils.isConsole(connection))
+    connectionType = 'console';
+  else if (utils.isArtemis(d))
+    connectionType = 'artemis';
+  else if (utils.isQpid(d))
+    connectionType = 'qpid';
+  else if (connection.role === 'edge')
+    connectionType = 'edge';
+  for (let c=0; c<containers.length; c++) {
+    let container = containers[c];
+    parts.push(`${container.source}-${container.linksDir}`);
+  }
+  return `${connectionType}:${parts.join(':')}`;
+};
 

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dc6b9a70/console/stand-alone/plugin/js/topology/nodes.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/topology/nodes.js b/console/stand-alone/plugin/js/topology/nodes.js
index 619d54c..5d003c6 100644
--- a/console/stand-alone/plugin/js/topology/nodes.js
+++ b/console/stand-alone/plugin/js/topology/nodes.js
@@ -138,9 +138,14 @@ export class Node {
   }
   uid() {
     if (!this.uuid)
-      this.uuid = utils.nameFromId(this.key);
+      this.uuid = this.container;
     return this.normals ? `${this.uuid}-${this.normals.length}` : this.uuid;
   }
+  setFixed(fixed) {
+    if (!fixed)
+      this.lat = this.lon = null;
+    this.fixed = fixed;
+  }
 }
 const nodeProperties = {
   // router types
@@ -220,9 +225,7 @@ export class Nodes {
   setNodesFixed (name, b) {
     this.nodes.some(function (n) {
       if (n.name === name) {
-        n.fixed = b;
-        if (!b)
-          n.lat = n.lon = null;
+        n.fixed(b);
         return true;
       }
     });
@@ -320,7 +323,7 @@ export class Nodes {
     }
     return undefined;
   }
-  getOrCreateNode (id, name, nodeType, nodeInfo, nodeIndex, x, y, 
+  getOrCreateNode (id, name, nodeType, nodeIndex, x, y, 
     connectionContainer, resultIndex, fixed, properties) {
     properties = properties || {};
     let gotNode = this.find(connectionContainer, properties, name);
@@ -335,9 +338,9 @@ export class Nodes {
     this.nodes.push(obj);
     return obj;
   }
-  addUsing (id, name, nodeType, nodeInfo, nodeIndex, x, y, 
+  addUsing (id, name, nodeType, nodeIndex, x, y, 
     connectContainer, resultIndex, fixed, properties) {
-    let obj = this.getOrCreateNode(id, name, nodeType, nodeInfo, nodeIndex, x, y, 
+    let obj = this.getOrCreateNode(id, name, nodeType, nodeIndex, x, y, 
       connectContainer, resultIndex, fixed, properties);
     this.nodes.push(obj);
     return obj;
@@ -368,7 +371,7 @@ export class Nodes {
         yInit *= -1;
       }
       let parts = id.split('/');
-      this.addUsing(id, name, parts[1], nodeInfo, this.nodes.length, position.x, position.y, name, undefined, position.fixed, {});
+      this.addUsing(id, name, parts[1], this.nodes.length, position.x, position.y, name, undefined, position.fixed, {});
     }
     return animate;
   }

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dc6b9a70/console/stand-alone/plugin/js/topology/qdrTopology.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/topology/qdrTopology.js b/console/stand-alone/plugin/js/topology/qdrTopology.js
index f8c1d7d..e5cb783 100644
--- a/console/stand-alone/plugin/js/topology/qdrTopology.js
+++ b/console/stand-alone/plugin/js/topology/qdrTopology.js
@@ -41,7 +41,7 @@ export class TopologyController {
     //  - nodes is an array of router/client info. these are the circles
     //  - links is an array of connections between the routers. these are the lines with arrows
     let nodes = new Nodes(QDRLog);
-    let links = new Links(QDRService, QDRLog);
+    let links = new Links(QDRLog);
     let forceData = {nodes: nodes, links: links};
 
     $scope.legendOptions = angular.fromJson(localStorage[TOPOOPTIONSKEY]) || {showTraffic: false, trafficType: 'dots', mapOpen: false, legendOpen: true};
@@ -148,11 +148,12 @@ export class TopologyController {
 
     $scope.setFixed = function(b) {
       if ($scope.contextNode) {
-        $scope.contextNode.fixed = b;
-        nodes.setNodesFixed($scope.contextNode.name, b);
+        $scope.contextNode.setFixed(b);
         nodes.savePositions();
         nodes.saveLonLat(backgroundMap, $scope.contextNode);
       }
+      if (!b)
+        animate = true;
       restart();
     };
     $scope.isFixed = function() {
@@ -234,7 +235,7 @@ export class TopologyController {
     // initialize the nodes and links array from the QDRService.topology._nodeInfo object
     var initForceGraph = function() {
       forceData.nodes = nodes = new Nodes(QDRLog);
-      forceData.links = links = new Links(QDRService, QDRLog);
+      forceData.links = links = new Links(QDRLog);
       let nodeInfo = QDRService.management.topology.nodeInfo();
       let nodeCount = Object.keys(nodeInfo).length;
 
@@ -278,7 +279,7 @@ export class TopologyController {
 
       // initialize the list of links
       let unknowns = [];
-      if (links.initializeLinks(nodeInfo, nodes, unknowns, localStorage, height)) {
+      if (links.initialize(nodeInfo, nodes, unknowns, localStorage, height)) {
         animate = true;
       }
       $scope.schema = QDRService.management.schema();
@@ -300,7 +301,7 @@ export class TopologyController {
       //  start|end, ''|selected highlighted, and each possible node radius
       {
         let sten = ['start', 'end'];
-        let states = ['', 'selected', 'highlighted'];
+        let states = ['', 'selected', 'highlighted', 'unknown'];
         let radii = Nodes.discrete();
         let defs = [];
         for (let isten=0; isten<sten.length; isten++) {
@@ -324,7 +325,7 @@ export class TopologyController {
           .attr('d', function (d) {
             return d.sten === 'end' ? 'M 0 -5 L 10 0 L 0 5 z' : 'M 10 -5 L 0 0 L 10 5 z';
           });
-        addStyles (sten, {selected: '#33F', highlighted: '#6F6'}, radii);
+        addStyles (sten, {selected: '#33F', highlighted: '#6F6', unknown: '#888'}, radii);
       }
       // gradient for sender/receiver client
       let grad = svg.append('svg:defs').append('linearGradient')
@@ -341,10 +342,8 @@ export class TopologyController {
       circle = svg.append('svg:g').attr('class', 'nodes').selectAll('g');
 
       // app starts here
-      if (unknowns.length === 0) {
-        restart(false);
-        force.start();
-      }
+      if (unknowns.length === 0)
+        restart();
       if (oldSelectedNode) {
         d3.selectAll('circle.inter-router').classed('selected', function (d) {
           if (d.key === oldSelectedNode.key) {
@@ -382,26 +381,31 @@ export class TopologyController {
 
     // To start up quickly, we only get the connection info for each router.
     // That means we don't have the router.link info when links.initialize() is first called.
-    // The router.link info is needed to determine which direction the arrows between routers should point.
+    // The router.link info is needed to determine which direction the arrows between routers
+    // and client should point. (Direction between interior routers is determined by connection.dir)
     // So, the first time through links.initialize() we keep track of the nodes for which we 
     // need router.link info and fill in that info here.
     var resolveUnknowns = function (nodeInfo, unknowns) {
       let unknownNodes = {};
-      // collapse the unknown node.keys using an object
+      // collapse the unknown nodes using an object
       for (let i=0; i<unknowns.length; ++i) {
-        unknownNodes[unknowns[i].key] = 1;
+        unknownNodes[unknowns[i]] = 1;
       }
       unknownNodes = Object.keys(unknownNodes);
       QDRService.management.topology.ensureEntities(unknownNodes, 
         [{entity: 'router.link', 
           attrs: ['linkType','connectionId','linkDir','owningAddr'], 
           force: true}], 
-        function (foo, results) {
-          forceData.links = links = new Links(QDRService, QDRLog);
-          links.initializeLinks(results, nodes, [], localStorage, height);
+        function () {
+          let nodeInfo = QDRService.management.topology.nodeInfo();
+          forceData.nodes = nodes = new Nodes(QDRLog);
+          nodes.initialize(nodeInfo, localStorage, width, height);
+          forceData.links = links = new Links(QDRLog);
+          links.initialize(nodeInfo, nodes, [], localStorage, height);
           animate = true;
           force.nodes(nodes.nodes).links(links.links).start();
-          restart(false);
+          nodes.saveLonLat(backgroundMap);
+          restart();
         });
     };
 
@@ -459,7 +463,7 @@ export class TopologyController {
     // Takes the forceData.nodes and forceData.links array and creates svg elements
     // Also updates any existing svg elements based on the updated values in forceData.nodes
     // and forceData.links
-    function restart(start) {
+    function restart() {
       if (!circle)
         return;
       circle.call(force.drag);
@@ -476,7 +480,11 @@ export class TopologyController {
         })
         .classed('highlighted', function(d) {
           return d.highlighted;
+        })
+        .classed('unknown', function (d) {
+          return !d.right && !d.left;
         });
+
       // reset the markers based on current highlighted/selected
       if (!$scope.legend.status.optionsOpen || $scope.legendOptions.trafficType === 'dots') {
         path.select('.link')
@@ -484,7 +492,7 @@ export class TopologyController {
             return d.right ? `url(${urlPrefix}#end${d.markerId('end')})` : null;
           })
           .attr('marker-start', function(d) {
-            return d.left ? `url(${urlPrefix}#start${d.markerId('start')})` : null;
+            return (d.left || (!d.left && !d.right)) ? `url(${urlPrefix}#start${d.markerId('start')})` : null;
           });
       }
       // add new links. if a link with a new uid is found in the data, add a new path
@@ -531,12 +539,15 @@ export class TopologyController {
           return d.right ? `url(${urlPrefix}#end${d.markerId('end')})` : null;
         })
         .attr('marker-start', function(d) {
-          return d.left ? `url(${urlPrefix}#start${d.markerId('start')})` : null;
+          return (d.left || (!d.left && !d.right)) ? `url(${urlPrefix}#start${d.markerId('start')})` : null;
         })
         .attr('id', function (d) {
           const si = d.source.uid(QDRService);
           const ti = d.target.uid(QDRService);
           return ['path', si, ti].join('-');
+        })
+        .classed('unknown', function (d) {
+          return !d.right && !d.left;
         });
 
       enterpath.append('path')
@@ -631,15 +642,15 @@ export class TopologyController {
           // check for drag
           mouseup_node = d;
 
-          let mySvg = this.parentNode.parentNode.parentNode;
+          let mySvg = d3.select('#SVG_ID').node();
           // if we dragged the node, make it fixed
           let cur_mouse = d3.mouse(mySvg);
           if (cur_mouse[0] != initial_mouse_down_position[0] ||
             cur_mouse[1] != initial_mouse_down_position[1]) {
-            d.fixed = true;
-            nodes.setNodesFixed(d.name, true);
+            d.setFixed(true);
             nodes.savePositions(d);
             nodes.saveLonLat(backgroundMap, d);
+            console.log('savedLonLat for fixed node');
             resetMouseVars();
             restart();
             return;
@@ -662,7 +673,7 @@ export class TopologyController {
           if (d.normals && !d.isConsole && !d.isArtemis) {
             doDialog(d);
           }
-          restart(false);
+          restart();
 
         })
         .on('dblclick', function(d) { // circle
@@ -730,8 +741,6 @@ export class TopologyController {
       if (!$scope.mousedown_node || !selected_node)
         return;
 
-      if (!start)
-        return;
       // set the graph in motion
       //QDRLog.debug("mousedown_node is " + mousedown_node);
       force.start();
@@ -747,35 +756,35 @@ export class TopologyController {
         .attr('transform', `translate(${Nodes.maxRadius()}, ${Nodes.maxRadius()})`)
         .selectAll('g');
       let legendNodes = new Nodes(QDRLog);
-      legendNodes.addUsing('Router', '', 'inter-router', '', undefined, 0, 0, 0, 0, false, {});
+      legendNodes.addUsing('Router', '', 'inter-router', undefined, 0, 0, 0, 0, false, {});
       if (!svg.selectAll('circle.edge').empty() || !svg.selectAll('circle._edge').empty()) {
-        legendNodes.addUsing('Router', 'Edge', 'edge', '', undefined, 0, 0, 1, 0, false, {});
+        legendNodes.addUsing('Router', 'Edge', 'edge', undefined, 0, 0, 1, 0, false, {});
       }
       if (!svg.selectAll('circle.console').empty()) {
-        legendNodes.addUsing('Console', 'Console', 'normal', '', undefined, 0, 0, 2, 0, false, {
+        legendNodes.addUsing('Console', 'Console', 'normal', undefined, 0, 0, 2, 0, false, {
           console_identifier: 'Dispatch console'
         });
       }
       if (!svg.selectAll('circle.client.in').empty()) {
-        legendNodes.addUsing('Sender', 'Sender', 'normal', '', undefined, 0, 0, 3, 0, false, {}).cdir = 'in';
+        legendNodes.addUsing('Sender', 'Sender', 'normal', undefined, 0, 0, 3, 0, false, {}).cdir = 'in';
       }
       if (!svg.selectAll('circle.client.out').empty()) {
-        legendNodes.addUsing('Receiver', 'Receiver', 'normal', '', undefined, 0, 0, 4, 0, false, {}).cdir = 'out';
+        legendNodes.addUsing('Receiver', 'Receiver', 'normal', undefined, 0, 0, 4, 0, false, {}).cdir = 'out';
       }
       if (!svg.selectAll('circle.client.inout').empty()) {
-        legendNodes.addUsing('Sender/Receiver', 'Sender/Receiver', 'normal', '', undefined, 0, 0, 5, 0, false, {}).cdir = 'both';
+        legendNodes.addUsing('Sender/Receiver', 'Sender/Receiver', 'normal', undefined, 0, 0, 5, 0, false, {}).cdir = 'both';
       }
       if (!svg.selectAll('circle.qpid-cpp').empty()) {
-        legendNodes.addUsing('Qpid broker', 'Qpid broker', 'route-container', '', undefined, 0, 0, 6, 0, false, {
+        legendNodes.addUsing('Qpid broker', 'Qpid broker', 'route-container', undefined, 0, 0, 6, 0, false, {
           product: 'qpid-cpp'
         });
       }
       if (!svg.selectAll('circle.artemis').empty()) {
-        legendNodes.addUsing('Artemis broker', 'Artemis broker', 'route-container', '', undefined, 0, 0, 7, 0, false,
+        legendNodes.addUsing('Artemis broker', 'Artemis broker', 'route-container', undefined, 0, 0, 7, 0, false,
           {product: 'apache-activemq-artemis'});
       }
       if (!svg.selectAll('circle.route-container').empty()) {
-        legendNodes.addUsing('Service', 'Service', 'route-container', 'external-service', undefined, 0, 0, 8, 0, false,
+        legendNodes.addUsing('Service', 'Service', 'route-container', undefined, 0, 0, 8, 0, false,
           {product: ' External Service'});
       }
       lsvg = lsvg.data(legendNodes.nodes, function(d) {
@@ -1059,8 +1068,8 @@ export class TopologyController {
           animate = nodes.initialize(nodeInfo, localStorage, width, height);
 
           let unknowns = [];
-          forceData.links = links = new Links(QDRService, QDRLog);
-          if (links.initializeLinks(nodeInfo, nodes, unknowns, localStorage, height)) {
+          forceData.links = links = new Links(QDRLog);
+          if (links.initialize(nodeInfo, nodes, unknowns, localStorage, height)) {
             animate = true;
           }
           if (unknowns.length > 0) {

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dc6b9a70/console/stand-alone/plugin/js/topology/traffic.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/topology/traffic.js b/console/stand-alone/plugin/js/topology/traffic.js
index 5b02494..d6bccad 100644
--- a/console/stand-alone/plugin/js/topology/traffic.js
+++ b/console/stand-alone/plugin/js/topology/traffic.js
@@ -457,14 +457,22 @@ class Dots extends TrafficAnimation {
           address === this.traffic.QDRService.utilities.addr_text(l[ioa]) &&
           l[ild] === cdir;
       }, this);
+      // we now have the links involved in traffic for this address that
+      // ingress/egress to/from this router (f).
+      // Now find the created node that the link is to
       for (let linkIndex=0; linkIndex<foundLinks.length; linkIndex++) {
         let nodeIndex = nodes.findIndex( function (node) {
           if (node.normals && node.key === key && (node.cdir === cdir || node.cdir === 'both')) {
-            let ni = node.normals.findIndex( function (normal) {
-              return normal.connectionId === foundLinks[linkIndex][ici];
+            return node.normals.some( function (normal) {
+              return normal.connectionId == foundLinks[linkIndex][ici];
             });
-            return ni >= 0;
-          } else
+          } else if (node.alsoConnectsTo) {
+            return node.alsoConnectsTo.some( function (ac2) {
+              return ac2.key === key && ac2.connectionId === foundLinks[linkIndex][ici] &&
+                (ac2.dir === cdir || ac2.dir === 'both');
+            });
+          }
+          else
             return false;
         });
         if (nodeIndex >= 0) {


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org