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 2017/04/19 12:17:20 UTC

[1/4] qpid-dispatch git commit: DISPATCH-745 Remove symlinks from hawtio to stand-alone

Repository: qpid-dispatch
Updated Branches:
  refs/heads/master 4c14b0a49 -> 6a1e66322


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6a1e6632/console/stand-alone/plugin/js/navbar.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/navbar.js b/console/stand-alone/plugin/js/navbar.js
index 5125748..0d7baf9 100644
--- a/console/stand-alone/plugin/js/navbar.js
+++ b/console/stand-alone/plugin/js/navbar.js
@@ -34,38 +34,44 @@ var QDR = (function (QDR) {
         content: '<i class="icon-cogs"></i> Connect',
         title: "Connect to a router",
         isValid: function () { return true; },
-        href: "#" + QDR.pluginRoot + "/connect"
+        href: "#!" + QDR.pluginRoot + "/connect",
+        name: "Connect"
     },
     {
-        content: '<i class="icon-home"></i> Overview',
+        content: '<i class="pficon-home"></i> Overview',
         title: "View router overview",
         isValid: function (QDRService) { return QDRService.isConnected(); },
-        href: "#" + QDR.pluginRoot + "/overview"
+        href: "#!" + QDR.pluginRoot + "/overview",
+        name: "Overview"
       },
     {
         content: '<i class="icon-list "></i> Entities',
         title: "View the attributes of the router entities",
         isValid: function (QDRService) { return QDRService.isConnected(); },
-        href: "#" + QDR.pluginRoot + "/list"
+        href: "#!" + QDR.pluginRoot + "/list",
+        name: "Entities"
       },
     {
         content: '<i class="icon-star-empty"></i> Topology',
         title: "View router network topology",
         isValid: function (QDRService) { return QDRService.isConnected(); },
-        href: "#" + QDR.pluginRoot + "/topology"
+        href: "#!" + QDR.pluginRoot + "/topology",
+        name: "Topology"
       },
     {
         content: '<i class="icon-bar-chart"></i> Charts',
         title: "View charts",
         isValid: function (QDRService, $location) { return QDRService.isConnected() && QDR.isStandalone; },
-        href: "#/charts"
+        href: "#!/charts",
+        name: "Charts"
     },
     {
         content: '<i class="icon-align-left"></i> Schema',
         title: "View dispatch schema",
         isValid: function (QDRService) { return QDRService.isConnected(); },
-        href: "#" + QDR.pluginRoot + "/schema",
-        right: true
+        href: "#!" + QDR.pluginRoot + "/schema",
+        right: true,
+        name: "Schema"
       }
   ];
   /**
@@ -77,17 +83,22 @@ var QDR = (function (QDR) {
    * The controller for this plugin's navigation bar
    *
    */
