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/17 16:43:30 UTC

qpid-dispatch git commit: DISPATCH-1168 Display additional detail on console's topology page

Repository: qpid-dispatch
Updated Branches:
  refs/heads/master e94f12579 -> 342dc04e7


DISPATCH-1168 Display additional detail on console's topology page


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

Branch: refs/heads/master
Commit: 342dc04e76556d034ac2230b58ec4b22f829f301
Parents: e94f125
Author: Ernest Allen <ea...@redhat.com>
Authored: Sat Nov 17 11:43:14 2018 -0500
Committer: Ernest Allen <ea...@redhat.com>
Committed: Sat Nov 17 11:43:14 2018 -0500

----------------------------------------------------------------------
 .../plugin/html/tmplClientDetail.html           | 184 ++++++++++++++++++-
 console/stand-alone/plugin/js/amqp/utilities.js |  49 ++++-
 .../plugin/js/dlgDetailController.js            | 140 +++++++++++---
 console/stand-alone/plugin/js/topology/map.js   |   7 +-
 console/stand-alone/plugin/js/topology/nodes.js |  12 +-
 .../plugin/js/topology/qdrTopology.js           |   8 +-
 .../stand-alone/plugin/js/topology/topoUtils.js |  71 +++----
 .../stand-alone/plugin/js/topology/traffic.js   |   7 +-
 8 files changed, 377 insertions(+), 101 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/342dc04e/console/stand-alone/plugin/html/tmplClientDetail.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/tmplClientDetail.html b/console/stand-alone/plugin/html/tmplClientDetail.html
index 18d985c..1195ab2 100644
--- a/console/stand-alone/plugin/html/tmplClientDetail.html
+++ b/console/stand-alone/plugin/html/tmplClientDetail.html
@@ -19,9 +19,33 @@
 
 <style>
     div.details {
-        max-height: 20em;
+        max-height: 21em;
         overflow: auto;
     }
+
+    table td.right {
+        text-align: right;
+    }
+    table td {
+        padding: .4em;
+        border-right: 1px solid #F0F0F0;
+    }
+    table tr.odd {
+        background-color: #F0F0F0;
+    }
+    table td.expander {
+        cursor: pointer;
+        width: 1em;
+    }
+    table tr.hiddenRow {
+        display: none;
+    }
+    div.details {
+        width: 100%;
+    }
+    div.details span.right {
+        float: right;
+    }
 </style>
 <!--
     This is the template for the client detail popup displayed when a group
@@ -29,12 +53,164 @@
 -->
 <div>
     <div class="modal-header">
-        <h3 class="modal-title">Detail for {{detail.title | safePlural}}</h3>
+        <h3 class="modal-title">Detail for {{detail.title}}</h3>
     </div>
     <div class="modal-body">
-        <div class="details" ng-bind-html="detail.details"></div>
+        <h4>{{detail.description}}</h4>
+        <div class="details" ng-include="detail.template">
+        </div>
     </div>
     <div class="modal-footer">
         <button class="btn btn-primary" type="button" ng-click="okClick()">Close</button>
     </div>
