Repository: qpid-dispatch
Updated Branches:
  refs/heads/master fc392d7cc -> 387044be4


No JIRA: bring stand-alone console up to date


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

Branch: refs/heads/master
Commit: 387044be4916c47b4499d2f567c9241ca77cc53d
Parents: fc392d7
Author: Ernest Allen <eal...@redhat.com>
Authored: Tue Apr 26 12:29:09 2016 -0400
Committer: Ernest Allen <eal...@redhat.com>
Committed: Tue Apr 26 12:29:09 2016 -0400

----------------------------------------------------------------------
 console/stand-alone/plugin/css/brokers.ttf      | Bin 0 -> 2272 bytes
 console/stand-alone/plugin/css/plugin.css       |  26 +
 console/stand-alone/plugin/css/qdrTopology.css  |  90 ++-
 .../stand-alone/plugin/html/qdrTopology.html    |  18 +-
 console/stand-alone/plugin/js/qdrService.js     | 265 +++++---
 console/stand-alone/plugin/js/qdrTopology.js    | 602 ++++++++++++-------
 6 files changed, 654 insertions(+), 347 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/387044be/console/stand-alone/plugin/css/brokers.ttf
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/css/brokers.ttf 
b/console/stand-alone/plugin/css/brokers.ttf
new file mode 100644
index 0000000..ae83968
Binary files /dev/null and b/console/stand-alone/plugin/css/brokers.ttf differ

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/387044be/console/stand-alone/plugin/css/plugin.css
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/css/plugin.css 
b/console/stand-alone/plugin/css/plugin.css
index 5097d64..ef8d2e1 100644
--- a/console/stand-alone/plugin/css/plugin.css
+++ b/console/stand-alone/plugin/css/plugin.css
@@ -656,3 +656,29 @@ div.login.container {
   opacity:1;
 }
 