-  QDR.module.controller("QDR.NavBarController", ['$scope', 'QDRService', 'QDRChartService', '$routeParams', '$location', function($scope, QDRService, QDRChartService, $routeParams, $location) {
+  QDR.module.controller("QDR.NavBarController", ['$rootScope', '$scope', 'QDRService', 'QDRChartService', '$routeParams', '$location', function($rootScope, $scope, QDRService, QDRChartService, $routeParams, $location) {
     $scope.breadcrumbs = QDR.breadcrumbs;
     $scope.isValid = function(link) {
+      if ($scope.isActive(link.href))
+        $rootScope.$broadcast("setCrumb", {name: link.name, title: link.content})
       return link.isValid(QDRService, $location);
     };
 
     $scope.isActive = function(href) {
-    // highlight the connect tab if we are on the root page
-    if (($location.path() === QDR.pluginRoot) && (href.split("#")[1] === QDR.pluginRoot + "/connect"))
-      return true
-        return href.split("#")[1] == $location.path();
+//QDR.log.info("isActive(" + href + ") location.path() is " + $location.path())
+      // highlight the connect tab if we are on the root page
+      if (($location.path() === QDR.pluginRoot) && (href.split("#")[1] === QDR.pluginRoot + "/connect")) {
+//QDR.log.info("isActive is returning true for connect page")
+        return true
+      }
+      return href.split("#")[1] === '!' + $location.path();
     };
 
     $scope.isRight = function (link) {
@@ -123,7 +134,7 @@ var QDR = (function (QDR) {
   }]);
 
   // controller for the edit/configure chart dialog
-  QDR.module.controller("QDR.ChartDialogController", function($scope, QDRChartService, $location, dialog, chart, updateTick, dashboard, adding) {
+  QDR.module.controller("QDR.ChartDialogController", function($scope, QDRChartService, $location, $uibModalInstance, chart, updateTick, dashboard, adding) {
     var dialogSvgChart = null;
     $scope.svgDivId = "dialogEditChart";    // the div id for the svg chart
 
@@ -166,7 +177,7 @@ var QDR = (function (QDR) {
 
     $scope.showChartsPage = function () {
       cleanup();
-      dialog.close(true);
+      $uibModalInstance.close(true);
       $location.path(QDR.pluginRoot + "/charts");
     };
 
@@ -180,7 +191,7 @@ var QDR = (function (QDR) {
     }
     $scope.okClick = function () {
       cleanup();
-      dialog.close(true);
+      $uibModalInstance.close(true);
     };
 
     var initRateSlider = function () {

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6a1e6632/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
index ec2efb2..0e5ee8f 100644
--- a/console/stand-alone/plugin/js/qdrList.js
+++ b/console/stand-alone/plugin/js/qdrList.js
@@ -28,8 +28,8 @@ var QDR = (function(QDR) {
    *
    * Controller for the main interface
    */
-  QDR.module.controller("QDR.ListController", ['$scope', '$location', '$dialog', '$filter', '$timeout', 'QDRService', 'QDRChartService',
-    function ($scope, $location, $dialog, $filter, $timeout, QDRService, QDRChartService) {
+  QDR.module.controller("QDR.ListController", ['$scope', '$location', '$uibModal', '$filter', '$timeout', 'QDRService', 'QDRChartService',
+    function ($scope, $location, $uibModal, $filter, $timeout, QDRService, QDRChartService) {
 
     var updateIntervalHandle = undefined;
     var updateInterval = 5000;
@@ -675,7 +675,7 @@ var QDR = (function(QDR) {
     }
 
     function doDialog(tmpl, chart) {
-        var d = $dialog.dialog({
+        var d = $uibModal.open({
           backdrop: true,
           keyboard: true,
           backdropClick: true,
@@ -691,7 +691,7 @@ var QDR = (function(QDR) {
               }
         });
 
-        d.open().then(function(result) { console.log("d.open().then"); });
+        d.result.then(function(result) { console.log("d.open().then"); });
 
     };
 
@@ -744,6 +744,7 @@ var QDR = (function(QDR) {
           var e = new Folder(entity)
           e.typeName = "entity"
           e.key = entity
+          e.isFolder = true
           e.expand = (expandedList.indexOf(entity) > -1)
           var placeHolder = new Folder("loading...")
           placeHolder.addClass = "loading"
@@ -769,6 +770,10 @@ var QDR = (function(QDR) {
         autoCollapse: $scope.largeNetwork,
         activeVisible: !$scope.largeNetwork,
         debugLevel: 0,
+        classNames: {
+          expander: 'fa-angle',
+          connector: 'dynatree-no-connector'
+          },
         children: entityTreeChildren
       })
       restartUpdate()

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6a1e6632/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
index 93391f1..0e5ee8f 100644
--- a/console/stand-alone/plugin/js/qdrListChart.js
+++ b/console/stand-alone/plugin/js/qdrListChart.js
@@ -21,121 +21,772 @@ under the License.
  */
 var QDR = (function(QDR) {
 
-  QDR.module.controller('QDR.ListChartController', function ($scope, dialog, $dialog, $location, QDRChartService, chart, nodeName) {
-    $scope.chart = chart;
-    $scope.dialogSvgChart = null;
-    var updateTimer = null;
-    $scope.svgDivId = "dialogChart";    // the div id for the svg chart
-
-    $scope.showChartsPage = function () {
-      cleanup();
-      dialog.close(true);
-      $location.path(QDR.pluginRoot + "/charts");
-    };
+  /**
+   * @method ListController
+   * @param $scope
+   * @param QDRService
+   *
+   * Controller for the main interface
+   */
+  QDR.module.controller("QDR.ListController", ['$scope', '$location', '$uibModal', '$filter', '$timeout', 'QDRService', 'QDRChartService',
+    function ($scope, $location, $uibModal, $filter, $timeout, QDRService, QDRChartService) {
 
-    $scope.addHChart = function () {
-      QDRChartService.addHDash($scope.chart);
-      cleanup();
-      dialog.close(true);
-    }
-
-    $scope.addToDashboardLink = function () {
-      var href = "#/" + QDR.pluginName + "/charts";
-      var size = angular.toJson({
-                  size_x: 2,
-                  size_y: 2
-                });
-
-      var params = angular.toJson({chid: $scope.chart.id()});
-      var title = "Dispatch - " + nodeName;
-      return "/hawtio/#/dashboard/add?tab=dashboard" +
-        "&href=" + encodeURIComponent(href) +
-        "&routeParams=" + encodeURIComponent(params) +
-        "&title=" + encodeURIComponent(title) +
-        "&size=" + encodeURIComponent(size);
-    };
+    var updateIntervalHandle = undefined;
+    var updateInterval = 5000;
+    var ListExpandedKey = "QDRListExpanded";
+    $scope.details = {};
+
+    $scope.tmplListTree = QDR.templatePath + 'tmplListTree.html';
+    $scope.selectedEntity = localStorage['QDRSelectedEntity'] || "address";
+    $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 () {
+          //QDR.log.debug("isValid UPDAATE? " + this.op)
+          //console.dump($scope.operations)
+          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;
+        var entity; // undefined since it is not supported in the GET-LOG call
+        QDRService.sendMethod($scope.currentNode.id, entity, {}, $scope.currentMode.op, {}, function (nodeName, entity, response, context) {
+          $scope.fetchingLog = false;
+          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)
+            return;
+          }
+          $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.expandAll = function () {
+      $("#entityTree").dynatree("getRoot").visit(function(node){
+                node.expand(true);
+            });
+    }
+    $scope.contractAll = function () {
+      $("#entityTree").dynatree("getRoot").visit(function(node){
+                node.expand(false);
+            });
+    }
+
+    if (!QDRService.connected) {
+      // we are not connected. we probably got here from a bookmark or manual page reload
+      QDRService.redirectWhenConnected("list");
+      return;
+    }
+    // we are currently connected. setup a handler to get notified if we are ever disconnected
+    QDRService.addDisconnectAction( function () {
+      QDRService.redirectWhenConnected("list")
+      $scope.$apply();
+    })
+
+    $scope.nodes = []
+    var excludedEntities = ["management", "org.amqp.management", "operationalEntity", "entity", "configurationEntity", "dummy", "console"];
+    var aggregateEntities = ["router.address"];
+    var classOverrides = {
+      "connection": function (row, nodeId) {
+        var isConsole = QDRService.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) {
+        var link = {nodeId: nodeId, connectionId: row.connectionId.value}
+        var isConsole = QDRService.isConsoleLink(link)
+        return isConsole ? "console" : row.linkType.value;
+      },
+      "router.address": function (row) {
+        var identity = QDRService.identity_clean(row.identity.value)
+        var address = QDRService.addr_text(identity)
+        var cls = QDRService.addr_class(identity)
+        if (address === "$management")
+          cls = "internal " + cls
+        return cls
+      }
+    }
+
+    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 = [];
+    var expandedList = angular.fromJson(localStorage[ListExpandedKey]) || [];
+    var onTreeNodeExpanded = function (expanded, node) {
+      // save the list of entities that are expanded
+      var tree = $("#entityTree").dynatree("getTree");
+      var list = [];
+      tree.visit( function (tnode) {
+        if (tnode.isExpanded()) {
+          list.push(tnode.data.key)
+        }
+      })
+      localStorage[ListExpandedKey] = JSON.stringify(list)
+
+      if (expanded)
+        onTreeSelected(node);
+    }
+    // a tree node was selected
+    var onTreeSelected = function (selectedNode) {
+      $timeout( function () {
+        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.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);   // update the table on the right
+          $("#entityTree").dynatree("getRoot").visit(function(node){
+             node.select(false);
+          });
+          selectedNode.select();
+        }
+      })
+    }
+
+    // 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]
+        var value = ""
+        if (angular.isDefined(entity['default']))
+          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 () {
+      var schemaEntity = QDRService.schema.entityTypes[$scope.selectedEntity]
+      return (schemaEntity.operations.indexOf("CREATE") > -1)
+    }
+
+    var stopUpdating = function () {
+      if (angular.isDefined(updateIntervalHandle)) {
+        clearInterval(updateIntervalHandle);
+      }
+      updateIntervalHandle = undefined;
+    }
+
+    // the data for the selected entity is available, populate the tree
+    var updateEntityChildren = function (entity, tableRows, expand) {
+      var tree = $("#entityTree").dynatree("getTree");
+      if (!tree.getNodeByKey) {
+        return stopUpdating()
+      }
+      var node = tree.getNodeByKey(entity)
+      var updatedDetails = false;
+      var scrollTreeDiv = $('.qdr-attributes.pane.left .pane-viewport')
+      var scrollTop = scrollTreeDiv.scrollTop();
+      node.removeChildren();
+      if (tableRows.length == 0) {
+          node.addChild({
+          addClass:   "no-data",
+              typeName:   "none",
+              title:      "no data",
+          key:        node.data.key + ".1"
+          })
+          if (expand) {
+              updateDetails(fromSchema(entity));
+                 $scope.selectedRecordName = entity;
+        }
+      } else {
+        tableRows.forEach( function (row) {
+          var addClass = entity;
+          if (classOverrides[entity]) {
+            addClass += " " + classOverrides[entity](row, $scope.currentNode.id);
+          }
+          var child = {
+                        typeName:   "attribute",
+                        addClass:   addClass,
+                        tooltip:    addClass,
+                        key:        row.name.value,
+                        title:      row.name.value,
+                        details:    row
+                    }
+          if (row.name.value === $scope.selectedRecordName) {
+            if (expand)
+              updateDetails(row); // update the table on the right
+            child.select = true;
+            updatedDetails = true;
+          }
+          node.addChild(child)
+        })
+      }
+      // if the selectedRecordName was not found, select the 1st one
+      if (expand && !updatedDetails && tableRows.length > 0) {
+        var row = tableRows[0];
+        $scope.selectedRecordName = row.name.value;
+        var node = tree.getNodeByKey($scope.selectedRecordName);
+        node.select(true);
+        updateDetails(row)  // update the table on the right
+      }
+      scrollTreeDiv.scrollTop(scrollTop)
+    }
+
+    var schemaProps = function (entityName, key, currentNode) {
+         var typeMap = {integer: 'number', string: 'text', path: 'text', boolean: 'boolean', map: 'textarea'};
+
+      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.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.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;
+    }
+
+    // update the table on the right
+    var updateDetails = function (row) {
+      var details = [];
+      $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: 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,
+
+          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;
+      })
+      setTimeout(applyDetails, 1, details)
+    }
+
+    var applyDetails = function (details) {
+      $scope.detailFields = details;
+      aggregateColumn();
+      $scope.$apply();
+      // ng-grid bug? the entire table doesn't always draw unless a reflow is triggered;
+      $(window).trigger('resize');
+    }
 
+    var restartUpdate = function () {
+      stopUpdating();
+      updateTableData($scope.selectedEntity, true);
+      updateIntervalHandle = setInterval(updateExpandedEntities, updateInterval);
+    }
+    var updateExpandedEntities = function () {
+      var tree = $("#entityTree").dynatree("getTree");
+      if (tree.visit) {
+        tree.visit( function (node) {
+          if (node.isExpanded()) {
+            updateTableData(node.data.key, node.data.key === $scope.selectedEntity)
+          }
+        })
+      } else {
+        stopUpdating();
+      }
+    }
 
-    $scope.addChartsPage = function () {
-      QDRChartService.addDashboard($scope.chart);
+    $scope.selectNode = function(node) {
+      $scope.selectedNode = node.name;
+      $scope.selectedNodeId = node.id;
+      setCurrentNode();
+      restartUpdate();
     };
+    $scope.$watch('selectedEntity', function(newValue, oldValue) {
+      if (newValue !== oldValue) {
+        localStorage['QDRSelectedEntity'] = $scope.selectedEntity;
+        restartUpdate();
+        $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 updateTableData = function (entity, expand) {
+      if (!QDRService.connected) {
+        // we are no longer connected. bail back to the connect page
+        $location.path("/" + QDR.pluginName + "/connect")
+        $location.search('org', "list");
+        return;
+      }
+      // don't update the data when on the operations tab
+      if ($scope.currentMode.id === 'operations') {
+        return;
+      }
+
+      var gotNodeInfo = function (nodeName, dotentity, response) {
+        var tableRows = [];
+        var records = response.results;
+        var aggregates = response.aggregates;
+        var attributeNames = response.attributeNames;
+        // If !attributeNmes then  there was an error getting the records for this entity
+        if (attributeNames) {
+          var nameIndex = attributeNames.indexOf("name");
+          var identityIndex = attributeNames.indexOf("identity");
+          var ent = QDRService.schema.entityTypes[entity];
+          for (var i=0; i<records.length; ++i) {
+            var record = records[i];
+            var aggregate = aggregates ? aggregates[i] : undefined;
+            var row = {};
+            var rowName;
+            if (nameIndex > -1) {
+              rowName = record[nameIndex];
+              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;
+            }
+            for (var j=0; j<attributeNames.length; ++j) {
+              var col = attributeNames[j];
+              row[col] = {value: record[j], type: undefined, graph: false, title: '', aggregate: '', aggregateTip: ''};
+              if (ent) {
+                var 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 : '';
+                      var tip = [];
+                      aggregate[j].detail.forEach( function (line) {
+                        tip.push(line);
+                      })
+                      row[col].aggregateTip = angular.toJson(tip);
+                    }
+                  }
+                }
+              }
+            }
+            tableRows.push(row);
+          }
+        }
 
-    $scope.delChartsPage = function () {
-      QDRChartService.delDashboard($scope.chart);
+        tableRows.sort( function (a, b) { return a.name.value.localeCompare(b.name.value) })
+        setTimeout(selectRow, 0, {entity: dotentity, 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.fetchEntity($scope.selectedNodeId, entity, [], gotNodeInfo);
+      }
     };
 
-    $scope.isOnChartsPage = function () {
-      return $scope.chart.dashboard;
+    // tableRows are the records that were returned, this populates the left hand table on the page
+    var selectRow = function (info) {
+      updateEntityChildren(info.entity, info.rows, info.expand);
+      fixTooltips();
     }
 
-    var showChart = function () {
-      // the chart divs are generated by angular and aren't available immediately
-      var div = angular.element("#" + $scope.svgDivId);
-      if (!div.width()) {
-        setTimeout(showChart, 100);
+    var titleFromAlt = function (alt) {
+      if (alt && alt.length) {
+        var data = angular.fromJson(alt);
+        var table = "<table class='tiptable'><tbody>";
+        data.forEach (function (row) {
+          table += "<tr>";
+          table += "<td>" + row.node + "</td><td align='right'>" + QDRService.pretty(row.val) + "</td>";
+          table += "</tr>"
+        })
+        table += "</tbody></table>"
+        return table;
+      }
+      return '';
+    }
+
+    var fixTooltips = function () {
+      if ($('.hastip').length == 0) {
+        setTimeout(fixTooltips, 100);
         return;
       }
-      dialogSvgChart = new QDRChartService.AreaChart($scope.chart);
-      $scope.dialogSvgChart = dialogSvgChart;
-      updateDialogChart();
+      $('.hastip').each( function (i, tip) {
+        var tipset = tip.getAttribute('tipset')
+        if (!tipset) {
+          $(tip).tipsy({html: true, className: 'subTip', opacity: 1, title: function () {
+            return titleFromAlt(this.getAttribute('alt'))
+          } });
+          tip.setAttribute('tipset', true)
+        } else {
+          var title = titleFromAlt(tip.getAttribute('alt'))
+          tip.setAttribute('original-title', title)
+        }
+      })
     }
-    showChart();
 
-    var updateDialogChart = function () {
-      if ($scope.dialogSvgChart)
-        $scope.dialogSvgChart.tick($scope.svgDivId);
-      if (updateTimer)
-        clearTimeout(updateTimer)
-      updateTimer = setTimeout(updateDialogChart, 1000);
+    $scope.detailFields = [];
+
+    $scope.addToGraph = function(rowEntity) {
+      var chart = QDRChartService.registerChart(
+        {nodeId: $scope.selectedNodeId,
+         entity: "." + $scope.selectedEntity,
+         name:   $scope.selectedRecordName,
+         attr:    rowEntity.name,
+         forceCreate: true});
+      doDialog('tmplListChart.html', chart);
+    }
+
+    $scope.addAllToGraph = function(rowEntity) {
+      var chart = QDRChartService.registerChart({
+        nodeId:     $scope.selectedNodeId,
+        entity:     $scope.selectedEntity,
+        name:       $scope.selectedRecordName,
+        attr:       rowEntity.name,
+        type:       "rate",
+        rateWindow: updateInterval,
+        visibleDuration: 1,
+        forceCreate: true,
+        aggregate:   true});
+      doDialog('tmplListChart.html', chart);
     }
 
-    var cleanup = function () {
-      if (updateTimer) {
-        clearTimeout(updateTimer);
-        updateTimer = null;
+    $scope.detailCols = [];
+    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">{{row.entity[col.field]}}<i ng-if="row.entity.graph" ng-click="addToGraph(row.entity)" ng-class="{\'icon-bar-chart\': row.entity.graph == true }"></i></div>'
+         },
+         {
+           field: 'attributeValue',
+           displayName: 'Value',
+           cellTemplate: '<div class="ngCellText" ng-class="{\'changed\': row.entity.changed == 1}"><span>{{row.getProperty(col.field)}}</span></div>'
+         }
+         ]
+        if (aggregateEntities.indexOf($scope.selectedEntity) > -1) {
+          $scope.detailCols.push(
+           {
+             width: '10%',
+             field: 'aggregateValue',
+             displayName: 'Aggregate',
+             cellTemplate: '<div class="hastip" alt="{{row.entity.aggregateTip}}"><span ng-class="{\'changed\': row.entity.changed == 1}">{{row.entity[col.field]}}</span><i ng-if="row.entity.graph" ng-click="addAllToGraph(row.entity)" ng-class="{\'icon-bar-chart\': row.entity.graph == true }"></i></div>',
+             cellClass: 'aggregate'
+           }
+          )
+        }
       }
-      if (!$scope.chart.hdash && !$scope.chart.dashboard)
-        QDRChartService.unRegisterChart($scope.chart);     // remove the chart
+      if ($scope.selectedRecordName === "")
+        $scope.detailCols = [];
+    }
 
+    // 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.details = {
+      data: 'detailFields',
+      columnDefs: "detailCols",
+      enableColumnResize: true,
+      multiSelect: false,
+      beforeSelectionChange: function() {
+          return false;
+      }
+    };
+    $scope.$on("$destroy", function( event ) {
+      //QDR.log.debug("scope destroyed for qdrList");
+      stopUpdating();
+    });
+
+    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"
+        Core.notification('success', note);
+        $scope.selectMode($scope.modes[0]);
+        restartUpdate();
+      }
     }
     $scope.ok = function () {
-      cleanup();
-      dialog.close(true);
-      };
-
-    $scope.editChart = function () {
-      doDialog('tmplChartConfig.html', chart)
-    }
-
-    function doDialog(template, chart) {
-
-      $dialog.dialog({
-      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
+      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;
+        } 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.sendMethod($scope.currentNode.id, $scope.selectedEntity, attributes, $scope.currentMode.op, undefined, gotMethodResponse)
+    }
+    $scope.remove = function () {
+      var attributes = {type: $scope.selectedEntity, name: $scope.selectedRecordName}
+      QDRService.sendMethod($scope.currentNode.id, $scope.selectedEntity, attributes, $scope.currentMode.op, undefined, gotMethodResponse)
+    }
+
+    function doDialog(tmpl, chart) {
+        var d = $uibModal.open({
+          backdrop: true,
+          keyboard: true,
+          backdropClick: true,
+          templateUrl: QDR.templatePath + tmpl,
+          controller: "QDR.ListChartController",
+          resolve: {
+                 chart: function() {
+                   return chart
+                 },
+                 nodeName: function () {
+                    return $scope.selectedNode
+                 }
+              }
+        });
+
+        d.result.then(function(result) { console.log("d.open().then"); });
+
+    };
+
+    var setCurrentNode = function () {
+      $scope.nodes.some( function (node, i) {
+        if (node.name === $scope.selectedNode) {
+          $scope.currentNode = $scope.nodes[i]
+          return true;
+        }
+      })
+    }
+
+    var treeReady = false;
+    var serviceReady = false;
+    $scope.largeNetwork = QDRService.isLargeNetwork()
+    // called after we know for sure the schema is fetched and the routers are all ready
+    QDRService.addUpdatedAction("initList", function () {
+      QDRService.stopUpdating();
+      QDRService.delUpdatedAction("initList")
+
+      $scope.nodes = QDRService.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];
         }
       }
-      }).open().then(function(result) {
-        $scope.ok()
-      });
+      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 e = new Folder(entity)
+          e.typeName = "entity"
+          e.key = entity
+          e.isFolder = true
+          e.expand = (expandedList.indexOf(entity) > -1)
+          var placeHolder = new Folder("loading...")
+          placeHolder.addClass = "loading"
+          e.children = [placeHolder]
+          entityTreeChildren.push(e)
+        }
+      })
+      serviceReady = true;
+      initTree();
+    })
+    $scope.treeReady = function () {
+      treeReady = true;
+      initTree();
+    }
+
+    var initTree = function () {
+      if (!treeReady || !serviceReady)
+        return;
+      $('#entityTree').dynatree({
+        onActivate: onTreeSelected,
+        onExpand: onTreeNodeExpanded,
+        selectMode: 1,
+        autoCollapse: $scope.largeNetwork,
+        activeVisible: !$scope.largeNetwork,
+        debugLevel: 0,
+        classNames: {
+          expander: 'fa-angle',
+          connector: 'dynatree-no-connector'
+          },
+        children: entityTreeChildren
+      })
+      restartUpdate()
+      updateExpandedEntities();
     };
+    QDRService.ensureAllEntities({entity: ".connection"}, function () {
+      QDRService.setUpdateEntities([".connection"])
+      QDRService.startUpdating();
+    })
+
 
-  });
+  }]);
 
-  return QDR;
+    return QDR;
 
 } (QDR || {}));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6a1e6632/console/stand-alone/plugin/js/qdrNewNode.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrNewNode.js b/console/stand-alone/plugin/js/qdrNewNode.js
index f6d035a..48af70f 100644
--- a/console/stand-alone/plugin/js/qdrNewNode.js
+++ b/console/stand-alone/plugin/js/qdrNewNode.js
@@ -21,7 +21,7 @@ under the License.
  */
 var QDR = (function(QDR) {
 
-  QDR.module.controller("QDR.NodeDialogController", function($scope, QDRService, dialog, newname) {
+  QDR.module.controller("QDR.NodeDialogController", function($scope, QDRService, $uibModalInstance, newname) {
     var schema = QDRService.schema;
     var myEntities = ['router', 'log', 'listener'];
     var typeMap = {
@@ -298,13 +298,13 @@ var QDR = (function(QDR) {
       // handle the download button click
       // copy the dialog's values to the original node
     $scope.download = function() {
-      dialog.close({
+      $uibModalInstance.close({
         entities: $scope.entities,
         annotations: annotations
       });
     }
     $scope.cancel = function() {
-      dialog.close()
+      $uibModalInstance.close()
     };
 
     $scope.selectAnnotationTab = function(tabName) {
@@ -327,7 +327,7 @@ var QDR = (function(QDR) {
 
   });
 
-  QDR.module.controller("QDR.DownloadDialogController", function($scope, QDRService, $templateCache, $window, dialog, results) {
+  QDR.module.controller("QDR.DownloadDialogController", function($scope, QDRService, $templateCache, $window, $uibModalInstance, results) {
     var result = results.entities;
     var annotations = results.annotations;
     var annotationKeys = Object.keys(annotations);
@@ -437,7 +437,7 @@ var QDR = (function(QDR) {
     }
 
     $scope.done = function() {
-      dialog.close();
+      $uibModalInstance.close();
     }
   });
 

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6a1e6632/console/stand-alone/plugin/js/qdrOverview.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrOverview.js b/console/stand-alone/plugin/js/qdrOverview.js
index d9f9367..303a91d 100644
--- a/console/stand-alone/plugin/js/qdrOverview.js
+++ b/console/stand-alone/plugin/js/qdrOverview.js
@@ -34,7 +34,7 @@ var QDR = (function (QDR) {
    *
    * Controller that handles the QDR overview page
    */
-  QDR.module.controller("QDR.OverviewController", ['$scope', 'QDRService', '$location', '$timeout', '$dialog', function($scope, QDRService, $location, $timeout, $dialog) {
+  QDR.module.controller("QDR.OverviewController", ['$scope', 'QDRService', '$location', '$timeout', '$uibModal', function($scope, QDRService, $location, $timeout, $uibModal) {
 
     console.log("QDR.OverviewControll started with location of " + $location.path() + " and connection of  " + QDRService.connected);
     var COLUMNSTATEKEY = 'QDRColumnKey.';
@@ -69,8 +69,8 @@ var QDR = (function (QDR) {
     $scope.isActive = function (nav) {
       return nav == $scope.activeTab;
     }
-    $scope.filteredLinkFields = []
-    $scope.Link = null;
+    $scope.linkFields = []
+    $scope.link = null;
     var refreshInterval = 5000
     $scope.modes = [
       {title: 'Overview', name: 'Overview', right: false}
@@ -208,6 +208,25 @@ var QDR = (function (QDR) {
       loadColState($scope.allRouters)
     }
 
+    $scope.routerFields = []
+    $scope.routerGrid = {
+      saveKey: 'routerGrid',
+      data: 'routerFields',
+      columnDefs: [
+        {
+          field: 'attribute',
+          displayName: 'Attribute',
+          saveKey: 'routerGrid',
+        },
+        {
+          field: 'value',
+          displayName: 'Value',
+        }
+      ],
+      enableColumnResize: true,
+      multiSelect: false
+    }
+
     $scope.router = null;
     // get info for a single router
     var routerInfo = function (node) {
@@ -459,20 +478,19 @@ var QDR = (function (QDR) {
         }
         return include;
       })
-      QDR.log.debug("setting linkFields in updateLinkGrid filteredLinks.length=" + filteredLinks.length)
-      $scope.filteredLinkFields = filteredLinks;
+      QDR.log.debug("setting linkFields in updateLinkGrid")
+      $scope.linkFields = filteredLinks;
       getLinkPagedData($scope.linkPagingOptions.pageSize, $scope.linkPagingOptions.currentPage);
       // if we have a selected link
-      if ($scope.Link) {
+      if ($scope.link) {
         // find the selected link in the array of all links
-        var links = $scope.filteredLinkFields.filter(function (link) {
-          return link.name === $scope.Link.data.fields.name;
+        var links = $scope.linkFields.filter(function (link) {
+          return link.name === $scope.link.data.fields.name;
         })
         if (links.length > 0) {
           // linkInfo() is the function that is called by dynatree when a link is selected
           // It is passed a dynatree node. We need to simulate that node type to update the link grid
-          $scope.Link.data.info({data: {title: links[0].title, fields: links[0]}})
-          //linkInfo({data: {title: links[0].title, fields: links[0]}})
+          linkInfo({data: {title: links[0].title, fields: links[0]}})
         }
       }
     }
@@ -484,9 +502,9 @@ var QDR = (function (QDR) {
       currentPage: 1
     };
     var getLinkPagedData = function (pageSize, page) {
-      $scope.totalLinks = $scope.filteredLinkFields.length
+      $scope.totalLinks = $scope.linkFields.length
       $scope.linksGrid.showFooter = $scope.totalLinks > 50
-      $scope.pagedLinkData = $scope.filteredLinkFields.slice((page - 1) * pageSize, page * pageSize);
+      $scope.pagedLinkData = $scope.linkFields.slice((page - 1) * pageSize, page * pageSize);
     }
     $scope.$watch('linkPagingOptions', function (newVal, oldVal) {
       if (newVal !== oldVal && newVal.currentPage !== oldVal.currentPage) {
@@ -497,6 +515,16 @@ var QDR = (function (QDR) {
     $scope.totalLinks = 0;
     $scope.pagedLinkData = []
     $scope.selectedLinks = []
+
+    var linkRowTmpl = `
+      <div ng-class="{linkDirIn: row.getProperty('linkDir') == 'in', linkDirOut: row.getProperty('linkDir') == 'out'}">
+        <div ng-style="{ 'cursor': row.cursor }" ng-repeat="col in renderedColumns" ng-class="col.colIndex()" class="ngCell {{col.cellClass}}">
+          <div class="ngVerticalBar" ng-style="{height: rowHeight}" ng-class="{ ngVerticalBarVisible: !$last }">&nbsp;</div>
+          <div ng-cell></div>
+        </div>
+      </div>
+    `;
+
     $scope.linksGrid = {
       saveKey: 'linksGrid',
       data: 'pagedLinkData',
@@ -573,8 +601,7 @@ var QDR = (function (QDR) {
       enableColumnResize: true,
       enableColumnReordering: true,
       showColumnMenu: true,
-      rowTemplate: 'linkRowTemplate.html',
-      // aggregateTemplate: "linkAggTemplate.html",
+      rowTemplate: linkRowTmpl,
       multiSelect: false,
       selectedItems: $scope.selectedLinks,
       plugins: [new ngGridFlexibleHeightPlugin()],
@@ -598,6 +625,7 @@ var QDR = (function (QDR) {
         })
 
     var loadColState = function (grid) {
+return;
       if (!grid)
         return;
       var columns = localStorage.getItem(COLUMNSTATEKEY+grid.saveKey);
@@ -626,17 +654,17 @@ var QDR = (function (QDR) {
     }
 
     var getAllLinkFields = function (completionCallbacks, selectionCallback) {
-      if (!$scope.filteredLinkFields) {
-        QDR.log.info("$scope.filteredLinkFields was not defined")
+      if (!$scope.linkFields) {
+        QDR.log.info("$scope.linkFields was not defined")
         return;
       }
       var nodeIds = QDRService.nodeIdList()
       var linkFields = []
       var now = Date.now()
       var rate = function (linkName, response, result) {
-        if (!$scope.filteredLinkFields)
+        if (!$scope.linkFields)
           return 0;
-        var oldname = $scope.filteredLinkFields.filter(function (link) {
+        var oldname = $scope.linkFields.filter(function (link) {
           return link.link === linkName
         })
         if (oldname.length === 1) {
@@ -895,10 +923,8 @@ var QDR = (function (QDR) {
         })
         if (expected === ++received) {
           connectionFields.sort ( function (a,b) { return a.host < b.host ? -1 : a.host > b.host ? 1 : 0})
-          $timeout( function () {
-            callbacks.forEach( function (cb) {
-              cb(connectionFields)
-            })
+          callbacks.forEach( function (cb) {
+            cb(connectionFields)
           })
         }
       }
@@ -907,14 +933,56 @@ var QDR = (function (QDR) {
       })
     }
 
-    var SingleEntityGrid = function (name) {
-      this.saveKey = name
-      this.data = name
-      this.columnDefs = [
+    $scope.addressFields = []
+    $scope.addressGrid = {
+      saveKey: 'addGrid',
+      data: 'addressFields',
+      columnDefs: [
+      {
+        field: 'attribute',
+        displayName: 'Attribute',
+        saveKey: 'addGrid',
+        width: '40%'
+      },
+      {
+        field: 'value',
+        displayName: 'Value',
+        width: '40%'
+      }
+      ],
+      enableColumnResize: true,
+      multiSelect: false
+    }
+
+    // get info for a single address
+    var addressInfo = function (address) {
+      $scope.address = address
+      var currentEntity = getCurrentLinksEntity();
+      // we are viewing the addressLinks page
+      if (currentEntity === 'Address' && entityModes[currentEntity].currentModeId === 'links') {
+        updateModeLinks()
+        scheduleNextUpdate()
+        return;
+      }
+
+      $scope.addressFields = [];
+      var fields = Object.keys(address.data.fields)
+      fields.forEach( function (field) {
+        if (field != "title" && field != "uid")
+          $scope.addressFields.push({attribute: field, value: address.data.fields[field]})
+      })
+      scheduleNextUpdate()
+      loadColState($scope.addressGrid);
+    }
+
+    $scope.singleLinkFields = []
+    $scope.linkGrid = {
+      saveKey: 'linkGrid',
+      data: 'singleLinkFields',
+      columnDefs: [
         {
           field: 'attribute',
           displayName: 'Attribute',
-          saveKey: this.saveKey,
           width: '40%'
         },
         {
@@ -922,43 +990,28 @@ var QDR = (function (QDR) {
           displayName: 'Value',
           width: '40%'
         }
-        ]
-      this.enableColumnResize = true
-      this.multiSelect = false
+      ],
+      enableColumnResize: true,
+      multiSelect: false
     }
-    $scope.addressFields = []
-    $scope.addressGrid = new SingleEntityGrid('addressFields')
-    $scope.connectionFields = []
-    $scope.connectionGrid = new SingleEntityGrid('connectionFields')
-    $scope.routerFields = []
-    $scope.routerGrid = new SingleEntityGrid('routerFields')
-    $scope.linkFields = []
-    $scope.linkGrid = new SingleEntityGrid('linkFields')
-
-    var SingleEntityInfo = function (entityName) {
-      return function (entity) {
-        if (!entity)
-          return
-        $scope[entityName] = entity
-        var currentEntity = getCurrentLinksEntity()
-        if (currentEntity === entityName && entityModes[currentEntity] && entityModes[currentEntity].currentModeId === 'links') {
-          updateModeLinks()
-          scheduleNextUpdate()
-          return
-        }
-        var filteredFields = []
-        var fields = Object.keys(entity.data.fields)
-        fields.forEach( function (field) {
-          if (field != "title" && field != "uid")
-            filteredFields.push({attribute: field, value: entity.data.fields[field]})
-        })
-        $timeout(() => $scope[entityName.toLowerCase()+'Fields'] = filteredFields)
-        //$scope[entityName.toLowerCase()+'Fields'] = filteredFields
-        console.log("-------------- " + entityName + "Fields -----------")
-        console.dump(filteredFields)
-        scheduleNextUpdate()
-        loadColState($scope[entityName.toLowerCase()+'Grid']);
+
+    // display the grid detail info for a single link
+    var linkInfo = function (link) {
+QDR.log.debug("linkInfo called for " + link.data.key)
+      if (!link) {
+        return;
       }
+      $scope.link = link
+
+      $scope.singleLinkFields = [];
+      var fields = Object.keys(link.data.fields)
+      var excludeFields = ["title", "uid", "uncounts", "rawDeliveryCount", "timestamp", "rawAddress"]
+      fields.forEach( function (field) {
+        if (excludeFields.indexOf(field) == -1)
+          $scope.singleLinkFields.push({attribute: field, value: link.data.fields[field]})
+      })
+      scheduleNextUpdate()
+      loadColState($scope.linkGrid);
     }
 
     // get info for a single connection
@@ -987,15 +1040,15 @@ var QDR = (function (QDR) {
               currentModeId: savedModeIds.Address,
               filter: function (response, result) {
           var owningAddr = QDRService.valFor(response.attributeNames, result, "owningAddr")
-          var id = $scope.Address.data.fields.uid
-          return (owningAddr === $scope.Address.data.fields.uid)
+          var id = $scope.address.data.fields.uid
+          return (owningAddr === $scope.address.data.fields.uid)
               }
           },
           Connection: {
               currentModeId: savedModeIds.Connection,
               filter: function (response, result) {
           var connectionId = QDRService.valFor(response.attributeNames, result, "connectionId")
-          return (connectionId === $scope.Connection.data.fields.identity)
+          return (connectionId === $scope.connection.data.fields.identity)
               }
           }
       }
@@ -1006,7 +1059,7 @@ var QDR = (function (QDR) {
       saveModeIds();
       if (mode.id === 'links') {
 QDR.log.debug("setting linkFields to [] in selectMode")
-        $scope.filteredLinkFields = [];
+        $scope.linkFields = [];
         getLinkPagedData($scope.linkPagingOptions.pageSize, $scope.linkPagingOptions.currentPage);
         updateModeLinks();
       }
@@ -1021,7 +1074,7 @@ QDR.log.debug("setting linkFields to [] in selectMode")
     var updateEntityLinkGrid = function (linkFields) {
       $timeout(function () {
         QDR.log.debug("setting linkFields in updateEntityLinkGrid");
-        $scope.filteredLinkFields = linkFields
+        $scope.linkFields = linkFields
         getLinkPagedData($scope.linkPagingOptions.pageSize, $scope.linkPagingOptions.currentPage);
       })
     }
@@ -1072,6 +1125,50 @@ QDR.log.debug("setting linkFields to [] in selectMode")
                 node.expand(false);
             })
     }
+    $scope.connectionFields = []
+    $scope.connectionGrid = {
+      saveKey: 'connGrid',
+      data: 'connectionFields',
+      columnDefs: [
+      {
+        field: 'attribute',
+        displayName: 'Attribute',
+        saveKey: 'connGrid',
+        width: '40%'
+      },
+      {
+        field: 'value',
+        displayName: 'Value',
+        width: '40%'
+      }
+      ],
+      enableColumnResize: true,
+      multiSelect: false
+    }
+
+    var connectionInfo = function (connection) {
+      if (!connection)
+        return;
+      $scope.connection = connection
+
+      var currentEntity = getCurrentLinksEntity();
+      // we are viewing the connectionLinks page
+      if (currentEntity === 'Connection' && entityModes[currentEntity].currentModeId === 'links') {
+        updateModeLinks()
+        scheduleNextUpdate()
+        return;
+      }
+
+      $scope.connectionFields = [];
+      var fields = Object.keys(connection.data.fields)
+      fields.forEach( function (field) {
+        if (field != "title" && field != "uid")
+          $scope.connectionFields.push({attribute: field, value: connection.data.fields[field]})
+      })
+      // this is missing an argument?
+      loadColState($scope.connectionGrid);
+    }
+
 
     var logModuleCellTemplate = '<div ng-click="logInfoFor(row, col)" class="ngCellText" ng-class="col.colIndex()"><span ng-cell-text>{{COL_FIELD | pretty}}</span></div>'
     $scope.logModule = {}
@@ -1149,29 +1246,32 @@ QDR.log.debug("setting linkFields to [] in selectMode")
     }
 
     function logDialog(row, col) {
-        var d = $dialog.dialog({
-          backdrop: false,
-          keyboard: true,
-          backdropClick: false,
-          templateUrl: 'viewLogs.html',
-          controller: "QDR.OverviewLogsController",
-          resolve: {
-            nodeName: function () {
-              return row.entity.nodeName
-            },
-            module: function () {
-              return row.entity.name
-            },
-            level: function () {
-              return col.displayName
-            },
-            nodeId: function () {
-              return row.entity.nodeId
-            },
-          }
-        });
-        d.open().then(function(result) { console.log("d.open().then"); });
-    };
+      var d = $uibModal.open({
+      animation: true,
+      templateUrl: 'viewLogs.html',
+      controller: 'QDR.OverviewLogsController',
+      resolve: {
+        nodeName: function () {
+          return row.entity.nodeName
+        },
+        module: function () {
+          return row.entity.name
+        },
+        level: function () {
+          return col.displayName
+        },
+        nodeId: function () {
+          return row.entity.nodeId
+        },
+      }
+    });
+
+    d.result.then(function (result) {
+      console.log("d.open().then");
+    }, function () {
+      console.log('Modal dismissed at: ' + new Date());
+    });
+  };
 
     var numberTemplate = '<div class="ngCellText" ng-class="col.colIndex()"><span ng-cell-text>{{COL_FIELD | pretty}}</span></div>'
     $scope.allLogFields = []
@@ -1420,12 +1520,7 @@ QDR.log.debug("setting linkFields to [] in selectMode")
     }
     // loads list that was saved
     var loadExpandedNodeList = function () {
-      try {
-        return angular.fromJson(localStorage[OVERVIEWEXPANDEDKEY]) || [];
-      } catch (e) {
-        QDR.log.debug("localStorage[OVERVIEWEXPANDEDKEY]=" + localStorage[OVERVIEWEXPANDEDKEY])
-        return ["Routers"]
-      }
+      return angular.fromJson(localStorage[OVERVIEWEXPANDEDKEY]) || [];
     }
     // called when a node is expanded
     // here we save the expanded node so it can be restored when the page reloads
@@ -1442,6 +1537,7 @@ QDR.log.debug("setting linkFields to [] in selectMode")
       })
       $scope.template = template[0];
     }
+    $scope.template = $scope.templates[0]
     // activated is called each time a tree node is clicked
     // based on which node is clicked, load the correct data grid template and start getting the data
     var activated = function (node) {
@@ -1476,7 +1572,8 @@ QDR.log.debug("setting linkFields to [] in selectMode")
 
     // we are currently connected. setup a handler to get notified if we are ever disconnected
     QDRService.addDisconnectAction( function () {
-      $timeout( () => QDRService.redirectWhenConnected("overview") )
+      QDRService.redirectWhenConnected("overview")
+      $scope.$apply();
     })
 
     /* --------------------------------------------------
@@ -1525,6 +1622,7 @@ QDR.log.debug("newly created node needs to be activated")
     routers.key = "Routers"
     routers.parent = "Routers"
     routers.addClass = "routers"
+    routers.isFolder = true
     topLevelChildren.push(routers)
     // called when the list of routers changes
     var updateRouterTree = function (nodes) {
@@ -1553,12 +1651,12 @@ QDR.log.debug("newly created node needs to be activated")
     addresses.key = "Addresses"
     addresses.parent = "Addresses"
     addresses.addClass = "addresses"
+    addresses.isFolder = true
     topLevelChildren.push(addresses)
     var updateAddressTree = function (addressFields) {
-      var info = SingleEntityInfo('Address')
       var worker = function (address) {
         var a = new Folder(address.title)
-        a.info = info
+        a.info = addressInfo
         a.key = address.uid
         a.fields = address
         a.type = "Address"
@@ -1595,15 +1693,15 @@ QDR.log.debug("newly created node needs to be activated")
     links.key = "Links"
     links.parent = "Links"
     links.addClass = "links"
+    links.isFolder = true
     topLevelChildren.push(links)
 
     // called both before the tree is created and whenever a background update is done
     var updateLinkTree = function (linkFields) {
-      var info = SingleEntityInfo('Link')
       var worker = function (link) {
         var l = new Folder(link.title)
         var isConsole = QDRService.isConsoleLink(link)
-        l.info = info
+        l.info = linkInfo
         l.key = link.uid
         l.fields = link
         l.type = "Link"
@@ -1628,15 +1726,15 @@ QDR.log.debug("newly created node needs to be activated")
     connections.key = "Connections"
     connections.parent = "Connections"
     connections.addClass = "connections"
+    connections.isFolder = true
     topLevelChildren.push(connections)
 
     updateConnectionTree = function (connectionFields) {
-      var info = SingleEntityInfo('Connection')
       var worker = function (connection) {
         var c = new Folder(connection.host)
         var isConsole = QDRService.isAConsole (connection.properties, connection.identity, connection.role, connection.routerId)
         c.type = "Connection"
-        c.info = info
+        c.info = connectionInfo
         c.key = connection.uid
         c.fields = connection
         if (isConsole)
@@ -1676,6 +1774,7 @@ QDR.log.debug("newly created node needs to be activated")
     logs.clickFolderMode = 1
     logs.key = "Logs"
     logs.parent = "Logs"
+    logs.isFolder = true
     if (QDRService.versionCheck('0.8.0'))
       topLevelChildren.push(logs)
     var initTreeAndGrid = function () {
@@ -1693,6 +1792,10 @@ QDR.log.debug("newly created node needs to be activated")
         activeVisible: !$scope.largeNetwork,
         selectMode: 1,
         debugLevel: 0,
+        classNames: {
+          expander: 'fa-angle',
+          connector: 'dynatree-no-connector'
+          },
         children: topLevelChildren
       })
       treeRoot = $("#overtree").dynatree("getRoot");

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6a1e6632/console/stand-alone/plugin/js/qdrOverviewLogsController.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrOverviewLogsController.js b/console/stand-alone/plugin/js/qdrOverviewLogsController.js
index fed1e3d..832d8aa 100644
--- a/console/stand-alone/plugin/js/qdrOverviewLogsController.js
+++ b/console/stand-alone/plugin/js/qdrOverviewLogsController.js
@@ -21,7 +21,7 @@ under the License.
  */
 var QDR = (function(QDR) {
 
-  QDR.module.controller('QDR.OverviewLogsController', function ($scope, dialog, QDRService, $timeout, nodeName, nodeId, module, level) {
+  QDR.module.controller('QDR.OverviewLogsController', function ($scope, $uibModalInstance, QDRService, $timeout, nodeName, nodeId, module, level) {
 
       var gotLogInfo = function (nodeId, entity, response, context) {
         var statusCode = context.message.application_properties.statusCode;
@@ -58,7 +58,7 @@ var QDR = (function(QDR) {
     $scope.nodeName = nodeName
     $scope.logFields = []
     $scope.ok = function () {
-      dialog.close(true);
+      $uibModalInstance.close(true);
     };
 
   });

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6a1e6632/console/stand-alone/plugin/js/qdrTopology.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrTopology.js b/console/stand-alone/plugin/js/qdrTopology.js
index 9899393..23da1bb 100644
--- a/console/stand-alone/plugin/js/qdrTopology.js
+++ b/console/stand-alone/plugin/js/qdrTopology.js
@@ -21,8 +21,9 @@ under the License.
  */
 var QDR = (function(QDR) {
 
-  QDR.module.controller('QDR.TopologyFormController', function($scope, QDRService) {
+  QDR.module.controller('QDR.TopologyFormController', function($scope, $rootScope, $timeout, QDRService) {
 
+    $scope.panelVisible = true  // show/hide the panel on the left
     $scope.attributes = []
     var nameTemplate = '<div title="{{row.entity.description}}" class="ngCellText {{row.entity.cls}}"><span>{{row.entity.attributeName}}</span></div>';
     var valueTemplate = '<div title="{{row.entity.attributeValue}}" class="ngCellText {{row.entity.cls}}"><span>{{row.entity.attributeValue}}</span></div>';
@@ -40,18 +41,18 @@ var QDR = (function(QDR) {
         cellTemplate: valueTemplate
       }]
     };
-    $scope.form = ''
+    $scope.form = 'router'
     $scope.$on('showEntityForm', function(event, args) {
       var attributes = args.attributes;
       var entityTypes = QDRService.schema.entityTypes[args.entity].attributes;
       attributes.forEach(function(attr) {
         attr.cls = ''
-QDR.log.debug("attr.description " + attr.description)
         if (attr.attributeName === 'Listening on')
           attr.cls = 'listening-on'
         if (entityTypes[attr.attributeName] && entityTypes[attr.attributeName].description) {
           attr.description = entityTypes[attr.attributeName].description
         }
+        //QDR.log.debug("attr.description " + attr.description)
       })
       $scope.attributes = attributes;
       $scope.form = args.entity;
@@ -59,6 +60,41 @@ QDR.log.debug("attr.description " + attr.description)
     $scope.$on('showAddForm', function(event) {
       $scope.form = 'add';
     })
+
+    $scope.hideLeftPane = function () {
+      d3.select(".qdr-topology-form")
+        .transition().duration(300).ease("sin-in")
+        .style("left" , "-309px")
+        .each("end", function () {
+          $timeout(function () {
+            QDR.log.debug("done with transition. setting scope ");
+            $scope.panelVisible = false
+            $rootScope.$broadcast('panel-resized')
+        })
+/*
+      d3.select(".qdr-topology-svg")
+        .transition().duration(300).ease("sin-in")
+        .style("margin-left", "30px")
+        .each("end", function () {
+          resize()
+          $timeout(function () {QDR.log.debug("done with transition. setting scope ");$scope.panelVisible = false})
+        })
+*/
+    })}
+
+    $scope.showLeftPane = function () {
+      d3.select(".qdr-topology-form")
+        .transition().duration(300).ease("sin-out")
+        .style("left" , "0px")
+
+      d3.select(".qdr-topology-svg")
+        .transition().duration(300).ease("sin-out")
+        .style("margin-left", "430px")
+        .each("end", function () {
+          resize()
+          $timeout(function () {QDR.log.debug("done with transition. setting scope ");$scope.panelVisible = true})
+        })
+    }
   })
 
   /**
@@ -66,41 +102,14 @@ QDR.log.debug("attr.description " + attr.description)
    *
    * Controller that handles the QDR topology page
    */
-  QDR.module.controller("QDR.TopologyController", ['$scope', '$rootScope', 'QDRService', '$location', '$timeout', '$dialog',
-    function($scope, $rootScope, QDRService, $location, $timeout, $dialog) {
+  QDR.module.controller("QDR.TopologyController", ['$scope', '$rootScope', 'QDRService', '$location', '$timeout', '$uibModal',
+    function($scope, $rootScope, QDRService, $location, $timeout, $uibModal) {
 
-      $scope.panelVisible = true  // show/hide the panel on the left
       $scope.multiData = []
       $scope.selectedClient = [];
       $scope.quiesceState = {}
       var dontHide = false;
 
-      $scope.hideLeftPane = function () {
-        d3.select(".qdr-topology.pane.left")
-          .transition().duration(300).ease("sin-in")
-          .style("left" , "-380px")
-
-        d3.select(".panel-adjacent")
-          .transition().duration(300).ease("sin-in")
-          .style("margin-left", "30px")
-          .each("end", function () {
-            resize()
-            $timeout(function () {QDR.log.debug("done with transition. setting scope ");$scope.panelVisible = false})
-          })
-      }
-      $scope.showLeftPane = function () {
-        d3.select(".qdr-topology.pane.left")
-          .transition().duration(300).ease("sin-out")
-          .style("left" , "0px")
-
-        d3.select(".panel-adjacent")
-          .transition().duration(300).ease("sin-out")
-          .style("margin-left", "430px")
-          .each("end", function () {
-            resize()
-            $timeout(function () {QDR.log.debug("done with transition. setting scope ");$scope.panelVisible = true})
-          })
-      }
       $scope.quiesceConnection = function(row) {
         var entity = row.entity;
         var state = $scope.quiesceState[entity.connectionId].state;
@@ -335,7 +344,6 @@ QDR.log.debug("attr.description " + attr.description)
       ];
       $scope.mode = "Diagram";
       $scope.contextNode = null; // node that is associated with the current context menu
-
       $scope.isModeActive = function(name) {
         if ((name == 'Add Router' || name == 'Diagram') && $scope.addingNode.step > 0)
           return true;
@@ -547,6 +555,10 @@ QDR.log.debug("attr.description " + attr.description)
           force.size(sizes).resume();
         }
       }
+
+      $scope.$on('panel-resized', function () {
+        resize()
+      })
       window.addEventListener('resize', resize);
       var sizes = getSizes()
       width = sizes[0]
@@ -990,7 +1002,7 @@ QDR.log.debug("attr.description " + attr.description)
                 });
               }
             }
-            $scope.$broadcast('showEntityForm', {
+            $rootScope.$broadcast('showEntityForm', {
               entity: entity,
               attributes: attributes
             })
@@ -2113,7 +2125,7 @@ QDR.log.debug("attr.description " + attr.description)
 
       function doAddDialog(NewRouterName) {
         QDRService.ensureAllEntities({entity: ".listener"}, function () {
-          var d = $dialog.dialog({
+          var d = $uibModal.open({
             dialogClass: "modal dlg-large",
             backdrop: true,
             keyboard: true,
@@ -2127,7 +2139,7 @@ QDR.log.debug("attr.description " + attr.description)
             }
           });
           $timeout(function () {
-            d.open().then(function(result) {
+            d.result.then(function(result) {
               if (result)
                 doDownloadDialog(result);
             });
@@ -2136,7 +2148,7 @@ QDR.log.debug("attr.description " + attr.description)
       };
 
       function doDownloadDialog(result) {
-        d = $dialog.dialog({
+        d = $uibModal.open({
           backdrop: true,
           keyboard: true,
           backdropClick: true,
@@ -2148,7 +2160,7 @@ QDR.log.debug("attr.description " + attr.description)
             }
           }
         });
-        d.open().then(function(result) {
+        d.result.then(function(result) {
           //QDR.log.debug("download dialog done")
         })
         if (!$scope.$$phase) $scope.$apply()


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


[3/4] qpid-dispatch git commit: DISPATCH-745 Remove symlinks from hawtio to stand-alone

Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6a1e6632/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
deleted file mode 120000
index de6a7a1..0000000
--- a/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../../stand-alone/plugin/js/qdrOverview.js
\ No newline at end of file
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js b/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js
new file mode 100644
index 0000000..d9f9367
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js
@@ -0,0 +1,1766 @@
+/*
+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.
+*/
+/**
+ * @module QDR
+ */
+/**
+ * @module QDR
+ */
+var QDR = (function (QDR) {
+
+  /**
+   * @method OverviewController
+   * @param $scope
+   * @param QDRService
+   * @param QDRChartServer
+   * dialogServer
+   * $location
+   *
+   * Controller that handles the QDR overview page
+   */
+  QDR.module.controller("QDR.OverviewController", ['$scope', 'QDRService', '$location', '$timeout', '$dialog', function($scope, QDRService, $location, $timeout, $dialog) {
+
+    console.log("QDR.OverviewControll started with location of " + $location.path() + " and connection of  " + QDRService.connected);
+    var COLUMNSTATEKEY = 'QDRColumnKey.';
+    var OVERVIEWEXPANDEDKEY = "QDROverviewExpanded"
+    var OVERVIEWACTIVATEDKEY = "QDROverviewActivated"
+    var FILTERKEY = "QDROverviewFilters"
+    var OVERVIEWMODEIDS = "QDROverviewModeIds"
+    var treeRoot;   // the dynatree root node. initialized once log data is received
+
+    // 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 "#/" + QDR.pluginName + "/attributes"; },
+        index: 0
+    },
+    {
+        content: '<i class="icon-leaf"></i> Operations',
+        title: "Execute operations on your selection",
+        isValid: function (workspace) { return true; },
+        href: function () { return "#/" + QDR.pluginName + "/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;
+    }
+    $scope.filteredLinkFields = []
+    $scope.Link = null;
+    var refreshInterval = 5000
+    $scope.modes = [
+      {title: 'Overview', name: 'Overview', right: false}
+    ];
+
+    $scope.tmplOverviewTree = QDR.templatePath + 'tmplOverviewTree.html';
+    $scope.templates = [
+      { name: 'Routers', url: 'routers.html'},
+      { name: 'Router', url: 'router.html'},
+      { name: 'Addresses', url: 'addresses.html'},
+      { name: 'Address', url: 'address.html'},
+      { name: 'Links', url: 'links.html'},
+      { name: 'Link', url: 'link.html'},
+      { name: 'Connections', url: 'connections.html'},
+      { name: 'Connection', url: 'connection.html'},
+      { name: 'Logs', url: 'logs.html'},
+      { name: 'Log', url: 'logModule.html'}
+    ];
+    var topLevelChildren = [];
+
+    $scope.allRouterSelected = function (row ) {
+      console.log("row selected" + row)
+    }
+    function afterSelectionChange(rowItem, checkAll) {
+      var nodeId = rowItem.entity.nodeId;
+      $("#overtree").dynatree("getTree").activateKey(nodeId);
+    }
+
+    $scope.routerPagingOptions = {
+      pageSizes: [50, 100, 500],
+      pageSize: 50,
+      currentPage: 1
+    };
+    var getPagedData = function (pageSize, page) {
+      $scope.totalRouters = $scope.allRouterFields.length
+      $scope.allRouters.showFooter = $scope.totalRouters > 50
+      $scope.pagedRouterFields = $scope.allRouterFields.slice((page - 1) * pageSize, page * pageSize);
+    }
+    $scope.$watch('pagingOptions', function (newVal, oldVal) {
+      if (newVal !== oldVal && newVal.currentPage !== oldVal.currentPage) {
+        getPagedData($scope.routerPagingOptions.pageSize, $scope.routerPagingOptions.currentPage);
+      }
+    }, true);
+
+    $scope.totalRouters = 0;
+    $scope.allRouterFields = [];
+    $scope.pagedRouterFields = [];
+    $scope.allRouterSelections = [];
+    $scope.allRouters = {
+      saveKey: 'allRouters',
+      data: 'pagedRouterFields',
+      columnDefs: [
+        {
+          field: 'id',
+          saveKey: 'allRouters',
+          displayName: 'Router'
+        },
+        {
+          field: 'area',
+          displayName: 'Area'
+        },
+        {
+          field: 'mode',
+          displayName: 'Mode'
+        },
+        {
+          field: 'connections',
+          displayName: 'External connections'
+        },
+        {
+          field: 'addrCount',
+          displayName: 'Address count'
+        },
+        {
+          field: 'linkCount',
+          displayName: 'Link count'
+        }
+      ],
+      enableColumnResize: true,
+      enablePaging: true,
+      showFooter: $scope.totalRouters > 50,
+      totalServerItems: 'totalRouters',
+      pagingOptions: $scope.routerPagingOptions,
+      multiSelect: false,
+      selectedItems: $scope.allRouterSelections,
+      plugins: [new ngGridFlexibleHeightPlugin()],
+      afterSelectionChange: function(data) {
+        if (data.selected) {
+          var selItem = $scope.allRouterSelections[0]
+          var nodeId = selItem.nodeId
+          // activate Routers->nodeId in the tree
+          $("#overtree").dynatree("getTree").activateKey(nodeId);
+        }
+      }
+    };
+
+    // get info for all routers
+    var allRouterInfo = function () {
+      var nodes = {}
+      // gets called each node/entity response
+      var gotNode = function (nodeName, entity, response) {
+        if (!nodes[nodeName])
+          nodes[angular.copy(nodeName)] = {}
+        nodes[nodeName][entity] = angular.copy(response);
+      }
+      // send the requests for all connection and router info for all routers
+      QDRService.fetchAllEntities([{entity: ".connection", attrs: ["role"]}], function () {
+        QDRService.fetchAllEntities([{entity: ".router"}], function () {
+          // we have all the data now in the nodes object
+          var allRouterFields = []
+          for (var node in nodes) {
+            var connections = 0
+            for (var i=0; i<nodes[node][".connection"].results.length; ++i) {
+              // we only requested "role" so it will be at [0]
+              if (nodes[node][".connection"].results[i][0] === 'inter-router')
+                ++connections
+            }
+            var routerRow = {connections: connections, nodeId: node, id: QDRService.nameFromId(node)}
+            nodes[node][".router"].attributeNames.forEach( function (routerAttr, i) {
+              if (routerAttr !== "routerId" && routerAttr !== "id")
+                routerRow[routerAttr] = nodes[node][".router"].results[0][i]
+            })
+            allRouterFields.push(routerRow)
+          }
+          $timeout(function () {
+            $scope.allRouterFields = allRouterFields
+            getPagedData($scope.routerPagingOptions.pageSize, $scope.routerPagingOptions.currentPage);
+            updateRouterTree(nodeIds)
+            scheduleNextUpdate()
+            //if ($scope.router)
+            //  routerInfo($scope.router)
+          })
+        }, gotNode)
+      }, gotNode)
+      loadColState($scope.allRouters)
+    }
+
+    $scope.router = null;
+    // get info for a single router
+    var routerInfo = function (node) {
+      $scope.router = node
+
+      var routerFields = [];
+      $scope.allRouterFields.some( function (field) {
+        if (field.id === node.data.title) {
+          Object.keys(field).forEach ( function (key) {
+            if (key !== '$$hashKey') {
+              var attr = (key === 'connections') ? 'External connections' : key
+              routerFields.push({attribute: attr, value: field[key]})
+            }
+          })
+          return true
+        }
+      })
+      $timeout(function () {$scope.routerFields = routerFields})
+      scheduleNextUpdate()
+      loadColState($scope.routerGrid);
+    }
+
+    $scope.addressPagingOptions = {
+      pageSizes: [50, 100, 500],
+      pageSize: 50,
+      currentPage: 1
+    };
+    var getAddressPagedData = function (pageSize, page) {
+      $scope.totalAddresses = $scope.addressesData.length
+      $scope.addressesGrid.showFooter = $scope.totalAddresses > 50
+      $scope.pagedAddressesData = $scope.addressesData.slice((page - 1) * pageSize, page * pageSize);
+    }
+    $scope.$watch('addressPagingOptions', function (newVal, oldVal) {
+      if (newVal !== oldVal && newVal.currentPage !== oldVal.currentPage) {
+        getAddressPagedData($scope.addressPagingOptions.pageSize, $scope.addressPagingOptions.currentPage);
+      }
+    }, true);
+
+    $scope.totalAddresses = 0;
+    $scope.pagedAddressesData = []
+    $scope.addressesData = []
+    $scope.selectedAddresses = []
+    $scope.addressesGrid = {
+      saveKey: 'addressesGrid',
+      data: 'pagedAddressesData',
+      columnDefs: [
+        {
+          field: 'address',
+          saveKey: 'addressesGrid',
+          displayName: 'address'
+        },
+        {
+          field: 'class',
+          displayName: 'class'
+        },
+        {
+          field: 'phase',
+          displayName: 'phase',
+          cellClass: 'grid-align-value'
+        },
+        {
+          field: 'inproc',
+          displayName: 'in-proc'
+        },
+        {
+          field: 'local',
+          displayName: 'local',
+          cellClass: 'grid-align-value'
+        },
+        {
+          field: 'remote',
+          displayName: 'remote',
+          cellClass: 'grid-align-value'
+        },
+        {
+          field: 'in',
+          displayName: 'in',
+          cellClass: 'grid-align-value'
+        },
+        {
+          field: 'out',
+          displayName: 'out',
+          cellClass: 'grid-align-value'
+        }
+      ],
+      enablePaging: true,
+      showFooter: $scope.totalAddresses > 50,
+      totalServerItems: 'totalAddresses',
+      pagingOptions: $scope.addressPagingOptions,
+      enableColumnResize: true,
+      multiSelect: false,
+      selectedItems: $scope.selectedAddresses,
+      plugins: [new ngGridFlexibleHeightPlugin()],
+      afterSelectionChange: function(data) {
+        if (data.selected) {
+          var selItem = data.entity;
+          var nodeId = selItem.uid
+          $("#overtree").dynatree("getTree").activateKey(nodeId);
+        }
+      }
+    };
+
+    // get info for all addresses
+    var allAddressInfo = function () {
+      var nodes = {}
+      // gets called each node/entity response
+      var gotNode = function (nodeName, entity, response) {
+        if (!nodes[nodeName])
+          nodes[nodeName] = {}
+        nodes[nodeName][entity] = angular.copy(response);
+      }
+      var addr_class = function (addr) {
+        if (!addr) return "-"
+            if (addr[0] == 'M')  return "mobile"
+            if (addr[0] == 'R')  return "router"
+            if (addr[0] == 'A')  return "area"
+            if (addr[0] == 'L')  return "local"
+            if (addr[0] == 'C')  return "link-incoming"
+            if (addr[0] == 'D')  return "link-outgoing"
+            if (addr[0] == 'T')  return "topo"
+            return "unknown: " + addr[0]
+      }
+      var addr_phase = function (addr) {
+        if (!addr)
+          return "-"
+        if (addr[0] == 'M')
+          return addr[1]
+        return ''
+      }
+      var prettyVal = function (val) {
+        return QDRService.pretty(val || "-")
+      }
+      var addressFields = []
+      var addressObjs = {}
+      // send the requests for all connection and router info for all routers
+      QDRService.fetchAllEntities({entity: ".router.address"}, function () {
+        for (var node in nodes) {
+          var response = nodes[node][".router.address"]
+          response.results.forEach( function (result) {
+            var address = QDRService.flatten(response.attributeNames, result)
+
+            var addNull = function (oldVal, newVal) {
+              if (oldVal != null && newVal != null)
+                return oldVal + newVal
+              if (oldVal != null)
+                return oldVal
+              return newVal
+            }
+
+            var uid = address.identity
+            var identity = QDRService.identity_clean(uid)
+
+            if (!addressObjs[QDRService.addr_text(identity)+QDRService.addr_class(identity)])
+              addressObjs[QDRService.addr_text(identity)+QDRService.addr_class(identity)] = {
+                address: QDRService.addr_text(identity),
+                'class': QDRService.addr_class(identity),
+                phase:   addr_phase(identity),
+                inproc:  address.inProcess,
+                local:   address.subscriberCount,
+                remote:  address.remoteCount,
+                'in':    address.deliveriesIngress,
+                out:     address.deliveriesEgress,
+                thru:    address.deliveriesTransit,
+                toproc:  address.deliveriesToContainer,
+                fromproc:address.deliveriesFromContainer,
+                uid:     uid
+              }
+            else {
+              var sumObj = addressObjs[QDRService.addr_text(identity)+QDRService.addr_class(identity)]
+              sumObj.inproc = addNull(sumObj.inproc, address.inproc)
+              sumObj.local = addNull(sumObj.local, address.local)
+              sumObj.remote = addNull(sumObj.remote, address.remote)
+              sumObj['in'] = addNull(sumObj['in'], address['in'])
+              sumObj.out = addNull(sumObj.out, address.out)
+              sumObj.thru = addNull(sumObj.thru, address.thru)
+              sumObj.toproc = addNull(sumObj.toproc, address.toproc)
+              sumObj.fromproc = addNull(sumObj.fromproc, address.fromproc)
+            }
+/*
+            addressFields.push({
+              address: QDRService.addr_text(identity),
+              'class': QDRService.addr_class(identity),
+              phase:   addr_phase(identity),
+              inproc:  prettySum("inProcess"),
+              local:   prettySum("subscriberCount"),
+              remote:  prettySum("remoteCount"),
+              'in':    prettySum("deliveriesIngress"),
+              out:     prettySum("deliveriesEgress"),
+              thru:    prettySum("deliveriesTransit"),
+              toproc:  prettySum("deliveriesToContainer"),
+              fromproc:prettySum("deliveriesFromContainer"),
+              uid:     uid
+            })
+*/
+          })
+        }
+        for (var obj in addressObjs) {
+          addressObjs[obj].inproc = prettyVal(addressObjs[obj].inproc)
+          addressObjs[obj].local = prettyVal(addressObjs[obj].local)
+          addressObjs[obj].remote = prettyVal(addressObjs[obj].remote)
+          addressObjs[obj]['in'] = prettyVal(addressObjs[obj]['in'])
+          addressObjs[obj].out = prettyVal(addressObjs[obj].out)
+          addressObjs[obj].thru = prettyVal(addressObjs[obj].thru)
+          addressObjs[obj].toproc = prettyVal(addressObjs[obj].toproc)
+          addressObjs[obj].fromproc = prettyVal(addressObjs[obj].fromproc)
+          addressFields.push(addressObjs[obj])
+        }
+        if (addressFields.length === 0)
+          return;
+        // update the grid's data
+        addressFields.sort ( function (a,b) {
+          return a.address + a['class'] < b.address + b['class'] ? -1 : a.address + a['class'] > b.address + b['class'] ? 1 : 0}
+        )
+        // callapse all records with same addres+class
+        for (var i=1; i<addressFields.length; ++i) {
+
+        }
+        addressFields[0].title = addressFields[0].address
+        for (var i=1; i<addressFields.length; ++i) {
+          // if this address is the same as the previous address, add a class to the display titles
+          if (addressFields[i].address === addressFields[i-1].address) {
+            addressFields[i-1].title = addressFields[i-1].address + " (" + addressFields[i-1]['class'] + ")"
+            addressFields[i].title = addressFields[i].address + " (" + addressFields[i]['class'] + ")"
+          } else
+            addressFields[i].title = addressFields[i].address
+        }
+        $timeout(function () {
+          $scope.addressesData = addressFields
+          getAddressPagedData($scope.addressPagingOptions.pageSize, $scope.addressPagingOptions.currentPage);
+          // repopulate the tree's child nodes
+          updateAddressTree(addressFields)
+          scheduleNextUpdate()
+        })
+      }, gotNode)
+      loadColState($scope.addressesGrid);
+    }
+
+    var updateLinkGrid = function ( linkFields ) {
+      // apply the filter
+      var filteredLinks = linkFields.filter( function (link) {
+        var include = true;
+        if ($scope.filter.endpointsOnly === "true") {
+          if (link.linkType !== 'endpoint')
+            include = false;
+        }
+        if ($scope.filter.hideConsoles) {
+          if (QDRService.isConsoleLink(link))
+            include = false;
+        }
+        return include;
+      })
+      QDR.log.debug("setting linkFields in updateLinkGrid filteredLinks.length=" + filteredLinks.length)
+      $scope.filteredLinkFields = filteredLinks;
+      getLinkPagedData($scope.linkPagingOptions.pageSize, $scope.linkPagingOptions.currentPage);
+      // if we have a selected link
+      if ($scope.Link) {
+        // find the selected link in the array of all links
+        var links = $scope.filteredLinkFields.filter(function (link) {
+          return link.name === $scope.Link.data.fields.name;
+        })
+        if (links.length > 0) {
+          // linkInfo() is the function that is called by dynatree when a link is selected
+          // It is passed a dynatree node. We need to simulate that node type to update the link grid
+          $scope.Link.data.info({data: {title: links[0].title, fields: links[0]}})
+          //linkInfo({data: {title: links[0].title, fields: links[0]}})
+        }
+      }
+    }
+
+    // get info for a all links
+    $scope.linkPagingOptions = {
+      pageSizes: [50, 100, 500],
+      pageSize: 50,
+      currentPage: 1
+    };
+    var getLinkPagedData = function (pageSize, page) {
+      $scope.totalLinks = $scope.filteredLinkFields.length
+      $scope.linksGrid.showFooter = $scope.totalLinks > 50
+      $scope.pagedLinkData = $scope.filteredLinkFields.slice((page - 1) * pageSize, page * pageSize);
+    }
+    $scope.$watch('linkPagingOptions', function (newVal, oldVal) {
+      if (newVal !== oldVal && newVal.currentPage !== oldVal.currentPage) {
+        getLinkPagedData($scope.linkPagingOptions.pageSize, $scope.linkPagingOptions.currentPage);
+      }
+    }, true);
+
+    $scope.totalLinks = 0;
+    $scope.pagedLinkData = []
+    $scope.selectedLinks = []
+    $scope.linksGrid = {
+      saveKey: 'linksGrid',
+      data: 'pagedLinkData',
+      columnDefs: [
+        {
+          field: 'link',
+          displayName: 'Link',
+          groupable:  false,
+          saveKey: 'linksGrid',
+          width: '11%'
+        },
+        {
+          field: 'linkType',
+          displayName: 'Link type',
+          groupable:  false,
+          width: '9%'
+        },
+        {
+          field: 'linkDir',
+          displayName: 'Link dir',
+          groupable:  false,
+          width: '8%'
+        },
+        {
+          field: 'adminStatus',
+          displayName: 'Admin status',
+          groupable:  false,
+          width: '9%'
+        },
+        {
+          field: 'operStatus',
+          displayName: 'Oper status',
+          groupable:  false,
+          width: '9%'
+        },
+        {
+          field: 'deliveryCount',
+          displayName: 'Delivery Count',
+          groupable:  false,
+          cellClass: 'grid-align-value',
+          width: '11%'
+        },
+        {
+          field: 'rate',
+          displayName: 'Rate',
+          groupable:  false,
+          cellClass: 'grid-align-value',
+          width: '8%'
+        },
+        {
+          field: 'uncounts',
+          displayName: 'Outstanding',
+          groupable:  false,
+          cellClass: 'grid-align-value',
+          width: '9%'
+        },
+        {
+          field: 'owningAddr',
+          displayName: 'Address',
+          groupable:  false,
+          width: '15%'
+        }/*,
+        {
+          displayName: 'Quiesce',
+                    cellClass: 'gridCellButton',
+                    cellTemplate: '<button title="{{quiesceLinkText(row)}} this link" type="button" ng-class="quiesceLinkClass(row)" class="btn" ng-click="quiesceLink(row, $event)" ng-disabled="quiesceLinkDisabled(row)">{{quiesceLinkText(row)}}</button>',
+          width: '10%'
+                }*/
+            ],
+      enablePaging: true,
+      showFooter: $scope.totalLinks > 50,
+      totalServerItems: 'totalLinks',
+      pagingOptions: $scope.linkPagingOptions,
+      enableColumnResize: true,
+      enableColumnReordering: true,
+      showColumnMenu: true,
+      rowTemplate: 'linkRowTemplate.html',
+      // aggregateTemplate: "linkAggTemplate.html",
+      multiSelect: false,
+      selectedItems: $scope.selectedLinks,
+      plugins: [new ngGridFlexibleHeightPlugin()],
+      afterSelectionChange: function(data) {
+        if (data.selected) {
+          var selItem = data.entity;
+          var nodeId = selItem.uid
+          $("#overtree").dynatree("getTree").activateKey(nodeId);
+        }
+            }
+    };
+
+
+    $scope.$on('ngGridEventColumns', function (e, columns) {
+      var saveInfo = columns.map( function (col) {
+        return [col.width, col.visible]
+      })
+      var saveKey = columns[0].colDef.saveKey
+      if (saveKey)
+                localStorage.setItem(COLUMNSTATEKEY+saveKey, JSON.stringify(saveInfo));
+        })
+
+    var loadColState = function (grid) {
+      if (!grid)
+        return;
+      var columns = localStorage.getItem(COLUMNSTATEKEY+grid.saveKey);
+      if (columns) {
+        var cols = JSON.parse(columns);
+        cols.forEach( function (col, index) {
+        if (grid.columnDefs[index]) {
+          grid.columnDefs[index].width = col[0];
+          grid.columnDefs[index].visible = col[1]
+        }
+        })
+      }
+    }
+    var allLinkInfo = function () {
+      var gridCallback = function (linkFields) {
+        QDRService.ensureAllEntities({entity: ".connection", force: true}, function () {
+          // only update the grid with these fields if the List tree node is selected
+          // this is becuase we are using the link grid in other places and we don't want to overwrite it
+          if ($scope.template.name === "Links")
+            updateLinkGrid(linkFields)
+          updateLinkTree(linkFields)
+        })
+      }
+      getAllLinkFields([gridCallback, scheduleNextUpdate])
+      loadColState($scope.linksGrid);
+    }
+
+    var getAllLinkFields = function (completionCallbacks, selectionCallback) {
+      if (!$scope.filteredLinkFields) {
+        QDR.log.info("$scope.filteredLinkFields was not defined")
+        return;
+      }
+      var nodeIds = QDRService.nodeIdList()
+      var linkFields = []
+      var now = Date.now()
+      var rate = function (linkName, response, result) {
+        if (!$scope.filteredLinkFields)
+          return 0;
+        var oldname = $scope.filteredLinkFields.filter(function (link) {
+          return link.link === linkName
+        })
+        if (oldname.length === 1) {
+          var elapsed = (now - oldname[0].timestamp) / 1000;
+          if (elapsed < 0)
+            return 0
+          var delivered = QDRService.valFor(response.attributeNames, result, "deliveryCount") - oldname[0].rawDeliveryCount
+          //QDR.log.debug("elapsed " + elapsed + " delivered " + delivered)
+          return elapsed > 0 ? parseFloat(Math.round((delivered/elapsed) * 100) / 100).toFixed(2) : 0;
+        } else {
+          //QDR.log.debug("unable to find old linkName")
+          return 0
+        }
+      }
+      var expected = nodeIds.length;
+      var received = 0;
+      var gotLinkInfo = function (nodeName, entity, response) {
+        response.results.forEach( function (result) {
+          var nameIndex = response.attributeNames.indexOf('name')
+          var prettyVal = function (field) {
+            var fieldIndex = response.attributeNames.indexOf(field)
+            if (fieldIndex < 0) {
+              return "-"
+            }
+            var val = result[fieldIndex]
+            return QDRService.pretty(val)
+          }
+          var uncounts = function () {
+            var und = QDRService.valFor(response.attributeNames, result, "undeliveredCount")
+            var uns = QDRService.valFor(response.attributeNames, result, "unsettledCount")
+            return QDRService.pretty(und + uns)
+          }
+          var linkName = function () {
+            var namestr = QDRService.nameFromId(nodeName)
+            return namestr + ':' + QDRService.valFor(response.attributeNames, result, "identity")
+          }
+          var fixAddress = function () {
+            var addresses = []
+            var owningAddr = QDRService.valFor(response.attributeNames, result, "owningAddr") || ""
+            var rawAddress = owningAddr
+            /*
+                 - "L*"  =>  "* (local)"
+                 - "M0*" =>  "* (direct)"
+                 - "M1*" =>  "* (dequeue)"
+                 - "MX*" =>  "* (phase X)"
+            */
+            var address = undefined;
+            var starts = {'L': '(local)', 'M0': '(direct)', 'M1': '(dequeue)'}
+            for (var start in starts) {
+              if (owningAddr.startsWith(start)) {
+                var ends = owningAddr.substr(start.length)
+                address = ends + " " + starts[start]
+                rawAddress = ends
+                break;
+              }
+            }
+            if (!address) {
+              // check for MX*
+              if (owningAddr.length > 3) {
+                if (owningAddr[0] === 'M') {
+                  var phase = parseInt(owningAddress.substr(1))
+                  if (phase && !isNaN(phase)) {
+                    var phaseStr = phase + "";
+                    var star = owningAddress.substr(2 + phaseStr.length)
+                    address = star + " " + "(phase " + phaseStr + ")"
+                  }
+                }
+              }
+            }
+            addresses[0] = address || owningAddr
+            addresses[1] = rawAddress
+            return addresses
+          }
+          if (!selectionCallback || selectionCallback(response, result)) {
+            var adminStatus = QDRService.valFor(response.attributeNames, result, "adminStatus")
+            var operStatus = QDRService.valFor(response.attributeNames, result, "operStatus")
+            var linkName = linkName()
+            var linkType = QDRService.valFor(response.attributeNames, result, "linkType")
+            var addresses = fixAddress();
+            var link = QDRService.flatten(response.attributeNames, result)
+            linkFields.push({
+              link:       linkName,
+              title:      linkName,
+              uncounts:   uncounts(),
+              operStatus: operStatus,
+              adminStatus:adminStatus,
+              owningAddr: addresses[0],
+
+              acceptedCount: prettyVal("acceptedCount"),
+              modifiedCount: prettyVal("modifiedCount"),
+              presettledCount: prettyVal("presettledCount"),
+              rejectedCount: prettyVal("rejectedCount"),
+              releasedCount: prettyVal("releasedCount"),
+              deliveryCount:prettyVal("deliveryCount") + " ",
+
+              rate: QDRService.pretty(rate(linkName, response, result)),
+              capacity: link.capacity,
+              undeliveredCount: link.undeliveredCount,
+              unsettledCount: link.unsettledCount,
+
+              rawAddress: addresses[1],
+              rawDeliveryCount: link.deliveryCount,
+              name: link.name,
+              linkName: link.linkName,
+              connectionId: link.connectionId,
+              linkDir: link.linkDir,
+              linkType: linkType,
+              peer: link.peer,
+              type: link.type,
+
+              uid:     linkName,
+              timestamp: now,
+              nodeId: nodeName,
+              identity: link.identity,
+            })
+          }
+        })
+        if (expected === ++received) {
+          linkFields.sort ( function (a,b) { return a.link < b.link ? -1 : a.link > b.link ? 1 : 0})
+          completionCallbacks.forEach( function (cb) {
+            cb(linkFields)
+          })
+        }
+      }
+      nodeIds.forEach( function (nodeId) {
+        QDRService.fetchEntity(nodeId, "router.link", [], gotLinkInfo);
+      })
+    }
+
+    $scope.connectionPagingOptions = {
+      pageSizes: [50, 100, 500],
+      pageSize: 50,
+      currentPage: 1
+    };
+    var getConnectionPagedData = function (pageSize, page) {
+      $scope.totalConnections = $scope.allConnectionFields.length
+      $scope.allConnectionGrid.showFooter = $scope.totalConnections > 50
+      $scope.pagedConnectionsData = $scope.allConnectionFields.slice((page - 1) * pageSize, page * pageSize);
+    }
+    $scope.$watch('connectionPagingOptions', function (newVal, oldVal) {
+      if (newVal !== oldVal && newVal.currentPage !== oldVal.currentPage) {
+        getConnectionPagedData($scope.connectionPagingOptions.pageSize, $scope.connectionPagingOptions.currentPage);
+      }
+    }, true);
+
+    $scope.totalConnections = 0;
+    $scope.pagedConnectionsData = []
+    $scope.allConnectionFields = []
+    $scope.allConnectionSelections = [];
+    $scope.allConnectionGrid = {
+      saveKey: 'allConnGrid',
+      data: 'pagedConnectionsData',
+      columnDefs: [
+      {
+        field: 'host',
+        saveKey: 'allConnGrid',
+        displayName: 'host'
+      },
+      {
+        field: 'container',
+        displayName: 'container'
+      },
+      {
+        field: 'role',
+        displayName: 'role'
+      },
+      {
+        field: 'dir',
+        displayName: 'dir'
+      },
+      {
+        field: 'security',
+        displayName: 'security'
+      },
+      {
+        field: 'authentication',
+        displayName: 'authentication'
+      },
+      ],
+      enablePaging: true,
+      showFooter: $scope.totalConnections > 50,
+      totalServerItems: 'totalConnections',
+      pagingOptions: $scope.connectionPagingOptions,
+      enableColumnResize: true,
+      multiSelect: false,
+      selectedItems: $scope.allConnectionSelections,
+      plugins: [new ngGridFlexibleHeightPlugin()],
+      afterSelectionChange: function(data) {
+        if (data.selected) {
+          var selItem = $scope.allConnectionSelections[0]
+          var nodeId = selItem.uid
+          // activate Routers->nodeId in the tree
+          $("#overtree").dynatree("getTree").activateKey(nodeId);
+        }
+      }
+    };
+    // get info for a all connections
+    var allConnectionInfo = function () {
+      getAllConnectionFields([updateConnectionGrid, updateConnectionTree, scheduleNextUpdate])
+      loadColState($scope.allConnectionGrid);
+    }
+    // called after conection data is available
+    var updateConnectionGrid = function (connectionFields) {
+      $scope.allConnectionFields = connectionFields;
+      getConnectionPagedData($scope.connectionPagingOptions.pageSize, $scope.connectionPagingOptions.currentPage);
+    }
+
+    // get the connection data for all nodes
+    // called periodically
+    // creates a connectionFields array and calls the callbacks (updateTree and updateGrid)
+    var getAllConnectionFields = function (callbacks) {
+      var nodeIds = QDRService.nodeIdList()
+      var connectionFields = [];
+      var expected = nodeIds.length;
+      var received = 0;
+      var gotConnectionInfo = function (nodeName, entity, response) {
+        response.results.forEach( function (result) {
+
+          var auth = "no_auth"
+          var sasl = QDRService.valFor(response.attributeNames, result, "sasl")
+          if (QDRService.valFor(response.attributeNames, result, "isAuthenticated")) {
+            auth = sasl
+            if (sasl === "ANONYMOUS")
+              auth = "anonymous-user"
+            else {
+              if (sasl === "GSSAPI")
+                sasl = "Kerberos"
+              if (sasl === "EXTERNAL")
+                sasl = "x.509"
+              auth = QDRService.valFor(response.attributeNames, result, "user") + "(" +
+                  QDRService.valFor(response.attributeNames, result, "sslCipher") + ")"
+              }
+          }
+
+          var sec = "no-security"
+          if (QDRService.valFor(response.attributeNames, result, "isEncrypted")) {
+            if (sasl === "GSSAPI")
+              sec = "Kerberos"
+            else
+              sec = QDRService.valFor(response.attributeNames, result, "sslProto") + "(" +
+                  QDRService.valFor(response.attributeNames, result, "sslCipher") + ")"
+          }
+
+          var host = QDRService.valFor(response.attributeNames, result, "host")
+          var connField = {
+            host: host,
+            security: sec,
+            authentication: auth,
+            routerId: nodeName,
+            uid: host + QDRService.valFor(response.attributeNames, result, "identity")
+          }
+          response.attributeNames.forEach( function (attribute, i) {
+            connField[attribute] = result[i]
+          })
+          connectionFields.push(connField)
+        })
+        if (expected === ++received) {
+          connectionFields.sort ( function (a,b) { return a.host < b.host ? -1 : a.host > b.host ? 1 : 0})
+          $timeout( function () {
+            callbacks.forEach( function (cb) {
+              cb(connectionFields)
+            })
+          })
+        }
+      }
+      nodeIds.forEach( function (nodeId) {
+        QDRService.fetchEntity(nodeId, ".connection", [], gotConnectionInfo)
+      })
+    }
+
+    var SingleEntityGrid = function (name) {
+      this.saveKey = name
+      this.data = name
+      this.columnDefs = [
+        {
+          field: 'attribute',
+          displayName: 'Attribute',
+          saveKey: this.saveKey,
+          width: '40%'
+        },
+        {
+          field: 'value',
+          displayName: 'Value',
+          width: '40%'
+        }
+        ]
+      this.enableColumnResize = true
+      this.multiSelect = false
+    }
+    $scope.addressFields = []
+    $scope.addressGrid = new SingleEntityGrid('addressFields')
+    $scope.connectionFields = []
+    $scope.connectionGrid = new SingleEntityGrid('connectionFields')
+    $scope.routerFields = []
+    $scope.routerGrid = new SingleEntityGrid('routerFields')
+    $scope.linkFields = []
+    $scope.linkGrid = new SingleEntityGrid('linkFields')
+
+    var SingleEntityInfo = function (entityName) {
+      return function (entity) {
+        if (!entity)
+          return
+        $scope[entityName] = entity
+        var currentEntity = getCurrentLinksEntity()
+        if (currentEntity === entityName && entityModes[currentEntity] && entityModes[currentEntity].currentModeId === 'links') {
+          updateModeLinks()
+          scheduleNextUpdate()
+          return
+        }
+        var filteredFields = []
+        var fields = Object.keys(entity.data.fields)
+        fields.forEach( function (field) {
+          if (field != "title" && field != "uid")
+            filteredFields.push({attribute: field, value: entity.data.fields[field]})
+        })
+        $timeout(() => $scope[entityName.toLowerCase()+'Fields'] = filteredFields)
+        //$scope[entityName.toLowerCase()+'Fields'] = filteredFields
+        console.log("-------------- " + entityName + "Fields -----------")
+        console.dump(filteredFields)
+        scheduleNextUpdate()
+        loadColState($scope[entityName.toLowerCase()+'Grid']);
+      }
+    }
+
+    // get info for a single connection
+    $scope.gridModes = [{
+        content: '<a><i class="icon-list"></i> Attriutes</a>',
+        id: 'attributes',
+        title: "View attributes"
+      },
+      {
+        content: '<a><i class="icon-link"></i> Links</a>',
+        id: 'links',
+        title: "Show links"
+      }
+    ];
+    var saveModeIds = function () {
+      var modeIds = {Address: entityModes.Address.currentModeId, Connection: entityModes.Connection.currentModeId}
+      localStorage[OVERVIEWMODEIDS] = JSON.stringify(modeIds)
+    }
+    var loadModeIds = function () {
+      return angular.fromJson(localStorage[OVERVIEWMODEIDS]) ||
+        {Address: 'attributes', Connection: 'attributes'}
+    }
+    var savedModeIds = loadModeIds()
+      var entityModes = {
+          Address: {
+              currentModeId: savedModeIds.Address,
+              filter: function (response, result) {
+          var owningAddr = QDRService.valFor(response.attributeNames, result, "owningAddr")
+          var id = $scope.Address.data.fields.uid
+          return (owningAddr === $scope.Address.data.fields.uid)
+              }
+          },
+          Connection: {
+              currentModeId: savedModeIds.Connection,
+              filter: function (response, result) {
+          var connectionId = QDRService.valFor(response.attributeNames, result, "connectionId")
+          return (connectionId === $scope.Connection.data.fields.identity)
+              }
+          }
+      }
+    $scope.selectMode = function (mode, entity) {
+      if (!mode || !entity)
+        return;
+      entityModes[entity].currentModeId = mode.id;
+      saveModeIds();
+      if (mode.id === 'links') {
+QDR.log.debug("setting linkFields to [] in selectMode")
+        $scope.filteredLinkFields = [];
+        getLinkPagedData($scope.linkPagingOptions.pageSize, $scope.linkPagingOptions.currentPage);
+        updateModeLinks();
+      }
+    }
+    $scope.isModeSelected = function (mode, entity) {
+      return mode.id === entityModes[entity].currentModeId
+    }
+    $scope.isModeVisible = function (entity, id) {
+      return entityModes[entity].currentModeId === id
+    }
+
+    var updateEntityLinkGrid = function (linkFields) {
+      $timeout(function () {
+        QDR.log.debug("setting linkFields in updateEntityLinkGrid");
+        $scope.filteredLinkFields = linkFields
+        getLinkPagedData($scope.linkPagingOptions.pageSize, $scope.linkPagingOptions.currentPage);
+      })
+    }
+    // based on which entity is selected, get and filter the links
+    var updateModeLinks = function () {
+      var currentEntity = getCurrentLinksEntity()
+      if (!currentEntity)
+        return;
+      var selectionCallback = entityModes[currentEntity].filter;
+      getAllLinkFields([updateEntityLinkGrid], selectionCallback)
+    }
+    var getCurrentLinksEntity = function () {
+      var currentEntity;
+      var active = $("#overtree").dynatree("getActiveNode");
+      if (active) {
+        currentEntity = active.data.type;
+      }
+      return currentEntity;
+    }
+
+    $scope.quiesceLinkClass = function (row) {
+      var stateClassMap = {
+        enabled: 'btn-primary',
+        disabled: 'btn-danger'
+      }
+      return stateClassMap[row.entity.adminStatus]
+    }
+
+    $scope.quiesceLink = function (row, $event) {
+      QDRService.quiesceLink(row.entity.nodeId, row.entity.name);
+      $event.stopPropagation()
+    }
+
+    $scope.quiesceLinkDisabled = function (row) {
+      return (row.entity.operStatus !== 'up' && row.entity.operStatus !== 'down')
+    }
+    $scope.quiesceLinkText = function (row) {
+      return row.entity.adminStatus === 'disabled' ? "Revive" : "Quiesce";
+    }
+
+    $scope.expandAll = function () {
+      $("#overtree").dynatree("getRoot").visit(function(node){
+                node.expand(true);
+            });
+    }
+    $scope.contractAll = function () {
+      $("#overtree").dynatree("getRoot").visit(function(node){
+                node.expand(false);
+            })
+    }
+
+    var logModuleCellTemplate = '<div ng-click="logInfoFor(row, col)" class="ngCellText" ng-class="col.colIndex()"><span ng-cell-text>{{COL_FIELD | pretty}}</span></div>'
+    $scope.logModule = {}
+    $scope.logModuleSelected = []
+    $scope.logModuleData = []
+    $scope.logModuleGrid = {
+      data: 'logModuleData',
+      columnDefs: [
+        {
+          field: 'nodeName',
+          displayName: 'Router',
+        },
+        {
+          field: 'enable',
+          displayName: 'Enable level',
+        },
+        {
+          field: 'noticeCount',
+          displayName: 'Notice',
+          cellTemplate: logModuleCellTemplate,
+          cellClass: 'grid-align-value',
+        },
+        {
+          field: 'infoCount',
+          displayName: 'Info',
+          cellTemplate: logModuleCellTemplate,
+          cellClass: 'grid-align-value',
+        },
+        {
+          field: 'traceCount',
+          displayName: 'Trace',
+          cellTemplate: logModuleCellTemplate,
+          cellClass: 'grid-align-value',
+        },
+        {
+          field: 'debugCount',
+          displayName: 'Debug',
+          cellTemplate: logModuleCellTemplate,
+          cellClass: 'grid-align-value',
+        },
+        {
+          field: 'warningCount',
+          displayName: 'Warning',
+          cellTemplate: logModuleCellTemplate,
+          cellClass: 'grid-align-value',
+        },
+        {
+          field: 'errorCount',
+          displayName: 'Error',
+          cellTemplate: logModuleCellTemplate,
+          cellClass: 'grid-align-value',
+        },
+        {
+          field: 'criticalCount',
+          displayName: 'Critical',
+          cellTemplate: logModuleCellTemplate,
+          cellClass: 'grid-align-value',
+        },
+      ],
+      enableColumnResize: true,
+      multiSelect: false,
+      selectedItems: $scope.logModuleSelected,
+      plugins: [new ngGridFlexibleHeightPlugin()],
+      afterSelectionChange: function(data) {
+        if (data.selected) {
+            var selItem = $scope.logModuleSelected[0]
+            var nodeId = selItem.nodeId
+
+        }
+      }
+    }
+
+    $scope.logInfoFor = function (row, col) {
+      logDialog(row, col)
+    }
+
+    function logDialog(row, col) {
+        var d = $dialog.dialog({
+          backdrop: false,
+          keyboard: true,
+          backdropClick: false,
+          templateUrl: 'viewLogs.html',
+          controller: "QDR.OverviewLogsController",
+          resolve: {
+            nodeName: function () {
+              return row.entity.nodeName
+            },
+            module: function () {
+              return row.entity.name
+            },
+            level: function () {
+              return col.displayName
+            },
+            nodeId: function () {
+              return row.entity.nodeId
+            },
+          }
+        });
+        d.open().then(function(result) { console.log("d.open().then"); });
+    };
+
+    var numberTemplate = '<div class="ngCellText" ng-class="col.colIndex()"><span ng-cell-text>{{COL_FIELD | pretty}}</span></div>'
+    $scope.allLogFields = []
+    $scope.allLogSelections = [];
+    $scope.allLogGrid = {
+      saveKey: 'allLogGrid',
+      data: 'allLogFields',
+      columnDefs: [
+        {
+          field: 'name',
+          saveKey: 'allLogGrid',
+          displayName: 'Module'
+        },
+/*        {
+          field: 'enable',
+          displayName: 'Enable'
+        },
+*/
+
+        {
+          field: 'noticeCount',
+          displayName: 'Notice',
+          cellTemplate: numberTemplate,
+          cellClass: 'grid-align-value',
+        },
+        {
+          field: 'infoCount',
+          displayName: 'Info',
+          cellTemplate: numberTemplate,
+          cellClass: 'grid-align-value',
+        },
+        {
+          field: 'traceCount',
+          displayName: 'Trace',
+          cellTemplate: numberTemplate,
+          cellClass: 'grid-align-value',
+        },
+        {
+          field: 'debugCount',
+          displayName: 'Debug',
+          cellTemplate: numberTemplate,
+          cellClass: 'grid-align-value',
+        },
+        {
+          field: 'warningCount',
+          displayName: 'Warning',
+          cellTemplate: numberTemplate,
+          cellClass: 'grid-align-value',
+        },
+        {
+          field: 'errorCount',
+          displayName: 'Error',
+          cellTemplate: numberTemplate,
+          cellClass: 'grid-align-value',
+        },
+        {
+          field: 'criticalCount',
+          displayName: 'Critical',
+          cellTemplate: numberTemplate,
+          cellClass: 'grid-align-value',
+        },
+      ],
+      //enableCellSelection: true,
+      enableColumnResize: true,
+      multiSelect: false,
+      selectedItems: $scope.allLogSelections,
+      plugins: [new ngGridFlexibleHeightPlugin()],
+
+      afterSelectionChange: function(data) {
+        if (data.selected) {
+            var selItem = $scope.allLogSelections[0]
+            var nodeId = selItem.name
+            // activate in the tree
+            $("#overtree").dynatree("getTree").activateKey(nodeId);
+        }
+      }
+
+    };
+
+    var allLogEntries = {}
+    var allLogInfo = function () {
+        // update the count of entries for each module
+        $scope.allLogFields = []
+        var logResults = {}
+        var logDetails = {}
+
+        var gotLogStats = function (node, entity, response) {
+          logDetails[node] = []
+          response.results.forEach( function (result) {
+            var oresult = QDRService.flatten(response.attributeNames, result)
+            // make a copy for the details grid since logResults has the same object reference
+            logDetails[node].push(angular.copy(oresult))
+            if (!(oresult.name in logResults)) {
+              logResults[oresult.name] = oresult
+            }
+            else {
+              response.attributeNames.forEach( function (attr, i) {
+                if (attr.substr(attr.length-5) === 'Count') {
+                  logResults[oresult.name][attr] += result[i]
+                }
+              })
+            }
+          })
+        }
+        var gotAllLogStats = function () {
+          var sortedModules = Object.keys(logResults)
+          sortedModules.sort(function (a,b) {return a<b?-1:a>b?1:0})
+          sortedModules.forEach( function (module) {
+            $scope.allLogFields.push(logResults[module])
+          })
+          allLogEntries = logDetails
+          updateLogTree($scope.allLogFields)
+        }
+        QDRService.fetchAllEntities({entity: 'logStats'}, gotAllLogStats, gotLogStats)
+/*
+        var q = QDR.queue(1)
+
+        var queuedSendMethod = function (node, callback) {
+          var gotLogInfo = function (nodeId, entity, response, context) {
+            var statusCode = context.message.application_properties.statusCode;
+            if (statusCode < 200 || statusCode >= 300) {
+              callback("getLog failed with statusCode of " + statusCode)
+            } else {
+              var logFields = response.map( function (result) {
+                return {
+                  nodeId: QDRService.nameFromId(nodeId),
+                  name: result[0],
+                  type: result[1],
+                  message: result[2],
+                  source: result[3],
+                  line: result[4],
+                  time: Date(result[5]).toString()
+                }
+              })
+              logResults.push.apply(logResults, logFields) // append new array to existing
+              callback(null)
+            }
+          }
+          QDRService.sendMethod(node, undefined, {}, "GET-LOG", {}, gotLogInfo)
+        }
+        for (var i=0; i<nodeIds.length; ++i) {
+          q.defer(queuedSendMethod, nodeIds[i])
+        }
+        q.await(function (error) {
+          if (!error) {
+            logResults.sort( function (a, b) {return b.name - a.name})
+            var allLogFields = $scope.allLogFields
+            $scope.allLogFields = [];
+            for (var i=0; i<allLogFields.length; ++i) {
+              $scope.allLogFields.push({module: allLogFields[i].module,
+                count: logResults.filter( function (entry) {
+                  return entry.name === allLogFields[i].module
+                }).length
+              })
+            }
+            $timeout(function () {allLogEntries = logResults; scheduleNextUpdate()})
+          }
+        })
+      }
+      if ($scope.allLogFields.length == 0) {
+        QDRService.fetchEntity(nodeIds[0], "logStats", [], function (nodeName, entity, response) {
+          var moduleIndex = response.attributeNames.indexOf("name")
+          response.results.sort( function (a,b) {return a[moduleIndex] < b[moduleIndex] ? -1 : a[moduleIndex] > b[moduleIndex] ? 1 : 0})
+          response.results.forEach( function (result) {
+            var log = QDRService.flatten(response.attributeNames, result)
+            $scope.allLogFields.push(log)
+          })
+          updateLogTree($scope.allLogFields)
+          haveLogFields()
+        })
+      } else {
+        haveLogFields()
+      }
+        */
+    }
+
+    $scope.logFields = []
+    // get info for a single log
+    var logInfo = function (node) {
+
+        var gotLogInfo = function (responses) {
+          $timeout(function () {
+            $scope.logModuleData = []
+            $scope.logModule.module = node.data.key
+            for (var n in responses) {
+              var moduleIndex = responses[n]['log'].attributeNames.indexOf("module")
+              var result = responses[n]['log'].results.filter( function (r) {
+                return r[moduleIndex] === node.data.key
+              })[0]
+              var logInfo = QDRService.flatten(responses[n]['log'].attributeNames, result)
+              var entry = allLogEntries[n]
+              entry.forEach( function (module) {
+                if (module.name === node.data.key) {
+                  module.nodeName = QDRService.nameFromId(n)
+                  module.nodeId = n
+                  module.enable = logInfo.enable
+                  $scope.logModuleData.push(module)
+                }
+              })
+            }
+            $scope.logModuleData.sort ( function (a,b) { return a.nodeName < b.nodeName? -1 : a.nodeName> b.nodeName? 1 : 0})
+            scheduleNextUpdate()
+          })
+        }
+        QDRService.fetchAllEntities({entity: 'log', attrs: ['module', 'enable']}, gotLogInfo)
+/*
+      $timeout(function () {
+        $scope.log = node
+        $scope.logFields = []
+        for (var n in allLogEntries) {
+          var entry = allLogEntries[n]
+          entry.forEach( function (module) {
+            if (module.name === node.data.key) {
+              module.nodeId = n
+              $scope.logFields.push(module)
+            }
+          })
+        }
+        scheduleNextUpdate()
+      })
+*/
+    }
+
+    var getExpandedList = function () {
+      if (!treeRoot)
+        return;
+      var list = [];
+      if (treeRoot.visit) {
+        treeRoot.visit(function(node){
+          if (node.isExpanded()) {
+            list.push(node.data.parent)
+          }
+          });
+      }
+      return list;
+    }
+
+    // loads the tree node name that was last selected
+    var loadActivatedNode = function () {
+      return localStorage[OVERVIEWACTIVATEDKEY] || 'Routers'
+    }
+    // saved the tree node name that is currently selected
+    var saveActivated = function (key) {
+      localStorage[OVERVIEWACTIVATEDKEY] = key;
+      lastKey = key;
+    }
+    // loads list that was saved
+    var loadExpandedNodeList = function () {
+      try {
+        return angular.fromJson(localStorage[OVERVIEWEXPANDEDKEY]) || [];
+      } catch (e) {
+        QDR.log.debug("localStorage[OVERVIEWEXPANDEDKEY]=" + localStorage[OVERVIEWEXPANDEDKEY])
+        return ["Routers"]
+      }
+    }
+    // called when a node is expanded
+    // here we save the expanded node so it can be restored when the page reloads
+    var saveExpanded = function () {
+      var list = getExpandedList();
+      localStorage[OVERVIEWEXPANDEDKEY] = JSON.stringify(list)
+      expandedNodeList = list
+    }
+
+    var setTemplate = function (node) {
+      var type = node.data.type;
+      var template = $scope.templates.filter( function (tpl) {
+        return tpl.name == type;
+      })
+      $scope.template = template[0];
+    }
+    // activated is called each time a tree node is clicked
+    // based on which node is clicked, load the correct data grid template and start getting the data
+    var activated = function (node) {
+      QDR.log.debug("node activated: " + node.data.title)
+      saveExpanded()
+      saveActivated(node.data.key)
+
+      setTemplate(node)
+      // the nodes info function will fetch the grids data
+      if (node.data.info) {
+        $timeout(function () {
+          if (node.data.key === node.data.parent) {
+            node.data.info()
+          }
+          else {
+            node.data.info(node)
+          }
+        })
+      }
+    }
+
+    var treeNodeExpanded = function (node) {
+      saveExpanded()
+      tick()
+    }
+    $scope.template = {url: ''};
+
+    if (!QDRService.connected) {
+      QDRService.redirectWhenConnected("overview")
+      return;
+    }
+
+    // we are currently connected. setup a handler to get notified if we are ever disconnected
+    QDRService.addDisconnectAction( function () {
+      $timeout( () => QDRService.redirectWhenConnected("overview") )
+    })
+
+    /* --------------------------------------------------
+     *
+     * setup the tree on the left
+     *
+     * -------------------------------------------------
+     */
+    // utility function called by each top level tree node when it needs to populate its child nodes
+    var updateLeaves = function (leaves, parentKey, parentFolder, worker) {
+      var scrollTree = $('.qdr-overview.pane.left .pane-viewport')
+      var scrollTop = scrollTree.scrollTop();
+      var tree = $("#overtree").dynatree("getTree")
+      var parentNode = tree.getNodeByKey(parentKey);
+      parentNode.removeChildren();
+
+      leaves.forEach( function (leaf) {
+        parentNode.addChild(worker(leaf))
+      })
+      scrollTree.scrollTop(scrollTop)
+      if (firstTime) {
+        var newActive = tree.getActiveNode();
+        if (newActive &&
+//            newActive.data.key === lastKey &&
+            newActive.data.key !== newActive.data.parent &&  // this is a leaf node
+            newActive.data.parent === parentKey) {          // the active node was just created
+          firstTime = false
+QDR.log.debug("newly created node needs to be activated")
+          activated(newActive)
+        }
+      }
+     }
+
+    // get saved tree state
+    var lastKey = loadActivatedNode();
+    var expandedNodeList = loadExpandedNodeList();
+    var firstTime = true;
+
+    // create a routers tree branch
+    var routers = new Folder("Routers")
+    routers.type = "Routers"
+    routers.info = allRouterInfo
+    routers.activate = lastKey === 'Routers'
+    routers.expand = (expandedNodeList.indexOf("Routers") > -1)
+    routers.clickFolderMode = 1
+    routers.key = "Routers"
+    routers.parent = "Routers"
+    routers.addClass = "routers"
+    topLevelChildren.push(routers)
+    // called when the list of routers changes
+    var updateRouterTree = function (nodes) {
+      var worker = function (node) {
+        var name = QDRService.nameFromId(node)
+        var router = new Folder(name)
+        router.type = "Router"
+        router.info = routerInfo
+        router.nodeId = node
+        router.key = node
+        router.addClass = "router"
+        router.parent = "Routers"
+        router.activate = lastKey === node
+        return router;
+      }
+      $timeout(function () {updateLeaves(nodes, "Routers", routers, worker)})
+    }
+
+    // create an addresses tree branch
+    var addresses = new Folder("Addresses")
+    addresses.type = "Addresses"
+    addresses.info = allAddressInfo
+    addresses.activate = lastKey === 'Addresses'
+    addresses.expand = (expandedNodeList.indexOf("Addresses") > -1)
+    addresses.clickFolderMode = 1
+    addresses.key = "Addresses"
+    addresses.parent = "Addresses"
+    addresses.addClass = "addresses"
+    topLevelChildren.push(addresses)
+    var updateAddressTree = function (addressFields) {
+      var info = SingleEntityInfo('Address')
+      var worker = function (address) {
+        var a = new Folder(address.title)
+        a.info = info
+        a.key = address.uid
+        a.fields = address
+        a.type = "Address"
+        a.tooltip = address['class'] + " address"
+        if (address.address === '$management')
+          a.tooltip = "internal " + a.tooltip
+        a.addClass = a.tooltip
+        a.activate = lastKey === address.uid
+        a.parent = "Addresses"
+        return a;
+      }
+      $timeout(function () {updateLeaves(addressFields, "Addresses", addresses, worker)})
+    }
+
+    $scope.$watch("filter", function (newValue, oldValue) {
+      if (newValue !== oldValue) {
+        $timeout(allLinkInfo);
+        localStorage[FILTERKEY] = JSON.stringify($scope.filter)
+      }
+    }, true)
+
+    $scope.filterToggle = function () {
+      var filter = $('#linkFilter')
+      filter.toggle();
+    }
+
+    $scope.filter = angular.fromJson(localStorage[FILTERKEY]) || {endpointsOnly: "true", hideConsoles: true};
+    var links = new Folder("Links")
+    links.type = "Links"
+    links.info = allLinkInfo
+    links.activate = lastKey === 'Links'
+    links.expand = (expandedNodeList.indexOf("Links") > -1)
+    links.clickFolderMode = 1
+    links.key = "Links"
+    links.parent = "Links"
+    links.addClass = "links"
+    topLevelChildren.push(links)
+
+    // called both before the tree is created and whenever a background update is done
+    var updateLinkTree = function (linkFields) {
+      var info = SingleEntityInfo('Link')
+      var worker = function (link) {
+        var l = new Folder(link.title)
+        var isConsole = QDRService.isConsoleLink(link)
+        l.info = info
+        l.key = link.uid
+        l.fields = link
+        l.type = "Link"
+        l.parent = "Links"
+        l.activate = lastKey === link.uid
+        if (isConsole)
+          l.tooltip = "console link"
+        else
+          l.tooltip = link.linkType  + " link"
+        l.addClass = l.tooltip
+        return l;
+      }
+      $timeout(function () {updateLeaves(linkFields, "Links", links, worker)})
+    }
+
+    var connections = new Folder("Connections")
+    connections.type = "Connections"
+    connections.info = allConnectionInfo
+    connections.activate = lastKey === 'Connections'
+    connections.expand = (expandedNodeList.indexOf("Connections") > -1)
+    connections.clickFolderMode = 1
+    connections.key = "Connections"
+    connections.parent = "Connections"
+    connections.addClass = "connections"
+    topLevelChildren.push(connections)
+
+    updateConnectionTree = function (connectionFields) {
+      var info = SingleEntityInfo('Connection')
+      var worker = function (connection) {
+        var c = new Folder(connection.host)
+        var isConsole = QDRService.isAConsole (connection.properties, connection.identity, connection.role, connection.routerId)
+        c.type = "Connection"
+        c.info = info
+        c.key = connection.uid
+        c.fields = connection
+        if (isConsole)
+          c.tooltip = "console connection"
+        else
+          c.tooltip = connection.role === "inter-router" ? "inter-router connection" : "external connection"
+        c.addClass = c.tooltip
+        c.parent = "Connections"
+        c.activate = lastKey === connection.uid
+        return c
+      }
+      $timeout(function () {updateLeaves(connectionFields, "Connections", connections, worker)})
+    }
+
+    var updateLogTree = function (logFields) {
+      var worker = function (log) {
+        var l = new Folder(log.name)
+        l.type = "Log"
+        l.info = logInfo
+        l.key = log.name
+        l.parent = "Logs"
+        l.activate = lastKey === l.key
+        l.addClass = "log"
+        return l
+      }
+      $timeout(function () {updateLeaves(logFields, "Logs", logs, worker)})
+    }
+
+    var htmlReady = false;
+    var dataReady = false;
+    $scope.largeNetwork = QDRService.isLargeNetwork()
+    var logs = new Folder("Logs")
+    logs.type = "Logs"
+    logs.info = allLogInfo
+    logs.activate = lastKey === 'Logs'
+    logs.expand = (expandedNodeList.indexOf("Logs") > -1)
+    logs.clickFolderMode = 1
+    logs.key = "Logs"
+    logs.parent = "Logs"
+    if (QDRService.versionCheck('0.8.0'))
+      topLevelChildren.push(logs)
+    var initTreeAndGrid = function () {
+      if (!htmlReady || !dataReady)
+        return;
+      var div = angular.element("#overtree");
+      if (!div.width()) {
+        setTimeout(initTreeAndGrid, 100);
+        return;
+      }
+      $('#overtree').dynatree({
+        onActivate: activated,
+        onExpand: treeNodeExpanded,
+        autoCollapse: $scope.largeNetwork,
+        activeVisible: !$scope.largeNetwork,
+        selectMode: 1,
+        debugLevel: 0,
+        children: topLevelChildren
+      })
+      treeRoot = $("#overtree").dynatree("getRoot");
+      tick()
+      loadColState($scope.allRouters);
+      loadColState($scope.routerGrid);
+      loadColState($scope.addressesGrid);
+      loadColState($scope.addressGrid);
+      loadColState($scope.linksGrid);
+      loadColState($scope.linkGrid);
+      loadColState($scope.allConnectionGrid);
+      loadColState($scope.connectionGrid);
+    }
+
+    $scope.overviewLoaded = function () {
+      htmlReady = true;
+      initTreeAndGrid();
+    }
+
+    var nodeIds = QDRService.nodeIdList()
+    //updateRouterTree(nodeIds);
+    // add placeholders for the top level tree nodes
+    var topLevelTreeNodes = [routers, addresses, links, connections, logs]
+    topLevelTreeNodes.forEach( function (parent) {
+      var placeHolder = new Folder("loading...")
+      placeHolder.addClass = "loading"
+      parent.children = [placeHolder]
+    })
+
+    var singleQ = null
+    var updateExpanded = function () {
+      if (!treeRoot)
+        return;
+      if (treeRoot.visit) {
+        treeRoot.visit(function(node){
+          if (node.isActive())
+            setTemplate(node)
+          if (node.isActive() || node.isExpanded()) {
+            if (node.data.key === node.data.parent) {
+              node.data.info()
+            }
+            else {
+              node.data.info(node)
+            }
+          }
+        })
+      }
+    }
+
+    var tickTimer;
+    var scheduleNextUpdate = function () {
+      clearTimeout(tickTimer)
+      tickTimer = setTimeout(tick, refreshInterval)
+    }
+    var tick = function () {
+      clearTimeout(tickTimer)
+      updateExpanded();
+    }
+    dataReady = true;
+    initTreeAndGrid();
+    $scope.$on("$destroy", function( event ) {
+      clearTimeout(tickTimer)
+      //QDRService.stopUpdating()
+      //QDRService.delUpdatedAction("overview")
+    });
+
+  }]);
+
+  return QDR;
+
+}(QDR || {}));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6a1e6632/console/hawtio/src/main/webapp/plugin/js/qdrOverviewLogsController.js
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrOverviewLogsController.js b/console/hawtio/src/main/webapp/plugin/js/qdrOverviewLogsController.js
deleted file mode 120000
index d48ecbb..0000000
--- a/console/hawtio/src/main/webapp/plugin/js/qdrOverviewLogsController.js
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../../stand-alone/plugin/js/qdrOverviewLogsController.js
\ No newline at end of file
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrOverviewLogsController.js b/console/hawtio/src/main/webapp/plugin/js/qdrOverviewLogsController.js
new file mode 100644
index 0000000..fed1e3d
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/js/qdrOverviewLogsController.js
@@ -0,0 +1,67 @@
+/*
+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.
+*/
+/**
+ * @module QDR
+ */
+var QDR = (function(QDR) {
+
+  QDR.module.controller('QDR.OverviewLogsController', function ($scope, dialog, QDRService, $timeout, nodeName, nodeId, module, level) {
+
+      var gotLogInfo = function (nodeId, entity, response, context) {
+        var statusCode = context.message.application_properties.statusCode;
+        if (statusCode < 200 || statusCode >= 300) {
+          Core.notification('error', context.message.application_properties.statusDescription);
+        } else {
+          var levelLogs = response.filter( function (result) {
+            if (result[1] == null)
+              result[1] = "error"
+            return result[1].toUpperCase() === level.toUpperCase() && result[0] === module
+          })
+          var logFields = levelLogs.map( function (result) {
+            return {
+              nodeId: QDRService.nameFromId(nodeId),
+              name: result[0],
+              type: result[1],
+              message: result[2],
+              source: result[3],
+              line: result[4],
+              time: Date(result[5]).toString()
+            }
+          })
+          $timeout(function () {
+            $scope.loading = false
+            $scope.logFields = logFields
+          })
+        }
+      }
+      QDRService.sendMethod(nodeId, undefined, {}, "GET-LOG", {module: module}, gotLogInfo)
+
+    $scope.loading = true
+    $scope.module = module
+    $scope.level = level
+    $scope.nodeName = nodeName
+    $scope.logFields = []
+    $scope.ok = function () {
+      dialog.close(true);
+    };
+
+  });
+  return QDR;
+
+} (QDR || {}));


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


[2/4] qpid-dispatch git commit: DISPATCH-745 Remove symlinks from hawtio to stand-alone

Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6a1e6632/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
deleted file mode 120000
index 36d5187..0000000
--- a/console/hawtio/src/main/webapp/plugin/js/qdrTopology.js
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../../stand-alone/plugin/js/qdrTopology.js
\ No newline at end of file
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrTopology.js b/console/hawtio/src/main/webapp/plugin/js/qdrTopology.js
new file mode 100644
index 0000000..9899393
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/js/qdrTopology.js
@@ -0,0 +1,2160 @@
+/*
+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.
+*/
+/**
+ * @module QDR
+ */
+var QDR = (function(QDR) {
+
+  QDR.module.controller('QDR.TopologyFormController', function($scope, QDRService) {
+
+    $scope.attributes = []
+    var nameTemplate = '<div title="{{row.entity.description}}" class="ngCellText {{row.entity.cls}}"><span>{{row.entity.attributeName}}</span></div>';
+    var valueTemplate = '<div title="{{row.entity.attributeValue}}" class="ngCellText {{row.entity.cls}}"><span>{{row.entity.attributeValue}}</span></div>';
+    $scope.topoGridOptions = {
+      data: 'attributes',
+      enableColumnResize: false,
+      multiSelect: false,
+      columnDefs: [{
+        field: 'attributeName',
+        displayName: 'Attribute',
+        cellTemplate: nameTemplate
+      }, {
+        field: 'attributeValue',
+        displayName: 'Value',
+        cellTemplate: valueTemplate
+      }]
+    };
+    $scope.form = ''
+    $scope.$on('showEntityForm', function(event, args) {
+      var attributes = args.attributes;
+      var entityTypes = QDRService.schema.entityTypes[args.entity].attributes;
+      attributes.forEach(function(attr) {
+        attr.cls = ''
+QDR.log.debug("attr.description " + attr.description)
+        if (attr.attributeName === 'Listening on')
+          attr.cls = 'listening-on'
+        if (entityTypes[attr.attributeName] && entityTypes[attr.attributeName].description) {
+          attr.description = entityTypes[attr.attributeName].description
+        }
+      })
+      $scope.attributes = attributes;
+      $scope.form = args.entity;
+    })
+    $scope.$on('showAddForm', function(event) {
+      $scope.form = 'add';
+    })
+  })
+
+  /**
+   * @method TopologyController
+   *
+   * Controller that handles the QDR topology page
+   */
+  QDR.module.controller("QDR.TopologyController", ['$scope', '$rootScope', 'QDRService', '$location', '$timeout', '$dialog',
+    function($scope, $rootScope, QDRService, $location, $timeout, $dialog) {
+
+      $scope.panelVisible = true  // show/hide the panel on the left
+      $scope.multiData = []
+      $scope.selectedClient = [];
+      $scope.quiesceState = {}
+      var dontHide = false;
+
+      $scope.hideLeftPane = function () {
+        d3.select(".qdr-topology.pane.left")
+          .transition().duration(300).ease("sin-in")
+          .style("left" , "-380px")
+
+        d3.select(".panel-adjacent")
+          .transition().duration(300).ease("sin-in")
+          .style("margin-left", "30px")
+          .each("end", function () {
+            resize()
+            $timeout(function () {QDR.log.debug("done with transition. setting scope ");$scope.panelVisible = false})
+          })
+      }
+      $scope.showLeftPane = function () {
+        d3.select(".qdr-topology.pane.left")
+          .transition().duration(300).ease("sin-out")
+          .style("left" , "0px")
+
+        d3.select(".panel-adjacent")
+          .transition().duration(300).ease("sin-out")
+          .style("margin-left", "430px")
+          .each("end", function () {
+            resize()
+            $timeout(function () {QDR.log.debug("done with transition. setting scope ");$scope.panelVisible = true})
+          })
+      }
+      $scope.quiesceConnection = function(row) {
+        var entity = row.entity;
+        var state = $scope.quiesceState[entity.connectionId].state;
+        if (state === 'enabled') {
+          // start quiescing all links
+          $scope.quiesceState[entity.connectionId].state = 'quiescing';
+        } else if (state === 'quiesced') {
+          // start reviving all links
+          $scope.quiesceState[entity.connectionId].state = 'reviving';
+        }
+        $scope.multiDetails.updateState(entity);
+        dontHide = true;
+        $scope.multiDetails.selectRow(row.rowIndex, true);
+        $scope.multiDetails.showLinksList(row)
+      }
+      $scope.quiesceDisabled = function(row) {
+        return $scope.quiesceState[row.entity.connectionId].buttonDisabled;
+      }
+      $scope.quiesceText = function(row) {
+        return $scope.quiesceState[row.entity.connectionId].buttonText;
+      }
+      $scope.quiesceClass = function(row) {
+        var stateClassMap = {
+          enabled: 'btn-primary',
+          quiescing: 'btn-warning',
+          reviving: 'btn-warning',
+          quiesced: 'btn-danger'
+        }
+        return stateClassMap[$scope.quiesceState[row.entity.connectionId].state];
+      }
+
+      $scope.multiData = []
+      $scope.multiDetails = {
+        data: 'multiData',
+        selectedItems: $scope.selectedClient,
+        multiSelect: false,
+        afterSelectionChange: function(obj) {
+          if (obj.selected && obj.orig) {
+            var detailsDiv = d3.select('#link_details')
+            var isVis = detailsDiv.style('display') === 'block';
+            if (!dontHide && isVis && $scope.connectionId === obj.entity.connectionId) {
+              hideLinkDetails();
+              return;
+            }
+            dontHide = false;
+            $scope.multiDetails.showLinksList(obj)
+          }
+        },
+        showLinksList: function(obj) {
+          $scope.linkData = obj.entity.linkData;
+          $scope.connectionId = obj.entity.connectionId;
+          var visibleLen = Math.min(obj.entity.linkData.length, 10)
+          QDR.log.debug("visibleLen is " + visibleLen)
+          var left = parseInt(d3.select('#multiple_details').style("left"))
+          var detailsDiv = d3.select('#link_details')
+          detailsDiv
+            .style({
+              display: 'block',
+              opacity: 1,
+              left: (left + 20) + "px",
+              top: (mouseY + 20 + $(document).scrollTop()) + "px",
+              height: ((visibleLen + 1) * 30) + 40 + "px", // +1 for the header row
+              'overflow-y': obj.entity.linkData > 10 ? 'scroll' : 'hidden'
+            })
+        },
+        updateState: function(entity) {
+          var state = $scope.quiesceState[entity.connectionId].state
+
+          // count enabled and disabled links for this connection
+          var enabled = 0,
+            disabled = 0;
+          entity.linkData.forEach(function(link) {
+            if (link.adminStatus === 'enabled')
+              ++enabled;
+            if (link.adminStatus === 'disabled')
+              ++disabled;
+          })
+
+          var linkCount = entity.linkData.length;
+          // if state is quiescing and any links are enabled, button should say 'Quiescing' and be disabled
+          if (state === 'quiescing' && (enabled > 0)) {
+            $scope.quiesceState[entity.connectionId].buttonText = 'Quiescing';
+            $scope.quiesceState[entity.connectionId].buttonDisabled = true;
+          } else
+          // if state is enabled and all links are disabled, button should say Revive and be enabled. set state to quisced
+          // if state is quiescing and all links are disabled, button should say 'Revive' and be enabled. set state to quiesced
+          if ((state === 'quiescing' || state === 'enabled') && (disabled === linkCount)) {
+            $scope.quiesceState[entity.connectionId].buttonText = 'Revive';
+            $scope.quiesceState[entity.connectionId].buttonDisabled = false;
+            $scope.quiesceState[entity.connectionId].state = 'quiesced'
+          } else
+          // if state is reviving and any links are disabled, button should say 'Reviving' and be disabled
+          if (state === 'reviving' && (disabled > 0)) {
+            $scope.quiesceState[entity.connectionId].buttonText = 'Reviving';
+            $scope.quiesceState[entity.connectionId].buttonDisabled = true;
+          } else
+          // if state is reviving or quiesced and all links are enabled, button should say 'Quiesce' and be enabled. set state to enabled
+          if ((state === 'reviving' || state === 'quiesced') && (enabled === linkCount)) {
+            $scope.quiesceState[entity.connectionId].buttonText = 'Quiesce';
+            $scope.quiesceState[entity.connectionId].buttonDisabled = false;
+            $scope.quiesceState[entity.connectionId].state = 'enabled'
+          }
+        },
+        columnDefs: [{
+            field: 'host',
+            cellTemplate: "titleCellTemplate.html",
+            headerCellTemplate: 'titleHeaderCellTemplate.html',
+            displayName: 'Connection host'
+          }, {
+            field: 'user',
+            cellTemplate: "titleCellTemplate.html",
+            headerCellTemplate: 'titleHeaderCellTemplate.html',
+            displayName: 'User'
+          }, {
+            field: 'properties',
+            cellTemplate: "titleCellTemplate.html",
+            headerCellTemplate: 'titleHeaderCellTemplate.html',
+            displayName: 'Properties'
+          }
+          /*,
+                {
+                  cellClass: 'gridCellButton',
+                  cellTemplate: '<button title="{{quiesceText(row)}} the links" type="button" ng-class="quiesceClass(row)" class="btn" ng-click="$event.stopPropagation();quiesceConnection(row)" ng-disabled="quiesceDisabled(row)">{{quiesceText(row)}}</button>'
+                }*/
+        ]
+      };
+      $scope.quiesceLinkClass = function(row) {
+        var stateClassMap = {
+          enabled: 'btn-primary',
+          disabled: 'btn-danger'
+        }
+        return stateClassMap[row.entity.adminStatus]
+      }
+      $scope.quiesceLink = function(row) {
+        QDRService.quiesceLink(row.entity.nodeId, row.entity.name);
+      }
+      $scope.quiesceLinkDisabled = function(row) {
+        return (row.entity.operStatus !== 'up' && row.entity.operStatus !== 'down')
+      }
+      $scope.quiesceLinkText = function(row) {
+        return row.entity.operStatus === 'down' ? "Revive" : "Quiesce";
+      }
+      $scope.linkData = [];
+      $scope.linkDetails = {
+        data: 'linkData',
+        columnDefs: [{
+            field: 'adminStatus',
+            cellTemplate: "titleCellTemplate.html",
+            headerCellTemplate: 'titleHeaderCellTemplate.html',
+            displayName: 'Admin state'
+          }, {
+            field: 'operStatus',
+            cellTemplate: "titleCellTemplate.html",
+            headerCellTemplate: 'titleHeaderCellTemplate.html',
+            displayName: 'Oper state'
+          }, {
+            field: 'dir',
+            cellTemplate: "titleCellTemplate.html",
+            headerCellTemplate: 'titleHeaderCellTemplate.html',
+            displayName: 'dir'
+          }, {
+            field: 'owningAddr',
+            cellTemplate: "titleCellTemplate.html",
+            headerCellTemplate: 'titleHeaderCellTemplate.html',
+            displayName: 'Address'
+          }, {
+            field: 'deliveryCount',
+            displayName: 'Delivered',
+            headerCellTemplate: 'titleHeaderCellTemplate.html',
+            cellClass: 'grid-values'
+
+          }, {
+            field: 'uncounts',
+            displayName: 'Outstanding',
+            headerCellTemplate: 'titleHeaderCellTemplate.html',
+            cellClass: 'grid-values'
+          }
+          /*,
+                {
+                  cellClass: 'gridCellButton',
+                  cellTemplate: '<button title="{{quiesceLinkText(row)}} this link" type="button" ng-class="quiesceLinkClass(row)" class="btn" ng-click="quiesceLink(row)" ng-disabled="quiesceLinkDisabled(row)">{{quiesceLinkText(row)}}</button>'
+                }*/
+        ]
+      }
+
+      if (!QDRService.connected) {
+        // we are not connected. we probably got here from a bookmark or manual page reload
+        QDRService.redirectWhenConnected("topology");
+        return;
+      }
+      // we are currently connected. setup a handler to get notified if we are ever disconnected
+      QDRService.addDisconnectAction(function() {
+        QDRService.redirectWhenConnected("topology");
+        $scope.$apply();
+      })
+
+      var urlPrefix = $location.absUrl();
+      urlPrefix = urlPrefix.split("#")[0]
+      QDR.log.debug("started QDR.TopologyController with urlPrefix: " + urlPrefix);
+
+      $scope.addingNode = {
+        step: 0,
+        hasLink: false,
+        trigger: ''
+      };
+
+      $scope.cancel = function() {
+        $scope.addingNode.step = 0;
+      }
+      $scope.editNewRouter = function() {
+        $scope.addingNode.trigger = 'editNode';
+      }
+
+      var NewRouterName = "__NEW__";
+      // mouse event vars
+      var selected_node = null,
+        selected_link = null,
+        mousedown_link = null,
+        mousedown_node = null,
+        mouseover_node = null,
+        mouseup_node = null,
+        initial_mouse_down_position = null;
+
+      $scope.schema = "Not connected";
+
+      $scope.modes = [{
+          title: 'Topology view',
+          name: 'Diagram',
+          right: false
+        },
+        /* {title: 'Add a new router node', name: 'Add Router', right: true} */
+      ];
+      $scope.mode = "Diagram";
+      $scope.contextNode = null; // node that is associated with the current context menu
+
+      $scope.isModeActive = function(name) {
+        if ((name == 'Add Router' || name == 'Diagram') && $scope.addingNode.step > 0)
+          return true;
+        return ($scope.mode == name);
+      }
+      $scope.selectMode = function(name) {
+        if (name == "Add Router") {
+          name = 'Diagram';
+          if ($scope.addingNode.step > 0) {
+            $scope.addingNode.step = 0;
+          } else {
+            // start adding node mode
+            $scope.addingNode.step = 1;
+          }
+        } else {
+          $scope.addingNode.step = 0;
+        }
+
+        $scope.mode = name;
+      }
+      $scope.$watch(function() { return $scope.addingNode.step }, function(newValue, oldValue) {
+        if (newValue == 0 && oldValue != 0) {
+          // we are cancelling the add
+
+          // find the New node
+          nodes.every(function(n, i) {
+            // for the placeholder node, the key will be __internal__
+            if (QDRService.nameFromId(n.key) == '__internal__') {
+              var newLinks = links.filter(function(e, i) {
+                  return e.source.id == n.id || e.target.id == n.id;
+                })
+                // newLinks is an array of links to remove
+              newLinks.map(function(e) {
+                  links.splice(links.indexOf(e), 1);
+                })
+                // i is the index of the node to remove
+              nodes.splice(i, 1);
+              force.nodes(nodes).links(links).start();
+              restart(false);
+              return false; // stop looping
+            }
+            return true;
+          })
+          updateForm(Object.keys(QDRService.topology.nodeInfo())[0], 'router', 0);
+
+        } else if (newValue > 0) {
+          // we are starting the add mode
+          $scope.$broadcast('showAddForm')
+
+          resetMouseVars();
+          selected_node = null;
+          selected_link = null;
+          // add a new node
+          var id = "amqp:/_topo/0/__internal__/$management";
+          var x = radiusNormal * 4;
+          var y = x;;
+          if (newValue > 1) { // add at current mouse position
+            var offset = jQuery('#topology').offset();
+            x = mouseX - offset.left + $(document).scrollLeft();
+            y = mouseY - offset.top + $(document).scrollTop();;
+          }
+          QDRService.ensureAllEntities({entity: ".router"}, function () {
+            NewRouterName = genNewName();
+            nodes.push(aNode(id, NewRouterName, "inter-router", undefined, nodes.length, x, y, undefined, true));
+            force.nodes(nodes).links(links).start();
+            restart(false);
+          })
+        }
+      })
+      $scope.isRight = function(mode) {
+        return mode.right;
+      }
+
+      // for ng-grid that shows details for multiple consoles/clients
+      // generate unique name for router and containerName
+      var genNewName = function() {
+        var nodeInfo = QDRService.topology.nodeInfo();
+        var nameIndex = 1;
+        var newName = "R." + nameIndex;
+
+        var names = [];
+        for (key in nodeInfo) {
+          var node = nodeInfo[key];
+          var router = node['.router'];
+          var attrNames = router.attributeNames;
+          var name = QDRService.valFor(attrNames, router.results[0], 'routerId')
+          if (!name)
+            name = QDRService.valFor(attrNames, router.results[0], 'name')
+          names.push(name);
+        }
+
+        while (names.indexOf(newName) >= 0) {
+          newName = "R." + nameIndex++;
+        }
+        return newName;
+      }
+
+      $scope.$watch(function() {
+        return $scope.addingNode.trigger
+      }, function(newValue, oldValue) {
+        if (newValue == 'editNode') {
+          $scope.addingNode.trigger = "";
+          editNode();
+        }
+      })
+
+      function editNode() {
+        doAddDialog(NewRouterName);
+      };
+      $scope.reverseLink = function() {
+        if (!mousedown_link)
+          return;
+        var d = mousedown_link;
+        var tmp = d.left;
+        d.left = d.right;;
+        d.right = tmp;
+        restart(false);
+        tick();
+      }
+      $scope.removeLink = function() {
+        if (!mousedown_link)
+          return;
+        var d = mousedown_link;
+        links.every(function(l, i) {
+          if (l.source.id == d.source.id && l.target.id == d.target.id) {
+            links.splice(i, 1);
+            force.links(links).start();
+            return false; // exit the 'every' loop
+          }
+          return true;
+        });
+        restart(false);
+        tick();
+      }
+      var setNodesFixed = function (name, b) {
+        nodes.some(function (n) {
+          if (n.name === name) {
+            n.fixed = b;
+            return true;
+          }
+        })
+      }
+      $scope.setFixed = function(b) {
+        if ($scope.contextNode) {
+          $scope.contextNode.fixed = b;
+          setNodesFixed($scope.contextNode.name, b)
+          savePositions()
+        }
+        restart();
+      }
+      $scope.isFixed = function() {
+        if (!$scope.contextNode)
+          return false;
+        return ($scope.contextNode.fixed & 0b1);
+      }
+
+      var mouseX, mouseY;
+      // event handlers for popup context menu
+      $(document).mousemove(function(e) {
+        mouseX = e.clientX;
+        mouseY = e.clientY;
+      });
+      $(document).mousemove();
+      $(document).click(function(e) {
+        $scope.contextNode = null;
+        $(".contextMenu").fadeOut(200);
+      });
+
+      var radii = {
+        'inter-router': 25,
+        'normal': 15,
+        'on-demand': 15,
+        'route-container': 15,
+      };
+      var radius = 25;
+      var radiusNormal = 15;
+      var svg, lsvg;
+      var force;
+      var animate = false; // should the force graph organize itself when it is displayed
+      var path, circle;
+      var savedKeys = {};
+      var dblckickPos = [0, 0];
+      var width = 0;
+      var height = 0;
+
+      var getSizes = function() {
+        var legendWidth = 143;
+        var gap = 5;
+        var width = $('#topology').width() - gap - legendWidth;
+        var top = $('#topology').offset().top
+        var tpformHeight = $('#topologyForm').height()
+        var height = Math.max(window.innerHeight, tpformHeight + top) - top - gap;
+        if (width < 10) {
+          QDR.log.info("page width and height are abnormal w:" + width + " height:" + height)
+          return [0, 0];
+        }
+        return [width, height]
+      }
+      var resize = function() {
+        if (!svg)
+          return;
+        var sizes = getSizes();
+        width = sizes[0]
+        height = sizes[1]
+        if (width > 0) {
+          // set attrs and 'resume' force
+          svg.attr('width', width);
+          svg.attr('height', height);
+          force.size(sizes).resume();
+        }
+      }
+      window.addEventListener('resize', resize);
+      var sizes = getSizes()
+      width = sizes[0]
+      height = sizes[1]
+      if (width <= 0 || height <= 0)
+        return
+
+      // set up initial nodes and links
+      //  - nodes are known by 'id', not by index in array.
+      //  - selected edges are indicated on the node (as a bold red circle).
+      //  - links are always source < target; edge directions are set by 'left' and 'right'.
+      var nodes = [];
+      var links = [];
+
+      var aNode = function(id, name, nodeType, nodeInfo, nodeIndex, x, y, resultIndex, fixed, properties) {
+        for (var i=0; i<nodes.length; ++i) {
+          if (nodes[i].name === name)
+            return nodes[i]
+        }
+        properties = properties || {};
+        var routerId = QDRService.nameFromId(id)
+        return {
+          key: id,
+          name: name,
+          nodeType: nodeType,
+          properties: properties,
+          routerId: routerId,
+          x: x,
+          y: y,
+          id: nodeIndex,
+          resultIndex: resultIndex,
+          fixed: !!+fixed,
+          cls: name == NewRouterName ? 'temp' : ''
+        };
+      };
+
+
+      var initForm = function(attributes, results, entityType, formFields) {
+
+        while (formFields.length > 0) {
+          // remove all existing attributes
+          formFields.pop();
+        }
+
+        for (var i = 0; i < attributes.length; ++i) {
+          var name = attributes[i];
+          var val = results[i];
+          var desc = "";
+          if (entityType.attributes[name])
+            if (entityType.attributes[name].description)
+              desc = entityType.attributes[name].description;
+
+          formFields.push({
+            'attributeName': name,
+            'attributeValue': val,
+            'description': desc
+          });
+        }
+      }
+
+      var getLinkDir = function (id, connection, onode) {
+        var links = onode[".router.link"]
+        if (!links) {
+          return "unknown"
+        }
+        var inCount = 0, outCount = 0
+        links.results.forEach( function (linkResult) {
+          var link = QDRService.flatten(links.attributeNames, linkResult)
+          if (link.linkType === "endpoint" && link.connectionId === connection.identity)
+            if (link.linkDir === "in")
+              ++inCount
+            else
+              ++outCount
+        })
+        if (inCount > 0 && outCount > 0)
+          return "both"
+        if (inCount > 0)
+          return "in"
+        if (outCount > 0)
+          return "out"
+        return "unknown"
+      }
+
+      var savePositions = function () {
+        nodes.forEach( function (d) {
+          localStorage[d.name] = angular.toJson({
+            x: Math.round(d.x),
+            y: Math.round(d.y),
+            fixed: d.fixed ? 1 : 0,
+          });
+        })
+      }
+
+      var initializeNodes = function (nodeInfo) {
+        var nodeCount = Object.keys(nodeInfo).length
+        var yInit = 50;
+        nodes = []
+        for (var id in nodeInfo) {
+          var name = QDRService.nameFromId(id);
+          // if we have any new nodes, animate the force graph to position them
+          var position = angular.fromJson(localStorage[name]);
+          if (!angular.isDefined(position)) {
+            animate = true;
+            position = {
+              x: Math.round(width / 4 + ((width / 2) / nodeCount) * nodes.length),
+              y: Math.round(height / 2 + Math.sin(nodes.length / (Math.PI*2.0)) * height / 4),
+              fixed: false,
+            };
+            //QDR.log.debug("new node pos (" + position.x + ", " + position.y + ")")
+          }
+          if (position.y > height) {
+            position.y = 200 - yInit;
+            yInit *= -1
+          }
+          nodes.push(aNode(id, name, "inter-router", nodeInfo, nodes.length, position.x, position.y, undefined, position.fixed));
+          //QDR.log.debug("adding node " + nodes.length-1);
+        }
+      }
+
+      var initializeLinks = function (nodeInfo, unknowns) {
+        links = [];
+        var source = 0;
+        var client = 1.0;
+        for (var id in nodeInfo) {
+          var onode = nodeInfo[id];
+          var conns = onode['.connection'].results;
+          var attrs = onode['.connection'].attributeNames;
+          //QDR.log.debug("external client parent is " + parent);
+          var normalsParent = {}; // 1st normal node for this parent
+
+          for (var j = 0; j < conns.length; j++) {
+            var connection = QDRService.flatten(attrs, conns[j])
+            var role = connection.role
+            var properties = connection.properties || {};
+            var dir = connection.dir
+            if (role == "inter-router") {
+              var connId = connection.container
+              var target = getContainerIndex(connId, nodeInfo);
+              if (target >= 0) {
+                getLink(source, target, dir, "", source + "-" + target);
+              }
+            } else if (role == "normal" || role == "on-demand" || role === "route-container") {
+              // not a router, but an external client
+              var name = QDRService.nameFromId(id) + "." + connection.identity;
+
+              // if we have any new clients, animate the force graph to position them
+              var position = angular.fromJson(localStorage[name]);
+              if (!angular.isDefined(position)) {
+                animate = true;
+                position = {
+                  x: Math.round(nodes[source].x + 40 * Math.sin(client / (Math.PI * 2.0))),
+                  y: Math.round(nodes[source].y + 40 * Math.cos(client / (Math.PI * 2.0))),
+                  fixed: false
+                };
+                //QDR.log.debug("new client pos (" + position.x + ", " + position.y + ")")
+              }// else QDR.log.debug("using previous location")
+              if (position.y > height) {
+                position.y = Math.round(nodes[source].y + 40 + Math.cos(client / (Math.PI * 2.0)))
+              }
+              var node = aNode(id, name, role, nodeInfo, nodes.length, position.x, position.y, j, position.fixed, properties)
+              var nodeType = QDRService.isAConsole(properties, connection.identity, role, node.key) ? "console" : "client"
+              if (role === 'normal') {
+                var cdir = getLinkDir(id, connection, onode)
+                if (cdir !== 'unknown') {
+                  node.user = connection.user
+                  node.isEncrypted = connection.isEncrypted
+                  node.host = connection.host
+                  node.connectionId = connection.identity
+                  node.cdir = cdir
+                  // determine arrow direction by using the link directions
+                  if (!normalsParent[nodeType+cdir]) {
+                    normalsParent[nodeType+cdir] = node;
+                    nodes.push(node);
+                    node.normals = [node];
+                    // now add a link
+                    getLink(source, nodes.length - 1, cdir, "small", connection.name);
+                    client++;
+                  } else {
+                    normalsParent[nodeType+cdir].normals.push(node)
+                  }
+                } else {
+                  unknowns.push(node)
+                }
+              } else {
+                nodes.push(node)
+                  // now add a link
+                getLink(source, nodes.length - 1, dir, "small", connection.name);
+                client++;
+              }
+            }
+          }
+          source++;
+        }
+      }
+
+      // vary the following force graph attributes based on nodeCount
+      // <= 6 routers returns min, >= 80 routers returns max, interpolate linearly
+      var forceScale = function(nodeCount, min, max) {
+        var count = nodeCount
+        if (nodeCount < 6) count = 6
+        if (nodeCount > 80) count = 80
+        var x = d3.scale.linear()
+          .domain([6,80])
+          .range([min, max]);
+//QDR.log.debug("forceScale(" + nodeCount + ", " + min + ", " + max + "  returns " + x(count) + " " + x(nodeCount))
+        return x(count)
+      }
+      var linkDistance = function (d, nodeCount) {
+        if (d.target.nodeType === 'inter-router')
+          return forceScale(nodeCount, 150, 70)
+        return forceScale(nodeCount, 75, 40)
+      }
+      var charge = function (d, nodeCount) {
+        if (d.nodeType === 'inter-router')
+          return forceScale(nodeCount, -1800, -900)
+        return -900
+      }
+      var gravity = function (d, nodeCount) {
+        return forceScale(nodeCount, 0.0001, 0.1)
+      }
+
+      // initialize the nodes and links array from the QDRService.topology._nodeInfo object
+      var initForceGraph = function() {
+        nodes = [];
+        links = [];
+        var nodeInfo = QDRService.topology.nodeInfo();
+        var nodeCount = Object.keys(nodeInfo).length
+
+        var oldSelectedNode = selected_node
+        var oldMouseoverNode = mouseover_node
+        mouseover_node = null;
+        selected_node = null;
+        selected_link = null;
+
+        savePositions();
+        d3.select("#SVG_ID").remove();
+        svg = d3.select('#topology')
+          .append('svg')
+          .attr("id", "SVG_ID")
+          .attr('width', width)
+          .attr('height', height)
+          .on("contextmenu", function(d) {
+            if (d3.event.defaultPrevented)
+              return;
+            d3.event.preventDefault();
+
+            if ($scope.addingNode.step != 0)
+              return;
+            if (d3.select('#svg_context_menu').style('display') !== 'block')
+              $(document).click();
+            d3.select('#svg_context_menu')
+              .style('left', (mouseX + $(document).scrollLeft()) + "px")
+              .style('top', (mouseY + $(document).scrollTop()) + "px")
+              .style('display', 'block');
+          })
+          .on('click', function(d) {
+            removeCrosssection()
+          });
+
+        $(document).keyup(function(e) {
+          if (e.keyCode === 27) {
+            removeCrosssection()
+          }
+        });
+
+        // the legend
+        d3.select("#svg_legend svg").remove();
+        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
+        mousedown_link = null;
+        mousedown_node = null;
+        mouseup_node = null;
+
+        // initialize the list of nodes
+        initializeNodes(nodeInfo)
+        savePositions()
+
+        // initialize the list of links
+        var unknowns = []
+        initializeLinks(nodeInfo, unknowns)
+        $scope.schema = QDRService.schema;
+        // init D3 force layout
+        force = d3.layout.force()
+          .nodes(nodes)
+          .links(links)
+          .size([width, height])
+          .linkDistance(function(d) { return linkDistance(d, nodeCount) })
+          .charge(function(d) { return charge(d, nodeCount) })
+          .friction(.10)
+          .gravity(function(d) { return gravity(d, nodeCount) })
+          .on('tick', tick)
+          .on('end', function () {savePositions()})
+          .start()
+
+        svg.append("svg:defs").selectAll('marker')
+          .data(["end-arrow", "end-arrow-selected", "end-arrow-small", "end-arrow-highlighted"]) // Different link/path types can be defined here
+          .enter().append("svg:marker") // This section adds in the arrows
+          .attr("id", String)
+          .attr("viewBox", "0 -5 10 10")
+          .attr("markerWidth", 4)
+          .attr("markerHeight", 4)
+          .attr("orient", "auto")
+          .classed("small", function (d) {return d.indexOf('small') > -1})
+          .append("svg:path")
+            .attr('d', 'M 0 -5 L 10 0 L 0 5 z')
+
+        svg.append("svg:defs").selectAll('marker')
+          .data(["start-arrow", "start-arrow-selected", "start-arrow-small", "start-arrow-highlighted"]) // Different link/path types can be defined here
+          .enter().append("svg:marker") // This section adds in the arrows
+          .attr("id", String)
+          .attr("viewBox", "0 -5 10 10")
+          .attr("refX", 5)
+          .attr("markerWidth", 4)
+          .attr("markerHeight", 4)
+          .attr("orient", "auto")
+          .append("svg:path")
+            .attr('d', 'M 10 -5 L 0 0 L 10 5 z');
+
+        var grad = svg.append("svg:defs").append("linearGradient")
+          .attr("id", "half-circle")
+          .attr("x1", "0%")
+          .attr("x2", "0%")
+          .attr("y1", "100%")
+          .attr("y2", "0%");
+        grad.append("stop").attr("offset", "50%").style("stop-color", "#C0F0C0");
+        grad.append("stop").attr("offset", "50%").style("stop-color", "#F0F000");
+
+        // handles to link and node element groups
+        path = svg.append('svg:g').selectAll('path'),
+          circle = svg.append('svg:g').selectAll('g');
+
+        // app starts here
+        restart(false);
+        force.start();
+        if (oldSelectedNode) {
+          d3.selectAll('circle.inter-router').classed("selected", function (d) {
+            if (d.key === oldSelectedNode.key) {
+              selected_node = d;
+              return true
+            }
+            return false
+          })
+        }
+        if (oldMouseoverNode && selected_node) {
+          d3.selectAll('circle.inter-router').each(function (d) {
+            if (d.key === oldMouseoverNode.key) {
+              mouseover_node = d
+              QDRService.ensureAllEntities([{entity: ".router.node", attrs: ["id","nextHop"]}], function () {
+                nextHop(selected_node, d);
+                restart();
+              })
+            }
+          })
+        }
+        setTimeout(function () {
+          updateForm(Object.keys(QDRService.topology.nodeInfo())[0], 'router', 0);
+        })
+
+        // if any clients don't yet have link directions, get the links for those nodes and restart the graph
+        if (unknowns.length > 0)
+          setTimeout(resolveUnknowns, 10, nodeInfo, unknowns)
+
+        var continueForce = function (extra) {
+          if (extra > 0) {
+            --extra
+            force.start()
+            setTimeout(continueForce, 100, extra)
+          }
+        }
+        continueForce(forceScale(nodeCount, 20, 200))  // give graph time to settle down
+      }
+
+      var resolveUnknowns = function (nodeInfo, unknowns) {
+        var unknownNodes = {}
+        // collapse the unknown node.keys using an object
+        for (var i=0; i<unknowns.length; ++i) {
+          unknownNodes[unknowns[i].key] = 1
+        }
+        unknownNodes = Object.keys(unknownNodes)
+          //QDR.log.debug("there were " + unknownNodes.length + " connections with normal links")
+          //console.dump(unknownNodes)
+
+        QDRService.ensureEntities(unknownNodes, {entity: ".router.link", attrs: ["linkType","connectionId","linkDir"], force: true}, function () {
+          initializeLinks(nodeInfo, [])
+          animate = true;
+          force.nodes(nodes).links(links).start();
+          restart(false);
+        })
+      }
+
+      function updateForm(key, entity, resultIndex) {
+        var nodeInfo = QDRService.topology.nodeInfo();
+        if (key in nodeInfo) {
+          QDRService.ensureEntities(key, [
+            {entity: '.'+entity},
+            {entity: '.listener', attrs: ["role", "port"]}], function () {
+            var onode = nodeInfo[key]
+            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)
+            })
+
+            // 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,
+                  description: 'The port on which this router is listening for connections'
+                });
+              }
+            }
+            $scope.$broadcast('showEntityForm', {
+              entity: entity,
+              attributes: attributes
+            })
+            if (!$scope.$$phase) $scope.$apply()
+          })
+        }
+      }
+
+      function getContainerIndex(_id, nodeInfo) {
+        var nodeIndex = 0;
+        for (var id in nodeInfo) {
+          if (QDRService.nameFromId(id) === _id)
+            return nodeIndex;
+          ++nodeIndex;
+        }
+        return -1;
+      }
+
+      function getLink(_source, _target, dir, cls, uid) {
+        for (var i = 0; i < links.length; i++) {
+          var s = links[i].source,
+              t = links[i].target;
+          if (typeof links[i].source == "object") {
+            s = s.id;
+            t = t.id;
+          }
+          if (s == _source && t == _target) {
+            return i;
+          }
+          // same link, just reversed
+          if (s == _target && t == _source) {
+            return -i;
+          }
+        }
+
+        //QDR.log.debug("creating new link (" + (links.length) + ") between " + nodes[_source].name + " and " + nodes[_target].name);
+        var link = {
+          source: _source,
+          target: _target,
+          left: dir != "out",
+          right: (dir == "out" || dir == "both"),
+          cls: cls,
+          uid: uid,
+        };
+        return links.push(link) - 1;
+      }
+
+
+      function resetMouseVars() {
+        mousedown_node = null;
+        mouseover_node = null;
+        mouseup_node = null;
+        mousedown_link = null;
+      }
+
+      // update force layout (called automatically each iteration)
+      function tick() {
+        circle.attr('transform', function(d) {
+          var cradius;
+          if (d.nodeType == "inter-router") {
+            cradius = d.left ? radius + 8 : radius;
+          } else {
+            cradius = d.left ? radiusNormal + 18 : radiusNormal;
+          }
+          d.x = Math.max(d.x, radiusNormal * 2);
+          d.y = Math.max(d.y, radiusNormal * 2);
+          d.x = Math.max(0, Math.min(width - cradius, d.x))
+          d.y = Math.max(0, Math.min(height - cradius, d.y))
+          return 'translate(' + d.x + ',' + d.y + ')';
+        });
+
+        // draw directed edges with proper padding from node centers
+        path.attr('d', function(d) {
+          //QDR.log.debug("in tick for d");
+          //console.dump(d);
+          var sourcePadding, targetPadding, r;
+
+          if (d.target.nodeType == "inter-router") {
+            r = radius;
+            //                       right arrow  left line start
+            sourcePadding = d.left ? radius + 8 : radius;
+            //                      left arrow      right line start
+            targetPadding = d.right ? radius + 16 : radius;
+          } else {
+            r = radiusNormal - 18;
+            sourcePadding = d.left ? radiusNormal + 18 : radiusNormal;
+            targetPadding = d.right ? radiusNormal + 16 : radiusNormal;
+          }
+          var dtx = Math.max(targetPadding, Math.min(width - r, d.target.x)),
+            dty = Math.max(targetPadding, Math.min(height - r, d.target.y)),
+            dsx = Math.max(sourcePadding, Math.min(width - r, d.source.x)),
+            dsy = Math.max(sourcePadding, Math.min(height - r, d.source.y));
+
+          var deltaX = dtx - dsx,
+            deltaY = dty - dsy,
+            dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
+            normX = deltaX / dist,
+            normY = deltaY / dist;
+          var sourceX = dsx + (sourcePadding * normX),
+            sourceY = dsy + (sourcePadding * normY),
+            targetX = dtx - (targetPadding * normX),
+            targetY = dty - (targetPadding * normY);
+          sourceX = Math.max(0, Math.min(width, sourceX))
+          sourceY = Math.max(0, Math.min(width, sourceY))
+          targetX = Math.max(0, Math.min(width, targetX))
+          targetY = Math.max(0, Math.min(width, targetY))
+
+          return 'M' + sourceX + ',' + sourceY + 'L' + targetX + ',' + targetY;
+        });
+
+        if (!animate) {
+          animate = true;
+          force.stop();
+        }
+      }
+
+      // highlight the paths between the selected node and the hovered node
+      function findNextHopNode(from, d) {
+        // d is the node that the mouse is over
+        // from is the selected_node ....
+        if (!from)
+          return null;
+
+        if (from == d)
+          return selected_node;
+
+        //QDR.log.debug("finding nextHop from: " + from.name + " to " + d.name);
+        var sInfo = QDRService.topology.nodeInfo()[from.key];
+
+        if (!sInfo) {
+          QDR.log.warn("unable to find topology node info for " + from.key);
+          return null;
+        }
+
+        // find the hovered name in the selected name's .router.node results
+        if (!sInfo['.router.node'])
+          return null;
+        var aAr = sInfo['.router.node'].attributeNames;
+        var vAr = sInfo['.router.node'].results;
+        for (var hIdx = 0; hIdx < vAr.length; ++hIdx) {
+          var addrT = QDRService.valFor(aAr, vAr[hIdx], "id");
+          if (addrT == d.name) {
+            //QDR.log.debug("found " + d.name + " at " + hIdx);
+            var nextHop = QDRService.valFor(aAr, vAr[hIdx], "nextHop");
+            //QDR.log.debug("nextHop was " + nextHop);
+            return (nextHop == null) ? nodeFor(addrT) : nodeFor(nextHop);
+          }
+        }
+        return null;
+      }
+
+      function nodeFor(name) {
+        for (var i = 0; i < nodes.length; ++i) {
+          if (nodes[i].name == name)
+            return nodes[i];
+        }
+        return null;
+      }
+
+      function linkFor(source, target) {
+        for (var i = 0; i < links.length; ++i) {
+          if ((links[i].source == source) && (links[i].target == target))
+            return links[i];
+          if ((links[i].source == target) && (links[i].target == source))
+            return links[i];
+        }
+        // the selected node was a client/broker
+        //QDR.log.debug("failed to find a link between ");
+        //console.dump(source);
+        //QDR.log.debug(" and ");
+        //console.dump(target);
+        return null;
+      }
+
+      function clearPopups() {
+        d3.select("#crosssection").style("display", "none");
+        $('.hastip').empty();
+        d3.select("#multiple_details").style("display", "none")
+        d3.select("#link_details").style("display", "none")
+        d3.select('#node_context_menu').style('display', 'none');
+
+      }
+
+      function removeCrosssection() {
+        setTimeout(function() {
+          d3.select("[id^=tooltipsy]").remove()
+          $('.hastip').empty();
+        }, 1010);
+        d3.select("#crosssection svg g").transition()
+          .duration(1000)
+          .attr("transform", "scale(0)")
+            .style("opacity", 0)
+            .each("end", function (d) {
+                d3.select("#crosssection svg").remove();
+                d3.select("#crosssection").style("display","none");
+            });
+        d3.select("#multiple_details").transition()
+          .duration(500)
+          .style("opacity", 0)
+          .each("end", function(d) {
+            d3.select("#multiple_details").style("display", "none")
+            stopUpdateConnectionsGrid();
+          })
+        hideLinkDetails();
+      }
+
+      function hideLinkDetails() {
+        d3.select("#link_details").transition()
+          .duration(500)
+          .style("opacity", 0)
+          .each("end", function(d) {
+            d3.select("#link_details").style("display", "none")
+          })
+      }
+
+      function clerAllHighlights() {
+        for (var i = 0; i < links.length; ++i) {
+          links[i]['highlighted'] = false;
+        }
+        for (var i=0; i<nodes.length; ++i) {
+          nodes[i]['highlighted'] = false;
+        }
+      }
+      // takes the nodes and links array of objects and adds svg elements for everything that hasn't already
+      // been added
+      function restart(start) {
+        circle.call(force.drag);
+
+        // path (link) group
+        path = path.data(links, function(d) {return d.uid});
+
+        // update existing links
+        path.classed('selected', function(d) {
+            return d === selected_link;
+          })
+          .classed('highlighted', function(d) {
+            return d.highlighted;
+          })
+          .classed('temp', function(d) {
+            return d.cls == 'temp';
+          })
+          .attr('marker-start', function(d) {
+            var sel = d === selected_link ? '-selected' : (d.cls === 'small' ? '-small' : '');
+            if (d.highlighted)
+              sel = "-highlighted"
+            return d.left ? 'url(' + urlPrefix + '#start-arrow' + sel + ')' : '';
+          })
+          .attr('marker-end', function(d) {
+            var sel = d === selected_link ? '-selected' : (d.cls === 'small' ? '-small' : '');
+            if (d.highlighted)
+              sel = "-highlighted"
+            return d.right ? 'url(' + urlPrefix + '#end-arrow' + sel + ')' : '';
+          })
+
+
+        // add new links. if links[] is longer than the existing paths, add a new path for each new element
+        path.enter().append('svg:path')
+          .attr('class', 'link')
+          .attr('marker-start', function(d) {
+            var sel = d === selected_link ? '-selected' : (d.cls === 'small' ? '-small' : '');
+            return d.left ? 'url(' + urlPrefix + '#start-arrow' + sel + ')' : '';
+          })
+          .attr('marker-end', function(d) {
+            var sel = d === selected_link ? '-selected' : (d.cls === 'small' ? '-small' : '');
+            return d.right ? 'url(' + urlPrefix + '#end-arrow' + sel + ')' : '';
+          })
+          .classed('temp', function(d) {
+            return d.cls == 'temp';
+          })
+          .classed('small', function(d) {
+            return d.cls == 'small';
+          })
+          .on('mouseover', function(d) { // mouse over a path
+            if ($scope.addingNode.step > 0) {
+              if (d.cls == 'temp') {
+                d3.select(this).classed('over', true);
+              }
+              return;
+            }
+            //QDR.log.debug("showing connections form");
+            var resultIndex = 0; // the connection to use
+            var left = d.left ? d.target : d.source;
+            // right is the node that the arrow points to, left is the other node
+            var right = d.left ? d.source : d.target;
+            var onode = QDRService.topology.nodeInfo()[left.key];
+            // loop through all the connections for left, and find the one for right
+            if (!onode || !onode['.connection'])
+              return;
+            // update the info dialog for the link the mouse is over
+            if (!selected_node && !selected_link) {
+              for (resultIndex = 0; resultIndex < onode['.connection'].results.length; ++resultIndex) {
+                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.routerId) {
+                  break;
+                }
+              }
+              // did not find connection. this is a connection to a non-interrouter node
+              if (resultIndex === onode['.connection'].results.length) {
+                // use the non-interrouter node's connection info
+                left = d.target;
+                resultIndex = left.resultIndex;
+              }
+              if (resultIndex)
+                updateForm(left.key, 'connection', resultIndex);
+            }
+
+            mousedown_link = d;
+            selected_link = mousedown_link;
+            restart();
+          })
+          .on('mouseout', function(d) { // mouse out of a path
+            if ($scope.addingNode.step > 0) {
+              if (d.cls == 'temp') {
+                d3.select(this).classed('over', false);
+              }
+              return;
+            }
+            //QDR.log.debug("showing connections form");
+            selected_link = null;
+            restart();
+          })
+          .on("contextmenu", function(d) {  // right click a path
+            $(document).click();
+            d3.event.preventDefault();
+            if (d.cls !== "temp")
+              return;
+
+            mousedown_link = d;
+            d3.select('#link_context_menu')
+              .style('left', (mouseX + $(document).scrollLeft()) + "px")
+              .style('top', (mouseY + $(document).scrollTop()) + "px")
+              .style('display', 'block');
+          })
+          // left click a path
+          .on("click", function (d) {
+            var clickPos = d3.mouse(this);
+            d3.event.stopPropagation();
+            clearPopups();
+            var showCrossSection = function() {
+              var diameter = 400;
+              var format = d3.format(",d");
+              var pack = d3.layout.pack()
+                  .size([diameter - 4, diameter - 4])
+                  .padding(-10)
+                  .value(function(d) { return d.size; });
+
+              d3.select("#crosssection svg").remove();
+              var svg = d3.select("#crosssection").append("svg")
+                  .attr("width", diameter)
+                  .attr("height", diameter)
+              var svgg = svg.append("g")
+                  .attr("transform", "translate(2,2)");
+
+              var root = {
+                name: " Links between " + d.source.name + " and " + d.target.name,
+                children: []
+              }
+              var nodeInfo = QDRService.topology.nodeInfo();
+              var connections = nodeInfo[d.source.key]['.connection'];
+              var containerIndex = connections.attributeNames.indexOf('container');
+              connections.results.some ( function (connection) {
+                if (connection[containerIndex] == d.target.routerId) {
+                  root.attributeNames = connections.attributeNames;
+                  root.obj = connection;
+                  root.desc = "Connection";
+                  return true;    // stop looping after 1 match
+                }
+                return false;
+              })
+
+              // find router.links where link.remoteContainer is d.source.name
+              var links = nodeInfo[d.source.key]['.router.link'];
+              var identityIndex = connections.attributeNames.indexOf('identity')
+              var roleIndex = connections.attributeNames.indexOf('role')
+              var connectionIdIndex = links.attributeNames.indexOf('connectionId');
+              var linkTypeIndex = links.attributeNames.indexOf('linkType');
+              var nameIndex = links.attributeNames.indexOf('name');
+              var linkDirIndex = links.attributeNames.indexOf('linkDir');
+
+              if (roleIndex < 0 || identityIndex < 0 || connectionIdIndex < 0
+                || linkTypeIndex < 0 || nameIndex < 0 || linkDirIndex < 0)
+                return;
+              links.results.forEach ( function (link) {
+                if (root.obj && link[connectionIdIndex] == root.obj[identityIndex] && link[linkTypeIndex] == root.obj[roleIndex])
+                  root.children.push (
+                    { name: " " + link[linkDirIndex] + " ",
+                    size: 100,
+                    obj: link,
+                    desc: "Link",
+                    attributeNames: links.attributeNames
+                  })
+              })
+              if (root.children.length == 0)
+                return;
+              var node = svgg.datum(root).selectAll(".node")
+                .data(pack.nodes)
+                .enter().append("g")
+                .attr("class", function(d) { return d.children ? "parent node hastip" : "leaf node hastip"; })
+                .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")" + (!d.children ? "scale(0.9)" : ""); })
+                .attr("title", function (d) {
+                  var title = "<h4>" + d.desc + "</h4><table class='tiptable'><tbody>";
+                  if (d.attributeNames)
+                    d.attributeNames.forEach( function (n, i) {
+                      title += "<tr><td>" + n + "</td><td>";
+                      title += d.obj[i] != null ? d.obj[i] : '';
+                      title += '</td></tr>';
+                    })
+                  title += "</tbody></table>"
+                  return title
+                })
+              node.append("circle")
+                .attr("r", function(d) { return d.r; });
+
+  //          node.filter(function(d) { return !d.children; }).append("text")
+              node.append("text")
+                .attr("dy", function (d) { return d.children ? "-10em" : ".5em"})
+                .style("text-anchor", "middle")
+                .text(function(d) {
+                    return d.name.substring(0, d.r / 3);
+                });
+              $('.hastip').tooltipsy({ alignTo: 'cursor'});
+              svgg.attr("transform", "translate(2,2) scale(0.01)")
+
+              var bounds = $("#topology").position()
+              d3.select("#crosssection")
+                .style("display", "block")
+                .style("left", (clickPos[0] + bounds.left) + "px")
+                .style("top", (clickPos[1] + bounds.top) + "px")
+
+              svgg.transition()
+                .attr("transform", "translate(2,2) scale(1)")
+                .each("end", function ()  {
+                  d3.selectAll("#crosssection g.leaf text").attr("dy", ".3em")
+                })
+            }
+            QDRService.ensureEntities(d.source.key, {entity: '.router.link', force: true}, showCrossSection)
+          })
+        // remove old links
+        path.exit().remove();
+
+
+        // circle (node) group
+        // nodes are known by id
+        circle = circle.data(nodes, function(d) {
+          return d.name;
+        });
+
+        // update existing nodes visual states
+        circle.selectAll('circle')
+          .classed('highlighted', function(d) {
+            return d.highlighted;
+          })
+          .classed('selected', function(d) {
+            return (d === selected_node)
+          })
+          .classed('fixed', function(d) {
+            return d.fixed
+          })
+
+        // 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')
+          .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]
+            })
+            .attr('fill', function (d) {
+              if (d.cdir === 'both' && !QDRService.isConsole(d)) {
+                return 'url(' + urlPrefix + '#half-circle)'
+              }
+              return null;
+            })
+            .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('in', function(d) {
+              return d.cdir == 'in'
+            })
+            .classed('out', function(d) {
+              return d.cdir == 'out'
+            })
+            .classed('inout', function(d) {
+              return d.cdir == 'both'
+            })
+            .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 QDRService.isConsole(d)
+            })
+            .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) {  // mouseover a circle
+            if ($scope.addingNode.step > 0) {
+              d3.select(this).attr('transform', 'scale(1.1)');
+              return;
+            }
+            if (!selected_node) {
+              if (d.nodeType === 'inter-router') {
+                //QDR.log.debug("showing general form");
+                updateForm(d.key, 'router', 0);
+              } else if (d.nodeType === 'normal' || d.nodeType === 'on-demand') {
+                //QDR.log.debug("showing connections form");
+                updateForm(d.key, 'connection', d.resultIndex);
+              }
+            }
+
+            if (d === mousedown_node)
+              return;
+            //if (d === selected_node)
+            //    return;
+            // enlarge target node
+            d3.select(this).attr('transform', 'scale(1.1)');
+            // highlight the next-hop route from the selected node to this node
+            mousedown_node = null;
+
+            if (!selected_node) {
+              return;
+            }
+            clerAllHighlights()
+            // we need .router.node info to highlight hops
+            QDRService.ensureAllEntities([{entity: ".router.node", attrs: ["id","nextHop"]}], function () {
+              mouseover_node = d  // save this node in case the topology changes so we can restore the highlights
+              nextHop(selected_node, d);
+              restart();
+            })
+          })
+          .on('mouseout', function(d) { // mouse out for a circle
+            // unenlarge target node
+            d3.select(this).attr('transform', '');
+            clerAllHighlights()
+            mouseover_node = null;
+            restart();
+          })
+          .on('mousedown', function(d) { // mouse down for circle
+            if (d3.event.button !== 0) { // ignore all but left button
+              return;
+            }
+            mousedown_node = d;
+            // mouse position relative to svg
+            initial_mouse_down_position = d3.mouse(this.parentElement.parentElement.parentElement).slice();
+          })
+          .on('mouseup', function(d) {  // mouse up for circle
+            if (!mousedown_node)
+              return;
+
+            selected_link = null;
+            // unenlarge target node
+            d3.select(this).attr('transform', '');
+
+            // check for drag
+            mouseup_node = d;
+            var mySvg = this.parentElement.parentElement.parentElement;
+            // if we dragged the node, make it fixed
+            var cur_mouse = d3.mouse(mySvg);
+            if (cur_mouse[0] != initial_mouse_down_position[0] ||
+              cur_mouse[1] != initial_mouse_down_position[1]) {
+              console.log("mouse pos changed. making this node fixed")
+              d.fixed = true;
+              setNodesFixed(d.name, true)
+              resetMouseVars();
+              restart();
+              return;
+            }
+
+            // we didn't drag, we just clicked on the node
+            if ($scope.addingNode.step > 0) {
+              if (d.nodeType !== 'inter-router')
+                return;
+              if (QDRService.nameFromId(d.key) == '__internal__')
+                return;
+
+              // add a link from the clicked node to the new node
+              getLink(d.id, nodes.length - 1, "in", "temp", "__internal__");
+              $scope.addingNode.hasLink = true;
+              if (!$scope.$$phase) $scope.$apply()
+                // add new elements to the svg
+              force.links(links).start();
+              restart();
+              return;
+
+            }
+
+            // if this node was selected, unselect it
+            if (mousedown_node === selected_node) {
+              selected_node = null;
+            } else {
+              if (d.nodeType !== 'normal' && d.nodeType !== 'on-demand')
+                selected_node = mousedown_node;
+            }
+            clerAllHighlights()
+            mousedown_node = null;
+            if (!$scope.$$phase) $scope.$apply()
+            restart(false);
+
+          })
+          .on("dblclick", function(d) { // circle
+            if (d.fixed) {
+              d.fixed = false
+              setNodesFixed(d.name, false)
+              restart() // redraw the node without a dashed line
+              force.start(); // let the nodes move to a new position
+            }
+            if (QDRService.nameFromId(d.key) == '__internal__') {
+              editNode();
+              if (!$scope.$$phase) $scope.$apply()
+            }
+          })
+          .on("contextmenu", function(d) {  // circle
+            $(document).click();
+            d3.event.preventDefault();
+            $scope.contextNode = d;
+            if (!$scope.$$phase) $scope.$apply() // we just changed a scope valiable during an async event
+            d3.select('#node_context_menu')
+              .style('left', (mouseX + $(document).scrollLeft()) + "px")
+              .style('top', (mouseY + $(document).scrollTop()) + "px")
+              .style('display', 'block');
+
+          })
+          .on("click", function(d) {  // circle
+            if (!mouseup_node)
+              return;
+            // clicked on a circle
+            clearPopups();
+            if (!d.normals) {
+              // circle was a router or a broker
+              if (QDRService.isArtemis(d)) {
+                var artemisPath = '/jmx/attributes?tab=artemis&con=Artemis'
+                if (QDR.isStandalone)
+                  window.location = $location.protocol() + '://localhost:8161/hawtio' + artemisPath
+                else
+                  $location.path(artemisPath)
+              }
+              return;
+            }
+            clickPos = d3.mouse(this);
+            d3.event.stopPropagation();
+            startUpdateConnectionsGrid(d);
+          })
+        //.attr("transform", function (d) {return "scale(" + (d.nodeType === 'normal' ? .5 : 1) + ")"})
+        //.transition().duration(function (d) {return d.nodeType === 'normal' ? 3000 : 0}).ease("elastic").attr("transform", "scale(1)")
+
+        var appendContent = function(g) {
+          // show node IDs
+          g.append('svg:text')
+            .attr('x', 0)
+            .attr('y', function(d) {
+              var y = 7;
+              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 QDRService.isConsole(d)
+            })
+            .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 (QDRService.isConsole(d)) {
+                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 (QDRService.isConsole(d)) {
+              return 'Dispatch console' + x
+            }
+            if (d.properties.product == 'qpid-cpp') {
+              return 'Broker - qpid-cpp' + x
+            }
+            if (QDRService.isArtemis(d)) {
+              return 'Broker - Artemis' + x
+            }
+            if (d.cdir === 'in')
+              return 'Sender' + x
+            if (d.cdir === 'out')
+              return 'Receiver' + x
+            if (d.cdir === 'both')
+              return 'Sender/Receiver' + 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();
+        var multiples = svg.selectAll('.multiple')
+        multiples.each(function(d) {
+          d.normals.forEach(function(n, i) {
+            if (i < d.normals.length - 1 && i < 3) // only show a few shadow circles
+              this.insert('svg:circle', ":first-child")
+              .attr('class', 'subcircle node')
+              .attr('r', 15 - i)
+              .attr('transform', "translate(" + 4 * (i + 1) + ", 0)")
+          }, d3.select(this))
+        })
+
+        // dynamically create the legend based on which node types are present
+        // the legend
+        d3.select("#svg_legend svg").remove();
+        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');
+        var legendNodes = [];
+        legendNodes.push(aNode("Router", "", "inter-router", undefined, 0, 0, 0, 0, false, {}))
+
+        if (!svg.selectAll('circle.console').empty()) {
+          legendNodes.push(aNode("Console", "", "normal", undefined, 1, 0, 0, 0, false, {
+            console_identifier: 'Dispatch console'
+          }))
+        }
+        if (!svg.selectAll('circle.client.in').empty()) {
+          var node = aNode("Sender", "", "normal", undefined, 2, 0, 0, 0, false, {})
+          node.cdir = "in"
+          legendNodes.push(node)
+        }
+        if (!svg.selectAll('circle.client.out').empty()) {
+          var node = aNode("Receiver", "", "normal", undefined, 3, 0, 0, 0, false, {})
+          node.cdir = "out"
+          legendNodes.push(node)
+        }
+        if (!svg.selectAll('circle.client.inout').empty()) {
+          var node = aNode("Sender/Receiver", "", "normal", undefined, 4, 0, 0, 0, false, {})
+          node.cdir = "both"
+          legendNodes.push(node)
+        }
+        if (!svg.selectAll('circle.qpid-cpp').empty()) {
+          legendNodes.push(aNode("Qpid broker", "", "on-demand", undefined, 5, 0, 0, 0, false, {
+            product: 'qpid-cpp'
+          }))
+        }
+        if (!svg.selectAll('circle.artemis').empty()) {
+          legendNodes.push(aNode("Artemis broker", "", "route-container", undefined, 6, 0, 0, 0, false, {product: 'apache-activemq-artemis'}))
+        }
+        lsvg = lsvg.data(legendNodes, function(d) {
+          return d.key;
+        });
+        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')
+        if (svgEl) {
+          var bb;
+          // firefox can throw an exception on getBBox on an svg element
+          try {
+            bb = svgEl.getBBox();
+          } catch (e) {
+            bb = {
+              y: 0,
+              height: 200,
+              x: 0,
+              width: 200
+            }
+          }
+          svgEl.style.height = (bb.y + bb.height) + 'px';
+          svgEl.style.width = (bb.x + bb.width) + 'px';
+        }
+
+        if (!mousedown_node || !selected_node)
+          return;
+
+        if (!start)
+          return;
+        // set the graph in motion
+        //QDR.log.debug("mousedown_node is " + mousedown_node);
+        force.start();
+
+      }
+
+      var startUpdateConnectionsGrid = function(d) {
+        // called after each topology update
+        var extendConnections = function() {
+          // force a fetch of the links for this node
+          QDRService.ensureEntities(d.key, {entity: ".router.link", force: true}, function () {
+            // the links for this node are now available
+            $scope.multiData = []
+            var normals = d.normals;
+            // find updated normals for d
+            d3.selectAll('.normal')
+              .each(function(newd) {
+                if (newd.id == d.id && newd.name == d.name) {
+                  normals = newd.normals;
+                }
+              });
+            if (normals) {
+              normals.forEach(function(n) {
+                var nodeInfo = QDRService.topology.nodeInfo();
+                var links = nodeInfo[n.key]['.router.link'];
+                var linkTypeIndex = links.attributeNames.indexOf('linkType');
+                var connectionIdIndex = links.attributeNames.indexOf('connectionId');
+                n.linkData = [];
+                links.results.forEach(function(link) {
+                  if (link[linkTypeIndex] === 'endpoint' && link[connectionIdIndex] === n.connectionId) {
+                    var l = {};
+                    l.owningAddr = QDRService.valFor(links.attributeNames, link, 'owningAddr');
+                    l.dir = QDRService.valFor(links.attributeNames, link, 'linkDir');
+                    if (l.owningAddr && l.owningAddr.length > 2)
+                      if (l.owningAddr[0] === 'M')
+                        l.owningAddr = l.owningAddr.substr(2)
+                      else
+                        l.owningAddr = l.owningAddr.substr(1)
+
+                    l.deliveryCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'deliveryCount'));
+                    l.uncounts = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'undeliveredCount') +
+                        QDRService.valFor(links.attributeNames, link, 'unsettledCount'))
+                      //l.undeliveredCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'undeliveredCount'));
+                      //l.unsettledCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'unsettledCount'));
+                    l.adminStatus = QDRService.valFor(links.attributeNames, link, 'adminStatus');
+                    l.operStatus = QDRService.valFor(links.attributeNames, link, 'operStatus');
+                    l.identity = QDRService.valFor(links.attributeNames, link, 'identity')
+                    l.connectionId = QDRService.valFor(links.attributeNames, link, 'connectionId')
+                    l.nodeId = n.key
+                    l.type = QDRService.valFor(links.attributeNames, link, 'type')
+                    l.name = QDRService.valFor(links.attributeNames, link, 'name')
+
+                    // TODO: remove this fake quiescing/reviving logic when the routers do the work
+                    initConnState(n.connectionId)
+                    if ($scope.quiesceState[n.connectionId].linkStates[l.identity])
+                      l.adminStatus = $scope.quiesceState[n.connectionId].linkStates[l.identity];
+                    if ($scope.quiesceState[n.connectionId].state == 'quiescing') {
+                      if (l.adminStatus === 'enabled') {
+                        // 25% chance of switching
+                        var chance = Math.floor(Math.random() * 2);
+                        if (chance == 1) {
+                          l.adminStatus = 'disabled';
+                          $scope.quiesceState[n.connectionId].linkStates[l.identity] = 'disabled';
+                        }
+                      }
+                    }
+                    if ($scope.quiesceState[n.connectionId].state == 'reviving') {
+                      if (l.adminStatus === 'disabled') {
+                        // 25% chance of switching
+                        var chance = Math.floor(Math.random() * 2);
+                        if (chance == 1) {
+                          l.adminStatus = 'enabled';
+                          $scope.quiesceState[n.connectionId].linkStates[l.identity] = 'enabled';
+                        }
+                      }
+                    }
+                    QDR.log.debug("pushing link state for " + l.owningAddr + " status: " + l.adminStatus)
+
+                    n.linkData.push(l)
+                  }
+                })
+                $scope.multiData.push(n)
+                if (n.connectionId == $scope.connectionId)
+                  $scope.linkData = n.linkData;
+                initConnState(n.connectionId)
+                $scope.multiDetails.updateState(n)
+              })
+            }
+            $scope.$apply();
+
+            d3.select('#multiple_details')
+              .style({
+                height: ((normals.length + 1) * 30) + 40 + "px",
+                'overflow-y': normals.length > 10 ? 'scroll' : 'hidden'
+              })
+          })
+        }
+        // register a notification function for when the topology is updated
+        QDRService.addUpdatedAction("normalsStats", extendConnections)
+        // call the function that gets the links right now
+        extendConnections();
+        clearPopups();
+        var display = 'block'
+        var left = mouseX + $(document).scrollLeft()
+        if (d.normals.length === 1) {
+          display = 'none'
+          left = left - 30;
+          mouseY = mouseY - 20
+        }
+        d3.select('#multiple_details')
+          .style({
+            display: display,
+            opacity: 1,
+            left: (mouseX + $(document).scrollLeft()) + "px",
+            top: (mouseY + $(document).scrollTop()) + "px"
+          })
+        if (d.normals.length === 1) {
+          // simulate a click on the connection to popup the link details
+          QDRService.ensureEntities(d.key, {entity: ".router.link", force: true}, function () {
+            $scope.multiDetails.showLinksList({
+              entity: d
+            })
+          })
+        }
+      }
+      var stopUpdateConnectionsGrid = function() {
+        QDRService.delUpdatedAction("normalsStats");
+      }
+
+      var initConnState = function(id) {
+        if (!angular.isDefined($scope.quiesceState[id])) {
+          $scope.quiesceState[id] = {
+            state: 'enabled',
+            buttonText: 'Quiesce',
+            buttonDisabled: false,
+            linkStates: {}
+          }
+        }
+      }
+
+      function nextHop(thisNode, d) {
+        if ((thisNode) && (thisNode != d)) {
+          var target = findNextHopNode(thisNode, d);
+          //QDR.log.debug("highlight link from node ");
+          //console.dump(nodeFor(selected_node.name));
+          //console.dump(target);
+          if (target) {
+            var hnode = nodeFor(thisNode.name)
+            var hlLink = linkFor(hnode, target);
+            //QDR.log.debug("need to highlight");
+            //console.dump(hlLink);
+            if (hlLink) {
+              hlLink['highlighted'] = true;
+              hnode['highlighted'] = true
+            }
+            else
+              target = null;
+          }
+          nextHop(target, d);
+        }
+        if (thisNode == d) {
+          var hnode = nodeFor(thisNode.name)
+          hnode['highlighted'] = true
+        }
+      }
+
+
+      function mousedown() {
+        // prevent I-bar on drag
+        //d3.event.preventDefault();
+
+        // because :active only works in WebKit?
+        svg.classed('active', true);
+      }
+
+      function hasChanged() {
+        // Don't update the underlying topology diagram if we are adding a new node.
+        // Once adding is completed, the topology will update automatically if it has changed
+        if ($scope.addingNode.step > 0)
+          return -2;
+        var nodeInfo = QDRService.topology.nodeInfo();
+        if (Object.keys(nodeInfo).length != Object.keys(savedKeys).length)
+          return Object.keys(nodeInfo).length > Object.keys(savedKeys).length ? 1 : -1;
+        // we may have dropped a node and added a different node in the same update cycle
+        for (var key in nodeInfo) {
+          // if this node isn't in the saved node list
+          if (!savedKeys.hasOwnProperty(key))
+            return 1;
+          // if the number of connections for this node chaanged
+          if (nodeInfo[key]['.connection'].results.length != savedKeys[key]) {
+            return -1;
+          }
+        }
+        return 0;
+      };
+
+      function saveChanged() {
+        savedKeys = {};
+        var nodeInfo = QDRService.topology.nodeInfo();
+        // save the number of connections per node
+        for (var key in nodeInfo) {
+          if (nodeInfo[key]['.connection'])
+            savedKeys[key] = nodeInfo[key]['.connection'].results.length;
+        }
+        //QDR.log.debug("saving current keys");
+        console.dump(savedKeys);
+      };
+      // we are about to leave the page, save the node positions
+      $rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
+        //QDR.log.debug("locationChangeStart");
+        savePositions()
+        $scope.addingNode.step = 0;
+      });
+      // When the DOM element is removed from the page,
+      // AngularJS will trigger the $destroy event on
+      // the scope
+      $scope.$on("$destroy", function(event) {
+        //QDR.log.debug("scope on destroy");
+        savePositions();
+        QDRService.setUpdateEntities([])
+        QDRService.stopUpdating();
+        QDRService.delUpdatedAction("normalsStats");
+        QDRService.delUpdatedAction("topology");
+        d3.select("#SVG_ID").remove();
+        window.removeEventListener('resize', resize);
+      });
+
+      function handleInitialUpdate() {
+        // we only need to update connections during steady-state
+        QDRService.setUpdateEntities([".connection"])
+        // we currently have all entities available on all routers
+        saveChanged();
+        animate = true;
+        initForceGraph();
+        // after the graph is displayed fetch all .router.node info. This is done so highlighting between nodes
+        // doesn't incur a delay
+        QDRService.ensureAllEntities([{entity: ".router.node", attrs: ["id","nextHop"]}], function () {})
+        // call this function every time a background update is done
+        QDRService.addUpdatedAction("topology", function() {
+          var changed = hasChanged()
+          // there is a new node, we need to get all of it's entities before drawing the graph
+          if (changed > 0) {
+            QDRService.delUpdatedAction("topology")
+            setupInitialUpdate()
+          } else if (changed === -1) {
+            // we lost a node (or a client), we can draw the new svg immediately
+            saveChanged();
+            var nodeInfo = QDRService.topology.nodeInfo();
+            initializeNodes(nodeInfo)
+
+            var unknowns = []
+            initializeLinks(nodeInfo, unknowns)
+            if (unknowns.length > 0) {
+              resolveUnknowns(nodeInfo, unknowns)
+            }
+            else {
+              force.nodes(nodes).links(links).start();
+              animate = true;
+              restart();
+            }
+
+            //initForceGraph();
+          } else {
+            //QDR.log.debug("topology didn't change")
+          }
+
+        })
+      }
+
+      function setupInitialUpdate() {
+        // make sure all router nodes have .connection info. if not then fetch any missing info
+        QDRService.ensureAllEntities(
+//          [{entity: ".connection"}, {entity: ".router.lin.router.link", attrs: ["linkType","connectionId","linkDir"]}],
+          [{entity: ".connection"}],
+          //[{entity: ".connection"}],
+            handleInitialUpdate)
+      }
+      setupInitialUpdate();
+      QDRService.startUpdating();
+
+      function doAddDialog(NewRouterName) {
+        QDRService.ensureAllEntities({entity: ".listener"}, function () {
+          var d = $dialog.dialog({
+            dialogClass: "modal dlg-large",
+            backdrop: true,
+            keyboard: true,
+            backdropClick: true,
+            controller: 'QDR.NodeDialogController',
+            templateUrl: 'node-config-template.html',
+            resolve: {
+              newname: function() {
+                return NewRouterName;
+              }
+            }
+          });
+          $timeout(function () {
+            d.open().then(function(result) {
+              if (result)
+                doDownloadDialog(result);
+            });
+          })
+        })
+      };
+
+      function doDownloadDialog(result) {
+        d = $dialog.dialog({
+          backdrop: true,
+          keyboard: true,
+          backdropClick: true,
+          controller: 'QDR.DownloadDialogController',
+          templateUrl: 'download-dialog-template.html',
+          resolve: {
+            results: function() {
+              return result;
+            }
+          }
+        });
+        d.open().then(function(result) {
+          //QDR.log.debug("download dialog done")
+        })
+        if (!$scope.$$phase) $scope.$apply()
+      };
+    }
+  ]);
+
+  return QDR;
+}(QDR || {}));


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


[4/4] qpid-dispatch git commit: DISPATCH-745 Remove symlinks from hawtio to stand-alone

Posted by ea...@apache.org.
DISPATCH-745 Remove symlinks from hawtio to stand-alone


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

Branch: refs/heads/master
Commit: 6a1e66322cf8b1298acc76bf3ed9d5bfeade2c3f
Parents: 4c14b0a
Author: Ernest Allen <ea...@redhat.com>
Authored: Wed Apr 19 08:16:53 2017 -0400
Committer: Ernest Allen <ea...@redhat.com>
Committed: Wed Apr 19 08:16:53 2017 -0400

----------------------------------------------------------------------
 .../hawtio/src/main/webapp/plugin/js/navbar.js  |  302 ++-
 .../hawtio/src/main/webapp/plugin/js/qdrList.js |  788 ++++++-
 .../src/main/webapp/plugin/js/qdrListChart.js   |  142 +-
 .../src/main/webapp/plugin/js/qdrNewNode.js     |  446 +++-
 .../src/main/webapp/plugin/js/qdrOverview.js    | 1767 +++++++++++++-
 .../plugin/js/qdrOverviewLogsController.js      |   68 +-
 .../src/main/webapp/plugin/js/qdrTopology.js    | 2161 +++++++++++++++++-
 console/stand-alone/plugin/js/navbar.js         |   43 +-
 console/stand-alone/plugin/js/qdrList.js        |   13 +-
 console/stand-alone/plugin/js/qdrListChart.js   |  835 ++++++-
 console/stand-alone/plugin/js/qdrNewNode.js     |   10 +-
 console/stand-alone/plugin/js/qdrOverview.js    |  309 ++-
 .../plugin/js/qdrOverviewLogsController.js      |    4 +-
 console/stand-alone/plugin/js/qdrTopology.js    |   88 +-
 14 files changed, 6709 insertions(+), 267 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6a1e6632/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
deleted file mode 120000
index e88b618..0000000
--- a/console/hawtio/src/main/webapp/plugin/js/navbar.js
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../../stand-alone/plugin/js/navbar.js
\ No newline at end of file
diff --git a/console/hawtio/src/main/webapp/plugin/js/navbar.js b/console/hawtio/src/main/webapp/plugin/js/navbar.js
new file mode 100644
index 0000000..5125748
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/js/navbar.js
@@ -0,0 +1,301 @@
+/*
+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.
+*/
+/**
+ * @module QDR
+ */
+var QDR = (function (QDR) {
+
+  /**
+   * @property breadcrumbs
+   * @type {{content: string, title: string, isValid: isValid, href: string}[]}
+   *
+   * Data structure that defines the sub-level tabs for
+   * our plugin, used by the navbar controller to show
+   * or hide tabs based on some criteria
+   */
+  QDR.breadcrumbs = [
+    {
+        content: '<i class="icon-cogs"></i> Connect',
+        title: "Connect to a router",
+        isValid: function () { return true; },
+        href: "#" + QDR.pluginRoot + "/connect"
+    },
+    {
+        content: '<i class="icon-home"></i> Overview',
+        title: "View router overview",
+        isValid: function (QDRService) { return QDRService.isConnected(); },
+        href: "#" + QDR.pluginRoot + "/overview"
+      },
+    {
+        content: '<i class="icon-list "></i> Entities',
+        title: "View the attributes of the router entities",
+        isValid: function (QDRService) { return QDRService.isConnected(); },
+        href: "#" + QDR.pluginRoot + "/list"
+      },
+    {
+        content: '<i class="icon-star-empty"></i> Topology',
+        title: "View router network topology",
+        isValid: function (QDRService) { return QDRService.isConnected(); },
+        href: "#" + QDR.pluginRoot + "/topology"
+      },
+    {
+        content: '<i class="icon-bar-chart"></i> Charts',
+        title: "View charts",
+        isValid: function (QDRService, $location) { return QDRService.isConnected() && QDR.isStandalone; },
+        href: "#/charts"
+    },
+    {
+        content: '<i class="icon-align-left"></i> Schema',
+        title: "View dispatch schema",
+        isValid: function (QDRService) { return QDRService.isConnected(); },
+        href: "#" + QDR.pluginRoot + "/schema",
+        right: true
+      }
+  ];
+  /**
+   * @function NavBarController
+   *
+   * @param $scope
+   * @param workspace
+   *
+   * The controller for this plugin's navigation bar
+   *
+   */
+  QDR.module.controller("QDR.NavBarController", ['$scope', 'QDRService', 'QDRChartService', '$routeParams', '$location', function($scope, QDRService, QDRChartService, $routeParams, $location) {
+    $scope.breadcrumbs = QDR.breadcrumbs;
+    $scope.isValid = function(link) {
+      return link.isValid(QDRService, $location);
+    };
+
+    $scope.isActive = function(href) {
+    // highlight the connect tab if we are on the root page
+    if (($location.path() === QDR.pluginRoot) && (href.split("#")[1] === QDR.pluginRoot + "/connect"))
+      return true
+        return href.split("#")[1] == $location.path();
+    };
+
+    $scope.isRight = function (link) {
+        return angular.isDefined(link.right);
+    };
+
+    $scope.hasChart = function (link) {
+        if (link.href == "#/charts") {
+            return QDRChartService.charts.some(function (c) { return c.dashboard });
+        }
+    }
+
+  $scope.isDashboardable = function () {
+    return  ($location.path().indexOf("schema") < 0 && $location.path().indexOf("connect") < 0);
+  }
+
+  $scope.addToDashboardLink = function () {
+    var href = "#" + $location.path();
+    var size = angular.toJson({
+                size_x: 2,
+                size_y: 2
+              });
+
+        var routeParams = angular.toJson($routeParams);
+        var title = "Dispatch Router";
+      return "/hawtio/#/dashboard/add?tab=dashboard" +
+            "&href=" + encodeURIComponent(href) +
+            "&routeParams=" + encodeURIComponent(routeParams) +
+            "&title=" + encodeURIComponent(title) +
+            "&size=" + encodeURIComponent(size);
+    };
+
+  }]);
+
+  // controller for the edit/configure chart dialog
+  QDR.module.controller("QDR.ChartDialogController", function($scope, QDRChartService, $location, dialog, chart, updateTick, dashboard, adding) {
+    var dialogSvgChart = null;
+    $scope.svgDivId = "dialogEditChart";    // the div id for the svg chart
+
+    var updateTimer = null;
+    $scope.chart = chart;  // the underlying chart object from the dashboard
+    $scope.dialogChart = $scope.chart.copy(); // the chart object for this dialog
+    $scope.userTitle = $scope.chart.title();
+
+    $scope.$watch('userTitle', function(newValue, oldValue) {
+    if (newValue !== oldValue) {
+      $scope.dialogChart.title(newValue);
+      dialogSvgChart.tick($scope.svgDivId);
+    }
+    })
+    $scope.$watch("dialogChart.areaColor", function (newValue, oldValue) {
+      if (newValue !== oldValue) {
+        if (dialogSvgChart)
+         dialogSvgChart.tick($scope.svgDivId);
+      }
+    })
+    $scope.$watch("dialogChart.lineColor", function (newValue, oldValue) {
+      if (newValue !== oldValue) {
+        if (dialogSvgChart)
+          dialogSvgChart.tick($scope.svgDivId);
+      }
+    })
+    $scope.$watch("dialogChart.type", function (newValue, oldValue) {
+      if (newValue !== oldValue) {
+        if (dialogSvgChart)
+          dialogSvgChart.tick($scope.svgDivId);
+      }
+    })
+
+    // the stored rateWindow is in milliseconds, but the slider is in seconds
+    $scope.rateWindow = $scope.chart.rateWindow / 1000;
+
+    $scope.addChartsPage = function () {
+      QDRChartService.addDashboard(dialogSvgChart.chart);
+    };
+
+    $scope.showChartsPage = function () {
+      cleanup();
+      dialog.close(true);
+      $location.path(QDR.pluginRoot + "/charts");
+    };
+
+    var cleanup = function () {
+      if (updateTimer) {
+        clearTimeout(updateTimer);
+        updateTimer = null;
+      }
+      if (!$scope.isOnChartsPage())
+        QDRChartService.unRegisterChart($scope.dialogChart);     // remove the chart
+    }
+    $scope.okClick = function () {
+      cleanup();
+      dialog.close(true);
+    };
+
+    var initRateSlider = function () {
+      if (document.getElementById('rateSlider')) {
+        $( "#rateSlider" ).slider({
+          value: $scope.rateWindow,
+          min: 1,
+          max: 10,
+          step: 1,
+          slide: function( event, ui ) {
+            $scope.rateWindow = ui.value;
+            $scope.dialogChart.rateWindow = ui.value * 1000;
+            $scope.$apply();
+            if (dialogSvgChart)
+              dialogSvgChart.tick($scope.svgDivId);
+          }
+        });
+      } else {
+        setTimeout(initRateSlider, 100)
+      }
+    }
+    initRateSlider();
+
+    var initDurationSlider = function () {
+      if (document.getElementById('durationSlider')) {
+        $( "#durationSlider" ).slider({
+          value: $scope.dialogChart.visibleDuration,
+          min: 1,
+          max: 10,
+          step: 1,
+          slide: function( event, ui ) {
+            $scope.visibleDuration = $scope.dialogChart.visibleDuration = ui.value;
+            $scope.$apply();
+            if (dialogSvgChart)
+              dialogSvgChart.tick($scope.svgDivId);
+          }
+        });
+      } else {
+        setTimeout(initDurationSlider, 100)
+      }
+    }
+    initDurationSlider();
+
+    $scope.adding = function () {
+      return adding
+    }
+
+    $scope.isOnChartsPage = function () {
+      if (adding)
+        return dialogSvgChart ? dialogSvgChart.chart.dashboard : false;
+      else
+        return $scope.chart.dashboard
+    }
+
+    // handle the Apply button click
+    // update the dashboard chart's properties
+    $scope.apply = function () {
+      $scope.chart.areaColor = $scope.dialogChart.areaColor;
+      $scope.chart.lineColor = $scope.dialogChart.lineColor;
+      $scope.chart.type = $scope.dialogChart.type;
+      $scope.chart.rateWindow = $scope.rateWindow * 1000;
+      $scope.chart.title($scope.dialogChart.title());
+      $scope.chart.visibleDuration = $scope.dialogChart.visibleDuration;
+      QDRChartService.saveCharts();
+      if (typeof updateTick === "function")
+        updateTick();
+    }
+
+    // add a new chart to the dashboard based on the current dialog settings
+    $scope.copyToDashboard = function () {
+        var chart = $scope.dialogChart.copy();
+        // set the new chart's dashboard state
+        QDRChartService.addDashboard(chart);
+        // notify the chart controller that it needs to display a new chart
+        dashboard.addChart(chart);
+    }
+
+    // update the chart on the popup dialog
+    var updateDialogChart = function () {
+      // draw the chart using the current data
+      if (dialogSvgChart)
+          dialogSvgChart.tick($scope.svgDivId);
+
+      // draw the chart again in 1 second
+      var updateRate = localStorage['updateRate'] ? localStorage['updateRate'] : 5000;
+      if (updateTimer)
+      clearTimeout(updateTimer);
+        updateTimer = setTimeout(updateDialogChart, updateRate);
+    }
+
+    var showChart = function () {
+      // ensure the div for our chart is loaded in the dom
+      var div = angular.element("#" + $scope.svgDivId);
+      if (!div.width()) {
+        setTimeout(showChart, 100);
+        return;
+      }
+      dialogSvgChart = new QDRChartService.AreaChart($scope.dialogChart);
+      $('input[name=lineColor]').val($scope.dialogChart.lineColor);
+      $('input[name=areaColor]').val($scope.dialogChart.areaColor);
+      $('input[name=areaColor]').on('input', function (e) {
+        $scope.dialogChart.areaColor = $(this).val();
+        updateDialogChart()
+      })
+      $('input[name=lineColor]').on('input', function (e) {
+        $scope.dialogChart.lineColor = $(this).val();
+        updateDialogChart()
+      })
+      if (updateTimer)
+        clearTimeout(updateTimer);
+          updateDialogChart();
+    }
+    showChart();
+  });
+
+  return QDR;
+
+} (QDR || {}));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6a1e6632/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
deleted file mode 120000
index 3955db5..0000000
--- a/console/hawtio/src/main/webapp/plugin/js/qdrList.js
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../../stand-alone/plugin/js/qdrList.js
\ No newline at end of file
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrList.js b/console/hawtio/src/main/webapp/plugin/js/qdrList.js
new file mode 100644
index 0000000..ec2efb2
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/js/qdrList.js
@@ -0,0 +1,787 @@
+/*
+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.
+*/
+/**
+ * @module QDR
+ */
+var QDR = (function(QDR) {
+
+  /**
+   * @method ListController
+   * @param $scope
+   * @param QDRService
+   *
+   * Controller for the main interface
+   */
+  QDR.module.controller("QDR.ListController", ['$scope', '$location', '$dialog', '$filter', '$timeout', 'QDRService', 'QDRChartService',
+    function ($scope, $location, $dialog, $filter, $timeout, QDRService, QDRChartService) {
+
+    var updateIntervalHandle = undefined;
+    var updateInterval = 5000;
+    var ListExpandedKey = "QDRListExpanded";
+    $scope.details = {};
+
+    $scope.tmplListTree = QDR.templatePath + 'tmplListTree.html';
+    $scope.selectedEntity = localStorage['QDRSelectedEntity'] || "address";
+    $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 () {
+          //QDR.log.debug("isValid UPDAATE? " + this.op)
+          //console.dump($scope.operations)
+          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;
+        var entity; // undefined since it is not supported in the GET-LOG call
+        QDRService.sendMethod($scope.currentNode.id, entity, {}, $scope.currentMode.op, {}, function (nodeName, entity, response, context) {
+          $scope.fetchingLog = false;
+          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)
+            return;
+          }
+          $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.expandAll = function () {
+      $("#entityTree").dynatree("getRoot").visit(function(node){
+                node.expand(true);
+            });
+    }
+    $scope.contractAll = function () {
+      $("#entityTree").dynatree("getRoot").visit(function(node){
+                node.expand(false);
+            });
+    }
+
+    if (!QDRService.connected) {
+      // we are not connected. we probably got here from a bookmark or manual page reload
+      QDRService.redirectWhenConnected("list");
+      return;
+    }
+    // we are currently connected. setup a handler to get notified if we are ever disconnected
+    QDRService.addDisconnectAction( function () {
+      QDRService.redirectWhenConnected("list")
+      $scope.$apply();
+    })
+
+    $scope.nodes = []
+    var excludedEntities = ["management", "org.amqp.management", "operationalEntity", "entity", "configurationEntity", "dummy", "console"];
+    var aggregateEntities = ["router.address"];
+    var classOverrides = {
+      "connection": function (row, nodeId) {
+        var isConsole = QDRService.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) {
+        var link = {nodeId: nodeId, connectionId: row.connectionId.value}
+        var isConsole = QDRService.isConsoleLink(link)
+        return isConsole ? "console" : row.linkType.value;
+      },
+      "router.address": function (row) {
+        var identity = QDRService.identity_clean(row.identity.value)
+        var address = QDRService.addr_text(identity)
+        var cls = QDRService.addr_class(identity)
+        if (address === "$management")
+          cls = "internal " + cls
+        return cls
+      }
+    }
+
+    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 = [];
+    var expandedList = angular.fromJson(localStorage[ListExpandedKey]) || [];
+    var onTreeNodeExpanded = function (expanded, node) {
+      // save the list of entities that are expanded
+      var tree = $("#entityTree").dynatree("getTree");
+      var list = [];
+      tree.visit( function (tnode) {
+        if (tnode.isExpanded()) {
+          list.push(tnode.data.key)
+        }
+      })
+      localStorage[ListExpandedKey] = JSON.stringify(list)
+
+      if (expanded)
+        onTreeSelected(node);
+    }
+    // a tree node was selected
+    var onTreeSelected = function (selectedNode) {
+      $timeout( function () {
+        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.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);   // update the table on the right
+          $("#entityTree").dynatree("getRoot").visit(function(node){
+             node.select(false);
+          });
+          selectedNode.select();
+        }
+      })
+    }
+
+    // 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]
+        var value = ""
+        if (angular.isDefined(entity['default']))
+          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 () {
+      var schemaEntity = QDRService.schema.entityTypes[$scope.selectedEntity]
+      return (schemaEntity.operations.indexOf("CREATE") > -1)
+    }
+
+    var stopUpdating = function () {
+      if (angular.isDefined(updateIntervalHandle)) {
+        clearInterval(updateIntervalHandle);
+      }
+      updateIntervalHandle = undefined;
+    }
+
+    // the data for the selected entity is available, populate the tree
+    var updateEntityChildren = function (entity, tableRows, expand) {
+      var tree = $("#entityTree").dynatree("getTree");
+      if (!tree.getNodeByKey) {
+        return stopUpdating()
+      }
+      var node = tree.getNodeByKey(entity)
+      var updatedDetails = false;
+      var scrollTreeDiv = $('.qdr-attributes.pane.left .pane-viewport')
+      var scrollTop = scrollTreeDiv.scrollTop();
+      node.removeChildren();
+      if (tableRows.length == 0) {
+          node.addChild({
+          addClass:   "no-data",
+              typeName:   "none",
+              title:      "no data",
+          key:        node.data.key + ".1"
+          })
+          if (expand) {
+              updateDetails(fromSchema(entity));
+                 $scope.selectedRecordName = entity;
+        }
+      } else {
+        tableRows.forEach( function (row) {
+          var addClass = entity;
+          if (classOverrides[entity]) {
+            addClass += " " + classOverrides[entity](row, $scope.currentNode.id);
+          }
+          var child = {
+                        typeName:   "attribute",
+                        addClass:   addClass,
+                        tooltip:    addClass,
+                        key:        row.name.value,
+                        title:      row.name.value,
+                        details:    row
+                    }
+          if (row.name.value === $scope.selectedRecordName) {
+            if (expand)
+              updateDetails(row); // update the table on the right
+            child.select = true;
+            updatedDetails = true;
+          }
+          node.addChild(child)
+        })
+      }
+      // if the selectedRecordName was not found, select the 1st one
+      if (expand && !updatedDetails && tableRows.length > 0) {
+        var row = tableRows[0];
+        $scope.selectedRecordName = row.name.value;
+        var node = tree.getNodeByKey($scope.selectedRecordName);
+        node.select(true);
+        updateDetails(row)  // update the table on the right
+      }
+      scrollTreeDiv.scrollTop(scrollTop)
+    }
+
+    var schemaProps = function (entityName, key, currentNode) {
+         var typeMap = {integer: 'number', string: 'text', path: 'text', boolean: 'boolean', map: 'textarea'};
+
+      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.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.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;
+    }
+
+    // update the table on the right
+    var updateDetails = function (row) {
+      var details = [];
+      $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: 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,
+
+          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;
+      })
+      setTimeout(applyDetails, 1, details)
+    }
+
+    var applyDetails = function (details) {
+      $scope.detailFields = details;
+      aggregateColumn();
+      $scope.$apply();
+      // ng-grid bug? the entire table doesn't always draw unless a reflow is triggered;
+      $(window).trigger('resize');
+    }
+
+    var restartUpdate = function () {
+      stopUpdating();
+      updateTableData($scope.selectedEntity, true);
+      updateIntervalHandle = setInterval(updateExpandedEntities, updateInterval);
+    }
+    var updateExpandedEntities = function () {
+      var tree = $("#entityTree").dynatree("getTree");
+      if (tree.visit) {
+        tree.visit( function (node) {
+          if (node.isExpanded()) {
+            updateTableData(node.data.key, node.data.key === $scope.selectedEntity)
+          }
+        })
+      } else {
+        stopUpdating();
+      }
+    }
+
+    $scope.selectNode = function(node) {
+      $scope.selectedNode = node.name;
+      $scope.selectedNodeId = node.id;
+      setCurrentNode();
+      restartUpdate();
+    };
+    $scope.$watch('selectedEntity', function(newValue, oldValue) {
+      if (newValue !== oldValue) {
+        localStorage['QDRSelectedEntity'] = $scope.selectedEntity;
+        restartUpdate();
+        $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 updateTableData = function (entity, expand) {
+      if (!QDRService.connected) {
+        // we are no longer connected. bail back to the connect page
+        $location.path("/" + QDR.pluginName + "/connect")
+        $location.search('org', "list");
+        return;
+      }
+      // don't update the data when on the operations tab
+      if ($scope.currentMode.id === 'operations') {
+        return;
+      }
+
+      var gotNodeInfo = function (nodeName, dotentity, response) {
+        var tableRows = [];
+        var records = response.results;
+        var aggregates = response.aggregates;
+        var attributeNames = response.attributeNames;
+        // If !attributeNmes then  there was an error getting the records for this entity
+        if (attributeNames) {
+          var nameIndex = attributeNames.indexOf("name");
+          var identityIndex = attributeNames.indexOf("identity");
+          var ent = QDRService.schema.entityTypes[entity];
+          for (var i=0; i<records.length; ++i) {
+            var record = records[i];
+            var aggregate = aggregates ? aggregates[i] : undefined;
+            var row = {};
+            var rowName;
+            if (nameIndex > -1) {
+              rowName = record[nameIndex];
+              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;
+            }
+            for (var j=0; j<attributeNames.length; ++j) {
+              var col = attributeNames[j];
+              row[col] = {value: record[j], type: undefined, graph: false, title: '', aggregate: '', aggregateTip: ''};
+              if (ent) {
+                var 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 : '';
+                      var 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) })
+        setTimeout(selectRow, 0, {entity: dotentity, 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.fetchEntity($scope.selectedNodeId, entity, [], gotNodeInfo);
+      }
+    };
+
+    // tableRows are the records that were returned, this populates the left hand table on the page
+    var selectRow = function (info) {
+      updateEntityChildren(info.entity, info.rows, info.expand);
+      fixTooltips();
+    }
+
+    var titleFromAlt = function (alt) {
+      if (alt && alt.length) {
+        var data = angular.fromJson(alt);
+        var table = "<table class='tiptable'><tbody>";
+        data.forEach (function (row) {
+          table += "<tr>";
+          table += "<td>" + row.node + "</td><td align='right'>" + QDRService.pretty(row.val) + "</td>";
+          table += "</tr>"
+        })
+        table += "</tbody></table>"
+        return table;
+      }
+      return '';
+    }
+
+    var fixTooltips = function () {
+      if ($('.hastip').length == 0) {
+        setTimeout(fixTooltips, 100);
+        return;
+      }
+      $('.hastip').each( function (i, tip) {
+        var tipset = tip.getAttribute('tipset')
+        if (!tipset) {
+          $(tip).tipsy({html: true, className: 'subTip', opacity: 1, title: function () {
+            return titleFromAlt(this.getAttribute('alt'))
+          } });
+          tip.setAttribute('tipset', true)
+        } else {
+          var title = titleFromAlt(tip.getAttribute('alt'))
+          tip.setAttribute('original-title', title)
+        }
+      })
+    }
+
+    $scope.detailFields = [];
+
+    $scope.addToGraph = function(rowEntity) {
+      var chart = QDRChartService.registerChart(
+        {nodeId: $scope.selectedNodeId,
+         entity: "." + $scope.selectedEntity,
+         name:   $scope.selectedRecordName,
+         attr:    rowEntity.name,
+         forceCreate: true});
+      doDialog('tmplListChart.html', chart);
+    }
+
+    $scope.addAllToGraph = function(rowEntity) {
+      var chart = QDRChartService.registerChart({
+        nodeId:     $scope.selectedNodeId,
+        entity:     $scope.selectedEntity,
+        name:       $scope.selectedRecordName,
+        attr:       rowEntity.name,
+        type:       "rate",
+        rateWindow: updateInterval,
+        visibleDuration: 1,
+        forceCreate: true,
+        aggregate:   true});
+      doDialog('tmplListChart.html', chart);
+    }
+
+    $scope.detailCols = [];
+    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">{{row.entity[col.field]}}<i ng-if="row.entity.graph" ng-click="addToGraph(row.entity)" ng-class="{\'icon-bar-chart\': row.entity.graph == true }"></i></div>'
+         },
+         {
+           field: 'attributeValue',
+           displayName: 'Value',
+           cellTemplate: '<div class="ngCellText" ng-class="{\'changed\': row.entity.changed == 1}"><span>{{row.getProperty(col.field)}}</span></div>'
+         }
+         ]
+        if (aggregateEntities.indexOf($scope.selectedEntity) > -1) {
+          $scope.detailCols.push(
+           {
+             width: '10%',
+             field: 'aggregateValue',
+             displayName: 'Aggregate',
+             cellTemplate: '<div class="hastip" alt="{{row.entity.aggregateTip}}"><span ng-class="{\'changed\': row.entity.changed == 1}">{{row.entity[col.field]}}</span><i ng-if="row.entity.graph" ng-click="addAllToGraph(row.entity)" ng-class="{\'icon-bar-chart\': row.entity.graph == true }"></i></div>',
+             cellClass: 'aggregate'
+           }
+          )
+        }
+      }
+      if ($scope.selectedRecordName === "")
+        $scope.detailCols = [];
+    }
+
+    // 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.details = {
+      data: 'detailFields',
+      columnDefs: "detailCols",
+      enableColumnResize: true,
+      multiSelect: false,
+      beforeSelectionChange: function() {
+          return false;
+      }
+    };
+    $scope.$on("$destroy", function( event ) {
+      //QDR.log.debug("scope destroyed for qdrList");
+      stopUpdating();
+    });
+
+    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"
+        Core.notification('success', note);
+        $scope.selectMode($scope.modes[0]);
+        restartUpdate();
+      }
+    }
+    $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;
+        } 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.sendMethod($scope.currentNode.id, $scope.selectedEntity, attributes, $scope.currentMode.op, undefined, gotMethodResponse)
+    }
+    $scope.remove = function () {
+      var attributes = {type: $scope.selectedEntity, name: $scope.selectedRecordName}
+      QDRService.sendMethod($scope.currentNode.id, $scope.selectedEntity, attributes, $scope.currentMode.op, undefined, gotMethodResponse)
+    }
+
+    function doDialog(tmpl, chart) {
+        var d = $dialog.dialog({
+          backdrop: true,
+          keyboard: true,
+          backdropClick: true,
+          templateUrl: QDR.templatePath + tmpl,
+          controller: "QDR.ListChartController",
+          resolve: {
+                 chart: function() {
+                   return chart
+                 },
+                 nodeName: function () {
+                    return $scope.selectedNode
+                 }
+              }
+        });
+
+        d.open().then(function(result) { console.log("d.open().then"); });
+
+    };
+
+    var setCurrentNode = function () {
+      $scope.nodes.some( function (node, i) {
+        if (node.name === $scope.selectedNode) {
+          $scope.currentNode = $scope.nodes[i]
+          return true;
+        }
+      })
+    }
+
+    var treeReady = false;
+    var serviceReady = false;
+    $scope.largeNetwork = QDRService.isLargeNetwork()
+    // called after we know for sure the schema is fetched and the routers are all ready
+    QDRService.addUpdatedAction("initList", function () {
+      QDRService.stopUpdating();
+      QDRService.delUpdatedAction("initList")
+
+      $scope.nodes = QDRService.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];
+        }
+      }
+      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 e = new Folder(entity)
+          e.typeName = "entity"
+          e.key = entity
+          e.expand = (expandedList.indexOf(entity) > -1)
+          var placeHolder = new Folder("loading...")
+          placeHolder.addClass = "loading"
+          e.children = [placeHolder]
+          entityTreeChildren.push(e)
+        }
+      })
+      serviceReady = true;
+      initTree();
+    })
+    $scope.treeReady = function () {
+      treeReady = true;
+      initTree();
+    }
+
+    var initTree = function () {
+      if (!treeReady || !serviceReady)
+        return;
+      $('#entityTree').dynatree({
+        onActivate: onTreeSelected,
+        onExpand: onTreeNodeExpanded,
+        selectMode: 1,
+        autoCollapse: $scope.largeNetwork,
+        activeVisible: !$scope.largeNetwork,
+        debugLevel: 0,
+        children: entityTreeChildren
+      })
+      restartUpdate()
+      updateExpandedEntities();
+    };
+    QDRService.ensureAllEntities({entity: ".connection"}, function () {
+      QDRService.setUpdateEntities([".connection"])
+      QDRService.startUpdating();
+    })
+
+
+  }]);
+
+    return QDR;
+
+} (QDR || {}));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6a1e6632/console/hawtio/src/main/webapp/plugin/js/qdrListChart.js
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrListChart.js b/console/hawtio/src/main/webapp/plugin/js/qdrListChart.js
deleted file mode 120000
index d1fcf4a..0000000
--- a/console/hawtio/src/main/webapp/plugin/js/qdrListChart.js
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../../stand-alone/plugin/js/qdrListChart.js
\ No newline at end of file
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrListChart.js b/console/hawtio/src/main/webapp/plugin/js/qdrListChart.js
new file mode 100644
index 0000000..93391f1
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/js/qdrListChart.js
@@ -0,0 +1,141 @@
+/*
+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.
+*/
+/**
+ * @module QDR
+ */
+var QDR = (function(QDR) {
+
+  QDR.module.controller('QDR.ListChartController', function ($scope, dialog, $dialog, $location, QDRChartService, chart, nodeName) {
+    $scope.chart = chart;
+    $scope.dialogSvgChart = null;
+    var updateTimer = null;
+    $scope.svgDivId = "dialogChart";    // the div id for the svg chart
+
+    $scope.showChartsPage = function () {
+      cleanup();
+      dialog.close(true);
+      $location.path(QDR.pluginRoot + "/charts");
+    };
+
+    $scope.addHChart = function () {
+      QDRChartService.addHDash($scope.chart);
+      cleanup();
+      dialog.close(true);
+    }
+
+    $scope.addToDashboardLink = function () {
+      var href = "#/" + QDR.pluginName + "/charts";
+      var size = angular.toJson({
+                  size_x: 2,
+                  size_y: 2
+                });
+
+      var params = angular.toJson({chid: $scope.chart.id()});
+      var 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 () {
+      // the chart divs are generated by angular and aren't available immediately
+      var div = angular.element("#" + $scope.svgDivId);
+      if (!div.width()) {
+        setTimeout(showChart, 100);
+        return;
+      }
+      dialogSvgChart = new QDRChartService.AreaChart($scope.chart);
+      $scope.dialogSvgChart = dialogSvgChart;
+      updateDialogChart();
+    }
+    showChart();
+
+    var updateDialogChart = function () {
+      if ($scope.dialogSvgChart)
+        $scope.dialogSvgChart.tick($scope.svgDivId);
+      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();
+      dialog.close(true);
+      };
+
+    $scope.editChart = function () {
+      doDialog('tmplChartConfig.html', chart)
+    }
+
+    function doDialog(template, chart) {
+
+      $dialog.dialog({
+      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
+        }
+      }
+      }).open().then(function(result) {
+        $scope.ok()
+      });
+    };
+
+  });
+
+  return QDR;
+
+} (QDR || {}));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6a1e6632/console/hawtio/src/main/webapp/plugin/js/qdrNewNode.js
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrNewNode.js b/console/hawtio/src/main/webapp/plugin/js/qdrNewNode.js
deleted file mode 120000
index 16b0e42..0000000
--- a/console/hawtio/src/main/webapp/plugin/js/qdrNewNode.js
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../../stand-alone/plugin/js/qdrNewNode.js
\ No newline at end of file
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrNewNode.js b/console/hawtio/src/main/webapp/plugin/js/qdrNewNode.js
new file mode 100644
index 0000000..f6d035a
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/js/qdrNewNode.js
@@ -0,0 +1,445 @@
+/*
+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.
+*/
+/**
+ * @module QDR
+ */
+var QDR = (function(QDR) {
+
+  QDR.module.controller("QDR.NodeDialogController", function($scope, QDRService, dialog, newname) {
+    var schema = QDRService.schema;
+    var myEntities = ['router', 'log', 'listener'];
+    var typeMap = {
+      integer: 'number',
+      string: 'text',
+      path: 'text',
+      boolean: 'boolean'
+    };
+    var newLinks = $('path.temp').toArray(); // jquery array of new links for the added router
+    var nodeInfo = QDRService.topology.nodeInfo();
+    var separatedEntities = []; // additional entities required if a link is reversed
+    var myPort = 0,
+      myAddr = '0.0.0.0'; // port and address for new router
+    $scope.entities = [];
+
+    // find max port number that is used in all the listeners
+    var getMaxPort = function(nodeInfo) {
+      var maxPort = 5674;
+      for (var key in nodeInfo) {
+        var node = nodeInfo[key];
+        var listeners = node['.listener'];
+        var attrs = listeners.attributeNames;
+        for (var i = 0; i < listeners.results.length; ++i) {
+          var res = listeners.results[i];
+          var port = QDRService.valFor(attrs, res, 'port');
+          if (parseInt(port, 10) > maxPort)
+            maxPort = parseInt(port, 10);
+        }
+      }
+      return maxPort;
+    }
+    var maxPort = getMaxPort(nodeInfo);
+
+    // construct an object that contains all the info needed for a single tab's fields
+    var entity = function(actualName, tabName, humanName, ent, icon, link) {
+      var nameIndex = -1; // the index into attributes that the name field was placed
+      var index = 0;
+      var info = {
+          actualName: actualName,
+          tabName: tabName,
+          humanName: humanName,
+          description: ent.description,
+          icon: angular.isDefined(icon) ? icon : '',
+          references: ent.references,
+          link: link,
+
+          attributes: $.map(ent.attributes, function(value, key) {
+            // skip identity and depricated fields
+            if (key == 'identity' || value.description.startsWith('Deprecated'))
+              return null;
+            var val = value['default'];
+            if (key == 'name')
+              nameIndex = index;
+            index++;
+            return {
+              name: key,
+              humanName: QDRService.humanify(key),
+              description: value.description,
+              type: 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
+            };
+          })
+        }
+        // move the 'name' attribute to the 1st position
+      if (nameIndex > -1) {
+        var tmp = info.attributes[0];
+        info.attributes[0] = info.attributes[nameIndex];
+        info.attributes[nameIndex] = tmp;
+      }
+      return info;
+    }
+
+    // remove the annotation fields
+    var stripAnnotations = function(entityName, ent, annotations) {
+      if (ent.references) {
+        var newEnt = {
+          attributes: {}
+        };
+        ent.references.forEach(function(annoKey) {
+          if (!annotations[annoKey])
+            annotations[annoKey] = {};
+          annotations[annoKey][entityName] = true; // create the key/consolidate duplicates
+          var keys = Object.keys(schema.annotations[annoKey].attributes);
+          for (var attrib in ent.attributes) {
+            if (keys.indexOf(attrib) == -1) {
+              newEnt.attributes[attrib] = ent.attributes[attrib];
+            }
+          }
+          // add a field for the reference name
+          newEnt.attributes[annoKey] = {
+            type: 'string',
+            description: 'Name of the ' + annoKey + ' section.',
+            'default': annoKey,
+            required: true
+          };
+        })
+        newEnt.references = ent.references;
+        newEnt.description = ent.description;
+        return newEnt;
+      }
+      return ent;
+    }
+
+    var annotations = {};
+    myEntities.forEach(function(entityName) {
+      var ent = schema.entityTypes[entityName];
+      var hName = QDRService.humanify(entityName);
+      if (entityName == 'listener')
+        hName = "Listener for clients";
+      var noAnnotations = stripAnnotations(entityName, ent, annotations);
+      var ediv = entity(entityName, entityName, hName, noAnnotations, undefined);
+      if (ediv.actualName == 'router') {
+        ediv.attributes.filter(function(attr) {
+          return attr.name == 'name'
+        })[0].value = newname;
+        // if we have any new links (connectors), then the router's mode should be interior
+        if (newLinks.length) {
+          var roleAttr = ediv.attributes.filter(function(attr) {
+            return attr.name == 'mode'
+          })[0];
+          roleAttr.value = roleAttr.selected = "interior";
+        }
+      }
+      if (ediv.actualName == 'container') {
+        ediv.attributes.filter(function(attr) {
+          return attr.name == 'containerName'
+        })[0].value = newname + "-container";
+      }
+      if (ediv.actualName == 'listener') {
+        // find max port number that is used in all the listeners
+        ediv.attributes.filter(function(attr) {
+          return attr.name == 'port'
+        })[0].value = ++maxPort;
+      }
+      // special case for required log.module since it doesn't have a default
+      if (ediv.actualName == 'log') {
+        var moduleAttr = ediv.attributes.filter(function(attr) {
+          return attr.name == 'module'
+        })[0];
+        moduleAttr.value = moduleAttr.selected = "DEFAULT";
+      }
+      $scope.entities.push(ediv);
+    })
+
+    // add a tab for each annotation that was found
+    var annotationEnts = [];
+    for (var key in annotations) {
+      ent = angular.copy(schema.annotations[key]);
+      ent.attributes.name = {
+        type: "string",
+        unique: true,
+        description: "Unique name that is used to refer to this set of attributes."
+      }
+      var ediv = entity(key, key + 'tab', QDRService.humanify(key), ent, undefined);
+      ediv.attributes.filter(function(attr) {
+        return attr.name == 'name'
+      })[0].value = key;
+      $scope.entities.push(ediv);
+      annotationEnts.push(ediv);
+    }
+
+    // add an additional listener tab if any links are reversed
+    ent = schema.entityTypes['listener'];
+    newLinks.some(function(link) {
+      if (link.__data__.right) {
+        var noAnnotations = stripAnnotations('listener', ent, annotations);
+        var ediv = entity("listener", "listener0", "Listener (internal)", noAnnotations, undefined);
+        ediv.attributes.filter(function(attr) {
+          return attr.name == 'port'
+        })[0].value = ++maxPort;
+        // connectors from other routers need to connect to this addr:port
+        myPort = maxPort;
+        myAddr = ediv.attributes.filter(function(attr) {
+          return attr.name == 'host'
+        })[0].value
+
+        // override the role. 'normal' is the default, but we want inter-router
+        ediv.attributes.filter(function(attr) {
+          return attr.name == 'role'
+        })[0].selected = 'inter-router';
+        separatedEntities.push(ediv);
+        return true; // stop looping
+      }
+      return false; // continue looping
+    })
+
+    // Add connector tabs for each new link on the topology graph
+    ent = schema.entityTypes['connector'];
+    newLinks.forEach(function(link, i) {
+      var noAnnotations = stripAnnotations('connector', ent, annotations);
+      var ediv = entity('connector', 'connector' + i, " " + link.__data__.source.name, noAnnotations, link.__data__.right, link)
+
+      // override the connector role. 'normal' is the default, but we want inter-router
+      ediv.attributes.filter(function(attr) {
+        return attr.name == 'role'
+      })[0].selected = 'inter-router';
+
+      // find the addr:port of the inter-router listener to use
+      var listener = nodeInfo[link.__data__.source.key]['.listener'];
+      var attrs = listener.attributeNames;
+      for (var i = 0; i < listener.results.length; ++i) {
+        var res = listener.results[i];
+        var role = QDRService.valFor(attrs, res, 'role');
+        if (role == 'inter-router') {
+          ediv.attributes.filter(function(attr) {
+              return attr.name == 'host'
+            })[0].value =
+            QDRService.valFor(attrs, res, 'host')
+          ediv.attributes.filter(function(attr) {
+              return attr.name == 'port'
+            })[0].value =
+            QDRService.valFor(attrs, res, 'port')
+          break;
+        }
+      }
+      if (link.__data__.right) {
+        // connectors from other nodes need to connect to the new router's listener addr:port
+        ediv.attributes.filter(function(attr) {
+          return attr.name == 'port'
+        })[0].value = myPort;
+        ediv.attributes.filter(function(attr) {
+          return attr.name == 'host'
+        })[0].value = myAddr;
+
+        separatedEntities.push(ediv)
+      } else
+        $scope.entities.push(ediv);
+    })
+    Array.prototype.push.apply($scope.entities, separatedEntities);
+
+    // update the description on all the annotation tabs
+    annotationEnts.forEach(function(ent) {
+      var shared = Object.keys(annotations[ent.actualName]);
+      ent.description += " These fields are shared by " + shared.join(" and ") + ".";
+
+    })
+
+    $scope.testPattern = function(attr) {
+      if (attr.rawtype == 'path')
+        return /^(\/)?([^/\0]+(\/)?)+$/;
+      //return /^(.*\/)([^/]*)$/;
+      return /(.*?)/;
+    }
+
+    $scope.attributeDescription = '';
+    $scope.attributeType = '';
+    $scope.attributeRequired = '';
+    $scope.attributeUnique = '';
+    $scope.active = 'router'
+    $scope.fieldsetDivs = "/fieldsetDivs.html"
+    $scope.setActive = function(tabName) {
+      $scope.active = tabName
+    }
+    $scope.isActive = function(tabName) {
+      return $scope.active === tabName
+    }
+    $scope.showDescription = function(attr, e) {
+        $scope.attributeDescription = attr.description;
+        var offset = jQuery(e.currentTarget).offset()
+        jQuery('.attr-description').offset({
+          top: offset.top
+        })
+
+        $scope.attributeType = "Type: " + JSON.stringify(attr.rawtype);
+        $scope.attributeRequired = attr.required ? 'required' : '';
+        $scope.attributeUnique = attr.unique ? 'Must be unique' : '';
+      }
+      // handle the download button click
+      // copy the dialog's values to the original node
+    $scope.download = function() {
+      dialog.close({
+        entities: $scope.entities,
+        annotations: annotations
+      });
+    }
+    $scope.cancel = function() {
+      dialog.close()
+    };
+
+    $scope.selectAnnotationTab = function(tabName) {
+      var tabs = $("#tabs").tabs();
+      tabs.tabs("select", tabName);
+    }
+
+    var initTabs = function() {
+        var div = angular.element("#tabs");
+        if (!div.width()) {
+          setTimeout(initTabs, 100);
+          return;
+        }
+        $("#tabs")
+          .tabs()
+          .addClass('ui-tabs-vertical ui-helper-clearfix');
+      }
+      // start the update loop
+    initTabs();
+
+  });
+
+  QDR.module.controller("QDR.DownloadDialogController", function($scope, QDRService, $templateCache, $window, dialog, results) {
+    var result = results.entities;
+    var annotations = results.annotations;
+    var annotationKeys = Object.keys(annotations);
+    var annotationSections = {};
+
+    // use the router's name as the file name if present
+    $scope.newRouterName = 'router';
+    result.forEach(function(e) {
+      if (e.actualName == 'router') {
+        e.attributes.forEach(function(a) {
+          if (a.name == 'name') {
+            $scope.newRouterName = a.value;
+          }
+        })
+      }
+    })
+    $scope.newRouterName = $scope.newRouterName + ".conf";
+
+    var template = $templateCache.get('config-file-header.html');
+    $scope.verbose = true;
+    $scope.$watch('verbose', function(newVal) {
+      if (newVal !== undefined) {
+        // recreate output using current verbose setting
+        getOutput();
+      }
+    })
+
+    var getOutput = function() {
+      $scope.output = template + '\n';
+      $scope.parts = [];
+      var commentChar = '#'
+      result.forEach(function(entity) {
+          // don't output a section for annotations, they get flattened into the entities
+          var section = "";
+          if (entity.icon) {
+            section += "##\n## Add to " + entity.link.__data__.source.name + "'s configuration file\n##\n";
+          }
+          section += "##\n## " + QDRService.humanify(entity.actualName) + " - " + entity.description + "\n##\n";
+          section += entity.actualName + " {\n";
+          entity.attributes.forEach(function(attribute) {
+            if (attribute.input == 'select')
+              attribute.value = attribute.selected;
+
+            // treat values with all spaces and empty strings as undefined
+            attribute.value = String(attribute.value).trim();
+            if (attribute.value === 'undefined' || attribute.value === '')
+              attribute.value = undefined;
+
+            if ($scope.verbose) {
+              commentChar = attribute.required || attribute.value != attribute['default'] ? ' ' : '#';
+              if (!attribute.value) {
+                commentChar = '#';
+                attribute.value = '';
+              }
+              section += commentChar + "    " + attribute.name + ":" + Array(Math.max(20 - attribute.name.length, 1)).join(" ") + attribute.value + Array(Math.max(20 - ((attribute.value) + "").length, 1)).join(" ") + '# ' + attribute.description + "\n";
+            } else {
+              if (attribute.value) {
+                if (attribute.value != attribute['default'] || attribute.required)
+                  section += "    " + attribute.name + ":" + Array(20 - attribute.name.length).join(" ") + attribute.value + "\n";
+
+              }
+            }
+          })
+          section += "}\n\n";
+          // if entity.icon is true, this is a connector intended for another router
+          if (entity.icon)
+            $scope.parts.push({
+              output: section,
+              link: entity.link,
+              name: entity.link.__data__.source.name,
+              references: entity.references
+            });
+          else
+            $scope.output += section;
+
+          // if this section is actually an annotation
+          if (annotationKeys.indexOf(entity.actualName) > -1) {
+            annotationSections[entity.actualName] = section;
+          }
+        })
+        // go back and add annotation sections to the parts
+      $scope.parts.forEach(function(part) {
+        for (var section in annotationSections) {
+          if (part.references.indexOf(section) > -1) {
+            part.output += annotationSections[section];
+          }
+        }
+      })
+      QDR.log.debug($scope.output);
+    }
+
+    // handle the download button click
+    $scope.download = function() {
+      var output = $scope.output + "\n\n"
+      var blob = new Blob([output], {
+        type: 'text/plain;charset=utf-16'
+      });
+      saveAs(blob, $scope.newRouterName);
+    }
+
+    $scope.downloadPart = function(part) {
+      var linkName = part.link.__data__.source.name + 'additional.conf';
+      var blob = new Blob([part.output], {
+        type: 'text/plain;charset=utf-16'
+      });
+      saveAs(blob, linkName);
+    }
+
+    $scope.done = function() {
+      dialog.close();
+    }
+  });
+
+  return QDR;
+}(QDR || {}));


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