qpid-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From eal...@apache.org
Subject qpid-dispatch git commit: No JIRA: bring stand-alone console up to date
Date Tue, 26 Apr 2016 16:29:33 GMT
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 <eallen@redhat.com>
Authored: Tue Apr 26 12:29:09 2016 -0400
Committer: Ernest Allen <eallen@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-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org


Mime
View raw message