You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ea...@apache.org on 2018/05/17 16:09:29 UTC

[4/6] qpid-dispatch git commit: DISPATCH-1000 Restore the html/css/js files that were in the npm repository

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/js/qdrList.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrList.js b/console/stand-alone/plugin/js/qdrList.js
new file mode 100644
index 0000000..056e686
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrList.js
@@ -0,0 +1,938 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+'use strict';
+/* global angular d3 */
+
+/**
+ * @module QDR
+ */
+var QDR = (function(QDR) {
+
+  /**
+   * Controller for the main interface
+   */
+  QDR.module.controller('QDR.ListController', ['$scope', '$location', '$uibModal', '$filter', '$timeout', 'QDRService', 'QDRChartService', 'uiGridConstants', '$sce',
+    function ($scope, $location, $uibModal, $filter, $timeout, QDRService, QDRChartService, uiGridConstants, $sce) {
+
+      QDR.log.debug('QDR.ListControll started with location of ' + $location.path() + ' and connection of  ' + QDRService.management.connection.is_connected());
+      let updateIntervalHandle = undefined,
+        updateInterval = 5000,
+        last_updated = 0,
+        updateNow = false,
+        ListExpandedKey = 'QDRListExpanded',
+        SelectedEntityKey = 'QDRSelectedEntity',
+        ActivatedKey = 'QDRActivatedKey';
+      $scope.details = {};
+
+      $scope.tmplListTree = QDR.templatePath + 'tmplListTree.html';
+      $scope.selectedEntity = localStorage[SelectedEntityKey] || 'address';
+      $scope.ActivatedKey = localStorage[ActivatedKey] || null;
+      if ($scope.selectedEntity == 'undefined')
+        $scope.selectedEntity = undefined;
+      $scope.selectedNode = localStorage['QDRSelectedNode'];
+      $scope.selectedNodeId = localStorage['QDRSelectedNodeId'];
+      $scope.selectedRecordName = localStorage['QDRSelectedRecordName'];
+      $scope.nodes = [];
+      $scope.currentNode = undefined;
+      $scope.modes = [
+        {
+          content: '<a><i class="icon-list"></i> Attributes</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-remove"></i> Delete</a>',
+          id: 'delete',
+          op: 'DELETE',
+          title: 'Delete',
+          isValid: function () { return $scope.operations.indexOf(this.op) > -1; }
+        },
+        {
+          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.fetchingLog = false;
+      $scope.selectMode = function (mode) {
+        $scope.currentMode = mode;
+        if (mode.id === 'log') {
+          $scope.logResults = [];
+          $scope.fetchingLog = true;
+          let entity; // undefined since it is not supported in the GET-LOG call
+          QDRService.management.connection.sendMethod($scope.currentNode.id, entity, {}, $scope.currentMode.op)
+            .then( function (response) {
+              let statusCode = response.context.message.application_properties.statusCode;
+              if (statusCode < 200 || statusCode >= 300) {
+                QDR.Core.notification('error', response.context.message.statusDescription);
+                QDR.log.error('Error ' + response.context.message.statusDescription);
+                return;
+              }
+              $timeout( function () {
+                $scope.fetchingLog = false;
+                $scope.logResults = response.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.isValid = function (mode) {
+        return mode.isValid();
+      };
+
+      $scope.expandAll = function () {
+        $('#entityTree').fancytree('getTree').visit(function(node){
+          node.setExpanded(true);
+        });
+      };
+      $scope.contractAll = function () {
+        $('#entityTree').fancytree('getTree').visit(function(node){
+          node.setExpanded(false);
+        });
+      };
+
+      if (!QDRService.management.connection.is_connected()) {
+      // we are not connected. we probably got here from a bookmark or manual page reload
+        QDR.redirectWhenConnected($location, 'list');
+        return;
+      }
+
+      let excludedEntities = ['management', 'org.amqp.management', 'operationalEntity', 'entity', 'configurationEntity', 'dummy', 'console'];
+      let aggregateEntities = ['router.address'];
+
+      let classOverrides = {
+        'connection': function (row, nodeId) {
+          let isConsole = QDRService.utilities.isAConsole (row.properties.value, row.identity.value, row.role.value, nodeId);
+          return isConsole ? 'console' : row.role.value === 'inter-router' ? 'inter-router' : 'external';
+        },
+        'router.link': function (row, nodeId) {
+          let link = {nodeId: nodeId, connectionId: row.connectionId.value};
+
+          let isConsole = QDRService.utilities.isConsole(QDRService.management.topology.getConnForLink(link));
+          return isConsole ? 'console' : row.linkType.value;
+        },
+        'router.address': function (row) {
+          let identity = QDRService.utilities.identity_clean(row.identity.value);
+          let address = QDRService.utilities.addr_text(identity);
+          let cls = QDRService.utilities.addr_class(identity);
+          if (address === '$management')
+            cls = 'internal ' + cls;
+          return cls;
+        }
+      };
+
+      var lookupOperations = function () {
+        let ops = QDRService.management.schema().entityTypes[$scope.selectedEntity].operations.filter( function (op) { return op !== 'READ';});
+        $scope.operation = ops.length ? ops[0] : '';
+        return ops;
+      };
+      let entityTreeChildren = [];
+      let expandedList = angular.fromJson(localStorage[ListExpandedKey]) || [];
+      let saveExpanded = function () {
+      // save the list of entities that are expanded
+        let tree = $('#entityTree').fancytree('getTree');
+        let list = [];
+        tree.visit( function (tnode) {
+          if (tnode.isExpanded()) {
+            list.push(tnode.key);
+          }
+        });
+        localStorage[ListExpandedKey] = JSON.stringify(list);
+      };
+
+      var onTreeNodeBeforeActivate = function (event, data) {
+      // if node is toplevel entity
+        if (data.node.data.typeName === 'entity') {
+          return false;
+          /*
+          // if the current active node is not this one and not one of its children
+          let active = data.tree.getActiveNode();
+          if (active && !data.node.isActive() && data.node.isExpanded()) {  // there is an active node and it's not this one
+            let any = false;
+            let children = data.node.getChildren();
+            if (children) {
+              any = children.some( function (child) {
+                return child.key === active.key;
+              });
+            }
+            if (!any) // none of the clicked on node's children was active
+              return false;  // don't activate, just collapse this top level node
+          }
+          */
+        }
+        return true;
+      };
+      var onTreeNodeExpanded = function () {
+        saveExpanded();
+        updateExpandedEntities();
+      };
+      // a tree node was selected
+      var onTreeNodeActivated = function (event, data) {
+        $scope.ActivatedKey = data.node.key;
+        let selectedNode = data.node;
+        $scope.selectedTreeNode = data.node;
+        if ($scope.currentMode.id === 'operations')
+          $scope.currentMode = $scope.modes[0];
+        else if ($scope.currentMode.id === 'log')
+          $scope.selectMode($scope.currentMode);
+        else if ($scope.currentMode.id === 'delete') {
+          // clicked on a tree node while on the delete screen -> switch to attribute screen
+          $scope.currentMode = $scope.modes[0];
+        }
+        if (selectedNode.data.typeName === 'entity') {
+          $scope.selectedEntity = selectedNode.key;
+          $scope.operations = lookupOperations();
+          updateNow = true;
+        } else if (selectedNode.data.typeName === 'attribute') {
+          if (!selectedNode.parent)
+            return;
+          let sameEntity = $scope.selectedEntity === selectedNode.parent.key;
+          $scope.selectedEntity = selectedNode.parent.key;
+          $scope.operations = lookupOperations();
+          $scope.selectedRecordName = selectedNode.key;
+          updateDetails(selectedNode.data.details);   // update the table on the right
+          if (!sameEntity) {
+            updateNow = true;
+          }
+        } else if (selectedNode.data.typeName === 'none') {
+          $scope.selectedEntity = selectedNode.parent.key;
+          $scope.selectedRecordName = $scope.selectedEntity;
+          updateDetails(fromSchema($scope.selectedEntity));
+        }
+      };
+      var getExpanded = function (tree) {
+        let list = [];
+        tree.visit( function (tnode) {
+          if (tnode.isExpanded()) {
+            list.push(tnode);
+          }
+        });
+        return list;
+      };
+      // fill in an empty results recoord based on the entities schema
+      var fromSchema = function (entityName) {
+        let row = {};
+        let schemaEntity = QDRService.management.schema().entityTypes[entityName];
+        for (let attr in schemaEntity.attributes) {
+          let entity = schemaEntity.attributes[attr];
+          let value = '';
+          if (angular.isDefined(entity['default'])) {
+            if (entity['type'] === 'integer')
+              value = parseInt(entity['default']); // some default values that are marked as integer are passed as string
+            else
+              value = entity['default'];
+          }
+          row[attr] = {
+            value: value,
+            type: entity.type,
+            graph: false,
+            title: entity.description,
+            aggregate: false,
+            aggregateTip: '',
+            'default': entity['default']
+          };
+        }
+        return row;
+      };
+      $scope.hasCreate = function () {
+        let schemaEntity = QDRService.management.schema().entityTypes[$scope.selectedEntity];
+        return (schemaEntity.operations.indexOf('CREATE') > -1);
+      };
+
+      var getActiveChild = function (node) {
+        let active = node.children.filter(function (child) {
+          return child.isActive();
+        });
+        if (active.length > 0)
+          return active[0].key;
+        return null;
+      };
+      // the data for the selected entity is available, populate the tree on the left
+      var updateTreeChildren = function (entity, tableRows, expand) {
+        let tree = $('#entityTree').fancytree('getTree'), node, newNode;
+        if (tree && tree.getNodeByKey) {
+          node = tree.getNodeByKey(entity);
+        }
+        if (!tree || !node) {
+          return;
+        }
+        let wasActive = node.isActive();
+        let wasExpanded = node.isExpanded();
+        let activeChildKey = getActiveChild(node);
+        node.removeChildren();
+        if (tableRows.length == 0) {
+          newNode = {
+            extraClasses:   'no-data',
+            typeName:   'none',
+            title:      'no data',
+            key:        node.key + '.1'
+          };
+          node.addNode(newNode);
+          if (expand) {
+            updateDetails(fromSchema(entity));
+            $scope.selectedRecordName = entity;
+          }
+        } else {
+          let children = tableRows.map( function (row) {
+            let addClass = entity;
+            if (classOverrides[entity]) {
+              addClass += ' ' + classOverrides[entity](row, $scope.currentNode.id);
+            }
+            let child = {
+              typeName:   'attribute',
+              extraClasses:   addClass,
+              tooltip:    addClass,
+              key:        row.name.value,
+              title:      row.name.value,
+              details:    row
+            };
+            return child;
+          });
+          node.addNode(children);
+        }
+        // top level node was expanded
+        if (wasExpanded)
+          node.setExpanded(true, {noAnimation: true, noEvents: true, noFocus: true});
+        // if the parent node was active, but none of the children were active, active the 1st child
+        if (wasActive) {
+          if (!activeChildKey) {
+            activeChildKey = node.children[0].key;
+          }
+        }
+        if (!tree.getActiveNode())
+          activeChildKey = $scope.ActivatedKey;
+        // re-active the previously active child node
+        if (activeChildKey) {
+          newNode = tree.getNodeByKey(activeChildKey);
+          // the node may not be there after the update
+          if (newNode)
+            newNode.setActive(true, {noFocus: true}); // fires the onTreeNodeActivated event for this node
+        }
+        //resizer();
+      };
+
+
+      var resizer = function () {
+      // this forces the tree and the grid to be the size of the browser window.
+      // the effect is that the tree and the grid will have vertical scroll bars if needed.
+      // the alternative is to let the tree and grid determine the size of the page and have
+      // the scroll bar on the window
+        // don't allow HTML in the tree titles
+        $('.fancytree-title').each( function () {
+          let unsafe = $(this).html();
+          $(this).html(unsafe.replace(/</g, '&lt;').replace(/>/g, '&gt;'));
+        });
+        let h = $scope.detailFields.length * 30 + 46;
+        $('.ui-grid-viewport').height(h);
+      };
+      $(window).resize(resizer);
+
+      var schemaProps = function (entityName, key, currentNode) {
+        let typeMap = {integer: 'number', string: 'text', path: 'text', boolean: 'boolean', map: 'textarea'};
+
+        let entity = QDRService.management.schema().entityTypes[entityName];
+        let value = entity.attributes[key];
+        // skip identity and depricated fields
+        if (!value)
+          return {input: 'input', type: 'disabled', required: false, selected: '', rawtype: 'string', disabled: true, 'default': ''};
+        let description = value.description || '';
+        let val = value['default'];
+        let 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.management.topology.fetchEntity(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.utilities.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) {
+        let value = attribute.attributeValue;
+        if ($scope.currentMode.op === 'CREATE' && attribute.name === 'identity')
+          value = '<assigned by system>';
+        return value;
+      };
+
+      // update the table on the right
+      var updateDetails = function (row) {
+        let details = [];
+        $scope.detailsObject = {};
+        let attrs = Object.keys(row).sort();
+        attrs.forEach( function (attr) {
+          let changed = $scope.detailFields.filter(function (old) {
+            return (old.name === attr) ? old.graph && old.rawValue != row[attr].value : false;
+          });
+          let schemaEntity = schemaProps($scope.selectedEntity, attr, $scope.currentNode);
+          details.push( {
+            attributeName:  QDRService.utilities.humanify(attr),
+            attributeValue: attr === 'port' ? row[attr].value : QDRService.utilities.pretty(row[attr].value),
+            name:           attr,
+            changed:        changed.length,
+            rawValue:       row[attr].value,
+            graph:          row[attr].graph,
+            title:          row[attr].title,
+            chartExists:    (QDRChartService.isAttrCharted($scope.currentNode.id, $scope.selectedEntity, row.name.value, attr)),
+            aggchartExists: (QDRChartService.isAttrCharted($scope.currentNode.id, $scope.selectedEntity, row.name.value, attr, true)),
+            aggregateValue: QDRService.utilities.pretty(row[attr].aggregate),
+            aggregateTip:   row[attr].aggregateTip,
+
+            input:          schemaEntity.input,
+            type:           schemaEntity.type,
+            required:       schemaEntity.required,
+            unique:         schemaEntity.unique,
+            selected:       schemaEntity.selected,
+            rawtype:        schemaEntity.rawtype,
+            disabled:       schemaEntity.disabled,
+            'default':      schemaEntity['default']
+          });
+          $scope.detailsObject[attr] = row[attr].value;
+        });
+        $scope.detailFields = details;
+        aggregateColumn();
+        resizer();
+      };
+
+      // called from html ng-style="getTableHeight()"
+      $scope.getTableHeight = function () {
+        return {
+          height: (Math.max($scope.detailFields.length, 15) * 30 + 46) + 'px'
+        };
+      };
+      var updateExpandedEntities = function () {
+        let tree = $('#entityTree').fancytree('getTree');
+        if (tree) {
+          let q = d3.queue(10);
+          let expanded = getExpanded(tree);
+          expanded.forEach( function (node) {
+            q.defer(q_updateTableData, node.key, node.key === $scope.selectedEntity);
+          });
+
+          q.await(function (error) {
+            if (error)
+              QDR.log.error(error.message);
+
+            if (!tree.getActiveNode()) {
+              if ($scope.ActivatedKey) {
+                let node = tree.getNodeByKey($scope.ActivatedKey);
+                if (node) {
+                  node.setActive(true, {noEvents: true});
+                }
+              }
+              if (!tree.getActiveNode()) {
+                let first = tree.getFirstChild();
+                if (first) {
+                  let child = first.getFirstChild();
+                  if (child)
+                    first = child;
+                }
+                first.setActive(true);
+              }
+            }
+
+            d3.selectAll('.ui-effects-placeholder').style('height', '0px');
+            resizer();
+
+            last_updated = Date.now();
+          });
+        }
+      };
+
+      // The selection dropdown (list of routers) was changed.
+      $scope.selectNode = function(node) {
+        $scope.selectedNode = node.name;
+        $scope.selectedNodeId = node.id;
+        $timeout( function () {
+          setCurrentNode();
+          updateNow = true;
+        });
+      };
+
+      $scope.$watch('ActivatedKey', function(newValue, oldValue) {
+        if (newValue !== oldValue) {
+          localStorage[ActivatedKey] = $scope.ActivatedKey;
+        }
+      });
+      $scope.$watch('selectedEntity', function(newValue, oldValue) {
+        if (newValue !== oldValue) {
+          localStorage['QDRSelectedEntity'] = $scope.selectedEntity;
+          $scope.operations = lookupOperations();
+        }
+      });
+      $scope.$watch('selectedNode', function(newValue, oldValue) {
+        if (newValue !== oldValue) {
+          localStorage['QDRSelectedNode'] = $scope.selectedNode;
+          localStorage['QDRSelectedNodeId'] = $scope.selectedNodeId;
+        }
+      });
+      $scope.$watch('selectedRecordName', function(newValue, oldValue) {
+        if (newValue != oldValue) {
+          localStorage['QDRSelectedRecordName'] = $scope.selectedRecordName;
+        }
+      });
+
+      /* Called periodically to refresh the data on the page */
+      var q_updateTableData = function (entity, expand, callback) {
+      // don't update the data when on the operations tabs
+        if ($scope.currentMode.id !== 'attributes') {
+          callback(null);
+          return;
+        }
+        var gotNodeInfo = function (nodeName, dotentity, response) {
+          let tableRows = [];
+          let records = response.results;
+          let aggregates = response.aggregates;
+          let attributeNames = response.attributeNames;
+          // If !attributeNmes then  there was an error getting the records for this entity
+          if (attributeNames) {
+            let nameIndex = attributeNames.indexOf('name');
+            let identityIndex = attributeNames.indexOf('identity');
+            let ent = QDRService.management.schema().entityTypes[entity];
+            for (let i=0; i<records.length; ++i) {
+              let record = records[i];
+              let aggregate = aggregates ? aggregates[i] : undefined;
+              let row = {};
+              let rowName;
+              if (nameIndex > -1) {
+                rowName = record[nameIndex];
+                if (!rowName && identityIndex > -1) {
+                  rowName = record[nameIndex] = (dotentity + '/' + record[identityIndex]);
+                }
+              }
+              if (!rowName) {
+                let msg = 'response attributeNames did not contain a name field';
+                QDR.log.error(msg);
+                callback(Error(msg));
+                return;
+              }
+              for (let j=0; j<attributeNames.length; ++j) {
+                let col = attributeNames[j];
+                row[col] = {value: record[j], type: undefined, graph: false, title: '', aggregate: '', aggregateTip: ''};
+                if (ent) {
+                  let att = ent.attributes[col];
+                  if (att) {
+                    row[col].type = att.type;
+                    row[col].graph = att.graph;
+                    row[col].title = att.description;
+
+                    if (aggregate) {
+                      if (att.graph) {
+                        row[col].aggregate = att.graph ? aggregate[j].sum : '';
+                        let tip = [];
+                        aggregate[j].detail.forEach( function (line) {
+                          tip.push(line);
+                        });
+                        row[col].aggregateTip = angular.toJson(tip);
+                      }
+                    }
+                  }
+                }
+              }
+              tableRows.push(row);
+            }
+            tableRows.sort( function (a, b) { return a.name.value.localeCompare(b.name.value); });
+            selectRow({entity: dotentity, rows: tableRows, expand: expand});
+          }
+          callback(null);  // let queue handler know we are done
+        };
+        // 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) {
+          let nodeIdList = QDRService.management.topology.nodeIdList();
+          QDRService.management.topology.getMultipleNodeInfo(nodeIdList, entity, [], gotNodeInfo, $scope.selectedNodeId);
+        } else {
+          QDRService.management.topology.fetchEntity($scope.selectedNodeId, entity, [], gotNodeInfo);
+        }
+      };
+
+      // tableRows are the records that were returned, this populates the left hand tree on the page
+      var selectRow = function (info) {
+        updateTreeChildren(info.entity, info.rows, info.expand);
+      };
+      $scope.detailFields = [];
+
+      $scope.addToGraph = function(rowEntity) {
+        let chart = QDRChartService.registerChart(
+          {nodeId: $scope.selectedNodeId,
+            entity: $scope.selectedEntity,
+            name:   $scope.selectedRecordName,
+            attr:    rowEntity.name,
+            forceCreate: true});
+
+        doDialog('tmplChartConfig.html', chart);
+      };
+
+      $scope.addAllToGraph = function(rowEntity) {
+        let chart = QDRChartService.registerChart({
+          nodeId:     $scope.selectedNodeId,
+          entity:     $scope.selectedEntity,
+          name:       $scope.selectedRecordName,
+          attr:       rowEntity.name,
+          type:       'rate',
+          rateWindow: updateInterval,
+          visibleDuration: 0.25,
+          forceCreate: true,
+          aggregate:   true});
+        doDialog('tmplChartConfig.html', chart);
+      };
+
+      // The ui-popover dynamic html
+      $scope.aggregateTip = '';
+      // disable popover tips for non-integer cells
+      $scope.aggregateTipEnabled = function (row) {
+        let tip = row.entity.aggregateTip;
+        return (tip && tip.length) ? 'true' : 'false';
+      };
+      // convert the aggregate data into a table for the popover tip
+      $scope.genAggregateTip = function (row) {
+        let tip = row.entity.aggregateTip;
+        if (tip && tip.length) {
+          let data = angular.fromJson(tip);
+          let table = '<table class=\'tiptable\'><tbody>';
+          data.forEach (function (row) {
+            table += '<tr>';
+            table += '<td>' + row.node + '</td><td align=\'right\'>' + QDRService.utilities.pretty(row.val) + '</td>';
+            table += '</tr>';
+          });
+          table += '</tbody></table>';
+          $scope.aggregateTip = $sce.trustAsHtml(table);
+        }
+      };
+      var aggregateColumn = function () {
+        if ((aggregateEntities.indexOf($scope.selectedEntity) > -1 && $scope.detailCols.length != 3) ||
+        (aggregateEntities.indexOf($scope.selectedEntity) == -1 && $scope.detailCols.length != 2)) {
+        // column defs have to be reassigned and not spliced, so no push/pop
+          $scope.detailCols = [
+            {
+              field: 'attributeName',
+              displayName: 'Attribute',
+              cellTemplate: '<div title="{{row.entity.title}}" class="listAttrName ui-grid-cell-contents">{{COL_FIELD CUSTOM_FILTERS | pretty}}<button ng-if="row.entity.graph" title="Click to view/add a graph" ng-click="grid.appScope.addToGraph(row.entity)" ng-class="{\'btn-success\': row.entity.chartExists}" class="btn"><i ng-class="{\'icon-bar-chart\': row.entity.graph == true }"></i></button></div>'
+            },
+            {
+              field: 'attributeValue',
+              displayName: 'Value',
+              cellTemplate: '<div class="ui-grid-cell-contents" ng-class="{\'changed\': row.entity.changed == 1}">{{COL_FIELD CUSTOM_FILTERS | pretty}}</div>'
+            }
+          ];
+          if (aggregateEntities.indexOf($scope.selectedEntity) > -1) {
+            $scope.detailCols.push(
+              {
+                width: '10%',
+                field: 'aggregateValue',
+                displayName: 'Aggregate',
+                cellTemplate: '<div popover-enable="{{grid.appScope.aggregateTipEnabled(row)}}" uib-popover-html="grid.appScope.aggregateTip" popover-append-to-body="true" ng-mouseover="grid.appScope.genAggregateTip(row)" popover-trigger="\'mouseenter\'" class="listAggrValue ui-grid-cell-contents" ng-class="{\'changed\': row.entity.changed == 1}">{{COL_FIELD CUSTOM_FILTERS}} <button title="Click to view/add a graph" ng-if="row.entity.graph" ng-click="grid.appScope.addAllToGraph(row.entity)" ng-class="{\'btn-success\': row.entity.aggchartExists}" class="btn"><i ng-class="{\'icon-bar-chart\': row.entity.graph == true }"></i></button></div>',
+                cellClass: 'aggregate'
+              }
+            );
+          }
+        }
+        if ($scope.selectedRecordName === '')
+          $scope.detailCols = [];
+
+        $scope.details.columnDefs = $scope.detailCols;
+        if ($scope.gridApi)
+          $scope.gridApi.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
+      };
+
+      $scope.gridApi = undefined;
+      // the table on the right of the page contains a row for each field in the selected record in the table on the left
+      $scope.desiredTableHeight = 340;
+      $scope.detailCols = [];
+      $scope.details = {
+        data: 'detailFields',
+        columnDefs: [
+          {
+            field: 'attributeName',
+            displayName: 'Attribute',
+            cellTemplate: '<div title="{{row.entity.title}}" class="listAttrName ui-grid-cell-contents">{{COL_FIELD CUSTOM_FILTERS | pretty}}<button ng-if="row.entity.graph" title="Click to view/add a graph" ng-click="grid.appScope.addToGraph(row.entity)" ng-class="{\'btn-success\': row.entity.chartExists}" class="btn"><i ng-class="{\'icon-bar-chart\': row.entity.graph == true }"></i></button></div>'
+          },
+          {
+            field: 'attributeValue',
+            displayName: 'Value',
+            cellTemplate: '<div class="ui-grid-cell-contents" ng-class="{\'changed\': row.entity.changed == 1}">{{COL_FIELD CUSTOM_FILTERS | pretty}}</div>'
+          }
+        ],
+        enableColumnResize: true,
+        enableHorizontalScrollbar: 0,
+        enableVerticalScrollbar: 0,
+        multiSelect: false,
+        jqueryUIDraggable: true,
+        onRegisterApi: function(gridApi) {
+          $scope.gridApi = gridApi;
+        }
+      };
+
+      $scope.$on('$destroy', function() {
+        clearTimeout(updateIntervalHandle);
+        $(window).off('resize', resizer);
+      });
+
+      function gotMethodResponse (entity, context) {
+        let statusCode = context.message.application_properties.statusCode, note;
+        if (statusCode < 200 || statusCode >= 300) {
+          note = 'Failed to ' + $filter('Pascalcase')($scope.currentMode.op) + ' ' + entity + ': ' + context.message.application_properties.statusDescription;
+          QDR.Core.notification('error', note);
+          QDR.log.error('Error ' + note);
+        } else {
+          note = entity + ' ' + $filter('Pascalcase')($scope.currentMode.op) + 'd';
+          QDR.Core.notification('success', note);
+          QDR.log.debug('Success ' + note);
+          $scope.selectMode($scope.modes[0]);
+        }
+      }
+      $scope.ok = function () {
+        let attributes = {};
+        $scope.detailFields.forEach( function (field) {
+          let value = field.rawValue;
+          if (field.input === 'input') {
+            if (field.type === 'text' || field.type === 'disabled')
+              value = field.attributeValue;
+          } else if (field.input === 'select') {
+            value = field.selected;
+          } else if (field.input === 'boolean') {
+            value = field.rawValue;
+          }
+          if (value === '')
+            value = undefined;
+
+          if ((value && value != field['default']) || field.required || (field.name === 'role')) {
+            if (field.name !== 'identity')
+              attributes[field.name] = value;
+          }
+        });
+        QDRService.management.connection.sendMethod($scope.currentNode.id, $scope.selectedEntity, attributes, $scope.currentMode.op)
+          .then(function (response) {gotMethodResponse($scope.selectedEntity, response.context);});
+      };
+      $scope.remove = function () {
+        let identity = $scope.selectedTreeNode.data.details.identity.value;
+        let attributes = {type: $scope.selectedEntity, identity: identity};
+        QDRService.management.connection.sendMethod($scope.currentNode.id, $scope.selectedEntity, attributes, $scope.currentMode.op)
+          .then(function (response) {gotMethodResponse($scope.selectedEntity, response.context);});
+      };
+
+      function doDialog(template, chart) {
+
+        $uibModal.open({
+          backdrop: true,
+          keyboard: true,
+          backdropClick: true,
+          templateUrl: QDR.templatePath + template,
+          controller: 'QDR.ChartDialogController',
+          resolve: {
+            chart: function() {
+              return chart;
+            },
+            updateTick: function () {
+              return function () {};
+            },
+            dashboard: function () {
+              return $scope;
+            },
+            adding: function () {
+              return true;
+            }
+          }
+        }).result.then(function() {
+          QDRChartService.unRegisterChart(chart);
+        });
+      }
+      var setCurrentNode = function () {
+        let currentNode;
+        $scope.nodes.some( function (node, i) {
+          if (node.name === $scope.selectedNode) {
+            currentNode = $scope.nodes[i];
+            return true;
+          }
+        });
+        if ($scope.currentNode !== currentNode)
+          $scope.currentNode = currentNode;
+      };
+
+      let treeReady = false;
+      let serviceReady = false;
+      $scope.largeNetwork = QDRService.management.topology.isLargeNetwork();
+      if ($scope.largeNetwork)
+        aggregateEntities = [];
+
+      // called after we know for sure the schema is fetched and the routers are all ready
+      QDRService.management.topology.addUpdatedAction('initList', function () {
+        QDRService.management.topology.stopUpdating();
+        QDRService.management.topology.delUpdatedAction('initList');
+
+        $scope.nodes = QDRService.management.topology.nodeList().sort(function (a, b) { return a.name.toLowerCase() > b.name.toLowerCase();});
+        // unable to get node list? Bail.
+        if ($scope.nodes.length == 0) {
+          $location.path('/' + QDR.pluginName + '/connect');
+          $location.search('org', 'list');
+        }
+        if (!angular.isDefined($scope.selectedNode)) {
+        //QDR.log.debug("selectedNode was " + $scope.selectedNode);
+          if ($scope.nodes.length > 0) {
+            $scope.selectedNode = $scope.nodes[0].name;
+            $scope.selectedNodeId = $scope.nodes[0].id;
+          //QDR.log.debug("forcing selectedNode to " + $scope.selectedNode);
+          }
+        }
+        setCurrentNode();
+        if ($scope.currentNode == undefined) {
+          if ($scope.nodes.length > 0) {
+            $scope.selectedNode = $scope.nodes[0].name;
+            $scope.selectedNodeId = $scope.nodes[0].id;
+            $scope.currentNode = $scope.nodes[0];
+          }
+        }
+        let sortedEntities = Object.keys(QDRService.management.schema().entityTypes).sort();
+        sortedEntities.forEach( function (entity) {
+          if (excludedEntities.indexOf(entity) == -1) {
+            if (!angular.isDefined($scope.selectedEntity))
+              $scope.selectedEntity = entity;
+            $scope.operations = lookupOperations();
+            let e = new QDR.Folder(entity);
+            e.typeName = 'entity';
+            e.key = entity;
+            e.expanded = (expandedList.indexOf(entity) > -1);
+            let placeHolder = new QDR.Leaf('loading...');
+            placeHolder.addClass = 'loading';
+            e.children = [placeHolder];
+            entityTreeChildren.push(e);
+          }
+        });
+        serviceReady = true;
+        initTree();
+      });
+      // called by ng-init="treeReady()" in tmplListTree.html
+      $scope.treeReady = function () {
+        treeReady = true;
+        initTree();
+      };
+
+      // this gets called once tree is initialized
+      var onTreeInitialized = function () {
+        updateExpandedEntities();
+      };
+
+      var initTree = function () {
+        if (!treeReady || !serviceReady)
+          return;
+        $('#entityTree').fancytree({
+          activate: onTreeNodeActivated,
+          expand: onTreeNodeExpanded,
+          beforeActivate: onTreeNodeBeforeActivate,
+          beforeSelect: function(event, data){
+            // A node is about to be selected: prevent this for folders:
+            if( data.node.isFolder() ){
+              return false;
+            }
+          },
+          init: onTreeInitialized,
+          selectMode: 1,
+          autoCollapse: $scope.largeNetwork,
+          activeVisible: !$scope.largeNetwork,
+          clickFolderMode: 3,
+          debugLevel: 0,
+          extraClasses: {
+            expander: 'fa-angle',
+            connector: 'fancytree-no-connector'
+          },
+          source: entityTreeChildren
+        });
+      };
+      QDRService.management.topology.ensureAllEntities({entity: 'connection'}, function () {
+        QDRService.management.topology.setUpdateEntities(['connection']);
+        // keep the list of routers up to date
+        QDRService.management.topology.startUpdating(true);
+      });
+
+      updateIntervalHandle = setInterval(function () {
+        if (!treeReady || !serviceReady)
+          return;
+
+        let now = Date.now();
+        if (((now - last_updated) >= updateInterval) || updateNow) {
+          updateNow = false;
+          $timeout( function () {
+            updateExpandedEntities();
+            resizer();
+          });
+        }
+      }, 100);
+
+    }]);
+
+  return QDR;
+
+} (QDR || {}));
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/js/qdrListChart.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrListChart.js b/console/stand-alone/plugin/js/qdrListChart.js
new file mode 100644
index 0000000..711046e
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrListChart.js
@@ -0,0 +1,146 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+'use strict';
+/* global angular */
+
+/**
+ * @module QDR
+ */
+
+var QDR = (function(QDR) {
+
+  QDR.module.controller('QDR.ListChartController', function ($scope, $uibModalInstance, $uibModal, $location, QDRService, QDRChartService, chart, nodeName) {
+
+    $scope.chart = chart;
+    $scope.dialogSvgChart = null;
+    let updateTimer = null;
+    $scope.svgDivId = 'dialogChart';    // the div id for the svg chart
+
+    $scope.showChartsPage = function () {
+      cleanup();
+      $uibModalInstance.close(true);
+      $location.path(QDR.pluginRoot + '/charts');
+    };
+
+    $scope.addHChart = function () {
+      QDRChartService.addHDash($scope.chart);
+      cleanup();
+      $uibModalInstance.close(true);
+    };
+
+    $scope.addToDashboardLink = function () {
+      let href = '#/' + QDR.pluginName + '/charts';
+      let size = angular.toJson({
+        size_x: 2,
+        size_y: 2
+      });
+
+      let params = angular.toJson({chid: $scope.chart.id()});
+      let title = 'Dispatch - ' + nodeName;
+      return '/hawtio/#/dashboard/add?tab=dashboard' +
+        '&href=' + encodeURIComponent(href) +
+        '&routeParams=' + encodeURIComponent(params) +
+        '&title=' + encodeURIComponent(title) +
+        '&size=' + encodeURIComponent(size);
+    };
+
+
+    $scope.addChartsPage = function () {
+      QDRChartService.addDashboard($scope.chart);
+    };
+
+    $scope.delChartsPage = function () {
+      QDRChartService.delDashboard($scope.chart);
+    };
+
+    $scope.isOnChartsPage = function () {
+      return $scope.chart.dashboard;
+    };
+
+    var showChart = function () {
+      // we need a width and height before generating the chart
+      let div = angular.element('#pfDialogChart');
+      if (!div.width()) {
+        setTimeout(showChart, 100);
+        return;
+      }
+      $scope.pfDialogSvgChart = new QDRChartService.pfAreaChart($scope.chart, 'pfDialogChart');
+      updateDialogChart();
+    };
+    showChart();
+
+    var updateDialogChart = function () {
+      if ($scope.pfDialogSvgChart) {
+        $scope.pfDialogSvgChart.tick();
+      }
+      if (updateTimer)
+        clearTimeout(updateTimer);
+      updateTimer = setTimeout(updateDialogChart, 1000);
+    };
+
+    var cleanup = function () {
+      if (updateTimer) {
+        clearTimeout(updateTimer);
+        updateTimer = null;
+      }
+      if (!$scope.chart.hdash && !$scope.chart.dashboard)
+        QDRChartService.unRegisterChart($scope.chart);     // remove the chart
+
+    };
+    $scope.ok = function () {
+      cleanup();
+      $uibModalInstance.close(true);
+    };
+
+    $scope.editChart = function () {
+      doDialog('tmplChartConfig.html', chart);
+    };
+
+    function doDialog(template, chart) {
+
+      $uibModal.open({
+        backdrop: true,
+        keyboard: true,
+        backdropClick: true,
+        templateUrl: QDR.templatePath + template,
+        controller: 'QDR.ChartDialogController',
+        resolve: {
+          chart: function() {
+            return chart;
+          },
+          updateTick: function () {
+            return function () {};
+          },
+          dashboard: function () {
+            return $scope;
+          },
+          adding: function () {
+            return true;
+          }
+        }
+      }).result.then(function() {
+        $scope.ok();
+      });
+    }
+
+  });
+
+  return QDR;
+
+} (QDR || {}));


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