http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/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
new file mode 100644
index 0000000..709702c
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrOverview.js
@@ -0,0 +1,1774 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+'use strict';
+/* global angular d3 */
+
+/**
+ * @module QDR
+ */
+var QDR = (function (QDR) {
+
+  /**
+   *
+   * Controller that handles the QDR overview page
+   */
+  QDR.module.controller('QDR.OverviewController', ['$scope', 'QDRService', 
'$location', '$timeout', '$uibModal', 'uiGridConstants', function($scope, 
QDRService, $location, $timeout, $uibModal) {
+
+    QDR.log.debug('QDR.OverviewControll started with location of ' + 
$location.path() + ' and connection of  ' + 
QDRService.management.connection.is_connected());
+    let updateIntervalHandle = undefined;
+    const updateInterval = 5000;
+
+    const COLUMNSTATEKEY = 'QDRColumnKey.';
+    const OVERVIEWEXPANDEDKEY = 'QDROverviewExpanded';
+    const OVERVIEWACTIVATEDKEY = 'QDROverviewActivated';
+    const FILTERKEY = 'QDROverviewFilters';
+    const OVERVIEWMODEIDS = 'QDROverviewModeIds';
+    let treeRoot;   // the fancytree 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 () { 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 () { 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.linkFields = [];
+    $scope.link = null;
+    $scope.modes = [
+      {title: 'Overview', name: 'Overview', right: false}
+    ];
+
+    $scope.tmplOverviewTree = QDR.templatePath + 'tmplOverviewTree.html';
+    $scope.templates = [
+      { name: 'Charts', url: 'overviewCharts.html'},
+      { 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'}
+    ];
+    let topLevelChildren = [];
+
+    var selectRow = function (gridApi) {
+      if (!gridApi.selection)
+        return;
+      gridApi.selection.on.rowSelectionChanged($scope,function(row){
+        let treeKey = row.grid.options.treeKey;
+        if (treeKey && row.entity[treeKey]) {
+          let key = row.entity[treeKey];
+          $('#overtree').fancytree('getTree').activateKey(key);
+        }
+      });
+    };
+    $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.allRouters = {
+      saveKey: 'allRouters',
+      treeKey: 'nodeId',
+      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,
+      enableHorizontalScrollbar: 0,
+      enableVerticalScrollbar: 0,
+      jqueryUIDraggable: true,
+      enablePaging: true,
+      showFooter: $scope.totalRouters > 50,
+      totalServerItems: 'totalRouters',
+      pagingOptions: $scope.routerPagingOptions,
+      multiSelect: false,
+      enableSelectAll: false,
+      onRegisterApi: selectRow,
+      enableSelectionBatchEvent: false,
+      enableRowHeaderSelection: false,
+      minRowsToShow: 
Math.min(QDRService.management.topology.nodeIdList().length, 50),
+      noUnselect: true,
+    };
+
+    // get info for all routers
+    var allRouterInfo = function (node, callback) {
+      let nodes = {};
+      // gets called each node/entity response
+      var gotNode = function (nodeName, entity, response) {
+        if (!nodes[nodeName])
+          nodes[angular.copy(nodeName)] = {};
+        nodes[nodeName][entity] = response;
+      };
+      // send the requests for all connection and router info for all routers
+      QDRService.management.topology.fetchAllEntities([
+        {entity: 'connection', attrs: ['role']},
+        {entity: 'router'}], function () {
+        // we have all the data now in the nodes object
+        let allRouterFields = [];
+        for (let node in nodes) {
+          let connections = 0;
+          for (let 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;
+          }
+          let routerRow = {connections: connections, nodeId: node, id: 
QDRService.management.topology.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);
+        }
+        $scope.allRouterFields = allRouterFields;
+        expandGridToContent('Routers', $scope.allRouterFields.length);
+        getPagedData($scope.routerPagingOptions.pageSize, 
$scope.routerPagingOptions.currentPage);
+        updateRouterTree(nodeIds);
+        callback(null);
+      }, gotNode);
+    };
+
+    $scope.routerFields = [];
+    $scope.routerGrid = {
+      saveKey: 'routerGrid',
+      data: 'routerFields',
+      columnDefs: [
+        {
+          field: 'attribute',
+          displayName: 'Attribute',
+          saveKey: 'routerGrid',
+        },
+        {
+          field: 'value',
+          displayName: 'Value',
+        }
+      ],
+      jqueryUIDraggable: true,
+      enableColumnResize: true,
+      enableHorizontalScrollbar: 0,
+      enableVerticalScrollbar: 0,
+      minRowsToShow: 25,
+      multiSelect: false
+    };
+
+    $scope.router = null;
+    // get info for a single router
+    var routerInfo = function (node, callback) {
+      $scope.router = node;
+
+      let routerFields = [];
+      $scope.allRouterFields.some( function (field) {
+        if (field.id === node.title) {
+          Object.keys(field).forEach ( function (key) {
+            let attr = (key === 'connections') ? 'External connections' : key;
+            routerFields.push({attribute: attr, value: field[key]});
+          });
+          return true;
+        }
+      });
+      $scope.routerFields = routerFields;
+      expandGridToContent('Router', $scope.routerFields.length);
+      callback(null);
+    };
+
+    $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',
+      treeKey: 'uid',
+      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,
+      enableHorizontalScrollbar: 0,
+      enableVerticalScrollbar: 0,
+      multiSelect: false,
+      enableSelectAll: false,
+      onRegisterApi: selectRow,
+      enableSelectionBatchEvent: false,
+      enableRowHeaderSelection: false,
+      noUnselect: true
+    };
+
+    // get info for all addresses
+    var allAddressInfo = function (address, callback) {
+      let 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_phase = function (addr) {
+        if (!addr)
+          return '-';
+        if (addr[0] == 'M')
+          return addr[1];
+        return '';
+      };
+      var prettyVal = function (val) {
+        return QDRService.utilities.pretty(val || '-');
+      };
+      let addressFields = [];
+      let addressObjs = {};
+      // send the requests for all connection and router info for all routers
+      QDRService.management.topology.fetchAllEntities({entity: 
'router.address'}, function () {
+        for (let node in nodes) {
+          let response = nodes[node]['router.address'];
+          response.results.forEach( function (result) {
+            let address = 
QDRService.utilities.flatten(response.attributeNames, result);
+
+            var addNull = function (oldVal, newVal) {
+              if (oldVal != null && newVal != null)
+                return oldVal + newVal;
+              if (oldVal != null)
+                return oldVal;
+              return newVal;
+            };
+
+            let uid = address.identity;
+            let identity = QDRService.utilities.identity_clean(uid);
+
+            if 
(!addressObjs[QDRService.utilities.addr_text(identity)+QDRService.utilities.addr_class(identity)])
+              
addressObjs[QDRService.utilities.addr_text(identity)+QDRService.utilities.addr_class(identity)]
 = {
+                address: QDRService.utilities.addr_text(identity),
+                'class': QDRService.utilities.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 {
+              let sumObj = 
addressObjs[QDRService.utilities.addr_text(identity)+QDRService.utilities.addr_class(identity)];
+              sumObj.inproc = addNull(sumObj.inproc, address.inProcess);
+              sumObj.local = addNull(sumObj.local, address.subscriberCount);
+              sumObj.remote = addNull(sumObj.remote, address.remoteCount);
+              sumObj['in'] = addNull(sumObj['in'], address.deliveriesIngress);
+              sumObj.out = addNull(sumObj.out, address.deliveriesEgress);
+              sumObj.thru = addNull(sumObj.thru, address.deliveriesTransit);
+              sumObj.toproc = addNull(sumObj.toproc, 
address.deliveriesToContainer);
+              sumObj.fromproc = addNull(sumObj.fromproc, 
address.deliveriesFromContainer);
+            }
+          });
+        }
+        for (let 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;}
+        );
+        addressFields[0].title = addressFields[0].address;
+        for (let 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;
+        }
+        $scope.addressesData = addressFields;
+        expandGridToContent('Addresses', $scope.addressesData.length);
+        getAddressPagedData($scope.addressPagingOptions.pageSize, 
$scope.addressPagingOptions.currentPage);
+        // repopulate the tree's child nodes
+        updateAddressTree(addressFields);
+        callback(null);
+      }, gotNode);
+    };
+
+    var updateLinkGrid = function ( linkFields ) {
+      // apply the filter
+      let filteredLinks = linkFields.filter( function (link) {
+        let include = true;
+        if ($scope.filter.endpointsOnly === 'true') {
+          if (link.linkType !== 'endpoint')
+            include = false;
+        }
+        if ($scope.filter.hideConsoles) {
+          if 
(QDRService.utilities.isConsole(QDRService.management.topology.getConnForLink(link)))
+            include = false;
+        }
+        return include;
+      });
+      QDR.log.info('setting linkFields in updateLinkGrid');
+      $scope.linkFields = filteredLinks;
+      expandGridToContent('Links', $scope.linkFields.length);
+      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
+        let 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 fancytree when a 
link is selected
+          // It is passed a fancytree node. We need to simulate that node type 
to update the link grid
+          linkInfo({data: {title: links[0].title, fields: links[0]}}, function 
() {$timeout(function (){});});
+        }
+      }
+    };
+
+    // get info for a all links
+    $scope.linkPagingOptions = {
+      pageSizes: [50, 100, 500],
+      pageSize: 50,
+      currentPage: 1
+    };
+    var getLinkPagedData = function (pageSize, page) {
+      $scope.totalLinks = $scope.linkFields.length;
+      $scope.linksGrid.showFooter = $scope.totalLinks > 50;
+      $scope.pagedLinkData = $scope.linkFields.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',
+      treeKey: 'uid',
+      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,
+      enableHorizontalScrollbar: 0,
+      enableVerticalScrollbar: 0,
+      enableColumnReordering: true,
+      showColumnMenu: true,
+      rowTemplate: 'linkRowTemplate.html',
+      minRowsToShow: 15,
+      multiSelect: false,
+      enableSelectAll: false,
+      onRegisterApi: selectRow,
+      enableSelectionBatchEvent: false,
+      enableRowHeaderSelection: false,
+      noUnselect: true
+    };
+
+    $scope.$on('ngGridEventColumns', function (e, columns) {
+      let saveInfo = columns.map( function (col) {
+        return [col.width, col.visible];
+      });
+      let saveKey = columns[0].colDef.saveKey;
+      if (saveKey)
+        localStorage.setItem(COLUMNSTATEKEY+saveKey, JSON.stringify(saveInfo));
+    });
+
+    var allLinkInfo = function (node, callback) {
+      var gridCallback = function (linkFields) {
+        QDRService.management.topology.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);
+          callback(null);
+        });
+      };
+      getAllLinkFields([gridCallback]);
+    };
+
+    var getAllLinkFields = function (completionCallbacks, selectionCallback) {
+      if (!$scope.linkFields) {
+        QDR.log.debug('$scope.linkFields was not defined!');
+        return;
+      }
+      let nodeIds = QDRService.management.topology.nodeIdList();
+      let linkFields = [];
+      let now = Date.now();
+      var rate = function (linkName, response, result) {
+        if (!$scope.linkFields)
+          return 0;
+        let oldname = $scope.linkFields.filter(function (link) {
+          return link.link === linkName;
+        });
+        if (oldname.length === 1) {
+          let elapsed = (now - oldname[0].timestamp) / 1000;
+          if (elapsed < 0)
+            return 0;
+          let delivered = QDRService.utilities.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;
+        }
+      };
+      let expected = nodeIds.length;
+      let received = 0;
+      var gotLinkInfo = function (nodeName, entity, response) {
+        if (response.results)
+          response.results.forEach( function (result) {
+            var prettyVal = function (field) {
+              let fieldIndex = response.attributeNames.indexOf(field);
+              if (fieldIndex < 0) {
+                return '-';
+              }
+              let val = result[fieldIndex];
+              return QDRService.utilities.pretty(val);
+            };
+            var uncounts = function () {
+              let und = QDRService.utilities.valFor(response.attributeNames, 
result, 'undeliveredCount');
+              let uns = QDRService.utilities.valFor(response.attributeNames, 
result, 'unsettledCount');
+              return QDRService.utilities.pretty(und + uns);
+            };
+            var getLinkName = function () {
+              let namestr = 
QDRService.management.topology.nameFromId(nodeName);
+              return namestr + ':' + 
QDRService.utilities.valFor(response.attributeNames, result, 'identity');
+            };
+            var fixAddress = function () {
+              let addresses = [];
+              let owningAddr = 
QDRService.utilities.valFor(response.attributeNames, result, 'owningAddr') || 
'';
+              let rawAddress = owningAddr;
+              /*
+                 - "L*"  =>  "* (local)"
+                 - "M0*" =>  "* (direct)"
+                 - "M1*" =>  "* (dequeue)"
+                 - "MX*" =>  "* (phase X)"
+            */
+              let address = undefined;
+              let starts = {'L': '(local)', 'M0': '(direct)', 'M1': 
'(dequeue)'};
+              for (let start in starts) {
+                if (owningAddr.startsWith(start)) {
+                  let 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') {
+                    let phase = parseInt(owningAddr.substr(1));
+                    if (phase && !isNaN(phase)) {
+                      let phaseStr = phase + '';
+                      let star = owningAddr.substr(2 + phaseStr.length);
+                      address = star + ' ' + '(phase ' + phaseStr + ')';
+                    }
+                  }
+                }
+              }
+              addresses[0] = address || owningAddr;
+              addresses[1] = rawAddress;
+              return addresses;
+            };
+            if (!selectionCallback || selectionCallback(response, result)) {
+              let adminStatus = 
QDRService.utilities.valFor(response.attributeNames, result, 'adminStatus');
+              let operStatus = 
QDRService.utilities.valFor(response.attributeNames, result, 'operStatus');
+              let linkName = getLinkName();
+              let linkType = 
QDRService.utilities.valFor(response.attributeNames, result, 'linkType');
+              let addresses = fixAddress();
+              let link = QDRService.utilities.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.utilities.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.management.topology.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',
+      treeKey: 'uid',
+      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,
+      enableHorizontalScrollbar: 0,
+      enableVerticalScrollbar: 0,
+      multiSelect: false,
+      enableSelectAll: false,
+      onRegisterApi: selectRow,
+      enableSelectionBatchEvent: false,
+      enableRowHeaderSelection: false,
+      noUnselect: true
+    };
+    // get info for a all connections
+    var allConnectionInfo = function (connection, callback) {
+      getAllConnectionFields([updateConnectionGrid, updateConnectionTree, 
function () {callback(null);}]);
+    };
+    // called after conection data is available
+    var updateConnectionGrid = function (connectionFields) {
+      $scope.allConnectionFields = connectionFields;
+      expandGridToContent('Connections', $scope.allConnectionFields.length);
+      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) {
+      let nodeIds = QDRService.management.topology.nodeIdList();
+      let connectionFields = [];
+      let expected = nodeIds.length;
+      let received = 0;
+      let gotConnectionInfo = function (nodeName, entity, response) {
+        response.results.forEach( function (result) {
+
+          let auth = 'no_auth';
+          let connection = 
QDRService.utilities.flatten(response.attributeNames, result);
+          let sasl = connection.sasl;
+          if (connection.isAuthenticated) {
+            auth = sasl;
+            if (sasl === 'ANONYMOUS')
+              auth = 'anonymous-user';
+            else {
+              if (sasl === 'GSSAPI')
+                sasl = 'Kerberos';
+              if (sasl === 'EXTERNAL')
+                sasl = 'x.509';
+              auth = connection.user + '(' + connection.sslCipher + ')';
+            }
+          }
+
+          let sec = 'no-security';
+          if (connection.isEncrypted) {
+            if (sasl === 'GSSAPI')
+              sec = 'Kerberos';
+            else
+              sec = connection.sslProto + '(' + connection.sslCipher + ')';
+          }
+
+          let host = connection.host;
+          let connField = {
+            host: host,
+            security: sec,
+            authentication: auth,
+            routerId: nodeName,
+            uid: host + connection.container + connection.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+a.container+a.identity < b.host+b.container+b.identity ?
+            -1 : a.host+a.container+a.identity > b.host+b.container+b.identity 
? 1 : 0;});
+          callbacks.forEach( function (cb) {
+            cb(connectionFields);
+          });
+        }
+      };
+      nodeIds.forEach( function (nodeId) {
+        QDRService.management.topology.fetchEntity(nodeId, 'connection', [], 
gotConnectionInfo);
+      });
+    };
+
+    $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,
+      enableHorizontalScrollbar: 0,
+      enableVerticalScrollbar: 0,
+      minRowsToShow: 11,
+      jqueryUIDraggable: true,
+      multiSelect: false
+    };
+
+    // get info for a single address
+    var addressInfo = function (address, callback) {
+      $scope.address = address;
+      let currentEntity = getCurrentLinksEntity();
+      // we are viewing the addressLinks page
+      if (currentEntity === 'Address' && 
entityModes[currentEntity].currentModeId === 'links') {
+        updateModeLinks();
+        callback(null);
+        return;
+      }
+
+      $scope.addressFields = [];
+      let 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]});
+      });
+      expandGridToContent('Address', $scope.addressFields.length);
+      callback(null);
+    };
+
+    $scope.singleLinkFields = [];
+    $scope.linkGrid = {
+      saveKey: 'linkGrid',
+      data: 'singleLinkFields',
+      columnDefs: [
+        {
+          field: 'attribute',
+          displayName: 'Attribute',
+          width: '40%'
+        },
+        {
+          field: 'value',
+          displayName: 'Value',
+          width: '40%'
+        }
+      ],
+      enableColumnResize: true,
+      enableHorizontalScrollbar: 0,
+      enableVerticalScrollbar: 0,
+      minRowsToShow: 24,
+      jqueryUIDraggable: true,
+      multiSelect: false
+    };
+
+    // display the grid detail info for a single link
+    var linkInfo = function (link, callback) {
+      if (!link) {
+        callback(null);
+        return;
+      }
+      $scope.link = link;
+
+      $scope.singleLinkFields = [];
+      let fields = Object.keys(link.data.fields);
+      let 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]});
+      });
+      expandGridToContent('Link', $scope.singleLinkFields.length);
+      callback(null);
+    };
+
+    // get info for a single connection
+    $scope.gridModes = [{
+      content: '<a><i class="icon-list"></i> Attributes</a>',
+      id: 'attributes',
+      title: 'View attributes'
+    },
+    {
+      content: '<a><i class="icon-link"></i> Links</a>',
+      id: 'links',
+      title: 'Show links'
+    }
+    ];
+    var saveModeIds = function () {
+      let 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();
+    let entityModes = {
+      Address: {
+        currentModeId: savedModeIds.Address,
+        filter: function (response, result) {
+          let owningAddr = 
QDRService.utilities.valFor(response.attributeNames, result, 'owningAddr');
+          return (owningAddr === $scope.address.data.fields.uid);
+        }
+      },
+      Connection: {
+        currentModeId: savedModeIds.Connection,
+        filter: function (response, result) {
+          let connectionId = 
QDRService.utilities.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') {
+        $scope.linkFields = [];
+        getLinkPagedData($scope.linkPagingOptions.pageSize, 
$scope.linkPagingOptions.currentPage);
+        updateModeLinks();
+      }
+      updateExpanded();
+    };
+    $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) {
+      $scope.linkFields = linkFields;
+      expandGridToContent('Link', $scope.linkFields.length);
+      getLinkPagedData($scope.linkPagingOptions.pageSize, 
$scope.linkPagingOptions.currentPage);
+    };
+    // based on which entity is selected, get and filter the links
+    var updateModeLinks = function () {
+      let currentEntity = getCurrentLinksEntity();
+      if (!currentEntity)
+        return;
+      getAllLinkFields([updateEntityLinkGrid], 
entityModes[currentEntity].filter);
+    };
+    var getCurrentLinksEntity = function () {
+      let currentEntity;
+      let active = $('#overtree').fancytree('getActiveNode');
+      if (active) {
+        currentEntity = active.type ? active.type : active.data.fields.type;
+      }
+      return currentEntity;
+    };
+
+    $scope.quiesceLinkClass = function (row) {
+      let stateClassMap = {
+        enabled: 'btn-primary',
+        disabled: 'btn-danger'
+      };
+      return stateClassMap[row.entity.adminStatus];
+    };
+
+    $scope.quiesceLink = function (row, $event) {
+      $event.stopPropagation();
+      QDRService.management.topology.quiesceLink(row.entity.nodeId, 
row.entity.name)
+        .then( function (results, context) {
+          let statusCode = context.message.application_properties.statusCode;
+          if (statusCode < 200 || statusCode >= 300) {
+            QDR.Core.notification('error', context.message.statusDescription);
+            QDR.log.error('Error ' + context.message.statusDescription);
+          }
+        });
+    };
+
+    $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').fancytree('getRoot').visit(function(node){
+        node.expand(true);
+      });
+    };
+    $scope.contractAll = function () {
+      $('#overtree').fancytree('getRoot').visit(function(node){
+        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,
+      enableHorizontalScrollbar: 0,
+      enableVerticalScrollbar: 0,
+      minRowsToShow: 21,
+      jqueryUIDraggable: true,
+      multiSelect: false
+    };
+
+    var connectionInfo = function (connection, callback) {
+      if (!connection) {
+        callback(null);
+        return;
+      }
+      $scope.connection = connection;
+
+      let currentEntity = getCurrentLinksEntity();
+      // we are viewing the connectionLinks page
+      if (currentEntity === 'Connection' && 
entityModes[currentEntity].currentModeId === 'links') {
+        updateModeLinks();
+        callback(null);
+        return;
+      }
+
+      $scope.connectionFields = [];
+      for (let field in connection.data.fields) {
+        if (field != 'title' && field != 'uid')
+          $scope.connectionFields.push({attribute: field, value: 
connection.data.fields[field]});
+      }
+      expandGridToContent('Connection', $scope.connectionFields.length);
+      callback(null);
+    };
+
+
+    const logModuleCellTemplate = '<div class="ui-grid-cell-contents" 
ng-click="grid.appScope.logInfoFor(row, col)">{{COL_FIELD CUSTOM_FILTERS | 
pretty}}</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,
+      enableHorizontalScrollbar: 0,
+      enableVerticalScrollbar: 0,
+      jqueryUIDraggable: true,
+      multiSelect: false,
+      enableSelectAll: false,
+      onRegisterApi: selectRow,
+      enableSelectionBatchEvent: false,
+      enableRowHeaderSelection: false,
+      minRowsToShow: 
Math.min(QDRService.management.topology.nodeIdList().length, 50),
+      noUnselect: true
+    };
+
+    $scope.logInfoFor = function (row, col) {
+      logDialog(row, col);
+    };
+
+    function logDialog(row, col) {
+      let 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 () {
+        QDR.log.debug('d.open().then');
+      }, function () {
+        QDR.log.debug('Modal dismissed at: ' + new Date());
+      });
+    }
+
+    const 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',
+      treeKey: 'name',
+      data: 'allLogFields',
+      columnDefs: [
+        {
+          field: 'name',
+          saveKey: 'allLogGrid',
+          displayName: 'Module'
+        },
+        {
+          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,
+      enableHorizontalScrollbar: 0,
+      enableVerticalScrollbar: 0,
+      minRowsToShow: 17,
+      multiSelect: false,
+      enableSelectAll: false,
+      onRegisterApi: selectRow,
+      enableSelectionBatchEvent: false,
+      enableRowHeaderSelection: false,
+      noUnselect: true
+    };
+
+    let allLogEntries = {};
+    var allLogInfo = function (log, callback) {
+      // update the count of entries for each module
+      $scope.allLogFields = [];
+      let logResults = {};
+      let logDetails = {};
+
+      var gotLogStats = function (node, entity, response) {
+        logDetails[node] = [];
+        response.results.forEach( function (result) {
+          let oresult = QDRService.utilities.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 () {
+        let sortedModules = Object.keys(logResults);
+        sortedModules.sort(function (a,b) {return a<b?-1:a>b?1:0;});
+        $scope.allLogFields = [];
+        sortedModules.forEach( function (module) {
+          $scope.allLogFields.push(logResults[module]);
+        });
+        expandGridToContent('Logs', $scope.allLogFields.length);
+        allLogEntries = logDetails;
+        updateLogTree($scope.allLogFields);
+        callback(null);
+      };
+      QDRService.management.topology.fetchAllEntities({entity: 'logStats'}, 
gotAllLogStats, gotLogStats);
+    };
+
+    var expandGridToContent = function (type, rows) {
+      let tree = $('#overtree').fancytree('getTree');
+      let node = null;
+      if (tree && tree.getActiveNode)
+        node = tree.getActiveNode();
+      if (node) {
+        if (node.type === type || node.data.type === type) {
+          let height = (rows+1) * 30 + 46; // header is 40px
+          let gridDetails = $('#overview-controller .grid');
+          gridDetails.css('height', height + 'px');
+        }
+      }
+    };
+
+    $scope.logFields = [];
+    // get info for a single log
+    var logInfo = function (node, callback) {
+
+      var gotLogInfo = function (responses) {
+        $scope.logModuleData = [];
+        $scope.logModule.module = node.key;
+        for (let n in responses) {
+          let moduleIndex = 
responses[n]['log'].attributeNames.indexOf('module');
+          let result = responses[n]['log'].results.filter( function (r) {
+            return r[moduleIndex] === node.key;
+          })[0];
+          let logInfo = 
QDRService.utilities.flatten(responses[n]['log'].attributeNames, result);
+          let entry = allLogEntries[n];
+          entry.forEach( function (module) {
+            if (module.name === node.key) {
+              module.nodeName = QDRService.management.topology.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;});
+        expandGridToContent('Log', $scope.logModuleData.length);
+        callback(null);
+      };
+      QDRService.management.topology.fetchAllEntities({entity: 'log', attrs: 
['module', 'enable']}, gotLogInfo);
+    };
+
+    var getExpandedList = function () {
+      if (!treeRoot)
+        return;
+      let list = [];
+      if (treeRoot.visit) {
+        treeRoot.visit(function(node){
+          if (node.isExpanded()) {
+            list.push(node.data.parentKey);
+          }
+        });
+      }
+      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;
+    };
+    // loads list that was saved
+    var loadExpandedNodeList = function () {
+      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
+    var saveExpanded = function () {
+      let list = getExpandedList();
+      localStorage[OVERVIEWEXPANDEDKEY] = JSON.stringify(list);
+      expandedNodeList = list;
+    };
+
+    var setTemplate = function (node) {
+      let type = node.type;
+      let template = $scope.templates.filter( function (tpl) {
+        return tpl.name == type;
+      });
+      $timeout( function () {
+        $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 onTreeNodeActivated = function (event, data) {
+      saveExpanded();
+      saveActivated(data.node.key);
+      $scope.ActivatedKey = data.node.key;
+      setTemplate(data.node);
+      updateExpanded();
+    };
+
+    var onTreeNodeExpanded = function () {
+      saveExpanded();
+      updateExpanded();
+    };
+
+    if (!QDRService.management.connection.is_connected()) {
+      QDR.redirectWhenConnected($location, 'overview');
+      return;
+    }
+    $scope.template = $scope.templates[0];
+
+    /* --------------------------------------------------
+     *
+     * setup the tree on the left
+     *
+     * -------------------------------------------------
+     */
+    var getActiveChild = function (node) {
+      let active = node.children.filter(function (child) {
+        return child.isActive();
+      });
+      if (active.length > 0)
+        return active[0].key;
+      return null;
+    };
+    // utility function called by each top level tree node when it needs to 
populate its child nodes
+    var updateLeaves = function (leaves, entity, worker) {
+      let tree = $('#overtree').fancytree('getTree'), node;
+      if (tree && tree.getNodeByKey) {
+        node = tree.getNodeByKey(entity);
+      }
+      if (!tree || !node) {
+        return;
+      }
+      let wasActive = node.isActive();
+      let wasExpanded = node.isExpanded();
+      let activeChildKey = getActiveChild(node);
+      node.removeChildren();
+      let children = [];
+      leaves.forEach( function (leaf) {
+        children.push(worker(leaf));
+      });
+      node.addNode(children);
+      // top level node was expanded
+      if (wasExpanded)
+        node.setExpanded(true, {noAnimation: true, noEvents: true});
+      if (wasActive) {
+        node.setActive(true, {noAnimation: true, noEvents: true});
+      } else {
+        // re-active the previously active child node
+        if (activeChildKey) {
+          let newNode = tree.getNodeByKey(activeChildKey);
+          // the node may not be there after the update
+          if (newNode)
+            newNode.setActive(true, {noAnimation: true, noEvents: true}); // 
fires the onTreeNodeActivated event for this node
+        }
+      }
+      resizer();
+    };
+
+    var resizer = function () {
+      // this forces the tree and the grid to be the size of the browser 
window.
+      // the effect is that the tree and the grid will have vertical scroll 
bars if needed.
+      // the alternative is to let the tree and grid determine the size of the 
page and have
+      // the scroll bar on the window
+      //let viewport = $('#overview-controller .pane-viewport');
+      //viewport.height( window.innerHeight - viewport.offset().top);
+
+      // don't allow HTML in the tree titles
+      $('.fancytree-title').each( function () {
+        let unsafe = $(this).html();
+        $(this).html(unsafe.replace(/</g, '&lt;').replace(/>/g, '&gt;'));
+      });
+
+      // remove the comments to allow the tree to take all the height it needs
+
+      let gridDetails = $('#overview-controller .grid');
+      if (gridDetails.offset())
+        gridDetails.height( window.innerHeight - gridDetails.offset().top);
+
+      let gridViewport = $('#overview-controller .ui-grid-viewport');
+      if (gridViewport.offset())
+        gridViewport.height( window.innerHeight - gridViewport.offset().top );
+      //gridViewport.
+
+    };
+    $(window).resize(resizer);
+
+    // get saved tree state
+    $scope.ActivatedKey = loadActivatedNode();
+    let expandedNodeList = loadExpandedNodeList();
+
+    var showCharts = function () {
+
+    };
+    let charts = new QDR.Folder('Charts');
+    charts.info = {fn: showCharts};
+    charts.type = 'Charts';  // for the charts template
+    charts.key = 'Charts';
+    charts.extraClasses = 'charts';
+    topLevelChildren.push(charts);
+
+    // create a routers tree branch
+    let routers = new QDR.Folder('Routers');
+    routers.type = 'Routers';
+    routers.info = {fn: allRouterInfo};
+    routers.expanded = (expandedNodeList.indexOf('Routers') > -1);
+    routers.key = 'Routers';
+    routers.parentKey= 'Routers';
+    routers.extraClasses = 'routers';
+    topLevelChildren.push(routers);
+    // called when the list of routers changes
+    var updateRouterTree = function (nodes) {
+      var worker = function (node) {
+        let name = QDRService.management.topology.nameFromId(node);
+        let router = new QDR.Leaf(name);
+        router.type = 'Router';
+        router.info = {fn: routerInfo};
+        router.nodeId = node;
+        router.key = node;
+        router.extraClasses = 'router';
+        router.parentKey = 'Routers';
+        return router;
+      };
+      updateLeaves(nodes, 'Routers', worker);
+    };
+
+    // create an addresses tree branch
+    let addresses = new QDR.Folder('Addresses');
+    addresses.type = 'Addresses';
+    addresses.info = {fn: allAddressInfo};
+    addresses.expanded = (expandedNodeList.indexOf('Addresses') > -1);
+    addresses.key = 'Addresses';
+    addresses.parentKey = 'Addresses';
+    addresses.extraClasses = 'addresses';
+    topLevelChildren.push(addresses);
+    var updateAddressTree = function (addressFields) {
+      var worker = function (address) {
+        let a = new QDR.Leaf(address.title);
+        a.info = {fn: addressInfo};
+        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.extraClasses = a.tooltip;
+        a.parentKey = 'Addresses';
+        return a;
+      };
+      updateLeaves(addressFields, 'Addresses', worker);
+    };
+
+    $scope.$watch('filter', function (newValue, oldValue) {
+      if (newValue !== oldValue) {
+        allLinkInfo(null, function () {$timeout(function (){});});
+        localStorage[FILTERKEY] = JSON.stringify($scope.filter);
+      }
+    }, true);
+
+    $scope.filterToggle = function () {
+      let filter = $('#linkFilter');
+      filter.toggle();
+    };
+
+    $scope.filter = angular.fromJson(localStorage[FILTERKEY]) || 
{endpointsOnly: 'true', hideConsoles: true};
+    let links = new QDR.Folder('Links');
+    links.type = 'Links';
+    links.info = {fn: allLinkInfo};
+    links.expanded = (expandedNodeList.indexOf('Links') > -1);
+    links.key = 'Links';
+    links.parentKey = 'Links';
+    links.extraClasses = 'links';
+    topLevelChildren.push(links);
+
+    // called both before the tree is created and whenever a background update 
is done
+    var updateLinkTree = function (linkFields) {
+      var worker = function (link) {
+        let l = new QDR.Leaf(link.title);
+        let isConsole = 
QDRService.utilities.isConsole(QDRService.management.topology.getConnForLink(link));
+        l.info = {fn: linkInfo};
+        l.key = link.uid;
+        l.fields = link;
+        l.type = 'Link';
+        l.parentKey = 'Links';
+        if (isConsole)
+          l.tooltip = 'console link';
+        else
+          l.tooltip = link.linkType  + ' link';
+        l.extraClasses = l.tooltip;
+        return l;
+      };
+      updateLeaves(linkFields, 'Links', worker);
+    };
+
+    let connections = new QDR.Folder('Connections');
+    connections.type = 'Connections';
+    connections.info = {fn: allConnectionInfo};
+    connections.expanded = (expandedNodeList.indexOf('Connections') > -1);
+    connections.key = 'Connections';
+    connections.parentKey = 'Connections';
+    connections.extraClasses = 'connections';
+    topLevelChildren.push(connections);
+
+    var updateConnectionTree = function (connectionFields) {
+      var worker = function (connection) {
+        let host = connection.host;
+        if (connection.name === 'connection/' && connection.role === 
'inter-router' && connection.host === '')
+          host = connection.container + ':' + connection.identity;
+
+        let c = new QDR.Leaf(host);
+        let isConsole = QDRService.utilities.isAConsole 
(connection.properties, connection.identity, connection.role, 
connection.routerId);
+        c.type = 'Connection';
+        c.info = {fn: connectionInfo};
+        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.extraClasses = c.tooltip;
+        c.parentKey = 'Connections';
+        return c;
+      };
+      updateLeaves(connectionFields, 'Connections', worker);
+    };
+
+    var updateLogTree = function (logFields) {
+      var worker = function (log) {
+        let l = new QDR.Leaf(log.name);
+        l.type = 'Log';
+        l.info = {fn: logInfo};
+        l.key = log.name;
+        l.parentKey = 'Logs';
+        l.extraClasses = 'log';
+        return l;
+      };
+      updateLeaves(logFields, 'Logs', worker);
+    };
+
+    let htmlReady = false;
+    let dataReady = false;
+    $scope.largeNetwork = QDRService.management.topology.isLargeNetwork();
+    let logs = new QDR.Folder('Logs');
+    logs.type = 'Logs';
+    logs.info = {fn: allLogInfo};
+    logs.expanded = (expandedNodeList.indexOf('Logs') > -1);
+    logs.key = 'Logs';
+    logs.parentKey = 'Logs';
+    if (QDRService.management.connection.versionCheck('0.8.0'))
+      topLevelChildren.push(logs);
+
+    var onTreeInitialized = function (event, data) {
+      treeRoot = data.tree;
+      let activeNode = treeRoot.getNodeByKey($scope.ActivatedKey);
+      if (activeNode)
+        activeNode.setActive(true);
+      updateExpanded();
+    };
+    var initTreeAndGrid = function () {
+      if (!htmlReady || !dataReady)
+        return;
+      let div = angular.element('#overtree');
+      if (!div.width()) {
+        setTimeout(initTreeAndGrid, 100);
+        return;
+      }
+      $('#overtree').fancytree({
+        activate:       onTreeNodeActivated,
+        expand:         onTreeNodeExpanded,
+        init:           onTreeInitialized,
+        autoCollapse:   $scope.largeNetwork,
+        activeVisible:! $scope.largeNetwork,
+        clickFolderMode: 1,
+        source:         topLevelChildren
+      });
+    };
+
+    $scope.overviewLoaded = function () {
+      htmlReady = true;
+      initTreeAndGrid();
+    };
+
+    let nodeIds = QDRService.management.topology.nodeIdList();
+    // add placeholders for the top level tree nodes
+    let topLevelTreeNodes = [routers, addresses, links, connections, logs];
+    topLevelTreeNodes.forEach( function (parent) {
+      let placeHolder = new QDR.Leaf('loading...');
+      placeHolder.extraClasses = 'loading';
+      parent.children = [placeHolder];
+    });
+
+    var updateExpanded = function () {
+      QDR.log.debug('updateExpandedEntities');
+      clearTimeout(updateIntervalHandle);
+
+      let tree = $('#overtree').fancytree('getTree');
+      if (tree && tree.visit) {
+        let q = d3.queue(10);
+        tree.visit( function (node) {
+          if (node.isActive() || node.isExpanded()) {
+            q.defer(node.data.info.fn, node);
+          }
+        });
+        q.await( function (error) {
+          if (error)
+            QDR.log.error(error.message);
+
+          // if there are no active nodes, activate the saved one
+          let tree = $('#overtree').fancytree('getTree');
+          if (!tree.getActiveNode()) {
+            let active = tree.getNodeByKey($scope.ActivatedKey);
+            if (active) {
+              active.setActive(true);
+            } else {
+              tree.getNodeByKey('Routers').setActive(true);
+            }
+          }
+
+          // fancytree animations sometimes stop short of shrinking its 
placeholder spans
+          d3.selectAll('.ui-effects-placeholder').style('height', '0px');
+          // once all expanded tree nodes have been update, schedule another 
update
+          $timeout(function () {
+            updateIntervalHandle = setTimeout(updateExpanded, updateInterval);
+          });
+        });
+      }
+    };
+
+    dataReady = true;
+    initTreeAndGrid();
+    $scope.$on('$destroy', function() {
+      clearTimeout(updateIntervalHandle);
+      $(window).off('resize', resizer);
+    });
+  }]);
+
+  return QDR;
+
+}(QDR || {}));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/js/qdrOverviewChartsController.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrOverviewChartsController.js 
b/console/stand-alone/plugin/js/qdrOverviewChartsController.js
new file mode 100644
index 0000000..e47e597
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrOverviewChartsController.js
@@ -0,0 +1,134 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+'use strict';
+/* global angular */
+/**
+ * @module QDR
+ */
+var QDR = (function(QDR) {
+
+  QDR.module.controller('QDR.OverviewChartsController', function ($scope, 
QDRService, QDRChartService, $timeout) {
+
+    $scope.overviewCharts = [];
+    let updateTimer;
+
+    // called when it is time to update the chart's data
+    var sum = function (request, saveResponse) {
+      let attrs = angular.copy(request.overrideAttrs);
+      let totalAttr = attrs.shift(); // remove totals attr
+      let attrEntities = [{entity: request.entity, attrs: attrs}];
+      QDRService.management.topology.fetchAllEntities(attrEntities, function 
(responses) {
+        let total = 0;
+        let response = {attributeNames: [totalAttr, 'name'], results: [[]]};
+        // for each router
+        for (let router in responses) {
+          let record = responses[router][request.entity];
+          let accessor = charts.find( function (chart) { return chart.nodeId 
=== request.nodeId;}).accessor;
+          // for each attribute-value (ie each address or each link)
+          for (let i=0; i<record.results.length; i++) {
+            total += accessor(record.attributeNames, record.results[i]);
+          }
+        }
+        response.results[0][0] = total;
+        response.results[0][1] = request.names()[0];
+        saveResponse(request.nodeId, request.entity, response);
+      });
+    };
+    let charts = [
+
+      {
+        nodeId:     '///Throughput/',
+        entity:     'router.address',
+        name:       'throughput',
+        overrideAttrs: ['throughput', 'deliveriesEgress'],
+        attr:       'throughput',
+        type:       'rate',
+        hideLabel:  true,
+        hideLegend:  true,
+        rateWindow: 5000,   // update data once every 5 seconds
+        visibleDuration: 1, // show data for the last 1 minute
+        forceCreate: true,
+        accessor: function (attributes, results) {
+          return results[attributes.indexOf('deliveriesEgress')];
+        },
+        override: sum  // called to fetch the chart data
+      },
+      {
+        nodeId:       '///Outstanding-Deliveries/',
+        entity:       'router.link',
+        name:         'outstandingDeliveries',
+        overrideAttrs:['outstandingDeliveries', 'undeliveredCount', 
'unsettledCount', 'linkType', 'linkDir'],
+        attr:         'outstandingDeliveries',
+        hideLabel:  true,
+        hideLegend:  true,
+        visibleDuration: 1, // show data for the last 1 minute
+        forceCreate:  true,
+        accessor: function (attributes, results) {
+          return results[attributes.indexOf('linkType')] === 'endpoint' && 
results[attributes.indexOf('linkDir')] === 'out'
+            ? results[attributes.indexOf('unsettledCount')] + 
results[attributes.indexOf('undeliveredCount')]
+            : 0;
+        },
+        now: new Date(),
+        override: sum  // called to fetch the chart data
+      }
+    ];
+    $scope.overviewCharts = charts.map( function (chart) {
+      let c = QDRChartService.registerChart(chart);
+      return new QDRChartService.pfAreaChart(c, c.id(), true);
+    });
+
+
+    // redraw the chart every update period
+    var updateCharts = function () {
+      $scope.overviewCharts.forEach(function (svgChart) {
+        svgChart.tick(svgChart.chart.id()); // on this page we are using the 
chart.id() as the div id in which to render the chart
+      });
+    };
+
+    var createCharts = function () {
+      // ensure the div for our chart is loaded in the dom
+      let div = angular.element('.chartContainer');
+      if (!div.width()) {
+        setTimeout(createCharts, 100);
+        return;
+      }
+      // create an svg object for each chart
+      $scope.overviewCharts.forEach ( function (c) {
+        // tell c3 to create the svg
+        c.generate(360);
+      });
+      // redraw the charts once every second
+      const updateRate = localStorage['updateRate'] ?  
localStorage['updateRate'] : 1000;
+      updateTimer = setInterval(updateCharts, updateRate);
+    };
+    $timeout( function () {
+      createCharts();
+    });
+
+    $scope.$on('$destroy', function() {
+      if (updateTimer)
+        clearInterval(updateTimer);
+      $scope.overviewCharts.forEach( function (svg) {
+        QDRChartService.unRegisterChart(svg.chart);
+      });
+    });
+  });
+  return QDR;
+
+} (QDR || {}));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/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
new file mode 100644
index 0000000..1e541a5
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrOverviewLogsController.js
@@ -0,0 +1,70 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+'use strict';
+/**
+ * @module QDR
+ */
+var QDR = (function(QDR) {
+
+  QDR.module.controller('QDR.OverviewLogsController', function ($scope, 
$uibModalInstance, QDRService, $timeout, nodeName, nodeId, module, level) {
+
+    var gotLogInfo = function (nodeId, response, context) {
+      let statusCode = context.message.application_properties.statusCode;
+      if (statusCode < 200 || statusCode >= 300) {
+        QDR.Core.notification('error', context.message.statusDescription);
+        QDR.log.info('Error ' + context.message.statusDescription);
+      } else {
+        let levelLogs = response.filter( function (result) {
+          if (result[1] == null)
+            result[1] = 'error';
+          return result[1].toUpperCase() === level.toUpperCase() && result[0] 
=== module;
+        });
+        let logFields = levelLogs.map( function (result) {
+          return {
+            nodeId: QDRService.management.topology.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.management.connection.sendMethod(nodeId, undefined, {}, 
'GET-LOG', {module: module})
+      .then( function (response) {gotLogInfo(nodeId, response.response, 
response.context);});
+
+    $scope.loading = true;
+    $scope.module = module;
+    $scope.level = level;
+    $scope.nodeName = nodeName;
+    $scope.logFields = [];
+    $scope.ok = function () {
+      $uibModalInstance.close(true);
+    };
+
+  });
+  return QDR;
+
+} (QDR || {}));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/js/qdrSchema.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrSchema.js 
b/console/stand-alone/plugin/js/qdrSchema.js
new file mode 100644
index 0000000..69569ea
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrSchema.js
@@ -0,0 +1,83 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+'use strict';
+/**
+ * @module QDR
+ */
+var QDR = (function (QDR) {
+
+  QDR.module.controller('QDR.SchemaController', ['$scope', '$location', 
'$timeout', 'QDRService', function($scope, $location, $timeout, QDRService) {
+    if (!QDRService.management.connection.is_connected()) {
+      QDR.redirectWhenConnected($location, 'schema');
+      return;
+    }
+    var onDisconnect = function () {
+      $timeout( function () {QDR.redirectWhenConnected('schema');});
+    };
+    // we are currently connected. setup a handler to get notified if we are 
ever disconnected
+    QDRService.management.connection.addDisconnectAction( onDisconnect );
+
+    var keys2kids = function (tree, obj) {
+      if (obj === Object(obj)) {
+        tree.children = [];
+        let keys = Object.keys(obj).sort();
+        for (let i=0; i<keys.length; ++i) {
+          let key = keys[i];
+          let kid = {title: key};
+          if (obj[key] === Object(obj[key])) {
+            kid.folder = true;
+            keys2kids(kid, obj[key]);
+          } else {
+            kid.title += (': ' + JSON.stringify(obj[key],null,2));
+          }
+          tree.children.push(kid);
+        }
+      }
+    };
+
+    let tree = [];
+    for (let key in QDRService.management.schema()) {
+      let kid = {title: key};
+      kid.folder = true;
+      let val = QDRService.management.schema()[key];
+      if (val === Object(val))
+        keys2kids(kid, val);
+      else
+        kid.title += (': ' + JSON.stringify(val,null,2));
+
+      tree.push(kid);
+    }
+    $('#schema').fancytree({
+      minExpandLevel: 2,
+      clickFolderMode: 3,
+      classNames: {
+        expander: 'fa-angle',
+        connector: 'fancytree-no-connector'
+      },
+      source: tree
+    });
+
+    $scope.$on('$destroy', function() {
+      QDRService.management.connection.delDisconnectAction( onDisconnect );
+    });
+
+  }]);
+
+  return QDR;
+}(QDR || {}));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/js/qdrService.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrService.js 
b/console/stand-alone/plugin/js/qdrService.js
new file mode 100644
index 0000000..f4effdd
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrService.js
@@ -0,0 +1,269 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+*/
+'use strict';
+/* global Promise */
+/**
+ * @module QDR
+ */
+var QDR = (function(QDR) {
+
+  // The QDR service handles the connection to the router
+  QDR.module.factory('QDRService', ['$timeout', '$location', 
function($timeout, $location) {
+    let dm = require('dispatch-management');
+    let self = {
+      management: new dm.Management($location.protocol()),
+      utilities: dm.Utilities,
+
+      onReconnect: function () {
+        self.management.connection.on('disconnected', self.onDisconnect);
+        let org = localStorage[QDR.LAST_LOCATION] || '/overview';
+        $timeout ( function () {
+          $location.path(org);
+          $location.search('org', null);
+          $location.replace();
+        });
+      },
+      onDisconnect: function () {
+        self.management.connection.on('connected', self.onReconnect);
+        $timeout( function () {
+          $location.path('/connect');
+          let curPath = $location.path();
+          let parts = curPath.split('/');
+          let org = parts[parts.length-1];
+          if (org && org.length > 0 && org !== 'connect') {
+            $location.search('org', org);
+          } else {
+            $location.search('org', null);
+          }
+          $location.replace();
+        });
+      },
+
+      connect: function (connectOptions) {
+        return new Promise ( function (resolve, reject) {
+          self.management.connection.connect(connectOptions)
+            .then( function (r) {
+              // if we are ever disconnected, show the connect page and wait 
for a reconnect
+              self.management.connection.on('disconnected', self.onDisconnect);
+
+              self.management.getSchema()
+                .then( function () {
+                  QDR.log.info('got schema after connection');
+                  self.management.topology.setUpdateEntities([]);
+                  QDR.log.info('requesting a topology');
+                  self.management.topology.get() // gets the list of routers
+                    .then( function () {
+                      QDR.log.info('got initial topology');
+                      let curPath = $location.path();
+                      let parts = curPath.split('/');
+                      let org = parts[parts.length-1];
+                      if (org === '' || org === 'connect') {
+                        org = localStorage[QDR.LAST_LOCATION] || 
QDR.pluginRoot + '/overview';
+                      }
+                      $timeout ( function () {
+                        $location.path(org);
+                        $location.search('org', null);
+                        $location.replace();
+                      });
+                    });
+                });
+              resolve(r);
+            }, function (e) {
+              reject(e);
+            });
+        });
+      },
+      disconnect: function () {
+        self.management.connection.disconnect();
+        delete self.management;
+        self.management = new dm.Management($location.protocol());
+      }
+
+
+    };
+
+    return self;
+  }]);
+
+  return QDR;
+
+}(QDR || {}));
+
+(function() {
+  console.dump = function(o) {
+    if (window.JSON && window.JSON.stringify)
+      QDR.log.info(JSON.stringify(o, undefined, 2));
+    else
+      console.log(o);
+  };
+})();
+
+if (!String.prototype.startsWith) {
+  String.prototype.startsWith = function (searchString, position) {
+    return this.substr(position || 0, searchString.length) === searchString;
+  };
+}
+
+if (!String.prototype.endsWith) {
+  String.prototype.endsWith = function(searchString, position) {
+    let subjectString = this.toString();
+    if (typeof position !== 'number' || !isFinite(position) || 
Math.floor(position) !== position || position > subjectString.length) {
+      position = subjectString.length;
+    }
+    position -= searchString.length;
+    let lastIndex = subjectString.lastIndexOf(searchString, position);
+    return lastIndex !== -1 && lastIndex === position;
+  };
+}
+
+// https://tc39.github.io/ecma262/#sec-array.prototype.findIndex
+if (!Array.prototype.findIndex) {
+  Object.defineProperty(Array.prototype, 'findIndex', {
+    value: function(predicate) {
+      // 1. Let O be ? ToObject(this value).
+      if (this == null) {
+        throw new TypeError('"this" is null or not defined');
+      }
+
+      let o = Object(this);
+
+      // 2. Let len be ? ToLength(? Get(O, "length")).
+      let len = o.length >>> 0;
+
+      // 3. If IsCallable(predicate) is false, throw a TypeError exception.
+      if (typeof predicate !== 'function') {
+        throw new TypeError('predicate must be a function');
+      }
+
+      // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
+      let thisArg = arguments[1];
+
+      // 5. Let k be 0.
+      let k = 0;
+
+      // 6. Repeat, while k < len
+      while (k < len) {
+        // a. Let Pk be ! ToString(k).
+        // b. Let kValue be ? Get(O, Pk).
+        // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O 
»)).
+        // d. If testResult is true, return k.
+        let kValue = o[k];
+        if (predicate.call(thisArg, kValue, k, o)) {
+          return k;
+        }
+        // e. Increase k by 1.
+        k++;
+      }
+
+      // 7. Return -1.
+      return -1;
+    }
+  });
+}
+
+// https://tc39.github.io/ecma262/#sec-array.prototype.find
+if (!Array.prototype.find) {
+  Object.defineProperty(Array.prototype, 'find', {
+    value: function(predicate) {
+      // 1. Let O be ? ToObject(this value).
+      if (this == null) {
+        throw new TypeError('"this" is null or not defined');
+      }
+
+      let o = Object(this);
+
+      // 2. Let len be ? ToLength(? Get(O, "length")).
+      let len = o.length >>> 0;
+
+      // 3. If IsCallable(predicate) is false, throw a TypeError exception.
+      if (typeof predicate !== 'function') {
+        throw new TypeError('predicate must be a function');
+      }
+
+      // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
+      let thisArg = arguments[1];
+
+      // 5. Let k be 0.
+      let k = 0;
+
+      // 6. Repeat, while k < len
+      while (k < len) {
+        // a. Let Pk be ! ToString(k).
+        // b. Let kValue be ? Get(O, Pk).
+        // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O 
»)).
+        // d. If testResult is true, return kValue.
+        let kValue = o[k];
+        if (predicate.call(thisArg, kValue, k, o)) {
+          return kValue;
+        }
+        // e. Increase k by 1.
+        k++;
+      }
+
+      // 7. Return undefined.
+      return undefined;
+    }
+  });
+}
+
+// 
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill
+if (!Array.prototype.fill) {
+  Object.defineProperty(Array.prototype, 'fill', {
+    value: function(value) {
+
+      // Steps 1-2.
+      if (this == null) {
+        throw new TypeError('this is null or not defined');
+      }
+
+      var O = Object(this);
+
+      // Steps 3-5.
+      var len = O.length >>> 0;
+
+      // Steps 6-7.
+      var start = arguments[1];
+      var relativeStart = start >> 0;
+
+      // Step 8.
+      var k = relativeStart < 0 ?
+        Math.max(len + relativeStart, 0) :
+        Math.min(relativeStart, len);
+
+      // Steps 9-10.
+      var end = arguments[2];
+      var relativeEnd = end === undefined ?
+        len : end >> 0;
+
+      // Step 11.
+      var final = relativeEnd < 0 ?
+        Math.max(len + relativeEnd, 0) :
+        Math.min(relativeEnd, len);
+
+      // Step 12.
+      while (k < final) {
+        O[k] = value;
+        k++;
+      }
+
+      // Step 13.
+      return O;
+    }
+  });
+}
\ No newline at end of file


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

Reply via email to