Repository: qpid-dispatch Updated Branches: refs/heads/master 8475aca8f -> 96ce4a6c8
DISPATCH-201 Updates for dispatch 0.6.0 Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/96ce4a6c Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/96ce4a6c Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/96ce4a6c Branch: refs/heads/master Commit: 96ce4a6c869c7caa1d8d762c9227da23fb98ca04 Parents: 8475aca Author: Ernest Allen <eal...@redhat.com> Authored: Tue Mar 29 10:42:05 2016 -0400 Committer: Ernest Allen <eal...@redhat.com> Committed: Tue Mar 29 10:42:05 2016 -0400 ---------------------------------------------------------------------- .../src/main/webapp/plugin/css/brokers.ttf | Bin 0 -> 2272 bytes .../src/main/webapp/plugin/css/plugin.css | 106 +++- .../src/main/webapp/plugin/css/qdrTopology.css | 76 ++- .../src/main/webapp/plugin/html/qdrList.html | 54 +- .../main/webapp/plugin/html/qdrOverview.html | 2 +- .../main/webapp/plugin/html/qdrTopology.html | 16 +- .../src/main/webapp/plugin/js/dispatchPlugin.js | 21 +- .../hawtio/src/main/webapp/plugin/js/navbar.js | 4 +- .../hawtio/src/main/webapp/plugin/js/qdrList.js | 260 ++++++++-- .../src/main/webapp/plugin/js/qdrOverview.js | 54 +- .../src/main/webapp/plugin/js/qdrService.js | 208 +++++--- .../src/main/webapp/plugin/js/qdrTopology.js | 492 ++++++++++++------- 12 files changed, 979 insertions(+), 314 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/96ce4a6c/console/hawtio/src/main/webapp/plugin/css/brokers.ttf ---------------------------------------------------------------------- diff --git a/console/hawtio/src/main/webapp/plugin/css/brokers.ttf b/console/hawtio/src/main/webapp/plugin/css/brokers.ttf new file mode 100644 index 0000000..ae83968 Binary files /dev/null and b/console/hawtio/src/main/webapp/plugin/css/brokers.ttf differ http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/96ce4a6c/console/hawtio/src/main/webapp/plugin/css/plugin.css ---------------------------------------------------------------------- diff --git a/console/hawtio/src/main/webapp/plugin/css/plugin.css b/console/hawtio/src/main/webapp/plugin/css/plugin.css index c6f5cc1..889bf72 100644 --- a/console/hawtio/src/main/webapp/plugin/css/plugin.css +++ b/console/hawtio/src/main/webapp/plugin/css/plugin.css @@ -279,10 +279,13 @@ div.qdrList li.active, ul.qdrListNodes li.active { div.qdr-attributes span.dynatree-selected a { background-color: #e0e0ff; } -div.qdr-attributes.pane { +div.qdr-attributes.pane, div.qdr-topology.pane { position: absolute; margin-left: 10px; } +div.qdr-topology.pane.left { + width: auto; +} /* the selected row in the name table */ div#main.qdr div.qdrList div.selected { @@ -428,8 +431,8 @@ ul.qdrTopoModes { background:#e0e0ff; } -.qdr-overview.pane.left, .qdr-attributes.pane.left { - top: 100px; +.qdr-overview.pane.left, .qdr-attributes.pane.left, .qdr-topology.pane.left { + top: 104px; } .qdr-overview.pane.left { left: 10px; @@ -723,3 +726,100 @@ span:not(.dynatree-has-children).allocator .dynatree-icon:before { .changed { color: #339933; } + +div.dispatch-router div.help { + width: auto; + padding: 1em; + background-color: lavender; + border-radius: 6px; + margin-top: 1em; + text-align: center; +} + +div.operations tr:nth-child(even) { + background: #f3f3f3; +} +div.operations tr:nth-child(odd), div.operations tr:last-child { + background: #fff; +} + +div.operations tr input { + margin: 0; + padding: 3px 6px; +} +div.operations table { + width: 100%; +} +div.operations th { + width: 50%; + border-bottom: 1px solid #cccccc; + text-align: left; +} +div.operations td:nth-child(odd), div.operations th:nth-child(odd) { + border-right: 1px solid #cccccc; +} +div.operations td:nth-child(odd) { + padding-left: 0; +} +div.operations td:nth-child(even), div.operations th:nth-child(even) { + padding-left: 5px; +} +div.operations th { + padding: 5px; +} +div.operations .tab-pane.active { + padding: 12px 12px 12px 0; +} +div.operations label { + padding-top: 4px; + margin-bottom: 4px; +} +.qdrListActions .ngGrid { + /*min-height: 40em; + height: 100%; */ +} +div.qdrListActions .ngViewport { + height: initial !important; +} + +div.operations .boolean { + padding-bottom: 0; +} + +table.log-entry { + margin-bottom: 1em; + border-top: 1px solid black; +} + +table.log-entry pre { + background-color: #f5f5f5; + color: inherit; + margin: 0; +} + +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/96ce4a6c/console/hawtio/src/main/webapp/plugin/css/qdrTopology.css ---------------------------------------------------------------------- diff --git a/console/hawtio/src/main/webapp/plugin/css/qdrTopology.css b/console/hawtio/src/main/webapp/plugin/css/qdrTopology.css index 0eac80d..bc7ccfc 100644 --- a/console/hawtio/src/main/webapp/plugin/css/qdrTopology.css +++ b/console/hawtio/src/main/webapp/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 { @@ -479,6 +510,13 @@ div.boolean { 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,6 +528,28 @@ div.boolean { } -.qdrListActions .ngGrid { - height: 100vh; +#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; +} + +#multiple_details .ngViewport { + height: auto !important; } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/96ce4a6c/console/hawtio/src/main/webapp/plugin/html/qdrList.html ---------------------------------------------------------------------- diff --git a/console/hawtio/src/main/webapp/plugin/html/qdrList.html b/console/hawtio/src/main/webapp/plugin/html/qdrList.html index adb249f..d07d3ca 100644 --- a/console/hawtio/src/main/webapp/plugin/html/qdrList.html +++ b/console/hawtio/src/main/webapp/plugin/html/qdrList.html @@ -26,12 +26,60 @@ under the License. </div> </hawtio-pane> <div class="row-fluid qdrListActions"> + <ul class="nav nav-tabs"> + <li ng-repeat="mode in modes" ng-show="isValid(mode)" ng-click="selectMode(mode)" ng-class="{active : isModeSelected(mode)}" title="{{mode.title}}" ng-bind-html-unsafe="mode.content"> </li> + </ul> <h4>{{selectedRecordName}}</h4> <div ng-show="currentMode.id === 'attributes'" class="selectedItems"> - <div ng-grid="details"></div> + <div ng-show="selectedRecordName === selectedEntity" class="no-content">There are no {{selectedEntity}}s</div> + <div ng-hide="selectedRecordName === selectedEntity" ng-grid="details"></div> </div> - <div ng-show="currentMode.id === 'operations'"> - Operations are not implemented yet. + <div class="operations" ng-show="currentMode.id === 'operations'"> + <!-- <div ng-grid="detailsCREATE"></div> --> + <fieldset ng-show="operation != ''"> + <table> + <tr> + <th>Attribute</th> + <th>Value</th> + </tr> + <tr title="{{attribute.title}}" ng-repeat="attribute in detailFields"> + <td><label for="{{attribute.name}}">{{attribute.name | humanify}}</label></td> + <!-- we can't do <input type="{angular expression}"> because... jquery throws an exception because... --> + <td> + <div ng-if="attribute.input == 'input'"> + <!-- ng-pattern="testPattern(attribute)" --> + <input ng-if="attribute.type == 'number'" type="number" name="{{attribute.name}}" id="{{attribute.name}}" ng-model="attribute.rawValue" ng-required="attribute.required" class="ui-widget-content ui-corner-all"/> + <input ng-if="attribute.type == 'text'" type="text" name="{{attribute.name}}" id="{{attribute.name}}" ng-model="attribute.attributeValue" ng-required="attribute.required" class="ui-widget-content ui-corner-all"/> + <span ng-if="attribute.type == 'disabled'" >{{getAttributeValue(attribute)}}</span> + </div> + <div ng-if="attribute.input == 'select'"> + <select id="{{attribute.name}}" ng-model="attribute.selected" ng-options="item for item in attribute.rawtype track by item"></select> + </div> + <div ng-if="attribute.input == 'boolean'" class="boolean"> + <label><input type="radio" ng-model="attribute.rawValue" ng-value="true"> True</label> + <label><input type="radio" ng-model="attribute.rawValue" ng-value="false"> False</label> + </div> + </td> + </tr> + <tr><td></td><td><button class="btn btn-primary" type="button" ng-click="ok()">{{operation | Pascalcase}}</button></td></tr> + </table> + </fieldset> + </div> + <div ng-show="currentMode.id === 'log'"> + <table class="log-entry" ng-repeat="entry in logResults track by $index"> + <tr> + <td align="left" colspan="2">{{entry.time}}</td> + </tr> + <tr> + <td>Type</td><td>{{entry.type}}</td> + </tr> + <tr> + <td>Source</td><td>{{entry.source}}:{{entry.line}}</td> + </tr> + <tr> + <td valign="middle">Message</td><td valign="middle"><pre>{{entry.message}}</pre></td> + </tr> + </table> </div> </div> </div> http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/96ce4a6c/console/hawtio/src/main/webapp/plugin/html/qdrOverview.html ---------------------------------------------------------------------- diff --git a/console/hawtio/src/main/webapp/plugin/html/qdrOverview.html b/console/hawtio/src/main/webapp/plugin/html/qdrOverview.html index 61d5143..2a66a94 100644 --- a/console/hawtio/src/main/webapp/plugin/html/qdrOverview.html +++ b/console/hawtio/src/main/webapp/plugin/html/qdrOverview.html @@ -47,7 +47,7 @@ under the License. <script type="text/ng-template" id="router.html"> <div class="row-fluid"> - <h3>Router {{router.data.title}}</h3> + <h3>Router {{router.data.title}} attributes</h3> <div class="gridStyle noHighlight" ng-grid="routerGrid"></div> </div> </script> http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/96ce4a6c/console/hawtio/src/main/webapp/plugin/html/qdrTopology.html ---------------------------------------------------------------------- diff --git a/console/hawtio/src/main/webapp/plugin/html/qdrTopology.html b/console/hawtio/src/main/webapp/plugin/html/qdrTopology.html index 704c8e2..61ca2c1 100644 --- a/console/hawtio/src/main/webapp/plugin/html/qdrTopology.html +++ b/console/hawtio/src/main/webapp/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 class="gridStyle" ng-grid="topoGridOptions"></div> </div> - <div ng-show="isConnections()"> + <div ng-show="form == 'connection'"> <h4>Connection Info</h4> - <div class="gridStyle" ng-grid="topoConnOptions"></div> + <div class="gridStyle" ng-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> @@ -71,7 +70,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/96ce4a6c/console/hawtio/src/main/webapp/plugin/js/dispatchPlugin.js ---------------------------------------------------------------------- diff --git a/console/hawtio/src/main/webapp/plugin/js/dispatchPlugin.js b/console/hawtio/src/main/webapp/plugin/js/dispatchPlugin.js index 59ea4d3..5061317 100644 --- a/console/hawtio/src/main/webapp/plugin/js/dispatchPlugin.js +++ b/console/hawtio/src/main/webapp/plugin/js/dispatchPlugin.js @@ -88,10 +88,12 @@ var QDR = (function(QDR) { $compileProvider.urlSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|blob):/); cur = $compileProvider.urlSanitizationWhitelist(); }) + .config(function (JSONFormatterConfigProvider) { // Enable the hover preview feature JSONFormatterConfigProvider.hoverPreviewEnabled = true; }) + .filter('to_trusted', function($sce){ return function(text) { return $sce.trustAsHtml(text); @@ -107,7 +109,15 @@ var QDR = (function(QDR) { var nameParts = name.split('/') return nameParts.length > 1 ? nameParts[nameParts.length-1] : name; }; - }); + }) + .filter('Pascalcase', function () { + return function (str) { + if (!str) + return ""; + return str.replace(/(\w)(\w*)/g, + function(g0,g1,g2){return g1.toUpperCase() + g2.toLowerCase();}); + } + }) /* QDR.module.config(['$locationProvider', function($locationProvider) { $locationProvider.html5Mode(true); @@ -134,6 +144,7 @@ var QDR = (function(QDR) { Core.addCSS("https://cdn.rawgit.com/mohsen1/json-formatter/master/dist/json-formatter.min.css"); Core.addCSS("https://cdnjs.cloudflare.com/ajax/libs/jquery.tipsy/1.0.2/jquery.tipsy.css"); Core.addCSS("https://code.jquery.com/ui/1.8.24/themes/base/jquery-ui.css"); + Core.addCSS("https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css"); // tell hawtio that we have our own custom layout for // our view @@ -217,16 +228,14 @@ var QDR = (function(QDR) { })(QDR || {}); - -// tell the hawtio plugin loader about our plugin so it can be -// bootstrapped with the rest of angular -hawtioPluginLoader.addModule(QDR.pluginName); - $.getScript('https://cdn.rawgit.com/angular-ui/ui-slider/master/src/slider.js', function() { hawtioPluginLoader.addModule('ui.slider'); }); $.getScript('https://cdn.rawgit.com/mohsen1/json-formatter/master/dist/json-formatter.min.js', function() { hawtioPluginLoader.addModule('jsonFormatter'); + // tell the hawtio plugin loader about our plugin so it can be + // bootstrapped with the rest of angular + hawtioPluginLoader.addModule(QDR.pluginName); }); // force an more modern version of d3 to load http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/96ce4a6c/console/hawtio/src/main/webapp/plugin/js/navbar.js ---------------------------------------------------------------------- diff --git a/console/hawtio/src/main/webapp/plugin/js/navbar.js b/console/hawtio/src/main/webapp/plugin/js/navbar.js index d305f38..8cfecdf 100644 --- a/console/hawtio/src/main/webapp/plugin/js/navbar.js +++ b/console/hawtio/src/main/webapp/plugin/js/navbar.js @@ -43,8 +43,8 @@ var QDR = (function (QDR) { href: "#/dispatch_plugin/overview" }, { - content: '<i class="icon-list "></i> Details', - title: "View the attributes of the router nodes", + content: '<i class="icon-list "></i> Entities', + title: "View the attributes of the router entities", isValid: function (QDRService) { return QDRService.isConnected(); }, href: "#/dispatch_plugin/list" }, http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/96ce4a6c/console/hawtio/src/main/webapp/plugin/js/qdrList.js ---------------------------------------------------------------------- diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrList.js b/console/hawtio/src/main/webapp/plugin/js/qdrList.js index 72fbc04..d816521 100644 --- a/console/hawtio/src/main/webapp/plugin/js/qdrList.js +++ b/console/hawtio/src/main/webapp/plugin/js/qdrList.js @@ -28,9 +28,10 @@ var QDR = (function(QDR) { * * Controller for the main interface */ - QDR.module.controller("QDR.ListController", ['$scope', '$location', '$dialog', 'localStorage', 'QDRService', 'QDRChartService', - function ($scope, $location, $dialog, localStorage, QDRService, QDRChartService) { + QDR.module.controller("QDR.ListController", ['$scope', '$location', '$dialog', '$filter', 'localStorage', 'QDRService', 'QDRChartService', + function ($scope, $location, $dialog, $filter, localStorage, QDRService, QDRChartService) { + $scope.details = []; if (!QDRService.connected) { // we are not connected. we probably got here from a bookmark or manual page reload $location.path("/dispatch_plugin/connect") @@ -43,6 +44,75 @@ var QDR = (function(QDR) { $scope.selectedNodeId = localStorage['QDRSelectedNodeId']; $scope.selectedRecordName = localStorage['QDRSelectedRecordName']; + $scope.modes = [{ + content: '<a><i class="icon-list"></i> Attriutes</a>', + id: 'attributes', + op: 'READ', + title: "View router attributes", + isValid: function () { return true; } + }, + { + content: '<a><i class="icon-edit"></i> Update</a>', + id: 'operations', + op: 'UPDATE', + title: "Update this attribute", + isValid: function () { return $scope.operations.indexOf(this.op) > -1 } + }, + { + content: '<a><i class="icon-plus"></i> Create</a>', + id: 'operations', + op: 'CREATE', + title: "Create a new attribute", + isValid: function () { return $scope.operations.indexOf(this.op) > -1 } + }, + { + content: '<a><i class="icon-chart"></i> Chart</a>', + id: 'charts', + op: 'graph', + title: "Graph attributes", + isValid: function () { return false; return $scope.detailFields.some( function (field ) { + return field.graph; + })} + }, + { + content: '<a><i class="icon-eye-open"></i> Fetch</a>', + id: 'log', + op: 'GET-LOG', + title: "Fetch recent log entries", + isValid: function () { return ($scope.selectedEntity === 'log') } + } + ]; + $scope.operations = [] + $scope.currentMode = $scope.modes[0]; + $scope.isModeSelected = function (mode) { + return mode === $scope.currentMode; + } + $scope.selectMode = function (mode) { + $scope.currentMode = mode; + if (mode.id === 'log') { + $scope.logResults = "getting recent log entries..."; + QDRService.sendMethod($scope.currentNode.id, $scope.selectedEntity, {}, $scope.currentMode.op, function (nodeName, entity, response, context) { + $scope.logResults = response.filter( function (entry) { + return entry[0] === $scope.detailsObject.module + }).sort( function (a, b) { + return b[5] - a[5] + }).map( function (entry) { + return { + type: entry[1], + message: entry[2], + source: entry[3], + line: entry[4], + time: Date(entry[5]).toString() + } + }) + $scope.$apply(); + }) + } + } + $scope.isValid = function (mode) { + return mode.isValid() + } + $scope.nodes = QDRService.nodeList().sort(function (a, b) { return a.name.toLowerCase() > b.name.toLowerCase()}); if (!angular.isDefined($scope.selectedNode)) { //QDR.log.debug("selectedNode was " + $scope.selectedNode); @@ -69,33 +139,20 @@ var QDR = (function(QDR) { return row.linkType.value; } } - $scope.modes = [ - { - content: '<a><i class="icon-list"></i> Attriutes</a>', - id: 'attributes', - title: "View router attributes", - isValid: function () { return true; } - }, - { - content: '<a><i class="icon-leaf"></i> Operations</a>', - id: 'operations', - title: "Execute operations", - isValid: function () { return true; } - } - ]; - $scope.currentMode = $scope.modes[0]; - $scope.isModeSelected = function (mode) { - return mode === $scope.currentMode; - } - $scope.selectMode = function (mode) { - $scope.currentMode = mode; + + var lookupOperations = function () { + var ops = QDRService.schema.entityTypes[$scope.selectedEntity].operations.filter( function (op) { return op !== 'READ'}); + $scope.operation = ops.length ? ops[0] : ""; + return ops; } var entityTreeChildren = []; - for (var entity in QDRService.schema.entityTypes) { + var sortedEntities = Object.keys(QDRService.schema.entityTypes).sort(); + sortedEntities.forEach( function (entity) { if (excludedEntities.indexOf(entity) == -1) { if (!angular.isDefined($scope.selectedEntity)) { $scope.selectedEntity = entity; + $scope.operations = lookupOperations() } var current = entity === $scope.selectedEntity; var e = new Folder(entity) @@ -106,7 +163,7 @@ var QDR = (function(QDR) { e.children = [placeHolder] entityTreeChildren.push(e) } - } + }) $scope.treeReady = function () { $('#entityTree').dynatree({ onActivate: onTreeSelected, @@ -123,10 +180,16 @@ var QDR = (function(QDR) { } // a tree node was selected var onTreeSelected = function (selectedNode) { + if ($scope.currentMode.id === 'operations') + $scope.currentMode = $scope.modes[0]; + else if ($scope.currentMode.id === 'log') + $scope.selectMode($scope.currentMode) if (selectedNode.data.typeName === "entity") { $scope.selectedEntity = selectedNode.data.key; + $scope.operations = lookupOperations() } else if (selectedNode.data.typeName === 'attribute') { $scope.selectedEntity = selectedNode.parent.data.key; + $scope.operations = lookupOperations() $scope.selectedRecordName = selectedNode.data.key; updateDetails(selectedNode.data.details); $("#entityTree").dynatree("getRoot").visit(function(node){ @@ -136,6 +199,28 @@ var QDR = (function(QDR) { } $scope.$apply(); } + // fill in an empty results recoord based on the entities schema + var fromSchema = function (entityName) { + var row = {} + var schemaEntity = QDRService.schema.entityTypes[entityName] + for (attr in schemaEntity.attributes) { + var entity = schemaEntity.attributes[attr] + row[attr] = { + value: "", + type: entity.type, + graph: false, + title: entity.description, + aggregate: false, + aggregateTip: '' + } + } + return row; + } + $scope.hasCreate = function () { + var schemaEntity = QDRService.schema.entityTypes[$scope.selectedEntity] + return (schemaEntity.operations.indexOf("CREATE") > -1) + } + // the data for the selected entity is available, populate the tree var updateEntityChildren = function (tableRows, expand) { var tree = $("#entityTree").dynatree("getTree"); @@ -148,8 +233,8 @@ var QDR = (function(QDR) { typeName: "none", title: "no data" }) - $scope.selectedRecordName = ""; - updateDetails({}); + $scope.selectedRecordName = $scope.selectedEntity; + updateDetails(fromSchema($scope.selectedEntity)); } else { tableRows.forEach( function (row) { var addClass = $scope.selectedEntity; @@ -184,24 +269,85 @@ var QDR = (function(QDR) { } } + var schemaProps = function (entityName, key, currentNode) { + var typeMap = {integer: 'number', string: 'text', path: 'text', boolean: 'boolean'}; + + var entity = QDRService.schema.entityTypes[entityName] + var value = entity.attributes[key] + // skip identity and depricated fields + if (!value) + return {input: 'input', type: 'disabled', required: false, selected: "", rawtype: 'string', disabled: true, 'default': ''} + var description = value.description || "" + var val = value['default']; + var disabled = (key == 'identity' || description.startsWith('Deprecated')) + // special cases + if (entityName == 'log' && key == 'module') { + return {input: 'input', type: 'disabled', required: false, selected: "", rawtype: 'string', disabled: true, 'default': ''} + } + if (entityName === 'linkRoutePattern' && key === 'connector') { + // turn input into a select. the values will be populated later + value.type = [] + // find all the connector names and populate the select + QDRService.getNodeInfo(currentNode.id, '.connector', ['name'], function (nodeName, dotentity, response) { + $scope.detailFields.some( function (field) { + if (field.name === 'connector') { + field.rawtype = response.results.map (function (result) {return result[0]}) + return true; + } + }) + }); + } + return { name: key, + humanName: QDRService.humanify(key), + description:value.description, + type: disabled ? 'disabled' : typeMap[value.type], + rawtype: value.type, + input: typeof value.type == 'string' ? value.type == 'boolean' ? 'boolean' : 'input' + : 'select', + selected: val ? val : undefined, + 'default': value['default'], + value: val, + required: value.required, + unique: value.unique, + disabled: disabled + }; + } + $scope.getAttributeValue = function (attribute) { + var value = attribute.attributeValue; + if ($scope.currentMode.op === "CREATE" && attribute.name === 'identity') + value = "<assigned by system>" + return value; + } var updateDetails = function (row) { var details = []; - for (var attr in row) { + $scope.detailsObject = {}; + var attrs = Object.keys(row).sort(); + attrs.forEach( function (attr) { var changed = $scope.detailFields.filter(function (old) { return (old.name === attr) ? old.graph && old.rawValue != row[attr].value : false; }) + var schemaEntity = schemaProps($scope.selectedEntity, attr, $scope.currentNode) details.push( { attributeName: QDRService.humanify(attr), - attributeValue: QDRService.pretty(row[attr].value), + attributeValue: attr === 'port' ? row[attr].value : QDRService.pretty(row[attr].value), name: attr, changed: changed.length, rawValue: row[attr].value, graph: row[attr].graph, title: row[attr].title, aggregateValue: QDRService.pretty(row[attr].aggregate), - aggregateTip: row[attr].aggregateTip + aggregateTip: row[attr].aggregateTip, + + input: schemaEntity.input, + type: schemaEntity.type, + required: schemaEntity.required, + selected: schemaEntity.selected, + rawtype: schemaEntity.rawtype, + disabled: schemaEntity.disabled, + 'default': schemaEntity['default'] }) - } + $scope.detailsObject[attr] = row[attr].value; + }) $scope.detailFields = details; aggregateColumn(); $scope.$apply(); @@ -224,6 +370,7 @@ var QDR = (function(QDR) { if (newValue !== oldValue) { localStorage['QDRSelectedEntity'] = $scope.selectedEntity; restartUpdate(); + $scope.operations = lookupOperations() } }) $scope.$watch('selectedNode', function(newValue, oldValue) { @@ -241,14 +388,19 @@ var QDR = (function(QDR) { $scope.tableRows = []; var selectedRowIndex = 0; var updateTableData = function (entity, expand) { + // don't update the data when on the operations tab + if ($scope.currentMode.id === 'operations') { + return; + } + var gotNodeInfo = function (nodeName, dotentity, response) { //QDR.log.debug("got results for " + nodeName); //console.dump(response); - var records = response.results; var aggregates = response.aggregates; var attributeNames = response.attributeNames; var nameIndex = attributeNames.indexOf("name"); + var identityIndex = attributeNames.indexOf("identity"); var ent = QDRService.schema.entityTypes[entity]; var tableRows = []; for (var i=0; i<records.length; ++i) { @@ -258,7 +410,11 @@ var QDR = (function(QDR) { var rowName; if (nameIndex > -1) { rowName = record[nameIndex]; - } else { + if (!rowName && identityIndex > -1) { + rowName = record[nameIndex] = (dotentity + '/' + record[identityIndex]) + } + } + if (!rowName) { QDR.log.error("response attributeNames did not contain a name field"); console.dump(response.attributeNames); return; @@ -290,15 +446,15 @@ var QDR = (function(QDR) { } tableRows.push(row); } + tableRows.sort( function (a, b) { return a.name.value.localeCompare(b.name.value) }) setTimeout(selectRow, 0, {rows: tableRows, expand: expand}); } - // if this entity should show an aggregate column, send the request to get the info for this entity from all the nedes if (aggregateEntities.indexOf(entity) > -1) { var nodeInfo = QDRService.topology.nodeInfo(); QDRService.getMultipleNodeInfo(Object.keys(nodeInfo), entity, [], gotNodeInfo, $scope.selectedNodeId); } else { - QDRService.getNodeInfo($scope.selectedNodeId, '.' + entity, [], gotNodeInfo); + QDRService.getNodeInfo($scope.selectedNodeId, entity, [], gotNodeInfo); } }; @@ -420,7 +576,6 @@ var QDR = (function(QDR) { return false; } }; - updateTableData($scope.selectedEntity, true); stop = setInterval(updateTableData, 5000, $scope.selectedEntity); @@ -432,6 +587,41 @@ var QDR = (function(QDR) { }; }); + function gotMethodResponse (nodeName, entity, response, context) { + var statusCode = context.message.application_properties.statusCode; + if (statusCode < 200 || statusCode >= 300) { + Core.notification('error', context.message.application_properties.statusDescription); + QDR.log.debug(context.message.application_properties.statusDescription) + } else { + var note = entity + " " + $filter('Pascalcase')($scope.currentMode.op) + "d" + QDR.log.info(note) + Core.notification('success', note); + $scope.selectMode($scope.modes[0]); + $scope.$apply(); + } + } + $scope.ok = function () { + var attributes = {} + $scope.detailFields.forEach( function (field) { + var value = field.rawValue; + if (field.input === 'input') { + if (field.type === 'text' || field.type === 'disabled') + value = field.attributeValue; + } + if (field.input === 'select') + value = field.selected; + + if (value != field['default'] || field.required || (field.name === 'role')) { + if (field.name !== 'identity') + attributes[field.name] = value + } + if (!attributes.type) + attributes.type = $scope.selectedEntity; + + }) + QDRService.sendMethod($scope.currentNode.id, $scope.selectedEntity, attributes, $scope.currentMode.op, gotMethodResponse) + } + function doDialog(template, chart) { var d = $dialog.dialog({ backdrop: true, http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/96ce4a6c/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js ---------------------------------------------------------------------- diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js b/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js index cc52868..bf75ffd 100644 --- a/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js +++ b/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js @@ -44,7 +44,31 @@ var QDR = (function (QDR) { $location.path("/dispatch_plugin/connect") return; } - + // we want attributes to be listed first, so add it at index 0 + $scope.subLevelTabs = [{ + content: '<i class="icon-list"></i> Attributes', + title: "View the attribute values on your selection", + isValid: function (workspace) { return true; }, + href: function () { return "#/dispatch-plugin/attributes"; }, + index: 0 + }, + { + content: '<i class="icon-leaf"></i> Operations', + title: "Execute operations on your selection", + isValid: function (workspace) { return true; }, + href: function () { return "#/dispatch-plugin/operations"; }, + index: 1 + }] + $scope.activeTab = $scope.subLevelTabs[0]; + $scope.setActive = function (nav) { + $scope.activeTab = nav; + } + $scope.isValid = function (nav) { + return nav.isValid() + } + $scope.isActive = function (nav) { + return nav == $scope.activeTab; + } var nodeIds = QDRService.nodeIdList(); var currentTimer; var refreshInterval = 5000 @@ -141,7 +165,28 @@ var QDR = (function (QDR) { if (expected == received) { allRouterFields.sort ( function (a,b) { return a.routerId < b.routerId ? -1 : a.routerId > b.routerId ? 1 : 0}) // now get each router's node info - QDRService.getMultipleNodeInfo(nodeIds, "router", [], function (nodeIds, entity, response) { + QDRService.getMultipleNodeInfo(nodeIds, "router", [], function (nodeIds, entity, responses) { + for(var r in responses) { + var result = responses[r] + var routerId = QDRService.valFor(result.attributeNames, result.results[0], "routerId") + allRouterFields.some( function (connField) { + if (routerId === connField.routerId) { + result.attributeNames.forEach ( function (attrName) { + connField[attrName] = QDRService.valFor(result.attributeNames, result.results[0], attrName) + }) + return true + } + return false + }) + } + $scope.allRouterFields = allRouterFields + $scope.$apply() + if (currentTimer) { + clearTimeout(currentTimer) + } + currentTimer = setTimeout(allRouterInfo, refreshInterval); +/* + var results = response.aggregates results.forEach ( function (result) { @@ -162,10 +207,11 @@ var QDR = (function (QDR) { clearTimeout(currentTimer) } currentTimer = setTimeout(allRouterInfo, refreshInterval); - }, nodeIds[0]) +*/ + }, nodeIds[0], false) } } - nodeIds.forEach ( function (nodeId) { + nodeIds.forEach ( function (nodeId, i) { QDRService.getNodeInfo(nodeId, ".connection", ["role"], gotNodeInfo) }) http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/96ce4a6c/console/hawtio/src/main/webapp/plugin/js/qdrService.js ---------------------------------------------------------------------- diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrService.js b/console/hawtio/src/main/webapp/plugin/js/qdrService.js index 06bf113..aebb71b 100644 --- a/console/hawtio/src/main/webapp/plugin/js/qdrService.js +++ b/console/hawtio/src/main/webapp/plugin/js/qdrService.js @@ -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]; } }, @@ -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 * @@ -482,74 +490,81 @@ The response looks like: }, 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); + if (aggregate) + self.aggregateNodeInfo(nodeNames, entity, selectedNodeId, responses, callback); + else { + callback(nodeNames, entity, responses) + } } } - 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); - } - 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) { self.getNodeInfo(id, '.'+entity, attrs, gotNodesResult); }) //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; @@ -564,13 +579,74 @@ 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; + self.sender.send({ + body: attrs, + properties: { + to: fullAddr, + reply_to: self.receiver.remote.attach.source.address, + correlation_id: ret.id + }, + application_properties: application_properties + }) + } + 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) @@ -581,13 +657,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) { @@ -649,7 +726,6 @@ The response looks like: self.receiver = null; self.sendable = false; } - var maybeStart = function () { if (okay.connection && okay.sender && okay.receiver && self.sendable && !self.connected) { QDR.log.info("okay to start") @@ -662,7 +738,7 @@ The response looks like: } } var onDisconnect = function () { - QDR.log.warn("Disconnected"); + //QDR.log.warn("Disconnected"); stop(); self.executeDisconnectActions(); } @@ -690,7 +766,7 @@ The response looks like: 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 @@ -724,7 +800,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/96ce4a6c/console/hawtio/src/main/webapp/plugin/js/qdrTopology.js ---------------------------------------------------------------------- diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrTopology.js b/console/hawtio/src/main/webapp/plugin/js/qdrTopology.js index ff5b696..a59c8d3 100644 --- a/console/hawtio/src/main/webapp/plugin/js/qdrTopology.js +++ b/console/hawtio/src/main/webapp/plugin/js/qdrTopology.js @@ -21,6 +21,44 @@ under the License. */ var QDR = (function (QDR) { + + QDR.module.controller('QDR.TopologyFormController', 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'; + }) + }) + + /** * @method TopologyController * @@ -38,37 +76,11 @@ var QDR = (function (QDR) { QDR.log.debug("started QDR.TopologyController with urlPrefix: " + $location.absUrl()); var urlPrefix = $location.absUrl(); - $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; @@ -77,24 +89,6 @@ var QDR = (function (QDR) { $scope.addingNode.trigger = 'editNode'; } - $scope.topoGridOptions = { - data: 'attributes', - enableColumnResize: true, - 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, @@ -122,16 +116,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; } @@ -160,12 +150,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; @@ -190,6 +179,29 @@ var QDR = (function (QDR) { return mode.right; } + // for ng-grid that shows details for multiple consoles/clients + $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' + } + ] + }; // generate unique name for router and containerName var genNewName = function () { @@ -283,9 +295,9 @@ var QDR = (function (QDR) { var radius = 25; var radiusNormal = 15; width = tpdiv.width() - gap; - height = $(document).height() - gap; + height = $('#main').height() - $('#topology').position().top - gap; - var svg; + var svg, lsvg; var force; var animate = false; // should the force graph organize itself when it is displayed var path, circle; @@ -299,7 +311,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]; @@ -310,6 +323,7 @@ var QDR = (function (QDR) { return { key: id, name: name, nodeType: nodeType, + properties: properties, containerName: containerName, x: x, y: y, @@ -340,8 +354,6 @@ var QDR = (function (QDR) { } } - //var drag; - // create an bare svg element and // initialize the nodes and links array from the QDRService.topology._nodeInfo object var initForceGraph = function () { //QDR.log.debug("initForceGraph called"); @@ -370,6 +382,20 @@ var QDR = (function (QDR) { 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; @@ -388,11 +414,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 = height / 2 - yInit; + 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); @@ -405,9 +431,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"); @@ -419,8 +449,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]); @@ -432,26 +460,36 @@ var QDR = (function (QDR) { } if (position.y > height) position.y = nodes[parent].y + 40 + Math.cos(Math.PI/2 * client) - //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 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) @@ -506,69 +544,54 @@ 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); } -*/ - // 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(); + $scope.$apply(); } - function getContainerIndex(_id) { var nodeIndex = 0; var nodeInfo = QDRService.topology.nodeInfo(); @@ -738,6 +761,12 @@ var QDR = (function (QDR) { 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 @@ -793,7 +822,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; } } @@ -803,7 +832,7 @@ var QDR = (function (QDR) { left = d.target; resultIndex = left.resultIndex; } - updateConnForm(left, resultIndex); + updateForm(left.key, 'connection', resultIndex); } // select link @@ -944,33 +973,30 @@ var QDR = (function (QDR) { circle.selectAll('circle') .classed('selected', function (d) { return (d === selected_node) }) .classed('fixed', function (d) { return (d.fixed & 0b1) }) + //.classed('multiple', function(d) { return (d.normals && d.normals.length > 1) } ) // 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; @@ -978,10 +1004,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); } } @@ -1057,19 +1083,11 @@ 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; @@ -1099,21 +1117,136 @@ var QDR = (function (QDR) { .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; @@ -1161,6 +1294,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(); //if ($location.path().startsWith("/topology")) --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@qpid.apache.org For additional commands, e-mail: commits-h...@qpid.apache.org