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 <[email protected]> Authored: Tue Apr 26 12:29:09 2016 -0400 Committer: Ernest Allen <[email protected]> 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: [email protected] For additional commands, e-mail: [email protected]