-</div>
\ No newline at end of file
+</div>
+
+<script type="text/ng-template" id="clients.html">
+    <table class="table table-striped table-bordered table-hover dataTable no-footer">
+        <thead>
+            <tr>
+                <th></th>
+                <th>Container</th>
+                <th>Encrypted</th>
+                <th>Host</th>
+                <th>Links</th>
+            </tr>
+        </thead>
+        <tr ng-repeat-start="(key, value) in detail.infoPerId" 
+            ng-class="{even: $even, odd: $odd}"
+            ng-click="expandClicked(key)">
+            <td class="expander">
+                <span class="fa"
+                        ng-class="expanded(key) ? 'fa-angle-down' : 'fa-angle-right'"
+                ></span>
+            </td>
+            <td>{{key}}</td><!-- Id -->
+            <td class="right">{{value.encrypted}}</td>
+            <td class="right">{{value.host}}</td>
+            <td class="right">{{value.linkCount}}</td>
+        </tr>
+        <tr ng-repeat-end
+            ng-class="{hiddenRow: !expanded(key)}"
+            ng-click="expandClicked(key)"
+        >
+            <td colspan="6">
+                <table class="table table-striped table-bordered dataTable no-footer">
+                    <thead>
+                        <tr>
+                            <td ng-repeat="field in linkFields">
+                                {{field | humanify}}
+                            </td>
+                        </tr>
+                    </thead>
+                    <tbody>
+                        <tr ng-repeat="link in value.links">
+                            <td ng-repeat="field in linkFields">
+                                {{link[field] | pretty }}
+                            </td>
+                        </tr>
+                    </tbody>
+                </table>
+            </td>
+        </tr>
+    </table>
+</script>
+
+<script type="text/ng-template" id="consoles.html">
+    here be console info
+</script>
+
+<script type="text/ng-template" id="edgeRouters.html">
+    <table class="table table-striped table-bordered table-hover dataTable no-footer">
+        <thead>
+            <tr>
+                <th></th>
+                <th>Id</th>
+                <th>Link Routes</th>
+                <th>Auto Links</th>
+                <th>Conns</th>
+                <th>Addrs</th>
+                <th>Accepted</td>
+            </tr>
+        </thead>
+        <tr ng-repeat-start="(key, value) in detail.infoPerId" 
+            ng-class="{even: $even, odd: $odd}"
+            ng-click="expandClicked(key)">
+            <td class="expander">
+                <span class="fa"
+                        ng-class="expanded(key) ? 'fa-angle-down' : 'fa-angle-right'"
+                ></span>
+            </td>
+            <td>{{key}}</td><!-- Id -->
+            <td class="right">{{value.linkRouteCount}}</td>
+            <td class="right">{{value.autoLinkCount}}</td>
+            <td class="right">{{value.connectionCount}}</td>
+            <td class="right">{{value.addrCount}}</td>
+            <td class="right">{{value.acceptedDeliveries | pretty}}</td>
+        </tr>
+        <tr ng-repeat-end
+            ng-class="{hiddenRow: !expanded(key)}"
+            ng-click="expandClicked(key)"
+        >
+            <td colspan="7">
+                    <h4>Edge router details</h4>
+                    <table class="table table-striped table-bordered dataTable no-footer">
+                        <tr ng-repeat="field in detailFields">
+                            <td>{{field | humanify}}</td>
+                            <td>{{value[field] | pretty}}</td>
+                        </tr>
+                    </table>
+                    <h4>Link routes</h4>
+                    <table class="table table-striped table-bordered dataTable no-footer">
+                        <thead>
+                            <tr>
+                                <td ng-repeat="field in linkRouteFields">
+                                    {{field | humanify}}
+                                </td>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            <tr ng-repeat="link in value.linkRoutes">
+                                <td ng-repeat="field in linkRouteFields">
+                                    {{link[field] | pretty }}
+                                </td>
+                            </tr>
+                        </tbody>
+                    </table>
+                    <h4>Autolinks</h4>
+                    <table class="table table-striped table-bordered dataTable no-footer">
+                        <thead>
+                            <tr>
+                                <td ng-repeat="field in autoLinkFields">
+                                    {{field | humanify}}
+                                </td>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            <tr ng-repeat="link in value.autoLinks">
+                                <td ng-repeat="field in autoLinkFields">
+                                    {{link[field] | pretty }}
+                                </td>
+                            </tr>
+                        </tbody>
+                    </table>
+                    <h4>Addresses</h4>
+                    <table class="table table-striped table-bordered dataTable no-footer">
+                        <thead>
+                            <tr>
+                                <td ng-repeat="field in addressFields">
+                                    {{field | humanify}}
+                                </td>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            <tr ng-repeat="link in value.addresses">
+                                <td ng-repeat="field in addressFields">
+                                    {{link[field] | pretty }}
+                                </td>
+                            </tr>
+                        </tbody>
+                    </table>
+            </td>
+        </tr>
+    </table>
+</script>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/342dc04e/console/stand-alone/plugin/js/amqp/utilities.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/amqp/utilities.js b/console/stand-alone/plugin/js/amqp/utilities.js
index 3f69002..c092bf4 100644
--- a/console/stand-alone/plugin/js/amqp/utilities.js
+++ b/console/stand-alone/plugin/js/amqp/utilities.js
@@ -46,6 +46,13 @@ var utils = {
     });
     return flat;
   },