+circle.node.normal.console {
+    fill: lightcyan;
+}
+
+text.console, text.on-demand, text.normal {
+       font-family: FontAwesome;
+       font-weight: normal;
+       font-size: 16px;
+}
+
+@font-face {
+    font-family:"Brokers";
+    src: url("brokers.ttf") /* TTF file for CSS3 browsers */
+}
+
+text.artemis.on-demand {
+    font-family: Brokers;
+    font-size: 20px;
+    font-weight: bold;
+}
+
+text.qpid-cpp.on-demand {
+    font-family: Brokers;
+    font-size: 18px;
+    font-weight: bold;
+}

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/387044be/console/stand-alone/plugin/css/qdrTopology.css
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/css/qdrTopology.css 
b/console/stand-alone/plugin/css/qdrTopology.css
index e1cf239..f8cffed 100644
--- a/console/stand-alone/plugin/css/qdrTopology.css
+++ b/console/stand-alone/plugin/css/qdrTopology.css
@@ -97,7 +97,11 @@ circle.node.normal {
     fill: #F0F000;
 }
 circle.node.on-demand {
-    fill: #00F000;
+    fill: #C0FFC0;
+}
+circle.node.on-demand.artemis {
+       fill: #FCC;
+       /*opacity: 0.2; */
 }
 
 circle.node.fixed {
@@ -148,6 +152,11 @@ text.id {
   font-weight: bold;
 }
 
+text.label {
+  text-anchor: start;
+  font-weight: bold;
+}
+
 .row-fluid.tertiary {
   position: relative;
   left: 20px;
@@ -159,7 +168,26 @@ text.id {
 
 .row-fluid.tertiary.panel {
   width: 410px;
-  height: 100%;
+  /*height: 100%; */
+}
+
+/*, div.qdrTopology div#multiple_details .ngViewport*/
+div#topologyForm .ngViewport, div#topologyForm .gridStyle {
+    height: inherit !important;
+       min-height: initial !important;
+       overflow: initial;
+}
+
+div#multiple_details {
+       height: 300px;
+       width: 500px;
+       display: none;
+       padding: 1em;
+    border: 1px solid;
+       position: absolute;
+       background-color: white;
+       max-height: 330px !important;
+    overflow: hidden;
 }
 
 .panel-adjacent {
@@ -170,10 +198,13 @@ text.id {
   border: 1px solid red;
 }
 #topologyForm {
-  border: 1px solid white;
-  padding: 2px;
-  position: relative;
-  top: -8px;
+    border: 1px solid white;
+    padding: 2px;
+    /* position: relative; */
+    /* top: -8px; */
+}
+div.qdr-topology.pane.left .ngViewport {
+    /* border: 1px solid lightgray; */
 }
 
 #topologyForm > div {
@@ -249,6 +280,7 @@ li.currentStep {
 }
 */
 
+/*
 .ui-tabs.ui-tabs-vertical {
     padding: 0;
     width: 48em;
@@ -307,6 +339,7 @@ li.currentStep {
 .ui-tabs li i.ui-icon {
     display: inline-block;
 }
+*/
 .ui-tabs .ui-tabs-panel {
     /* padding-top: 0 !important; */
 }
@@ -371,17 +404,18 @@ li.currentStep {
   overflow-x: hidden;
 }
 
-.entity-fields div.boolean label:first-child {
+div.boolean label:first-child {
     float: left;
     margin-right: 1em;
 }
-.entity-fields div.boolean {
+div.boolean {
     padding-bottom: 1em;
 }
 
-.entity-fields label.ng-binding {
+.entity-fields label {
     font-weight: 600;
     margin-top: 0.5em;
+       display: inline;
 }
 
 .aggregate {
@@ -479,6 +513,13 @@ li.currentStep {
   stroke-width: 3px;
 }
 
+circle.subcircle {
+    stroke-width: 1px;
+    /* stroke-dasharray: 2; */
+    fill-opacity: 0;
+    stroke: black;
+}
+
 .leaf circle {
   fill: #6fa8dc;
   fill-opacity: 0.95;
@@ -490,9 +531,32 @@ li.currentStep {
 
 }
 
-.tabs-left .nav-tabs {
-       float: left;
+#svg_legend {
+    position: absolute;
+    top: 110px;
+    right: 0;
+    border: 1px solid #ccc;
+    border-radius: 5px;
+    background-color: #fcfcfc;
+    margin-right: 1.3em;
+       padding: 1em;
+}
+
+#svg_legend svg {
+    height: 235px;
+    width: 180px;
+}
+
+#multiple_details div.gridStyle {
+/*     height: 50em; */
+       min-height: 70px !important;
+       height: auto !important;
 }
-.tabs-left .nav-tabs > li {
-       float: initial;
+
+#multiple_details .ngViewport {
+    height: auto !important;
+}
+
+div.topoGrid .ui-grid-viewport {
+       overflow: hidden !important;
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/387044be/console/stand-alone/plugin/html/qdrTopology.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/qdrTopology.html 
b/console/stand-alone/plugin/html/qdrTopology.html
index 0cea1b6..2c21b21 100644
--- a/console/stand-alone/plugin/html/qdrTopology.html
+++ b/console/stand-alone/plugin/html/qdrTopology.html
@@ -17,19 +17,18 @@ specific language governing permissions and limitations
 under the License.
 -->
 <div class="qdrTopology row-fluid" ng-controller="QDR.TopologyController">
-    <div class="tertiary left panel">
+    <div class="qdr-topology pane left" 
ng-controller="QDR.TopologyFormController">
         <div id="topologyForm" ng-class="{selected : isSelected()}">
             <!-- <div ng-repeat="form in forms" ng-show="isVisible(form)" 
ng-class='{selected : isSelected(form)}'> -->
-
-            <div ng-show="isGeneral()">
+            <div ng-show="form == 'router'">
                 <h4>Router Info</h4>
-                <div ng-style="getTableHeight(attributes)" ui-grid-auto-resize 
ui-grid="topoGridOptions"></div>
+                <div class="topoGrid" ng-style="getTableHeight(attributes)" 
ui-grid-auto-resize ui-grid="topoGridOptions"></div>
             </div>
-            <div ng-show="isConnections()">
+            <div ng-show="form == 'connection'">
                 <h4>Connection Info</h4>
-                <div ng-style="getTableHeight(connAttributes)" 
ui-grid-auto-resize ui-grid="topoConnOptions"></div>
+                <div class="topoGrid" ng-style="getTableHeight(attributes)" 
ui-grid-auto-resize ui-grid="topoGridOptions"></div>
             </div>
-            <div id="addNodeForm" ng-show="isAddNode()">
+            <div id="addNodeForm" ng-show="form == 'add'">
                 <h4>Add a new router</h4>
                 <ul>
                     <li>Click on an existing router to create a connection to 
the new router</li>
@@ -72,7 +71,10 @@ under the License.
                 <li ng-click="removeLink()">Remove connection</li>
             </ul>
         </div>
-
+        <div id="svg_legend"></div>
+        <div id="multiple_details">
+            <div class="gridStyle" ng-grid="multiDetails"></div>
+        </div>
     </div>
 </div>
 <!--

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/387044be/console/stand-alone/plugin/js/qdrService.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrService.js 
b/console/stand-alone/plugin/js/qdrService.js
index 47029d7..28ed680 100644
--- a/console/stand-alone/plugin/js/qdrService.js
+++ b/console/stand-alone/plugin/js/qdrService.js
@@ -116,7 +116,7 @@ var QDR = (function(QDR) {
          connectionError: undefined,
 
       isConnected: function() {
-        return self.gotTopology;
+        return self.connected;
       },
 
     correlator: {
@@ -143,7 +143,7 @@ var QDR = (function(QDR) {
         // called by receiver's on('message') handler when a response arrives
         resolve: function(context) {
                        var correlationID = 
context.message.properties.correlation_id;
-            this._objects[correlationID].resolver(context.message.body);
+            this._objects[correlationID].resolver(context.message.body, 
context);
             delete this._objects[correlationID];
         }
     },
@@ -163,7 +163,7 @@ var QDR = (function(QDR) {
     },
     stopUpdating: function () {
         if (angular.isDefined(self.stop)) {
-            QDR.log.info("stoptUpdating called")
+            QDR.log.info("stopUpdating called")
             clearInterval(self.stop);
             self.stop = undefined;
         }
@@ -247,6 +247,14 @@ var QDR = (function(QDR) {
           return null;
       },
 
+               isArtemis: function (d) {
+                       return d.nodeType ==='on-demand' && 
!d.properties.product;
+               },
+
+               isQpid: function (d) {
+                       return d.nodeType ==='on-demand' && (d.properties && 
d.properties.product === 'qpid-cpp');
+               },
+
       /*
        * send the management messages that build up the topology
        *
@@ -280,10 +288,18 @@ var QDR = (function(QDR) {
 
             // get the list of nodes to query.
             // once this completes, we will get the info for each node returned
-            self.getRemoteNodeInfo( function (response) {
+            self.getRemoteNodeInfo( function (response, context) {
                 //QDR.log.debug("got remote node list of ");
                 //console.dump(response);
                 if( Object.prototype.toString.call( response ) === '[object 
Array]' ) {
+                                       if (response.length === 0) {
+                                               // there is only one router, 
get its node id from the reeciiver
+                                               
//"amqp:/_topo/0/Router.A/temp.aSO3+WGaoNUgGVx"
+                                               var address = 
context.receiver.remote.attach.source.address;
+                                               var addrParts = 
address.split('/')
+                                               
addrParts.splice(addrParts.length-1, 1, '$management')
+                                               response = [addrParts.join('/')]
+                                       }
                     // we expect a response for each of these nodes
                     self.topology.wait(self.timeout);
                     for (var i=0; i<response.length; ++i) {
@@ -456,8 +472,8 @@ The response looks like:
         // first get the list of remote node names
                self.correlator.request(
                 ret = self.sendMgmtQuery('GET-MGMT-NODES')
-            ).then(ret.id, function(response) {
-                callback(response);
+            ).then(ret.id, function(response, context) {
+                callback(response, context);
                 self.topology.cleanUp(response);
             }, ret.error);
       },
@@ -476,88 +492,25 @@ The response looks like:
         self.correlator.request(
             ret = self.sendQuery(nodeName, entity, attrs)
         ).then(ret.id, function(response) {
-            // TODO: file a bug against rhea - large numbers are coming back 
as Uint8Array
-                       response.results.forEach( function (result) {
-                               result.forEach( function (val, i) {
-                                       if (val instanceof Uint8Array) {
-                                               var ua2num = function(ua) {
-                               var n = 0;
-                               for (var i = 0; i<ua.length; i++) {
-                                   n *= 256;
-                                   n += ua[i];
-                               }
-                               return n;
-                           }
-                           result[i] = ua2num(val);
-                                       }
-                               })
-                       })
             callback(nodeName, entity, response);
             //self.topology.addNodeInfo(nodeName, entity, response);
             //self.topology.cleanUp(response);
         }, ret.error);
       },
 
-               getMultipleNodeInfo: function (nodeNames, entity, attrs, 
callback, selectedNodeId) {
+               getMultipleNodeInfo: function (nodeNames, entity, attrs, 
callback, selectedNodeId, aggregate) {
+                       if (!angular.isDefined(aggregate))
+                               aggregate = true;
                        var responses = {};
                        var gotNodesResult = function (nodeName, dotentity, 
response) {
                                responses[nodeName] = response;
                                if (Object.keys(responses).length == 
nodeNames.length) {
-                                       aggregateNodeInfo(nodeNames, entity, 
responses, callback);
-                               }
-                       }
-
-                       var aggregateNodeInfo = function (nodeNames, entity, 
responses, callback) {
-                               //QDR.log.debug("got all results for  " + 
entity);
-                               // aggregate the responses
-                               var newResponse = {};
-                               var thisNode = responses[selectedNodeId];
-                               newResponse['attributeNames'] = 
thisNode.attributeNames;
-                               newResponse['results'] = thisNode.results;
-                               newResponse['aggregates'] = [];
-                               for (var i=0; i<thisNode.results.length; ++i) {
-                                       var result = thisNode.results[i];
-                                       var vals = [];
-                                       result.forEach( function (val) {
-                                               vals.push({sum: val, detail: 
[]})
-                                       })
-                                       newResponse.aggregates.push(vals);
+                                       if (aggregate)
+                                               
self.aggregateNodeInfo(nodeNames, entity, selectedNodeId, responses, callback);
+                                       else {
+                                               callback(nodeNames, entity, 
responses)
+                                       }
                                }
-                               var nameIndex = 
thisNode.attributeNames.indexOf("name");
-                               var ent = self.schema.entityTypes[entity];
-                               var ids = Object.keys(responses);
-                               ids.sort();
-                               ids.forEach( function (id) {
-                                       var response = responses[id];
-                                       var results = response.results;
-                                       results.forEach( function (result) {
-                                               // find the matching result in 
the aggregates
-                                               var found = 
newResponse.aggregates.some( function (aggregate, j) {
-                                                       if 
(aggregate[nameIndex].sum === result[nameIndex]) {
-                                                               // result and 
aggregate are now the same record, add the graphable values
-                                                               
newResponse.attributeNames.forEach( function (key, i) {
-                                                                       if 
(ent.attributes[key] && ent.attributes[key].graph) {
-                                                                               
if (id != selectedNodeId)
-                                                                               
        aggregate[i].sum += result[i];
-                                                                               
aggregate[i].detail.push({node: self.nameFromId(id)+':', val: result[i]})
-                                                                       }
-                                                               })
-                                                               return true; // 
stop looping
-                                                       }
-                                                       return false; // 
continute looking for the aggregate record
-                                               })
-                                               if (!found) {
-                                                       // this attribute was 
not found in the aggregates yet
-                                                       // because it was not 
in the selectedNodeId's results
-                                                       var vals = [];
-                                                       result.forEach( 
function (val) {
-                                                               vals.push({sum: 
val, detail: []})
-                                                       })
-                                                       
newResponse.aggregates.push(vals)
-                                               }
-                                       })
-                               })
-                               callback(nodeNames, entity, newResponse);
                        }
 
                        nodeNames.forEach( function (id) {
@@ -566,6 +519,60 @@ The response looks like:
                        //TODO: implement a timeout in case not all requests 
complete
                },
 
+               aggregateNodeInfo: function (nodeNames, entity, selectedNodeId, 
responses, callback) {
+                       //QDR.log.debug("got all results for  " + entity);
+                       // aggregate the responses
+                       var newResponse = {};
+                       var thisNode = responses[selectedNodeId];
+                       newResponse['attributeNames'] = thisNode.attributeNames;
+                       newResponse['results'] = thisNode.results;
+                       newResponse['aggregates'] = [];
+                       for (var i=0; i<thisNode.results.length; ++i) {
+                               var result = thisNode.results[i];
+                               var vals = [];
+                               result.forEach( function (val) {
+                                       vals.push({sum: val, detail: []})
+                               })
+                               newResponse.aggregates.push(vals);
+                       }
+                       var nameIndex = thisNode.attributeNames.indexOf("name");
+                       var ent = self.schema.entityTypes[entity];
+                       var ids = Object.keys(responses);
+                       ids.sort();
+                       ids.forEach( function (id) {
+                               var response = responses[id];
+                               var results = response.results;
+                               results.forEach( function (result) {
+                                       // find the matching result in the 
aggregates
+                                       var found = 
newResponse.aggregates.some( function (aggregate, j) {
+                                               if (aggregate[nameIndex].sum 
=== result[nameIndex]) {
+                                                       // result and aggregate 
are now the same record, add the graphable values
+                                                       
newResponse.attributeNames.forEach( function (key, i) {
+                                                               if 
(ent.attributes[key] && ent.attributes[key].graph) {
+                                                                       if (id 
!= selectedNodeId)
+                                                                               
aggregate[i].sum += result[i];
+                                                                       
aggregate[i].detail.push({node: self.nameFromId(id)+':', val: result[i]})
+                                                               }
+                                                       })
+                                                       return true; // stop 
looping
+                                               }
+                                               return false; // continute 
looking for the aggregate record
+                                       })
+                                       if (!found) {
+                                               // this attribute was not found 
in the aggregates yet
+                                               // because it was not in the 
selectedNodeId's results
+                                               var vals = [];
+                                               result.forEach( function (val) {
+                                                       vals.push({sum: val, 
detail: []})
+                                               })
+                                               
newResponse.aggregates.push(vals)
+                                       }
+                               })
+                       })
+                       callback(nodeNames, entity, newResponse);
+               },
+
+
       getSchema: function () {
         //QDR.log.debug("getting schema");
         var ret;
@@ -580,13 +587,77 @@ The response looks like:
         }, ret.error);
       },
 
-    sendQuery: function(toAddr, entity, attrs) {
+      getNodeInfo: function (nodeName, entity, attrs, callback) {
+        //QDR.log.debug("getNodeInfo called with nodeName: " + nodeName + " 
and entity " + entity);
+        var ret;
+        self.correlator.request(
+            ret = self.sendQuery(nodeName, entity, attrs)
+        ).then(ret.id, function(response) {
+            callback(nodeName, entity, response);
+            //self.topology.addNodeInfo(nodeName, entity, response);
+            //self.topology.cleanUp(response);
+        }, ret.error);
+      },
+
+       sendMethod: function (nodeId, entity, attrs, operation, callback) {
+               var ret;
+               self.correlator.request(
+                       ret = self._sendMethod(nodeId, entity, attrs, operation)
+               ).then(ret.id, function (response, context) {
+                               callback(nodeId, entity, response, context);
+               }, ret.error);
+       },
+
+       _fullAddr: function (toAddr) {
         var toAddrParts = toAddr.split('/');
         if (toAddrParts.shift() != "amqp:") {
             self.topology.error(Error("unexpected format for router address: " 
+ toAddr));
             return;
         }
-        var fullAddr =  self.toAddress + "/" + toAddrParts.join('/');
+        //var fullAddr =  self.toAddress + "/" + toAddrParts.join('/');
+        var fullAddr =  toAddrParts.join('/');
+               return fullAddr;
+       },
+
+       _sendMethod: function (toAddr, entity, attrs, operation) {
+               var fullAddr = self._fullAddr(toAddr);
+               var ret = {id: self.correlator.corr()};
+               if (!self.sender || !self.sendable) {
+                       ret.error = "no sender"
+                       return ret;
+               }
+               try {
+                       var application_properties = {
+                               operation:  operation
+                       }
+                       if (attrs.type)
+                               application_properties.type = attrs.type;
+                       if (attrs.name)
+                               application_properties.name = attrs.name;
+                       var msg = {
+                       body: attrs,
+                       properties: {
+                           to:                     fullAddr,
+                        reply_to:               
self.receiver.remote.attach.source.address,
+                           correlation_id:         ret.id
+                       },
+                       application_properties: application_properties
+            }
+            self.sender.send( msg );
+                       console.dump("------- method called -------")
+            console.dump (msg)
+               }
+               catch (e) {
+                       error = "error sending: " + e;
+                       QDR.log.error(error)
+                       ret.error = error;
+               }
+               return ret;
+       },
+
+    sendQuery: function(toAddr, entity, attrs, operation) {
+        operation = operation || "QUERY"
+               var fullAddr = self._fullAddr(toAddr);
 
                var body;
         if (attrs)
@@ -597,13 +668,14 @@ The response looks like:
             body = {
                 "attributeNames": [],
             }
-
-               return self._send(body, fullAddr, "QUERY", 
"org.apache.qpid.dispatch" + entity);
+               if (entity[0] === '.')
+                       entity = entity.substr(1, entity.length-1)
+               //return self._send(body, fullAddr, operation, entity);
+               return self._send(body, fullAddr, operation, 
"org.apache.qpid.dispatch." + entity);
     },
 
     sendMgmtQuery: function (operation) {
-               // TODO: file bug against dispatch - We should be able to just 
pass body: [], but that generates an 'invalid body'
-               return self._send([' '], self.toAddress + "/$management", 
operation);
+               return self._send([], "/$management", operation);
     },
 
        _send: function (body, to, operation, entityType) {
@@ -641,6 +713,7 @@ The response looks like:
 
       disconnect: function() {
         self.connection.close();
+               self.errorText = "Disconnected."
       },
 
       connect: function(options) {
@@ -656,9 +729,6 @@ The response looks like:
 
                        var stop = function (context) {
                                //self.stopUpdating();
-                               if (self.connected) {
-                                   $rootScope.$broadcast('newAlert', { type: 
'danger', msg: 'Connection to ' + baseAddress + " was lost. Retrying..." });
-                               }
                                okay.sender = false;
                                okay.receiver = false;
                                okay.connected = false;
@@ -676,13 +746,18 @@ The response looks like:
                                        self.sender = sender;
                                        self.receiver = receiver;
                                        self.onSubscription();
-                                       $rootScope.$broadcast("clearAlerts");
+                                       self.gotTopology = false;
                                }
                        }
+                       var onDisconnect = function () {
+                               //QDR.log.warn("Disconnected");
+                               stop();
+                               self.executeDisconnectActions();
+                       }
 
                        QDR.log.debug("****** calling rhea.connect ********")
             var connection = self.rhea.connect({
-                    connection_details:ws('ws://' + baseAddress),
+                    connection_details:ws('ws://' + baseAddress, ["binary", 
"AMQWSB10"]),
                     reconnect:true,
                     properties: {console_identifier: 'Dispatch console'}
             });
@@ -693,15 +768,17 @@ The response looks like:
                                okay.sender = false;
                        })
                        connection.on('disconnected', function (context) {
-                               QDR.log.warn("disconnected");
-                               stop();
-                               self.errorText = "Error: Connection failed."
+                               onDisconnect();
+                               self.errorText = "Error: Connection failed"
                                self.executeDisconnectActions();
                                self.connectionError = true;
                        })
-                       connection.on('connection_close', function (context) { 
QDR.log.warn("connection_close"); stop()})
+                       connection.on('connection_close', function (context) {
+                               onDisconnect();
+                               self.errorText = "Disconnected"
+                       })
 
-                       var sender = connection.open_sender("/$management");
+                       var sender = connection.open_sender();
                        sender.on('sender_open', function (context) {
                                QDR.log.debug("sender_opened")
                                okay.sender = true
@@ -735,7 +812,7 @@ The response looks like:
 (function() {
     console.dump = function(object) {
         if (window.JSON && window.JSON.stringify)
-            console.log(JSON.stringify(object));
+            console.log(JSON.stringify(object,undefined,2));
         else
             console.log(object);
     };

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/387044be/console/stand-alone/plugin/js/qdrTopology.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrTopology.js 
b/console/stand-alone/plugin/js/qdrTopology.js
index c8921ff..72741ee 100644
--- a/console/stand-alone/plugin/js/qdrTopology.js
+++ b/console/stand-alone/plugin/js/qdrTopology.js
@@ -22,60 +22,102 @@ under the License.
 var QDR = (function (QDR) {
 
   /**
-   * @method SettingsController
+   * @function TopologyFormController
+   *
    * @param $scope
-   * @param QDRServer
+   * @param QDRService
+   *
+   * The controller for the topology page's connection/router info form on the 
left
    *
-   * Controller that handles the QDR settings page
    */
+       QDR.module.controller('QDR.TopologyFormController', ['$scope', 
'QDRService',
+       function ($scope, QDRService) {
+
+               $scope.attributes = []
+        var generalCellTemplate = '<div class="ngCellText"><span 
title="{{row.entity.description}}">{{row.entity.attributeName}}</span></div>';
+        $scope.topoGridOptions = {
+            data: 'attributes',
+                       enableColumnResize: true,
+                       multiSelect: false,
+            columnDefs: [
+            {
+                field: 'attributeName',
+                displayName: 'Attribute',
+                cellTemplate: generalCellTemplate
+            },
+            {
+                field: 'attributeValue',
+                displayName: 'Value'
+            }
+            ]
+        };
+               $scope.form = ''
+               $scope.$on('showEntityForm', function (event, args) {
+                       var attributes = args.attributes;
+                       var entityTypes = 
QDRService.schema.entityTypes[args.entity].attributes;
+                       Object.keys(attributes).forEach( function (attr) {
+                               if (entityTypes[attr])
+                                       attributes[attr].description = 
entityTypes[attr]
+                       })
+                       $scope.attributes = attributes;
+                       $scope.form = args.entity;
+               })
+               $scope.$on('showAddForm', function (event) {
+                       $scope.form = 'add';
+               })
+               $scope.getTableHeight = function (rows) {
+            return {height: ((rows.length+2) * 30) + "px"};
+        }
+
+       }])
 
   /**
-   * @function NavBarController
-   *
-   * @param $scope
-   * @param workspace
-   *
-   * The controller for this plugin's navigation bar
+   * @method TopologyController
    *
+   * Controller that handles the QDR topology page
    */
-   
     QDR.module.controller("QDR.TopologyController", ['$scope', '$rootScope', 
'uiGridConstants', 'QDRService', '$uibModal', '$location', '$timeout',
     function($scope, $rootScope, uiGridConstants, QDRService, $uibModal, 
$location, $timeout) {
 
-               QDR.log.debug("started QDR.TopologyController with 
location.url: " + $location.url());
+               $scope.multiData = [{name: ''}, {name: ''}, {name: ''}]
+        $scope.multiDetails = {
+            data: 'multiData',
+            columnDefs: [
+            {
+                field: 'host',
+                displayName: 'Host'
+            },
+            {
+                field: 'user',
+                displayName: 'User'
+            },
+                       {
+                               field: 'properties',
+                               displayName: 'Properties'
+                       },
+                       {
+                               field: 'isEncrypted',
+                               displayName: 'Encrypted'
+                       }
+            ]
+        };
+
+               if (!QDRService.connected) {
+                       // we are not connected. we probably got here from a 
bookmark or manual page reload
+                       $location.path("/dispatch_plugin/connect")
+                       $location.search('org', "topology");
+                       return;
+               }
+
+               QDR.log.debug("started QDR.TopologyController with urlPrefix: " 
+ $location.absUrl());
+               //var urlPrefix = $location.absUrl();
                var urlPrefix = window.location.pathname;
 
-               $scope.attributes = [];
-        $scope.connAttributes = [];
-        $scope.topoForm = "general";
-        $scope.topoFormSelected = "";
                $scope.addingNode = {
                        step: 0,
                        hasLink: false,
                        trigger: ''
-               }; // shared object about the node that is be       
$scope.topoForm = "general";
-
-        var generalCellTemplate = '<div class="ngCellText"><span 
title="{{row.entity.description}}">{{row.entity.attributeName}}</span></div>';
-
-               $scope.isGeneral = function () {
-           //QDR.log.debug("$scope.topoForm=" + $scope.topoForm)
-           return $scope.topoForm === 'general';
                };
-               $scope.isConnections = function () {
-           //QDR.log.debug("$scope.topoForm=" + $scope.topoForm)
-           return $scope.topoForm === 'connections';
-               };
-               $scope.isAddNode = function () {
-           //QDR.log.debug("$scope.topoForm=" + $scope.topoForm)
-                       return $scope.topoForm === 'addNode';
-               }
-
-               $scope.getTableHeight = function (rows) {
-               return {height: (rows.length * 30) + "px"};
-               }
-        $scope.isSelected = function () {
-            return ($scope.topoFormSelected != "");
-        }
 
         $scope.cancel = function () {
             $scope.addingNode.step = 0;
@@ -84,26 +126,6 @@ var QDR = (function (QDR) {
                        $scope.addingNode.trigger = 'editNode';
                }
 
-        $scope.topoGridOptions = {
-            data: 'attributes',
-                       enableColumnResize: true,
-                       enableHorizontalScrollbar: 
uiGridConstants.scrollbars.NEVER,
-            enableVerticalScrollbar: uiGridConstants.scrollbars.NEVER,
-                       multiSelect: false,
-            columnDefs: [
-            {
-                field: 'attributeName',
-                displayName: 'Attribute',
-                cellTemplate: generalCellTemplate
-            },
-            {
-                field: 'attributeValue',
-                displayName: 'Value'
-            }
-            ]
-        };
-        $scope.topoConnOptions = angular.copy($scope.topoGridOptions);
-        $scope.topoConnOptions.data = 'connAttributes';
                var NewRouterName = "__NEW__";
            // mouse event vars
            var selected_node = null,
@@ -132,16 +154,12 @@ var QDR = (function (QDR) {
                        if (name == "Add Router") {
                                name = 'Diagram';
                                if ($scope.addingNode.step > 0) {
-                                       $scope.topoForm = 'general'
-                                       $scope.topoFormSelected = '';
                                        $scope.addingNode.step = 0;
                                } else {
                                        // start adding node mode
                                        $scope.addingNode.step = 1;
                                }
                        } else {
-                               $scope.topoForm = 'general'
-                               $scope.topoFormSelected = '';
                                $scope.addingNode.step = 0;
                        }
 
@@ -170,12 +188,11 @@ var QDR = (function (QDR) {
                                        }
                                        return true;
                                })
-                               $scope.topoForm = 'general'
-                               $scope.topoFormSelected = '';
+                               
updateForm(Object.keys(QDRService.topology.nodeInfo())[0], 'router', 0);
+
                        } else if (newValue > 0) {
                                // we are starting the add mode
-                               $scope.topoForm = 'addNode';
-                $scope.topoFormSelected = 'addNode';
+                               $scope.$broadcast('showAddForm')
 
                                resetMouseVars();
                 selected_node = null;
@@ -200,7 +217,7 @@ var QDR = (function (QDR) {
                        return mode.right;
                }
 
-
+               // for ng-grid that shows details for multiple consoles/clients
                // generate unique name for router and containerName
                var genNewName = function () {
                        var nodeInfo = QDRService.topology.nodeInfo();
@@ -295,11 +312,12 @@ var QDR = (function (QDR) {
            width = tpdiv.width() - gap;
            height = $(document).height() - gap;
 
-           var svg;
+           var svg, lsvg;
                var force;
                var animate = false; // should the force graph organize itself 
when it is displayed
                var path, circle;
                var savedKeys = {};
+               var dblckickPos = [0,0];
 
            // set up initial nodes and links
            //  - nodes are known by 'id', not by index in array.
@@ -308,7 +326,8 @@ var QDR = (function (QDR) {
                var nodes = [];
                var links = [];
 
-               var aNode = function (id, name, nodeType, nodeInfo, nodeIndex, 
x, y, resultIndex, fixed) {
+               var aNode = function (id, name, nodeType, nodeInfo, nodeIndex, 
x, y, resultIndex, fixed, properties) {
+                       properties = properties || {};
                        var containerName;
                        if (nodeInfo) {
                                var node = nodeInfo[id];
@@ -319,6 +338,7 @@ var QDR = (function (QDR) {
                        return {   key: id,
                                name: name,
                                nodeType: nodeType,
+                               properties: properties,
                                containerName: containerName,
                                x: x,
                                y: y,
@@ -378,11 +398,23 @@ var QDR = (function (QDR) {
                       .style('display', 'block');
                 })
                 .on('click', function (d) {
-                                       
d3.select("#crosssection").style("display","none");
-                                       d3.select("#crosssection svg").remove();
+                    removeCrosssection()
+                });
 
+                $(document).keyup(function(e) {
+                  if (e.keyCode === 27) {
+                    removeCrosssection()
+                  }
                 });
 
+                       // the legend
+                       lsvg = d3.select("#svg_legend")
+                               .append('svg')
+                               .attr('id', 'svglegend')
+                       lsvg = lsvg.append('svg:g')
+                               .attr('transform', 
'translate('+(radii['inter-router']+2)+','+(radii['inter-router']+2)+')')
+                               .selectAll('g');
+
                        // mouse event vars
                        selected_node = null;
                        selected_link = null;
@@ -401,9 +433,11 @@ var QDR = (function (QDR) {
                                if (!angular.isDefined(position)) {
                                    animate = true;
                                    position = {x: width / 4 + ((width / 
2)/nodeCount) * nodes.length,
-                                               y: height / 2 + yInit,
+                                               y: 200 + yInit,
                                                fixed: false};
                                }
+                               if (position.y > height)
+                                       position.y = 200 - yInit;
                                nodes.push( aNode(id, name, "inter-router", 
nodeInfo, nodes.length, position.x, position.y, undefined, position.fixed) );
                                yInit *= -1;
                                //QDR.log.debug("adding node " + 
nodes.length-1);
@@ -417,9 +451,13 @@ var QDR = (function (QDR) {
                                var onode = nodeInfo[id];
                                var conns = onode['.connection'].results;
                                var attrs = onode['.connection'].attributeNames;
+                               var parent = 
getNodeIndex(QDRService.nameFromId(id));
+                               //QDR.log.debug("external client parent is " + 
parent);
+                               var normalsParent = {console: undefined, 
client: undefined}; // 1st normal node for this parent
 
                                for (var j = 0; j < conns.length; j++) {
                     var role = QDRService.valFor(attrs, conns[j], "role");
+                    var properties = QDRService.valFor(attrs, conns[j], 
"properties") || {};
                     var dir = QDRService.valFor(attrs, conns[j], "dir");
                                        if (role == "inter-router") {
                                                var connId = 
QDRService.valFor(attrs, conns[j], "container");
@@ -431,8 +469,6 @@ var QDR = (function (QDR) {
                                                //QDR.log.debug("found an 
external client for " + id);
                                                var name = 
QDRService.nameFromId(id) + "." + client;
                                                //QDR.log.debug("external 
client name is  " + name + " and the role is " + role);
-                                               var parent = 
getNodeIndex(QDRService.nameFromId(id));
-                                               //QDR.log.debug("external 
client parent is " + parent);
 
                         // if we have any new clients, animate the force graph 
to position them
                         var position = angular.fromJson(localStorage[name]);
@@ -442,49 +478,38 @@ var QDR = (function (QDR) {
                                         y: nodes[parent].y + 40 + 
Math.cos(Math.PI/2 * client),
                                         fixed: false};
                         }
-                                               //QDR.log.debug("adding node " 
+ nodeIndex);
-                                               nodes.push(     aNode(id, name, 
role, nodeInfo, nodes.length, position.x, position.y, j, position.fixed) );
-                                               // now add a link
-                                               getLink(parent, nodes.length-1, 
dir);
-                                               client++;
-
-/*
-                           var container = QDRService.valFor(attrs, conns[j], 
"container");
-                           var parts = container.split('.')
-                           if (parts.length) {
-                               var city = parts[parts.length-1]
-                               if (city === 'TelAvivYafo')
-                                   city = 'Tel Aviv-Yafo'
-                               if (possibleCities.indexOf(city) > -1) {
-                                   if (cities.indexOf(city) == -1) {
-                                       cities.push(city);
-                                   }
-                               }
-                           } else {
-                               // there was no city
-                                   var user = QDRService.valFor(attrs, 
conns[j], "user");
-                               city = 'Boston'
-                            if (cities.indexOf(city) == -1 && role == 'normal' 
&& user != "anonymous") {
-                                cities.push(city);
-                            }
-
-                           }
-*/
+                                               if (position.y > height)
+                                                       position.y = 
nodes[parent].y + 40 + Math.cos(Math.PI/2 * client)
+                                               var node = aNode(id, name, 
role, nodeInfo, nodes.length, position.x, position.y, j, position.fixed, 
properties)
+                                               var nodeType = role === 
'normal' ? (properties.console_identifier == 'Dispatch console' ? 'console' : 
'client') : 'broker';
+                                               if (role === 'normal') {
+                                                       node.user = 
QDRService.valFor(attrs, conns[j], "user")
+                                                       node.isEncrypted = 
QDRService.valFor(attrs, conns[j], "isEncrypted")
+                                                       node.host = 
QDRService.valFor(attrs, conns[j], "host")
+
+                                                       if 
(!normalsParent[nodeType]) {
+                                                               
normalsParent[nodeType] = node;
+                                                               nodes.push(     
node );
+                                                               node.normals = 
[node];
+                                                               // now add a 
link
+                                                               getLink(parent, 
nodes.length-1, dir);
+                                                               client++;
+                                                       } else {
+
+                                                               
normalsParent[nodeType].normals.push(node)
+                                                       }
+                                               } else {
+                                                       nodes.push( node)
+                                                       // now add a link
+                                                       getLink(parent, 
nodes.length-1, dir);
+                                                       client++;
+                                               }
                                        }
                                }
                                source++;
                        }
 
             $scope.schema = QDRService.schema;
-                       // add a row for each attribute in .router 
attributeNames array
-                       for (var id in nodeInfo) {
-                               var onode = nodeInfo[id];
-
-                initForm(onode['.connection'].attributeNames, 
onode['.connection'].results[0], QDRService.schema.entityTypes.connection, 
$scope.connAttributes);
-                initForm(onode['.router'].attributeNames, 
onode['.router'].results[0], QDRService.schema.entityTypes.router, 
$scope.attributes);
-                
-                               break;
-                       }
                        // init D3 force layout
                        force = d3.layout.force()
                                .nodes(nodes)
@@ -539,16 +564,12 @@ var QDR = (function (QDR) {
                        // app starts here
                        restart(false);
            force.start();
-               }
-/*
-               function dragstart(d) {
-                 d3.select(this).classed("fixed", d.fixed = true);
-               }
+                       setTimeout(function () {
+                   updateForm(Object.keys(QDRService.topology.nodeInfo())[0], 
'router', 0);
+                       }, 10)
 
-               function dblclick(d) {
-                 d3.select(this).classed("fixed", d.fixed = false);
                }
-*/
+
         var initGlobe = function (clients) {
                        d3.select(window)
                                .on("mousemove", mousemove)
@@ -760,59 +781,48 @@ var QDR = (function (QDR) {
                        }
         }
 
-        // called when we mouseover a node
-        // we need to update the table
-               function updateNodeForm (d) {
-                       //QDR.log.debug("update form info for ");
-                       //console.dump(d);
+               function updateForm (key, entity, resultIndex) {
                        var nodeInfo = QDRService.topology.nodeInfo();
-                       var onode = nodeInfo[d.key];
+                       var onode = nodeInfo[key]
                        if (onode) {
-                               var nodeResults = onode['.router'].results[0];
-                               var nodeAttributes = 
onode['.router'].attributeNames;
-
-                for (var i=0; i<$scope.attributes.length; ++i) {
-                    var idx = 
nodeAttributes.indexOf($scope.attributes[i].attributeName);
-                    if (idx > -1) {
-                        if ($scope.attributes[i].attributeValue != 
nodeResults[idx]) {
-                            // highlight the changed data
-                            $scope.attributes[i].attributeValue = 
nodeResults[idx];
-
-                        }
-                    }
-                }
-                       }
-            $scope.topoForm = "general";
-            $scope.$apply();
-               }
+                               var nodeResults = onode['.' + 
entity].results[resultIndex]
+                               var nodeAttributes = onode['.' + 
entity].attributeNames
+                               var attributes = nodeResults.map( function 
(row, i) {
+                                       return {
+                                               attributeName: 
nodeAttributes[i],
+                                               attributeValue: row
+                                       }
+                               })
+                               // sort by attributeName
+                               attributes.sort( function (a, b) { return 
a.attributeName.localeCompare(b.attributeName) })
 
-               function updateConnForm (d, resultIndex) {
-                       var nodeInfo = QDRService.topology.nodeInfo();
-                       var onode = nodeInfo[d.key];
-                       if (onode && onode['.connection']) {
-                               var nodeResults = 
onode['.connection'].results[resultIndex];
-                               var nodeAttributes = 
onode['.connection'].attributeNames;
-
-                for (var i=0; i<$scope.connAttributes.length; ++i) {
-                    var idx = 
nodeAttributes.indexOf($scope.connAttributes[i].attributeName);
-                    if (idx > -1) {
-                       try {
-                        if ($scope.connAttributes[i].attributeValue != 
nodeResults[idx]) {
-                            // highlight the changed data
-                            $scope.connAttributes[i].attributeValue = 
nodeResults[idx];
+                               // move the Name first
+                               var nameIndex = attributes.findIndex ( function 
(attr) {
+                                       return attr.attributeName === 'name'
+                               })
+                               if (nameIndex >= 0)
+                                       attributes.splice(0, 0, 
attributes.splice(nameIndex, 1)[0]);
+                               // get the list of ports this router is 
listening on
+                               if (entity === 'router') {
+                                       var listeners = 
onode['.listener'].results;
+                                       var listenerAttributes = 
onode['.listener'].attributeNames;
+                                       var normals = listeners.filter ( 
function (listener) {
+                                               return QDRService.valFor( 
listenerAttributes, listener, 'role') === 'normal';
+                                       })
+                                       var ports = []
+                                       normals.forEach (function 
(normalListener) {
+                                               ports.push(QDRService.valFor( 
listenerAttributes, normalListener, 'port'))
+                                       })
+                                       // add as 2nd row
+                                       if (ports.length)
+                                               attributes.splice(1, 0, 
{attributeName: 'Listening on', attributeValue: ports});
+                               }
 
-                        }
-                        } catch (err) {
-                                                       QDR.log.error("error 
updating form" + err)
-                        }
-                    }
-                }
+                               $scope.$broadcast('showEntityForm', {entity: 
entity, attributes: attributes})
                        }
-            $scope.topoForm = "connections";
-            $scope.$apply();
+                       if (!$scope.$$phase) $scope.$apply()
                }
 
-
         function getContainerIndex(_id) {
             var nodeIndex = 0;
             var nodeInfo = QDRService.topology.nodeInfo();
@@ -969,11 +979,31 @@ var QDR = (function (QDR) {
             return null;
         }
 
+               function removeCrosssection() {
+                       setTimeout(function () {
+                               d3.select("[id^=tooltipsy]").remove()
+                               $('.hastip').empty();
+                       }, 1010);
+                       d3.select("#crosssection svg g").transition()
+                .duration(1000)
+                               .attr("transform", 
"translate("+(dblckickPos[0]-140) + "," + (dblckickPos[1]-100) + ") scale(0)")
+                .style("opacity", 0)
+                .each("end", function (d) {
+                    d3.select("#crosssection svg").remove();
+                    d3.select("#crosssection").style("display","none");
+                });
+            d3.select("#multiple_details").transition()
+                .duration(500)
+                .style("opacity", 0)
+                .each("end", function (d) {
+                    d3.select("#multiple_details").style("display", "none")
+                })
+               }
+
            // takes the nodes and links array of objects and adds svg elements 
for everything that hasn't already
            // been added
            function restart(start) {
                circle.call(force.drag);
-               //svg.classed('ctrl', true);
 
                // path (link) group
                path = path.data(links);
@@ -1022,7 +1052,7 @@ var QDR = (function (QDR) {
                             var conn = 
onode['.connection'].results[resultIndex];
                             /// find the connection whose container is the 
right's name
                             var name = 
QDRService.valFor(onode['.connection'].attributeNames, conn, "container");
-                            if (name == right.name) {
+                            if (name == right.containerName) {
                                 break;
                             }
                         }
@@ -1032,14 +1062,11 @@ var QDR = (function (QDR) {
                             left = d.target;
                             resultIndex = left.resultIndex;
                         }
-                        updateConnForm(left, resultIndex);
+                        updateForm(left.key, 'connection', resultIndex);
                     }
 
-                                       // select link
                                        mousedown_link = d;
                                        selected_link = mousedown_link;
-                                       //selected_node = null;
-                                       //mousedown_node = null;
                                        restart();
                                })
                    .on('mouseout', function (d) {
@@ -1049,11 +1076,8 @@ var QDR = (function (QDR) {
                                    }
                                    return;
                                  }
-                                       //QDR.log.debug("showing connections 
form");
-                                       // select link
+                               //QDR.log.debug("showing connections form");
                                        selected_link = null;
-                                       //selected_node = null;
-                                       //mousedown_node = null;
                                        restart();
                                })
                    .on("contextmenu", function(d) {
@@ -1068,23 +1092,24 @@ var QDR = (function (QDR) {
                       .style('top', (mouseY + $(document).scrollTop()) + "px")
                       .style('display', 'block');
                 })
-                .on("dblclick", function (d) {
-                    var pos = d3.mouse(this);
+                .on("click", function (d) {
+                    dblckickPos = d3.mouse(this);
+                    d3.event.stopPropagation();
                     var diameter = 400;
                     var format = d3.format(",d");
                     var pack = d3.layout.pack()
                         .size([diameter - 4, diameter - 4])
-                        .padding(3)
+                        .padding(-10)
                         .value(function(d) { return d.size; });
 
                     var svg = d3.select("#crosssection").append("svg")
                         .attr("width", diameter)
-                        .attr("height", diameter);
+                        .attr("height", diameter)
                     var svgg = svg.append("g")
                         .attr("transform", "translate(2,2)");
 
                                        svg.on('click', function (d) {
-                               
d3.select("#crosssection").style("display","none");
+                                               removeCrosssection();
                                        })
 
                                        var root = {
@@ -1109,6 +1134,8 @@ var QDR = (function (QDR) {
                                        containerIndex = 
links.attributeNames.indexOf('remoteContainer');
                                        var nameIndex = 
links.attributeNames.indexOf('name');
                                        var linkDirIndex = 
links.attributeNames.indexOf('linkDir');
+                                       if (containerIndex < 0 || nameIndex < 0 
|| linkDirIndex < 0)
+                                               return;
                                        links.results.forEach ( function (link) 
{
                                                if (link[containerIndex] == 
d.target.containerName)
                                                        root.children.push (
@@ -1125,7 +1152,7 @@ var QDR = (function (QDR) {
                              .data(pack.nodes)
                            .enter().append("g")
                              .attr("class", function(d) { return d.children ? 
"parent node hastip" : "leaf node hastip"; })
-                             .attr("transform", function(d) { return 
"translate(" + d.x + "," + d.y + ")"; })
+                             .attr("transform", function(d) { return 
"translate(" + d.x + "," + d.y + ")" + (!d.children ? "scale(0.9)" : ""); })
                              .attr("title", function (d) {
                                  var title = "<h4>" + d.desc + "</h4><table 
class='tiptable'><tbody>";
                                  if (d.attributeNames)
@@ -1150,7 +1177,10 @@ var QDR = (function (QDR) {
                              });
 
                                        $('.hastip').tooltipsy({ alignTo: 
'cursor'});
+                                       svgg.attr("transform", 
"translate("+(dblckickPos[0]-140) + "," + (dblckickPos[1]-100) + ") 
scale(0.01)")
                        d3.select("#crosssection").style("display","block");
+
+                                       svgg.transition().attr("transform", 
"translate(2,2) scale(1)")
                 })
 
 
@@ -1170,31 +1200,27 @@ var QDR = (function (QDR) {
                    .classed('fixed', function (d) { return (d.fixed & 0b1) })
 
                        // add new circle nodes. if nodes[] is longer than the 
existing paths, add a new path for each new element
-               var g = circle.enter().append('svg:g');
-
-                       // add new circles and set their attr/class/behavior
-               g.append('svg:circle')
-                   .attr('class', 'node')
-                   .attr('r', function (d) {
-                       return radii[d.nodeType];
-                   })
-                   .classed('fixed', function (d) {return d.fixed})
-                           .classed('temp', function(d) { return 
QDRService.nameFromId(d.key) == '__internal__'; } )
-                           .classed('normal', function(d) { return d.nodeType 
== 'normal' } )
-                           .classed('inter-router', function(d) { return 
d.nodeType == 'inter-router' } )
-                           .classed('on-demand', function(d) { return 
d.nodeType == 'on-demand' } )
-
-/*
-                   .style('fill', function (d) {
-                       var sColor = colors[d.nodeType];
-                       return (d === selected_node) ? 
d3.rgb(sColor).brighter().toString() : d3.rgb(sColor);
-                   })
-                   .style('stroke', function (d) {
-                       var sColor = colors[d.nodeType];
-                       return d3.rgb(sColor).darker().toString();
-                   })
-*/
-                   .on('mouseover', function (d) {
+               var g = circle.enter().append('svg:g')
+                           .classed('multiple', function(d) { return 
(d.normals && d.normals.length > 1)  } )
+
+                       var appendCircle = function (g) {
+                               // add new circles and set their 
attr/class/behavior
+                       return g.append('svg:circle')
+                           .attr('class', 'node')
+                           .attr('r', function (d) {
+                               return radii[d.nodeType];
+                           })
+                           .classed('fixed', function (d) {return d.fixed})
+                       .classed('temp', function(d) { return 
QDRService.nameFromId(d.key) == '__internal__'; } )
+                       .classed('normal', function(d) { return d.nodeType == 
'normal' } )
+                       .classed('inter-router', function(d) { return 
d.nodeType == 'inter-router' } )
+                       .classed('on-demand', function(d) { return d.nodeType 
== 'on-demand' } )
+                       .classed('console', function(d) { return 
d.properties.console_identifier == 'Dispatch console' } )
+                       .classed('artemis', function(d) { return 
QDRService.isArtemis(d) } )
+                       .classed('qpid-cpp', function(d) { return 
QDRService.isQpid(d) } )
+                       .classed('client', function(d) { return d.nodeType === 
'normal' && !d.properties.console_identifier } )
+                       }
+                       appendCircle(g).on('mouseover', function (d) {
                        if ($scope.addingNode.step > 0) {
                                d3.select(this).attr('transform', 'scale(1.1)');
                                                return;
@@ -1202,10 +1228,10 @@ var QDR = (function (QDR) {
                                        if (!selected_node) {
                         if (d.nodeType === 'inter-router') {
                             //QDR.log.debug("showing general form");
-                            updateNodeForm(d);
+                            updateForm(d.key, 'router', 0);
                         } else if (d.nodeType === 'normal' || d.nodeType === 
'on-demand') {
                             //QDR.log.debug("showing connections form");
-                            updateConnForm(d, d.resultIndex);
+                            updateForm(d.key, 'connection', d.resultIndex);
                         }
                                        }
 
@@ -1270,7 +1296,7 @@ var QDR = (function (QDR) {
                                                // add a link from the clicked 
node to the new node
                                                getLink(d.id, nodes.length-1, 
"in", "temp");
                                                $scope.addingNode.hasLink = 
true;
-                                               $scope.$apply();
+                                               if (!$scope.$$phase) 
$scope.$apply()
                                                // add new elements to the svg
                                                force.links(links).start();
                                                restart();
@@ -1281,25 +1307,17 @@ var QDR = (function (QDR) {
                                        // if this node was selected, unselect 
it
                     if (mousedown_node === selected_node) {
                         selected_node = null;
-                        $scope.topoFormSelected = "";
                     }
                     else {
-                        selected_node = mousedown_node;
-                        if (d.nodeType === 'inter-router') {
-                            //QDR.log.debug("showing general form");
-                            updateNodeForm(d);
-                            $scope.topoFormSelected = "general";
-                        } else if (d.nodeType === 'normal' || d.nodeType === 
'on-demand') {
-                            //QDR.log.debug("showing connections form");
-                            updateConnForm(d, d.resultIndex);
-                            $scope.topoFormSelected = "connections";
-                        }
+                                               // don't select nodes that 
represent multiple clients/consoles
+                        if (!d.normals || d.normals.length < 2)
+                            selected_node = mousedown_node;
                     }
                     for (var i=0; i<links.length; ++i) {
                         links[i]['highlighted'] = false;
                     }
                        mousedown_node = null;
-                    $scope.$apply();
+                                       if (!$scope.$$phase) $scope.$apply()
                     restart(false);
 
                    })
@@ -1310,34 +1328,149 @@ var QDR = (function (QDR) {
                        }
                        if (QDRService.nameFromId(d.key) == '__internal__') {
                            editNode();
-                           $scope.$apply();
+                                               if (!$scope.$$phase) 
$scope.$apply()
                        }
                    })
                    .on("contextmenu", function(d) {
                        $(document).click();
                     d3.event.preventDefault();
                        $scope.contextNode = d;
-                       $scope.$apply();    // we just changed a scope valiable 
during an async event
+                                       if (!$scope.$$phase) $scope.$apply()    
 // we just changed a scope valiable during an async event
                     d3.select('#node_context_menu')
                       .style('left', (mouseX + $(document).scrollLeft()) + 
"px")
                       .style('top', (mouseY + $(document).scrollTop()) + "px")
                       .style('display', 'block');
 
-                });
+                })
+                .on("click", function (d) {
+                                       if (!d.normals || d.normals.length < 2) 
{
+                                   if ( QDRService.isArtemis(d) && 
Core.ConnectionName === 'Artemis' ) {
+                                                       
$location.path('/jmx/attributes?tab=artemis&con=Artemis')
+                                               }
+                                               return;
+                                       }
+                    clickPos = d3.mouse(this);
+                    d3.event.stopPropagation();
+                    $scope.multiData = []
+                    d.normals.forEach( function (n) {
+                        $scope.multiData.push(n)
+                    })
+                    $scope.$apply();
+                    d3.select('#multiple_details')
+                        .style({
+                            display: 'block',
+                            opacity: 1,
+                            height: (d.normals.length + 1) * 30 + "px",
+                            'overflow-y': d.normals.length > 10 ? 'scroll' : 
'hidden',
+                                   left: (mouseX + $(document).scrollLeft()) + 
"px",
+                            top:  (mouseY + $(document).scrollTop()) + "px"})
+                               })
 
-               // show node IDs
-               g.append('svg:text')
-                   .attr('x', 0)
-                   .attr('y', 4)
-                   .attr('class', 'id')
-                   .text(function (d) {
-                       return (d.nodeType === 'normal' || d.nodeType == 
'on-demand') ? d.name.slice(-1) :
-                           d.name.length>7 ? d.name.substr(0,6)+'...' : d.name;
-               });
+                       var appendContent = function (g) {
+                       // show node IDs
+                       g.append('svg:text')
+                           .attr('x', 0)
+                           .attr('y', function (d) {
+                               var y = 6;
+                               if (QDRService.isArtemis(d))
+                                   y = 8;
+                               else if (QDRService.isQpid(d))
+                                   y = 9;
+                               else if (d.nodeType === 'inter-router')
+                                   y = 4;
+                               return y;})
+                           .attr('class', 'id')
+                       .classed('console', function(d) { return 
d.properties.console_identifier == 'Dispatch console' } )
+                       .classed('normal', function(d) { return d.nodeType === 
'normal' } )
+                       .classed('on-demand', function(d) { return d.nodeType 
=== 'on-demand' } )
+                       .classed('artemis', function(d) { return 
QDRService.isArtemis(d) } )
+                       .classed('qpid-cpp', function(d) { return 
QDRService.isQpid(d) } )
+                           .text(function (d) {
+                               if (d.properties.console_identifier == 
'Dispatch console') {
+                                   return '\uf108'; // icon-desktop for this 
console
+                               }
+                                               if (QDRService.isArtemis(d)) {
+                                                       return '\ue900'
+                                               }
+                               if (QDRService.isQpid(d)) {
+                                   return '\ue901';
+                               }
+                                               if (d.nodeType === 'normal')
+                                                       return '\uf109'; // 
icon-laptop for clients
+                               return d.name.length>7 ? 
d.name.substr(0,6)+'...' : d.name;
+                       });
+                       }
+                       appendContent(g)
+
+                       var appendTitle = function (g) {
+                       g.append("svg:title").text(function (d) {
+                       var x = '';
+                       if (d.normals && d.normals.length > 1)
+                           x = " x " + d.normals.length;
+                           if (d.properties.console_identifier == 'Dispatch 
console') {
+                           return 'Dispatch console' + x
+                       }
+                           if (d.properties.product == 'qpid-cpp') {
+                           return 'Broker - qpid-cpp' + x
+                       }
+                           if ( QDRService.isArtemis(d) ) {
+                           return 'Broker - Artemis' + x
+                       }
+                           return d.nodeType == 'normal' ? 'client' + x : 
(d.nodeType == 'on-demand' ? 'broker' : 'Router ' + d.name)
+                       })
+                       }
+                       appendTitle(g);
 
                // remove old nodes
                circle.exit().remove();
 
+                       // add subcircles
+                       svg.selectAll('.subcircle').remove();
+
+                       svg.selectAll('.multiple')
+                               .insert('svg:circle', '.normal')
+                                       .attr('class', 'subcircle')
+                                       .attr('r', 18)
+
+
+                       // dynamically create the legend based on which node 
types are present
+                       var legendNodes = [];
+                       legendNodes.push(aNode("Router", "", "inter-router", 
undefined, 0, 0, 0, 0, false, {}))
+
+                       if (!svg.selectAll('circle.console').empty()) {
+                               legendNodes.push(aNode("Dispatch console", "", 
"normal", undefined, 1, 0, 0, 0, false, {console_identifier: 'Dispatch 
console'}))
+                       }
+                       if (!svg.selectAll('circle.client').empty()) {
+                               legendNodes.push(aNode("Client", "", "normal", 
undefined, 2, 0, 0, 0, false, {}))
+                       }
+                       if (!svg.selectAll('circle.qpid-cpp').empty()) {
+                               legendNodes.push(aNode("Qpid cpp broker", "", 
"on-demand", undefined, 3, 0, 0, 0, false, {product: 'qpid-cpp'}))
+                       }
+                       if (!svg.selectAll('circle.artemis').empty()) {
+                               legendNodes.push(aNode("Artemis broker", "", 
"on-demand", undefined, 4, 0, 0, 0, false, {}))
+                       }
+                   lsvg = lsvg.data(legendNodes, function (d) {
+                   return d.id;
+            });
+               var lg = lsvg.enter().append('svg:g')
+                               .attr('transform', function (d, i) {
+                                       // 45px between lines and add 10px 
space after 1st line
+                                       return "translate(0, 
"+(45*i+(i>0?10:0))+")"
+                               })
+                       appendCircle(lg)
+                       appendContent(lg)
+                       appendTitle(lg)
+                       lg.append('svg:text')
+                               .attr('x', 35)
+                               .attr('y', 6)
+                               .attr('class', "label")
+                               .text(function (d) {return d.key })
+                       lsvg.exit().remove();
+                       var svgEl = document.getElementById("svglegend"),
+                               bb = svgEl.getBBox();
+                       svgEl.style.height = bb.y + bb.height;
+                       svgEl.style.width = bb.x + bb.width;
+
                if (!mousedown_node || !selected_node)
                    return;
 
@@ -1385,6 +1518,7 @@ var QDR = (function (QDR) {
                 saveChanged();
                 // TODO: update graph nodes instead of rebuilding entire graph
                 d3.select("#SVG_ID").remove();
+                d3.select("#svg_legend svg").remove();
                 animate = true;
                 initForceGraph();
                 initGlobe(cities);
@@ -1397,6 +1531,10 @@ var QDR = (function (QDR) {
         });
 
                function hasChanged () {
+                       // Don't update the underlying topology diagram if we 
are adding a new node.
+                       // Once adding is completed, the topology will update 
automatically if it has changed
+                       if ($scope.addingNode.step > 0)
+                               return false;
                        var nodeInfo = QDRService.topology.nodeInfo();
                        if (Object.keys(nodeInfo).length != 
Object.keys(savedKeys).length)
                                return true;


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

Reply via email to