+  flattenAll: function (entity) {
+    let results = [];
+    for (let i=0; i<entity.results.length; i++) {
+      results.push(this.flatten(entity.attributeNames, entity.results[i]));
+    }
+    return results;
+  },
   copy: function (obj) {
     if (obj)
       return JSON.parse(JSON.stringify(obj));
@@ -107,15 +114,16 @@ var utils = {
     }
     return null;
   },
-  // count the number records with a specific value for a field
-  countFor: function (aAr, vAr, key, val) {
-    let count = 0;
+  // return a map with unique values and their counts for a field
+  countsFor: function (aAr, vAr, key) {
+    let counts = {};
     let idx = aAr.indexOf(key);
     for (let i=0; i<vAr.length; i++) {
-      if (vAr[idx] === val)
-        count++;
+      if (!counts[vAr[i][idx]])
+        counts[vAr[i][idx]] = 0;
+      counts[vAr[i][idx]]++;
     }
-    return count;
+    return counts;
   },
   // extract the name of the router from the router id
   nameFromId: function (id) {
@@ -173,6 +181,35 @@ var utils = {
       rates[field] = list.length > 1 ? cumulative / (list.length-1) : 0;
     }
     return rates;
+  },
+  connSecurity: function (conn) {
+    if (!conn.isEncrypted)
+      return 'no-security';
+    if (conn.sasl === 'GSSAPI')
+      return 'Kerberos';
+    return conn.sslProto + '(' + conn.sslCipher + ')';
+  },
+  connAuth: function (conn) {
+    if (!conn.isAuthenticated)
+      return 'no-auth';
+    let sasl = conn.sasl;
+    if (sasl === 'GSSAPI')
+      sasl = 'Kerberos';
+    else if (sasl === 'EXTERNAL')
+      sasl = 'x.509';
+    else if (sasl === 'ANONYMOUS')
+      return 'anonymous-user';
+    if (!conn.user)
+      return sasl;
+    return conn.user + '(' + sasl + ')';
+  },
+  connTenant: function (conn) {
+    if (!conn.tenant) {
+      return '';
+    }
+    if (conn.tenant.length > 1)
+      return conn.tenant.replace(/\/$/, '');
   }
+
 };
 export { utils };
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/342dc04e/console/stand-alone/plugin/js/dlgDetailController.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/dlgDetailController.js b/console/stand-alone/plugin/js/dlgDetailController.js
index 4ada0d8..f95d529 100644
--- a/console/stand-alone/plugin/js/dlgDetailController.js
+++ b/console/stand-alone/plugin/js/dlgDetailController.js
@@ -22,54 +22,137 @@ export class DetailDialogController {
   constructor(QDRService, $scope, $timeout, $uibModalInstance, $sce, d) {
     this.controllerName = 'QDR.DetailDialogController';
 
+    let expandedRows = new Set();
     $scope.d = d;  // the node object
     $scope.detail = {
-      title: d.normals.length + ' ' + (d.nodeType === 'edge' ? 'edge routers' : 'clients'),
-      header: '',
-      details: 'loading...'
+      template: 'edgeRouters.html'
     };
+    $scope.detailFields = [
+      'version',
+      'mode',
+      'presettledDeliveries',
+      'droppedPresettledDeliveries',
+      'acceptedDeliveries',
+      'rejectedDeliveries',
+      'releasedDeliveries',
+      'modifiedDeliveries',
+      'deliveriesIngress',
+      'deliveriesEgress',
+      'deliveriesTransit',
+      'deliveriesIngressRouteContainer',
+      'deliveriesEgressRouteContainer'
+    ];
+    $scope.linkFields = [
+      'linkType',
+      'owningAddr',
+      'priority',
+      'acceptedCount',
+      'unsettledCount'
+    ];
+    $scope.linkRouteFields = [
+      'prefix',
+      'direction',
+      'containerId'
+    ];
+    $scope.autoLinkFields = [
+      'addr',
+      'direction',
+      'containerId'
+    ];
+    $scope.addressFields = [
+      'prefix',
+      'distribution'
+    ];
 
     $scope.okClick = function () {
       $uibModalInstance.close(true);
     };
+    $scope.expandClicked = function (id) {
+      if (expandedRows.has(id)) {
+        expandedRows.delete(id);
+      } else {
+        expandedRows.add(id);
+      }
+    };
+    $scope.expanded = function (id) {
+      return expandedRows.has(id);
+    };
+
     let groupDetail = function () {
-      let q_getEdgeInfo = function (n, response, callback) {
+      let q_getEdgeInfo = function (n, infoPerId, callback) {
         let nodeId = QDRService.utilities.idFromName(n.container, '_edge');
         QDRService.management.topology.fetchEntities(nodeId, 
-          [{entity: 'connection', attrs: []},
-            {entity: 'router.link', attrs: []}],
+          [{entity: 'router', attrs: []},
+            {entity: 'router.link', attrs: []},
+            {entity: 'linkRoute', attrs: $scope.linkRouteFields},
+            {entity: 'autoLink', attrs: $scope.autoLinkFields},
+            {entity: 'address', attrs: []},
+          ],
           function (results) {
-            response.HTML += '<tr>';
-            response.HTML += `<td>${QDRService.utilities.nameFromId(nodeId)}</td>`;
-  
-            let connection = results[nodeId].connection;
-            // count endpoint connections
-            let endpoints = QDRService.utilities.countFor(connection.attributeNames, connection.results, 'role', 'endpoint');
-            response.HTML += `<td align='right'>${endpoints}</td>`;
-  
-            //let link = results[nodeId]['router.link'];
-            //let conn = QDRService.utilities.flatten(results.attributeNames, )
-            response.HTML += '</tr>';
+            let r = results[nodeId].router;
+            infoPerId[n.container] = QDRService.utilities.flatten(r.attributeNames, r.results[0]);
+            infoPerId[n.container].linkRoutes = QDRService.utilities.flattenAll(results[nodeId].linkRoute);
+            infoPerId[n.container].autoLinks = QDRService.utilities.flattenAll(results[nodeId].autoLink);
+            infoPerId[n.container].addresses = QDRService.utilities.flattenAll(results[nodeId].address);
             callback(null);
           });
       };
       return new Promise( (function (resolve) {
-        let response = {
-          header: '<table><tr><td>Id</td><td>Endpoints</td></tr></table>', 
-          HTML: '<table><tr><td>Id</td><td>endpoints</td></tr>'
-        };
+        let infoPerId = {};
         if (d.nodeType === 'edge') {
+          $scope.detail.template = 'edgeRouters.html';
+          $scope.detail.title = 'edge router';
           let q = d3.queue(10);
           for (let n=0; n<d.normals.length; n++) {
-            q.defer(q_getEdgeInfo, d.normals[n], response);
+            q.defer(q_getEdgeInfo, d.normals[n], infoPerId);
           }
           q.await(function () {
-            response.HTML += '</table>';
-            resolve(response);
+            resolve({
+              description: 'Expand an edge router to see more info',
+              infoPerId: infoPerId
+            });
+          });
+        } else if (d.isConsole) {
+          $scope.detail.template = 'consoles.html';
+          $scope.detail.title = 'console';
+          resolve({
+            description: ''
           });
         } else {
-          response.HTML += '<tr><td>Details for clients go here</td</tr></table>';
-          resolve(response);
+          $scope.detail.template = 'clients.html';
+          $scope.detail.title = 'client';
+          QDRService.management.topology.fetchEntities(d.key, 
+            [{entity: 'router.link', attrs: []}],
+            function (results) {
+              let links = results[d.key]['router.link'];
+              for (let i=0; i<d.normals.length; i++) {
+                let n = d.normals[i];
+                let conn = {};
+                let connectionIndex = links.attributeNames.indexOf('connectionId');
+                infoPerId[n.container] = conn;
+                conn.container = n.container;
+                conn.encrypted = n.encrypted;
+                conn.host = n.host;
+                conn.links = [];
+                for (let l=0; l<links.results.length; l++) {
+                  if (links.results[l][connectionIndex] === n.connectionId) {
+                    let link = QDRService.utilities.flatten(links.attributeNames, links.results[l]);
+                    link.owningAddr = QDRService.utilities.addr_text(link.owningAddr);
+                    conn.links.push(link);
+                  }
+                }
+                conn.linkCount = conn.links.length;
+              }
+              let dir = d.cdir === 'in' ? 'inbound' : d.cdir === 'both' ? 'in and outbound' : 'outbound';
+              let count = d.normals.length;
+              let verb = count > 1 ? 'are' : 'is';
+              let preposition = d.cdir === 'in' ? 'to' : d.cdir === 'both' ? 'for' : 'from';
+              let plural = count > 1 ? 's': '';
+              resolve({
+                description: `There ${verb} ${count} ${dir} connection${plural} ${preposition} ${d.routerId} with role ${d.nodeType}`,
+                infoPerId: infoPerId
+              });
+            });
         }
       }));
     };
@@ -77,8 +160,9 @@ export class DetailDialogController {
     groupDetail()
       .then( function (det) {
         $timeout( function () {
-          $scope.detail.header = $sce.trustAsHtml(det.header);
-          $scope.detail.details = $sce.trustAsHtml(det.HTML);
+          $scope.detail.title = `${d.normals.length} ${$scope.detail.title}${d.normals.length > 1 ? 's' : ''}`;
+          $scope.detail.description = det.description;
+          $scope.detail.infoPerId = det.infoPerId;
         });
       });
   }

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/342dc04e/console/stand-alone/plugin/js/topology/map.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/topology/map.js b/console/stand-alone/plugin/js/topology/map.js
index 9886245..c14008d 100644
--- a/console/stand-alone/plugin/js/topology/map.js
+++ b/console/stand-alone/plugin/js/topology/map.js
@@ -31,7 +31,7 @@ export class BackgroundMap { // eslint-disable-line no-unused-vars
     this.initialized = false;
     this.notify = notifyFn;
     $scope.mapOptions = angular.fromJson(localStorage[MAPOPTIONSKEY]) || {areaColor: defaultLandColor, oceanColor: defaultOceanColor};
-    initLast(this);
+    this.last = {translate: [0,0], scale: null};
   }
   updateLandColor(color) {
     localStorage[MAPOPTIONSKEY] = JSON.stringify(this.$scope.mapOptions);
@@ -116,7 +116,6 @@ export class BackgroundMap { // eslint-disable-line no-unused-vars
           .on('dblclick.zoom', null);
 
       // async load of data file. calls resolve when this completes to let caller know
-      //d3.json('plugin/data/world-110m.json', function(error, world) {
       d3.json('plugin/data/countries.json', function(error, world) {
         if (error) 
           reject(error);
@@ -249,7 +248,3 @@ function getMapBounds(projection, maxlat) {
   
   return [xymin,xymax];
 }
-function initLast(map) {
-  map.last = {translate: [0,0], scale: null};
-}
-

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/342dc04e/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 22ee458..411f942 100644
--- a/console/stand-alone/plugin/js/topology/nodes.js
+++ b/console/stand-alone/plugin/js/topology/nodes.js
@@ -35,9 +35,9 @@ export class Node {
     this.isConsole = QDRService.utilities.isConsole(this);
     this.isArtemis = QDRService.utilities.isArtemis(this);
   }
-  title () {
+  title (hide) {
     let x = '';
-    if (this.normals && this.normals.length > 1)
+    if (this.normals && this.normals.length > 1 && !hide)
       x = ' x ' + this.normals.length;
     if (this.isConsole)
       return 'Dispatch console' + x;
@@ -77,7 +77,7 @@ export class Node {
   }
 
   clientTooltip () {
-    let type = this.title();
+    let type = this.title(true);
     let title = '';
     title += `<table class="popupTable"><tr><td>Type</td><td>${type}</td></tr>`;
     if (!this.normals || this.normals.length < 2)
@@ -85,8 +85,8 @@ export class Node {
     else {
       title += `<tr><td>Count</td><td>${this.normals.length}</td></tr>`;
     }
-    //title += '<tr><td colspan=2 class="more-info">Click circle for more info</td></tr></table>';
-    title += '</table>';
+    if (!this.isConsole && !this.isArtemis)
+      title += '<tr><td colspan=2 class="more-info">Click circle for more info</td></tr></table>';
     return title;
   }
 
@@ -268,7 +268,7 @@ export class Nodes {
   }
   // Convert node's x,y coordinates to longitude, lattitude
   saveLonLat (backgroundMap, nodes) {
-    if (!backgroundMap)
+    if (!backgroundMap || !backgroundMap.initialized)
       return;
     // didn't pass nodes, use all nodes
     if (!nodes)

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/342dc04e/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 aa5aea6..96a996d 100644
--- a/console/stand-alone/plugin/js/topology/qdrTopology.js
+++ b/console/stand-alone/plugin/js/topology/qdrTopology.js
@@ -495,8 +495,10 @@ export class TopologyController {
           d.selected = true;
           let updateTooltip = function () {
             $timeout(function () {
-              $scope.trustedpopoverContent = $sce.trustAsHtml(connectionPopupHTML(d, QDRService));
-              displayTooltip(event);
+              if (d.selected) {
+                $scope.trustedpopoverContent = $sce.trustAsHtml(connectionPopupHTML(d, QDRService));
+                displayTooltip(event);
+              }
             });
           };
           // update the contents of the popup tooltip each time the data is polled
@@ -658,7 +660,7 @@ export class TopologyController {
           $scope.mousedown_node = null;
           if (!$scope.$$phase) $scope.$apply();
           // handle clicking on nodes that represent multiple sub-nodes
-          if (d.normals) {
+          if (d.normals && !d.isConsole && !d.isArtemis) {
             doDialog(d);
           }
           restart(false);

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/342dc04e/console/stand-alone/plugin/js/topology/topoUtils.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/topology/topoUtils.js b/console/stand-alone/plugin/js/topology/topoUtils.js
index ab77a34..cbed6c0 100644
--- a/console/stand-alone/plugin/js/topology/topoUtils.js
+++ b/console/stand-alone/plugin/js/topology/topoUtils.js
@@ -66,6 +66,7 @@ export function connectionPopupHTML (d, QDRService) {
     linkRateHistory = {};
     return;
   }
+  let utils = QDRService.utilities;
   let getConnsArray = function (d, conn) {
     let conns = [conn];
     if (d.cls === 'small') {
@@ -73,7 +74,7 @@ export function connectionPopupHTML (d, QDRService) {
       let normals = d.target.normals ? d.target.normals : d.source.normals;
       for (let n=0; n<normals.length; n++) {
         if (normals[n].resultIndex !== undefined) {
-          conns.push(QDRService.utilities.flatten(onode['connection'].attributeNames,
+          conns.push(utils.flatten(onode['connection'].attributeNames,
             onode['connection'].results[normals[n].resultIndex]));
         }
       }
@@ -97,7 +98,7 @@ export function connectionPopupHTML (d, QDRService) {
       let out = '';
       out = ar[0];
       for (let i=1; i<ar.length; i++) {
-        let sep = sepfn(ar[i]);
+        let sep = sepfn(ar[i], i===ar.length-1);
         out += (sep[0] + sep[1]);
       }
       return out;
@@ -111,19 +112,23 @@ export function connectionPopupHTML (d, QDRService) {
     let links = [];
     let hasAddress = false;
     for (let n=0; n<nodeLinks.results.length; n++) {
-      let link = QDRService.utilities.flatten(nodeLinks.attributeNames, nodeLinks.results[n]);
+      let link = utils.flatten(nodeLinks.attributeNames, nodeLinks.results[n]);
+      let allZero = true;
       if (link.linkType !== 'router-control') {
         if (isLinkFor(link.connectionId, conns)) {
           if (link.owningAddr)
             hasAddress = true;
           if (link.name) {
-            let rate = QDRService.utilities.rates(link, fields, linkRateHistory, link.name, 1);
+            let rates = utils.rates(link, fields, linkRateHistory, link.name, 1);
             // replace the raw value with the rate
             for (let i=0; i<fields.length; i++) {
-              link[fields[i]] = rate[fields[i]];
+              if (rates[fields[i]] > 0)
+                allZero = false;
+              link[fields[i]] = rates[fields[i]];
             }
           }
-          links.push(link);
+          if (!allZero)
+            links.push(link);
         }
       }
     }
@@ -148,6 +153,8 @@ export function connectionPopupHTML (d, QDRService) {
     let td = fields;
     th.unshift('dir');
     td.unshift('linkDir');
+    th.push('priority');
+    td.push('priority');
     // add an address field if any of the links had an owningAddress
     if (hasAddress) {
       th.unshift('address');
@@ -158,7 +165,7 @@ export function connectionPopupHTML (d, QDRService) {
       let rth = th.map( function (t) {
         if (t.endsWith('Count'))
           t = t.replace('Count', 'Rate');
-        return QDRService.utilities.humanify(t);
+        return utils.humanify(t);
       });
       return rth;
     };
@@ -172,16 +179,20 @@ export function connectionPopupHTML (d, QDRService) {
       let link = links[l];
       let vals = td.map( function (f) {
         if (f === 'owningAddr') {
-          let identity = QDRService.utilities.identity_clean(link.owningAddr);
-          return QDRService.utilities.addr_text(identity);
+          let identity = utils.identity_clean(link.owningAddr);
+          return utils.addr_text(identity);
         }
         return link[f];
       });
-      let joinedVals = fnJoin(vals, function (v1) {
-        return ['</td><td' + (isNaN(+v1) ? '': ' align="right"') + '>', QDRService.utilities.pretty(v1 || '0', ',.2f')];
+      let joinedVals = fnJoin(vals, function (v1, last) {
+        return [`</td><td${(isNaN(+v1) ? '': ' align="right"')}>`, last ? v1 : utils.pretty(v1 || '0', ',.2f')];
       });
       HTML += `<tr><td> ${joinedVals} </td></tr>`;
     }
+    // no rows were added
+    if (links.length === 0) {
+      HTML += `<tr><td align="center" colspan="${th.length}">Calculating rates, or rates were all zero</td></tr>`;
+    }
     HTML += '</table>';
     return HTMLHeading + HTML;
   };
@@ -190,37 +201,9 @@ export function connectionPopupHTML (d, QDRService) {
   // left is the connection with dir 'in'
   let right = d.left ? d.target : d.source;
   let onode = QDRService.management.topology.nodeInfo()[left.key];
-  let connSecurity = function (conn) {
-    if (!conn.isEncrypted)
-      return 'no-security';
-    if (conn.sasl === 'GSSAPI')
-      return 'Kerberos';
-    return conn.sslProto + '(' + conn.sslCipher + ')';
-  };
-  let connAuth = function (conn) {
-    if (!conn.isAuthenticated)
-      return 'no-auth';
-    let sasl = conn.sasl;
-    if (sasl === 'GSSAPI')
-      sasl = 'Kerberos';
-    else if (sasl === 'EXTERNAL')
-      sasl = 'x.509';
-    else if (sasl === 'ANONYMOUS')
-      return 'anonymous-user';
-    if (!conn.user)
-      return sasl;
-    return conn.user + '(' + sasl + ')';
-  };
-  let connTenant = function (conn) {
-    if (!conn.tenant) {
-      return '';
-    }
-    if (conn.tenant.length > 1)
-      return conn.tenant.replace(/\/$/, '');
-  };
   // loop through all the connections for left, and find the one for right
   let rightIndex = onode['connection'].results.findIndex( function (conn) {
-    return QDRService.utilities.valFor(onode['connection'].attributeNames, conn, 'container') === right.routerId;
+    return utils.valFor(onode['connection'].attributeNames, conn, 'container') === right.routerId;
   });
   if (rightIndex < 0) {
     // we have a connection to a client/service
@@ -233,16 +216,16 @@ export function connectionPopupHTML (d, QDRService) {
   let HTML = '';
   if (rightIndex >= 0) {
     let conn = onode['connection'].results[rightIndex];
-    conn = QDRService.utilities.flatten(onode['connection'].attributeNames, conn);
+    conn = utils.flatten(onode['connection'].attributeNames, conn);
     let conns = getConnsArray(d, conn);
     if (conns.length === 1) {
       HTML += '<h5>Connection'+(conns.length > 1 ? 's' : '')+'</h5>';
       HTML += '<table class="popupTable"><tr class="header"><td>Security</td><td>Authentication</td><td>Tenant</td><td>Host</td>';
 
       for (let c=0; c<conns.length; c++) {
-        HTML += ('<tr><td>' + connSecurity(conns[c]) + '</td>');
-        HTML += ('<td>' + connAuth(conns[c]) + '</td>');
-        HTML += ('<td>' + (connTenant(conns[c]) || '--') + '</td>');
+        HTML += ('<tr><td>' + utils.connSecurity(conns[c]) + '</td>');
+        HTML += ('<td>' + utils.connAuth(conns[c]) + '</td>');
+        HTML += ('<td>' + (utils.connTenant(conns[c]) || '--') + '</td>');
         HTML += ('<td>' + conns[c].host + '</td>');
         HTML += '</tr>';
       }

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/342dc04e/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 610e01f..9727f3f 100644
--- a/console/stand-alone/plugin/js/topology/traffic.js
+++ b/console/stand-alone/plugin/js/topology/traffic.js
@@ -449,16 +449,16 @@ class Dots extends TrafficAnimation {
       const ioa = links.attributeNames.indexOf('owningAddr');
       const ici = links.attributeNames.indexOf('connectionId');
       const ild = links.attributeNames.indexOf('linkDir');
-      let linkIndex = links.results.findIndex( function (l) {
+      let foundLinks = links.results.filter( function (l) {
         return (l[ilt] === 'endpoint' || l[ilt] === 'edge-downlink') && 
           address === this.traffic.QDRService.utilities.addr_text(l[ioa]) &&
           l[ild] === cdir;
       }, this);
-      if (linkIndex >= 0) {
+      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 === links.results[linkIndex][ici];
+              return normal.connectionId === foundLinks[linkIndex][ici];
             });
             return ni >= 0;
           } else
@@ -471,7 +471,6 @@ class Dots extends TrafficAnimation {
           if (!hops[key])
             hops[key] = [];
           hops[key].push({ val: val, back: !sender, address: address });
-          return;
         }
       }
     }


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